feat: Programmatic implementation of /gsd:set-profile command (#1007)
* perf: make `/gsd:set-profile`'s implementation more programmatic * perf: run the set-profile script without Claude needing to invoke it - inject the script's output as dynamic context into the set-profile skill, so that Claude doesn't need to invoke it and can simply read + print the output to the user - reference: https://code.claude.com/docs/en/skills#inject-dynamic-context * feat: improve output message for case where model profile wasn't changed * feat: specify haiku model for set-profile command since it's so simple * fix: remove ' (default)' from previousProfile to avoid false negative for didChange * chore: add docstring to MODEL_PROFILES with note about the analogous markdown reference table * chore: delete set-profile workflow file that's no longer needed
This commit is contained in:
@@ -1,34 +1,12 @@
|
||||
---
|
||||
name: gsd:set-profile
|
||||
description: Switch model profile for GSD agents (quality/balanced/budget/inherit)
|
||||
argument-hint: <profile>
|
||||
argument-hint: <profile (quality|balanced|budget|inherit)>
|
||||
model: haiku
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Bash
|
||||
---
|
||||
|
||||
<objective>
|
||||
Switch the model profile used by GSD agents. Controls which Claude model each agent uses, balancing quality vs token spend.
|
||||
Show the following output to the user verbatim, with no extra commentary:
|
||||
|
||||
Routes to the set-profile workflow which handles:
|
||||
- Argument validation (quality/balanced/budget/inherit)
|
||||
- Config file creation if missing
|
||||
- Profile update in config.json
|
||||
- Confirmation with model table display
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/set-profile.md
|
||||
</execution_context>
|
||||
|
||||
<process>
|
||||
**Follow the set-profile workflow** from `@~/.claude/get-shit-done/workflows/set-profile.md`.
|
||||
|
||||
The workflow handles all logic including:
|
||||
1. Profile argument validation
|
||||
2. Config file ensuring
|
||||
3. Config reading and updating
|
||||
4. Model table generation from MODEL_PROFILES
|
||||
5. Confirmation display
|
||||
</process>
|
||||
!`node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" config-set-model-profile $ARGUMENTS --raw`
|
||||
|
||||
@@ -377,6 +377,11 @@ async function main() {
|
||||
break;
|
||||
}
|
||||
|
||||
case "config-set-model-profile": {
|
||||
config.cmdConfigSetModelProfile(cwd, args[1], raw);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'config-get': {
|
||||
config.cmdConfigGet(cwd, args[1], raw);
|
||||
break;
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
const { safeReadFile, loadConfig, isGitIgnored, execGit, normalizePhaseName, comparePhaseNum, getArchivedPhaseDirs, generateSlugInternal, getMilestoneInfo, resolveModelInternal, MODEL_PROFILES, toPosixPath, output, error, findPhaseInternal } = require('./core.cjs');
|
||||
const { safeReadFile, loadConfig, isGitIgnored, execGit, normalizePhaseName, comparePhaseNum, getArchivedPhaseDirs, generateSlugInternal, getMilestoneInfo, resolveModelInternal, toPosixPath, output, error, findPhaseInternal } = require('./core.cjs');
|
||||
const { extractFrontmatter } = require('./frontmatter.cjs');
|
||||
const { MODEL_PROFILES } = require('./model-profiles.cjs');
|
||||
|
||||
function cmdGenerateSlug(text, raw) {
|
||||
if (!text) {
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { output, error } = require('./core.cjs');
|
||||
const {
|
||||
VALID_PROFILES,
|
||||
getAgentToModelMapForProfile,
|
||||
formatAgentToModelMapAsTable,
|
||||
} = require('./model-profiles.cjs');
|
||||
|
||||
const VALID_CONFIG_KEYS = new Set([
|
||||
'mode', 'granularity', 'parallelization', 'commit_docs', 'model_profile',
|
||||
@@ -29,7 +34,13 @@ function validateKnownConfigKeyPath(keyPath) {
|
||||
}
|
||||
}
|
||||
|
||||
function cmdConfigEnsureSection(cwd, raw) {
|
||||
/**
|
||||
* Ensures the config file exists (creates it if needed).
|
||||
*
|
||||
* Does not call `output()`, so can be used as one step in a command without triggering `exit(0)` in
|
||||
* the happy path. But note that `error()` will still `exit(1)` out of the process.
|
||||
*/
|
||||
function ensureConfigFile(cwd) {
|
||||
const configPath = path.join(cwd, '.planning', 'config.json');
|
||||
const planningDir = path.join(cwd, '.planning');
|
||||
|
||||
@@ -44,9 +55,7 @@ function cmdConfigEnsureSection(cwd, raw) {
|
||||
|
||||
// Check if config already exists
|
||||
if (fs.existsSync(configPath)) {
|
||||
const result = { created: false, reason: 'already_exists' };
|
||||
output(result, raw, 'exists');
|
||||
return;
|
||||
return { created: false, reason: 'already_exists' };
|
||||
}
|
||||
|
||||
// Detect Brave Search API key availability
|
||||
@@ -65,7 +74,9 @@ function cmdConfigEnsureSection(cwd, raw) {
|
||||
const depthToGranularity = { quick: 'coarse', standard: 'standard', comprehensive: 'fine' };
|
||||
userDefaults.granularity = depthToGranularity[userDefaults.depth] || userDefaults.depth;
|
||||
delete userDefaults.depth;
|
||||
try { fs.writeFileSync(globalDefaultsPath, JSON.stringify(userDefaults, null, 2), 'utf-8'); } catch {}
|
||||
try {
|
||||
fs.writeFileSync(globalDefaultsPath, JSON.stringify(userDefaults, null, 2), 'utf-8');
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -97,32 +108,37 @@ function cmdConfigEnsureSection(cwd, raw) {
|
||||
|
||||
try {
|
||||
fs.writeFileSync(configPath, JSON.stringify(defaults, null, 2), 'utf-8');
|
||||
const result = { created: true, path: '.planning/config.json' };
|
||||
output(result, raw, 'created');
|
||||
return { created: true, path: '.planning/config.json' };
|
||||
} catch (err) {
|
||||
error('Failed to create config.json: ' + err.message);
|
||||
}
|
||||
}
|
||||
|
||||
function cmdConfigSet(cwd, keyPath, value, raw) {
|
||||
/**
|
||||
* Command to ensure the config file exists (creates it if needed).
|
||||
*
|
||||
* Note that this exits the process (via `output()`) even in the happy path; use
|
||||
* `ensureConfigFile()` directly if you need to avoid this.
|
||||
*/
|
||||
function cmdConfigEnsureSection(cwd, raw) {
|
||||
const ensureConfigFileResult = ensureConfigFile(cwd);
|
||||
if (ensureConfigFileResult.created) {
|
||||
output(ensureConfigFileResult, raw, 'created');
|
||||
} else {
|
||||
output(ensureConfigFileResult, raw, 'exists');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a value in the config file, allowing nested values via dot notation (e.g.,
|
||||
* "workflow.research").
|
||||
*
|
||||
* Does not call `output()`, so can be used as one step in a command without triggering `exit(0)` in
|
||||
* the happy path. But note that `error()` will still `exit(1)` out of the process.
|
||||
*/
|
||||
function setConfigValue(cwd, keyPath, parsedValue) {
|
||||
const configPath = path.join(cwd, '.planning', 'config.json');
|
||||
|
||||
if (!keyPath) {
|
||||
error('Usage: config-set <key.path> <value>');
|
||||
}
|
||||
|
||||
validateKnownConfigKeyPath(keyPath);
|
||||
|
||||
if (!VALID_CONFIG_KEYS.has(keyPath)) {
|
||||
error(`Unknown config key: "${keyPath}". Valid keys: ${[...VALID_CONFIG_KEYS].sort().join(', ')}`);
|
||||
}
|
||||
|
||||
// Parse value (handle booleans and numbers)
|
||||
let parsedValue = value;
|
||||
if (value === 'true') parsedValue = true;
|
||||
else if (value === 'false') parsedValue = false;
|
||||
else if (!isNaN(value) && value !== '') parsedValue = Number(value);
|
||||
|
||||
// Load existing config or start with empty object
|
||||
let config = {};
|
||||
try {
|
||||
@@ -143,18 +159,46 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
|
||||
}
|
||||
current = current[key];
|
||||
}
|
||||
const previousValue = current[keys[keys.length - 1]]; // Capture previous value before overwriting
|
||||
current[keys[keys.length - 1]] = parsedValue;
|
||||
|
||||
// Write back
|
||||
try {
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
||||
const result = { updated: true, key: keyPath, value: parsedValue };
|
||||
output(result, raw, `${keyPath}=${parsedValue}`);
|
||||
return { updated: true, key: keyPath, value: parsedValue, previousValue };
|
||||
} catch (err) {
|
||||
error('Failed to write config.json: ' + err.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Command to set a value in the config file, allowing nested values via dot notation (e.g.,
|
||||
* "workflow.research").
|
||||
*
|
||||
* Note that this exits the process (via `output()`) even in the happy path; use `setConfigValue()`
|
||||
* directly if you need to avoid this.
|
||||
*/
|
||||
function cmdConfigSet(cwd, keyPath, value, raw) {
|
||||
if (!keyPath) {
|
||||
error('Usage: config-set <key.path> <value>');
|
||||
}
|
||||
|
||||
validateKnownConfigKeyPath(keyPath);
|
||||
|
||||
if (!VALID_CONFIG_KEYS.has(keyPath)) {
|
||||
error(`Unknown config key: "${keyPath}". Valid keys: ${[...VALID_CONFIG_KEYS].sort().join(', ')}`);
|
||||
}
|
||||
|
||||
// Parse value (handle booleans and numbers)
|
||||
let parsedValue = value;
|
||||
if (value === 'true') parsedValue = true;
|
||||
else if (value === 'false') parsedValue = false;
|
||||
else if (!isNaN(value) && value !== '') parsedValue = Number(value);
|
||||
|
||||
const setConfigValueResult = setConfigValue(cwd, keyPath, parsedValue);
|
||||
output(setConfigValueResult, raw, `${keyPath}=${parsedValue}`);
|
||||
}
|
||||
|
||||
function cmdConfigGet(cwd, keyPath, raw) {
|
||||
const configPath = path.join(cwd, '.planning', 'config.json');
|
||||
|
||||
@@ -191,8 +235,73 @@ function cmdConfigGet(cwd, keyPath, raw) {
|
||||
output(current, raw, String(current));
|
||||
}
|
||||
|
||||
/**
|
||||
* Command to set the model profile in the config file.
|
||||
*
|
||||
* Note that this exits the process (via `output()`) even in the happy path.
|
||||
*/
|
||||
function cmdConfigSetModelProfile(cwd, profile, raw) {
|
||||
if (!profile) {
|
||||
error(`Usage: config-set-model-profile <${VALID_PROFILES.join('|')}>`);
|
||||
}
|
||||
|
||||
const normalizedProfile = profile.toLowerCase().trim();
|
||||
if (!VALID_PROFILES.includes(normalizedProfile)) {
|
||||
error(`Invalid profile '${profile}'. Valid profiles: ${VALID_PROFILES.join(', ')}`);
|
||||
}
|
||||
|
||||
// Ensure config exists (create if needed)
|
||||
ensureConfigFile(cwd);
|
||||
|
||||
// Set the model profile in the config
|
||||
const { previousValue } = setConfigValue(cwd, 'model_profile', normalizedProfile, raw);
|
||||
const previousProfile = previousValue || 'balanced';
|
||||
|
||||
// Build result value / message and return
|
||||
const agentToModelMap = getAgentToModelMapForProfile(normalizedProfile);
|
||||
const result = {
|
||||
updated: true,
|
||||
profile: normalizedProfile,
|
||||
previousProfile,
|
||||
agentToModelMap,
|
||||
};
|
||||
const rawValue = getCmdConfigSetModelProfileResultMessage(
|
||||
normalizedProfile,
|
||||
previousProfile,
|
||||
agentToModelMap
|
||||
);
|
||||
output(result, raw, rawValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message to display for the result of the `config-set-model-profile` command when
|
||||
* displaying raw output.
|
||||
*/
|
||||
function getCmdConfigSetModelProfileResultMessage(
|
||||
normalizedProfile,
|
||||
previousProfile,
|
||||
agentToModelMap
|
||||
) {
|
||||
const agentToModelTable = formatAgentToModelMapAsTable(agentToModelMap);
|
||||
const didChange = previousProfile !== normalizedProfile;
|
||||
const paragraphs = didChange
|
||||
? [
|
||||
`✓ Model profile set to: ${normalizedProfile} (was: ${previousProfile})`,
|
||||
'Agents will now use:',
|
||||
agentToModelTable,
|
||||
'Next spawned agents will use the new profile.',
|
||||
]
|
||||
: [
|
||||
`✓ Model profile is already set to: ${normalizedProfile}`,
|
||||
'Agents are using:',
|
||||
agentToModelTable,
|
||||
];
|
||||
return paragraphs.join('\n\n');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
cmdConfigEnsureSection,
|
||||
cmdConfigSet,
|
||||
cmdConfigGet,
|
||||
cmdConfigSetModelProfile,
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
const { MODEL_PROFILES } = require('./model-profiles.cjs');
|
||||
|
||||
// ─── Path helpers ────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -13,26 +14,6 @@ function toPosixPath(p) {
|
||||
return p.split(path.sep).join('/');
|
||||
}
|
||||
|
||||
// ─── Model Profile Table ─────────────────────────────────────────────────────
|
||||
|
||||
const MODEL_PROFILES = {
|
||||
'gsd-planner': { quality: 'opus', balanced: 'opus', budget: 'sonnet' },
|
||||
'gsd-roadmapper': { quality: 'opus', balanced: 'sonnet', budget: 'sonnet' },
|
||||
'gsd-executor': { quality: 'opus', balanced: 'sonnet', budget: 'sonnet' },
|
||||
'gsd-phase-researcher': { quality: 'opus', balanced: 'sonnet', budget: 'haiku' },
|
||||
'gsd-project-researcher': { quality: 'opus', balanced: 'sonnet', budget: 'haiku' },
|
||||
'gsd-research-synthesizer': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
|
||||
'gsd-debugger': { quality: 'opus', balanced: 'sonnet', budget: 'sonnet' },
|
||||
'gsd-codebase-mapper': { quality: 'sonnet', balanced: 'haiku', budget: 'haiku' },
|
||||
'gsd-verifier': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
|
||||
'gsd-plan-checker': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
|
||||
'gsd-integration-checker': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
|
||||
'gsd-nyquist-auditor': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
|
||||
'gsd-ui-researcher': { quality: 'opus', balanced: 'sonnet', budget: 'haiku' },
|
||||
'gsd-ui-checker': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
|
||||
'gsd-ui-auditor': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
|
||||
};
|
||||
|
||||
// ─── Output helpers ───────────────────────────────────────────────────────────
|
||||
|
||||
function output(result, raw, rawValue) {
|
||||
@@ -501,7 +482,6 @@ function getMilestonePhaseFilter(cwd) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
MODEL_PROFILES,
|
||||
output,
|
||||
error,
|
||||
safeReadFile,
|
||||
|
||||
68
get-shit-done/bin/lib/model-profiles.cjs
Normal file
68
get-shit-done/bin/lib/model-profiles.cjs
Normal file
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Mapping of GSD agent to model for each profile.
|
||||
*
|
||||
* Should be in sync with the profiles table in `get-shit-done/references/model-profiles.md`. But
|
||||
* possibly worth making this the single source of truth at some point, and removing the markdown
|
||||
* reference table in favor of programmatically determining the model to use for an agent (which
|
||||
* would be faster, use fewer tokens, and be less error-prone).
|
||||
*/
|
||||
const MODEL_PROFILES = {
|
||||
'gsd-planner': { quality: 'opus', balanced: 'opus', budget: 'sonnet' },
|
||||
'gsd-roadmapper': { quality: 'opus', balanced: 'sonnet', budget: 'sonnet' },
|
||||
'gsd-executor': { quality: 'opus', balanced: 'sonnet', budget: 'sonnet' },
|
||||
'gsd-phase-researcher': { quality: 'opus', balanced: 'sonnet', budget: 'haiku' },
|
||||
'gsd-project-researcher': { quality: 'opus', balanced: 'sonnet', budget: 'haiku' },
|
||||
'gsd-research-synthesizer': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
|
||||
'gsd-debugger': { quality: 'opus', balanced: 'sonnet', budget: 'sonnet' },
|
||||
'gsd-codebase-mapper': { quality: 'sonnet', balanced: 'haiku', budget: 'haiku' },
|
||||
'gsd-verifier': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
|
||||
'gsd-plan-checker': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
|
||||
'gsd-integration-checker': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
|
||||
'gsd-nyquist-auditor': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
|
||||
'gsd-ui-researcher': { quality: 'opus', balanced: 'sonnet', budget: 'haiku' },
|
||||
'gsd-ui-checker': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
|
||||
'gsd-ui-auditor': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
|
||||
};
|
||||
const VALID_PROFILES = Object.keys(MODEL_PROFILES['gsd-planner']);
|
||||
|
||||
/**
|
||||
* Formats the agent-to-model mapping as a human-readable table (in string format).
|
||||
*
|
||||
* @param {Object<string, string>} agentToModelMap - A mapping from agent to model
|
||||
* @returns {string} A formatted table string
|
||||
*/
|
||||
function formatAgentToModelMapAsTable(agentToModelMap) {
|
||||
const agentWidth = Math.max('Agent'.length, ...Object.keys(agentToModelMap).map((a) => a.length));
|
||||
const modelWidth = Math.max(
|
||||
'Model'.length,
|
||||
...Object.values(agentToModelMap).map((m) => m.length)
|
||||
);
|
||||
const sep = '─'.repeat(agentWidth + 2) + '┼' + '─'.repeat(modelWidth + 2);
|
||||
const header = ' ' + 'Agent'.padEnd(agentWidth) + ' │ ' + 'Model'.padEnd(modelWidth);
|
||||
let agentToModelTable = header + '\n' + sep + '\n';
|
||||
for (const [agent, model] of Object.entries(agentToModelMap)) {
|
||||
agentToModelTable += ' ' + agent.padEnd(agentWidth) + ' │ ' + model.padEnd(modelWidth) + '\n';
|
||||
}
|
||||
return agentToModelTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a mapping from agent to model for the given model profile.
|
||||
*
|
||||
* @param {string} normalizedProfile - The normalized (lowercase and trimmed) profile name
|
||||
* @returns {Object<string, string>} A mapping from agent to model for the given profile
|
||||
*/
|
||||
function getAgentToModelMapForProfile(normalizedProfile) {
|
||||
const agentToModelMap = {};
|
||||
for (const [agent, profileToModelMap] of Object.entries(MODEL_PROFILES)) {
|
||||
agentToModelMap[agent] = profileToModelMap[normalizedProfile];
|
||||
}
|
||||
return agentToModelMap;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
MODEL_PROFILES,
|
||||
VALID_PROFILES,
|
||||
formatAgentToModelMapAsTable,
|
||||
getAgentToModelMapForProfile,
|
||||
};
|
||||
@@ -1,82 +0,0 @@
|
||||
<purpose>
|
||||
Switch the model profile used by GSD agents. Controls which Claude model each agent uses, balancing quality vs token spend.
|
||||
</purpose>
|
||||
|
||||
<required_reading>
|
||||
Read all files referenced by the invoking prompt's execution_context before starting.
|
||||
</required_reading>
|
||||
|
||||
<process>
|
||||
|
||||
<step name="validate">
|
||||
Validate argument:
|
||||
|
||||
```
|
||||
if $ARGUMENTS.profile not in ["quality", "balanced", "budget", "inherit"]:
|
||||
Error: Invalid profile "$ARGUMENTS.profile"
|
||||
Valid profiles: quality, balanced, budget, inherit
|
||||
EXIT
|
||||
```
|
||||
</step>
|
||||
|
||||
<step name="ensure_and_load_config">
|
||||
Ensure config exists and load current state:
|
||||
|
||||
```bash
|
||||
node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" config-ensure-section
|
||||
INIT=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" state load)
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
```
|
||||
|
||||
This creates `.planning/config.json` with defaults if missing and loads current config.
|
||||
</step>
|
||||
|
||||
<step name="update_config">
|
||||
Read current config from state load or directly:
|
||||
|
||||
Update `model_profile` field:
|
||||
```json
|
||||
{
|
||||
"model_profile": "$ARGUMENTS.profile"
|
||||
}
|
||||
```
|
||||
|
||||
Write updated config back to `.planning/config.json`.
|
||||
</step>
|
||||
|
||||
<step name="confirm">
|
||||
Display confirmation with model table for selected profile:
|
||||
|
||||
```
|
||||
✓ Model profile set to: $ARGUMENTS.profile
|
||||
|
||||
Agents will now use:
|
||||
|
||||
[Show table from MODEL_PROFILES in gsd-tools.cjs for selected profile]
|
||||
|
||||
Example:
|
||||
| Agent | Model |
|
||||
|-------|-------|
|
||||
| gsd-planner | opus |
|
||||
| gsd-executor | sonnet |
|
||||
| gsd-verifier | haiku |
|
||||
| ... | ... |
|
||||
|
||||
Next spawned agents will use the new profile.
|
||||
```
|
||||
|
||||
Map profile names:
|
||||
- quality: use "quality" column from MODEL_PROFILES
|
||||
- balanced: use "balanced" column from MODEL_PROFILES
|
||||
- budget: use "budget" column from MODEL_PROFILES
|
||||
- inherit: use "inherit" for all agents
|
||||
</step>
|
||||
|
||||
</process>
|
||||
|
||||
<success_criteria>
|
||||
- [ ] Argument validated
|
||||
- [ ] Config file ensured
|
||||
- [ ] Config updated with new model_profile
|
||||
- [ ] Confirmation displayed with model table
|
||||
</success_criteria>
|
||||
@@ -14,7 +14,6 @@ const os = require('os');
|
||||
const {
|
||||
loadConfig,
|
||||
resolveModelInternal,
|
||||
MODEL_PROFILES,
|
||||
escapeRegex,
|
||||
generateSlugInternal,
|
||||
normalizePhaseName,
|
||||
|
||||
Reference in New Issue
Block a user