@opengsd/gsd-core 1.6.1 → 1.7.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +20 -0
- package/.claude-plugin/plugin.json +1 -1
- package/.opencode/plugins/gsd-core.js +711 -0
- package/agents/gsd-advisor-researcher.md +2 -0
- package/agents/gsd-ai-researcher.md +1 -1
- package/agents/gsd-assumptions-analyzer.md +2 -0
- package/agents/gsd-code-fixer.md +2 -0
- package/agents/gsd-code-reviewer.md +2 -0
- package/agents/gsd-codebase-mapper.md +2 -0
- package/agents/gsd-debugger.md +2 -0
- package/agents/gsd-doc-writer.md +2 -0
- package/agents/gsd-eval-auditor.md +2 -0
- package/agents/gsd-executor.md +9 -6
- package/agents/gsd-integration-checker.md +2 -0
- package/agents/gsd-nyquist-auditor.md +2 -0
- package/agents/gsd-phase-researcher.md +2 -0
- package/agents/gsd-plan-checker.md +2 -0
- package/agents/gsd-planner.md +2 -0
- package/agents/gsd-project-researcher.md +2 -0
- package/agents/gsd-research-synthesizer.md +2 -0
- package/agents/gsd-roadmapper.md +2 -0
- package/agents/gsd-security-auditor.md +2 -0
- package/agents/gsd-ui-auditor.md +2 -0
- package/agents/gsd-ui-checker.md +2 -0
- package/agents/gsd-ui-researcher.md +2 -0
- package/agents/gsd-verifier.md +5 -2
- package/bin/gsd-mcp-server.js +31 -0
- package/bin/install.js +411 -1146
- package/commands/gsd/review.md +6 -0
- package/gemini-extension.json +1 -1
- package/gsd-core/bin/gsd-tools.cjs +134 -8
- package/gsd-core/bin/lib/adapter-declarative.cjs +35 -0
- package/gsd-core/bin/lib/adapter-imperative.cjs +52 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +231 -0
- package/gsd-core/bin/lib/capability-lifecycle.cjs +7 -7
- package/gsd-core/bin/lib/capability-loader.cjs +45 -9
- package/gsd-core/bin/lib/capability-lock.cjs +2 -2
- package/gsd-core/bin/lib/capability-registry.cjs +891 -82
- package/gsd-core/bin/lib/capability-source.cjs +26 -11
- package/gsd-core/bin/lib/capability-validator.cjs +222 -2
- package/gsd-core/bin/lib/cli-skew-check.cjs +44 -0
- package/gsd-core/bin/lib/command-aliases.cjs +8 -0
- package/gsd-core/bin/lib/commands.cjs +2 -1
- package/gsd-core/bin/lib/config.cjs +27 -0
- package/gsd-core/bin/lib/embedding-adapter.cjs +27 -0
- package/gsd-core/bin/lib/external-descriptor-trust.cjs +70 -0
- package/gsd-core/bin/lib/frontmatter.cjs +53 -6
- package/gsd-core/bin/lib/handshake-serialized.cjs +70 -0
- package/gsd-core/bin/lib/hook-bus.cjs +81 -0
- package/gsd-core/bin/lib/host-integration-sdk.cjs +53 -0
- package/gsd-core/bin/lib/host-integration.cjs +469 -0
- package/gsd-core/bin/lib/init.cjs +35 -7
- package/gsd-core/bin/lib/install-engine.cjs +755 -0
- package/gsd-core/bin/lib/install-profiles.cjs +35 -4
- package/gsd-core/bin/lib/installer-migrations.cjs +1 -1
- package/gsd-core/bin/lib/mcp-server.cjs +194 -0
- package/gsd-core/bin/lib/milestone.cjs +68 -40
- package/gsd-core/bin/lib/model-adapter.cjs +50 -0
- package/gsd-core/bin/lib/phase-id.cjs +18 -0
- package/gsd-core/bin/lib/phase.cjs +57 -90
- package/gsd-core/bin/lib/phases-command-router.cjs +4 -3
- package/gsd-core/bin/lib/planning-workspace.cjs +1 -1
- package/gsd-core/bin/lib/probe-core.cjs +132 -2
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +129 -13
- package/gsd-core/bin/lib/roadmap-command-router.cjs +3 -2
- package/gsd-core/bin/lib/roadmap-parser.cjs +21 -11
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +3 -2
- package/gsd-core/bin/lib/roadmap.cjs +33 -22
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +65 -9
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +54 -4
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +5 -2
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +1 -1
- package/gsd-core/bin/lib/runtime-name-policy.cjs +160 -30
- package/gsd-core/bin/lib/shell-command-projection.cjs +16 -0
- package/gsd-core/bin/lib/stale-bake-guard.cjs +254 -0
- package/gsd-core/bin/lib/state-command-router.cjs +4 -0
- package/gsd-core/bin/lib/state-io.cjs +55 -0
- package/gsd-core/bin/lib/state-transition.cjs +1603 -0
- package/gsd-core/bin/lib/state.cjs +327 -683
- package/gsd-core/bin/lib/surface.cjs +4 -1
- package/gsd-core/bin/lib/validate.cjs +2 -1
- package/gsd-core/bin/lib/verify.cjs +6 -4
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +12 -2
- package/gsd-core/bin/lib/workstream-inventory.cjs +28 -0
- package/gsd-core/bin/lib/workstream.cjs +4 -4
- package/gsd-core/bin/shared/config-schema.manifest.json +9 -0
- package/gsd-core/references/agent-skills-bootstrap.md +60 -0
- package/gsd-core/references/honest-verifier.md +105 -0
- package/gsd-core/references/model-profiles.md +27 -0
- package/gsd-core/references/reviewer-instances.md +99 -0
- package/gsd-core/workflows/autonomous.md +30 -32
- package/gsd-core/workflows/complete-milestone.md +6 -10
- package/gsd-core/workflows/execute-phase.md +1 -1
- package/gsd-core/workflows/forensics.md +3 -3
- package/gsd-core/workflows/help/modes/full.md +1 -1
- package/gsd-core/workflows/manager.md +15 -15
- package/gsd-core/workflows/milestone-summary.md +3 -3
- package/gsd-core/workflows/new-milestone.md +6 -0
- package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +42 -0
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +102 -0
- package/gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md +23 -0
- package/gsd-core/workflows/plan-phase.md +4 -159
- package/gsd-core/workflows/review.md +33 -2
- package/gsd-core/workflows/thread.md +4 -4
- package/gsd-core/workflows/verify-phase.md +11 -4
- package/gsd-core/workflows/verify-work.md +1 -2
- package/hooks/dist/gsd-graphify-update.sh +7 -1
- package/hooks/gsd-graphify-update.sh +7 -1
- package/package.json +6 -4
- package/scripts/ci-test-scope.cjs +38 -9
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -1
- package/scripts/lint-regression-test-names.allowlist.json +3 -0
- package/scripts/lint-test-file-count.allowlist.json +19 -5
- package/scripts/mutation-matrix.cjs +45 -3
- package/scripts/prompt-injection-scan.sh +8 -0
- package/scripts/run-tests.cjs +51 -1
- package/scripts/sync-manifest-versions.cjs +66 -14
- package/skills/gsd-review/SKILL.md +6 -0
- package/scripts/lint-windows-test-portability.cjs +0 -178
|
@@ -9,6 +9,30 @@
|
|
|
9
9
|
// In .cts (CommonJS output) files, `require` is available as a global.
|
|
10
10
|
const _require = require;
|
|
11
11
|
const path = _require('node:path');
|
|
12
|
+
/**
|
|
13
|
+
* Asserts that `destSubpath` resolves to a path inside `configDir`.
|
|
14
|
+
*
|
|
15
|
+
* Rejects any path that escapes the configDir root (e.g. "../../etc") and any
|
|
16
|
+
* path containing a NUL byte. This is a security gate for Phase B of
|
|
17
|
+
* ADR-1239: third-party descriptors must never be able to write outside the
|
|
18
|
+
* designated config home directory.
|
|
19
|
+
*
|
|
20
|
+
* @param configDir - The root config directory (e.g. ~/.claude).
|
|
21
|
+
* @param destSubpath - The relative path declared by the runtime descriptor.
|
|
22
|
+
* @returns The resolved absolute path under configDir.
|
|
23
|
+
* @throws {Error} if destSubpath escapes configDir or contains a NUL byte.
|
|
24
|
+
*/
|
|
25
|
+
function assertDestWithinConfigHome(configDir, destSubpath) {
|
|
26
|
+
if (destSubpath.includes('\0')) {
|
|
27
|
+
throw new Error(`destSubpath "${destSubpath}" contains a NUL byte and is not valid`);
|
|
28
|
+
}
|
|
29
|
+
const root = path.resolve(configDir);
|
|
30
|
+
const resolved = path.resolve(configDir, destSubpath);
|
|
31
|
+
if (resolved === root || !resolved.startsWith(root + path.sep)) {
|
|
32
|
+
throw new Error(`destSubpath "${destSubpath}" must be a strict subpath of configHome "${configDir}" — not configHome itself or outside it (escapes configHome)`);
|
|
33
|
+
}
|
|
34
|
+
return resolved;
|
|
35
|
+
}
|
|
12
36
|
function errorMessage(err) {
|
|
13
37
|
if (err instanceof Error)
|
|
14
38
|
return err.message;
|
|
@@ -36,10 +60,34 @@ function createRuntimeArtifactInstallPlan(args) {
|
|
|
36
60
|
platform,
|
|
37
61
|
resolveAttribution,
|
|
38
62
|
};
|
|
63
|
+
// ADR-1235 §1: build agentCtx once per plan so agents kind entries can apply
|
|
64
|
+
// the CORRECT pre-converter cross-cutting (path rewrites → attribution → converter
|
|
65
|
+
// → normalize). This mirrors the exact per-file order in the inline agent loop
|
|
66
|
+
// in bin/install.js (lines 9330-9415). agentCtx is passed as the second arg
|
|
67
|
+
// to kind.stage() for agents kind entries with a converter (convertedAgentsKind).
|
|
68
|
+
// NO _stampNonClaudeRuntimeDefaults — agents are NOT stamped in the inline loop.
|
|
69
|
+
const os = _require('node:os');
|
|
70
|
+
const homedirFn = homedir ?? (() => os.homedir());
|
|
71
|
+
const resolvedTarget = path.resolve(layout.configDir).replace(/\\/g, '/');
|
|
72
|
+
const homeDir = homedirFn().replace(/\\/g, '/');
|
|
73
|
+
const isGlobal = scope === 'global';
|
|
74
|
+
const isOpencode = layout.runtime === 'opencode';
|
|
75
|
+
const isWindowsHost = (platform ?? process.platform) === 'win32';
|
|
76
|
+
const pathPrefix = conversionExports._computePathPrefix({ isGlobal, isOpencode, isWindowsHost, resolvedTarget, homeDir });
|
|
77
|
+
const attribution = resolveAttribution ? resolveAttribution(layout.runtime) : undefined;
|
|
78
|
+
const agentCtx = { runtime: layout.runtime, pathPrefix, attribution };
|
|
39
79
|
for (const kind of layout.kinds) {
|
|
40
80
|
let stagedDir;
|
|
41
81
|
try {
|
|
42
|
-
|
|
82
|
+
if (kind.kind === 'agents') {
|
|
83
|
+
// ADR-1235 §1: pass agentCtx so stageAgentsForRuntimeWithConverter applies
|
|
84
|
+
// the full inline-loop order: pathRewrites → attribution → converter → normalize.
|
|
85
|
+
// The cross-cutting is now PRE-converter (inside staging), not POST.
|
|
86
|
+
stagedDir = kind.stage(resolvedProfile, agentCtx);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
stagedDir = kind.stage(resolvedProfile);
|
|
90
|
+
}
|
|
43
91
|
}
|
|
44
92
|
catch (err) {
|
|
45
93
|
return { ok: false, kind: 'stage_failed', message: errorMessage(err), cleanupDirs, failedKind: kind.kind };
|
|
@@ -54,6 +102,8 @@ function createRuntimeArtifactInstallPlan(args) {
|
|
|
54
102
|
const rewrittenDir = rewriteStagedSkillBodies(stagedDir, rewriteOpts);
|
|
55
103
|
sourceDir = addCleanupDir(cleanupDirs, stagedDir, rewrittenDir);
|
|
56
104
|
}
|
|
105
|
+
// agents kind: cross-cutting already applied INSIDE kind.stage() via agentCtx.
|
|
106
|
+
// No POST-step needed. sourceDir stays as stagedDir.
|
|
57
107
|
}
|
|
58
108
|
catch (err) {
|
|
59
109
|
return { ok: false, kind: 'rewrite_failed', message: errorMessage(err), cleanupDirs, failedKind: kind.kind };
|
|
@@ -61,7 +111,7 @@ function createRuntimeArtifactInstallPlan(args) {
|
|
|
61
111
|
items.push({
|
|
62
112
|
kind: kind.kind,
|
|
63
113
|
sourceDir,
|
|
64
|
-
destDir:
|
|
114
|
+
destDir: assertDestWithinConfigHome(layout.configDir, kind.destSubpath),
|
|
65
115
|
});
|
|
66
116
|
}
|
|
67
117
|
return { ok: true, plan: { items, cleanupDirs } };
|
|
@@ -70,8 +120,8 @@ function createRuntimeArtifactUninstallPlan(layout) {
|
|
|
70
120
|
return {
|
|
71
121
|
items: layout.kinds.map((kind) => ({
|
|
72
122
|
kind: kind.kind,
|
|
73
|
-
destDir:
|
|
123
|
+
destDir: assertDestWithinConfigHome(layout.configDir, kind.destSubpath),
|
|
74
124
|
})),
|
|
75
125
|
};
|
|
76
126
|
}
|
|
77
|
-
module.exports = { createRuntimeArtifactInstallPlan, createRuntimeArtifactUninstallPlan };
|
|
127
|
+
module.exports = { assertDestWithinConfigHome, createRuntimeArtifactInstallPlan, createRuntimeArtifactUninstallPlan };
|
|
@@ -156,13 +156,16 @@ function convertedAgentsKind(destSubpath, prefix, converterName, configDir, scop
|
|
|
156
156
|
kind: 'agents',
|
|
157
157
|
destSubpath,
|
|
158
158
|
prefix,
|
|
159
|
-
stage: (resolved) => {
|
|
159
|
+
stage: (resolved, agentCtx) => {
|
|
160
160
|
// isGlobal is threaded so scope-aware agent converters (copilot, antigravity)
|
|
161
161
|
// choose global-home vs workspace-relative paths; converters that only take
|
|
162
162
|
// (content) ignore the extra positional arg. Mirrors skillsKind's scope
|
|
163
163
|
// threading (#1173).
|
|
164
164
|
const converter = conversionExports[converterName];
|
|
165
|
-
|
|
165
|
+
// ADR-1235 §1: when agentCtx is provided (by createRuntimeArtifactInstallPlan
|
|
166
|
+
// for descriptor-driven runtimes), thread it through so stageAgentsForRuntimeWithConverter
|
|
167
|
+
// can apply the full pre-converter + post-converter sequence in the correct order.
|
|
168
|
+
return stageAgentsForRuntimeWithConverter(findAgentsSourceRoot(configDir), resolved, converter, scope === 'global', agentCtx);
|
|
166
169
|
},
|
|
167
170
|
};
|
|
168
171
|
}
|
|
@@ -84,7 +84,7 @@ function atomicWriteFileSync(target, data, options) {
|
|
|
84
84
|
__atomicWrittenTmps.add(tmp);
|
|
85
85
|
try {
|
|
86
86
|
node_fs_1.default.writeFileSync(tmp, data, options);
|
|
87
|
-
|
|
87
|
+
shellCmdProjection.retryRenameSync(tmp, target);
|
|
88
88
|
// Successful rename: the tmp path no longer exists, but leave it in the
|
|
89
89
|
// Set so _cleanTmpFiles can recognise it as installer-owned if it somehow
|
|
90
90
|
// lingers (e.g. a rename succeeded but left a stale entry on some FS).
|
|
@@ -19,6 +19,10 @@ exports.canonicalizeRuntimeName = canonicalizeRuntimeName;
|
|
|
19
19
|
exports.resolveRuntimeNameFromCandidates = resolveRuntimeNameFromCandidates;
|
|
20
20
|
exports.getProjectInstructionFile = getProjectInstructionFile;
|
|
21
21
|
exports.getDirName = getDirName;
|
|
22
|
+
exports.getRuntimeLabel = getRuntimeLabel;
|
|
23
|
+
exports.getGlobalConfigHomeFragment = getGlobalConfigHomeFragment;
|
|
24
|
+
exports.runtimeFlags = runtimeFlags;
|
|
25
|
+
exports.getRuntimeNewProjectCommand = getRuntimeNewProjectCommand;
|
|
22
26
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
23
27
|
const node_path_1 = __importDefault(require("node:path"));
|
|
24
28
|
const FALLBACK_ALIASES = {
|
|
@@ -152,35 +156,161 @@ function getProjectInstructionFile(runtime) {
|
|
|
152
156
|
* `bin/install.js` re-exports this same function for back-compat.
|
|
153
157
|
*/
|
|
154
158
|
function getDirName(runtime) {
|
|
155
|
-
if (runtime
|
|
156
|
-
return '.
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
return '.kilo';
|
|
163
|
-
if (runtime === 'codex')
|
|
164
|
-
return '.codex';
|
|
165
|
-
if (runtime === 'antigravity')
|
|
166
|
-
return '.agents';
|
|
167
|
-
if (runtime === 'cursor')
|
|
168
|
-
return '.cursor';
|
|
169
|
-
if (runtime === 'windsurf')
|
|
170
|
-
return '.windsurf';
|
|
171
|
-
if (runtime === 'augment')
|
|
172
|
-
return '.augment';
|
|
173
|
-
if (runtime === 'trae')
|
|
174
|
-
return '.trae';
|
|
175
|
-
if (runtime === 'qwen')
|
|
176
|
-
return '.qwen';
|
|
177
|
-
if (runtime === 'hermes')
|
|
178
|
-
return '.hermes';
|
|
179
|
-
if (runtime === 'kimi')
|
|
180
|
-
return '.kimi-code';
|
|
181
|
-
if (runtime === 'codebuddy')
|
|
182
|
-
return '.codebuddy';
|
|
183
|
-
if (runtime === 'cline')
|
|
184
|
-
return '.cline';
|
|
159
|
+
if (!runtime)
|
|
160
|
+
return '.claude';
|
|
161
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
162
|
+
const { runtimes } = require('./capability-registry.cjs');
|
|
163
|
+
const dir = runtimes[runtime]?.runtime?.localConfigDir;
|
|
164
|
+
if (typeof dir === 'string' && dir.length > 0)
|
|
165
|
+
return dir;
|
|
185
166
|
return '.claude';
|
|
186
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Curated short display labels for the install/uninstall console output, keyed
|
|
170
|
+
* by canonical runtime id. The SINGLE source of truth consumed by both
|
|
171
|
+
* `install()` and `uninstall()` in bin/install.js via `getRuntimeLabel`.
|
|
172
|
+
*
|
|
173
|
+
* Collapses the two duplicated `runtimeLabel` assignment chains that previously
|
|
174
|
+
* lived inline in bin/install.js (ADR-1239 Phase B, #1679) — the add-a-host tax:
|
|
175
|
+
* a new runtime meant remembering to add a label line in BOTH chains, and they
|
|
176
|
+
* had drifted out of sync (uninstall omitted `cline` and used a different
|
|
177
|
+
* `kimi` value than install). This table is the curated canonical resolution:
|
|
178
|
+
* - kimi: install 'Kimi' / uninstall 'Kimi CLI' → 'Kimi CLI' (majority + descriptor title)
|
|
179
|
+
* - cline: install 'Cline' / uninstall (omitted) → 'Cline' (majority + descriptor title)
|
|
180
|
+
*
|
|
181
|
+
* Voice: these are the SHORT UI labels, intentionally distinct from the
|
|
182
|
+
* descriptor `title` (the long product name — e.g. "OpenAI Codex CLI",
|
|
183
|
+
* "GitHub Copilot", "Gemini CLI") which serves documentation/registry display,
|
|
184
|
+
* not the install console. A future slice may relocate this to a
|
|
185
|
+
* `runtime.label` descriptor field; until then this table is the source.
|
|
186
|
+
*
|
|
187
|
+
* Lookup is RAW-ID only (no alias expansion) — callers pass an already-
|
|
188
|
+
* canonicalized runtime id, keeping the label surface explicit. Unknown/empty
|
|
189
|
+
* ids fall back to 'Claude Code' (the always-safe default, fail-closed).
|
|
190
|
+
*
|
|
191
|
+
* The drift-guard test (tests/runtime-label-policy.test.cjs) pins this table's
|
|
192
|
+
* id set to the capability-registry runtime id set, so adding/removing a runtime
|
|
193
|
+
* forces a deliberate update here.
|
|
194
|
+
*/
|
|
195
|
+
const RUNTIME_LABELS = {
|
|
196
|
+
claude: 'Claude Code',
|
|
197
|
+
opencode: 'OpenCode',
|
|
198
|
+
gemini: 'Gemini',
|
|
199
|
+
kilo: 'Kilo',
|
|
200
|
+
codex: 'Codex',
|
|
201
|
+
copilot: 'Copilot',
|
|
202
|
+
antigravity: 'Antigravity',
|
|
203
|
+
cursor: 'Cursor',
|
|
204
|
+
windsurf: 'Windsurf',
|
|
205
|
+
augment: 'Augment',
|
|
206
|
+
trae: 'Trae',
|
|
207
|
+
qwen: 'Qwen Code',
|
|
208
|
+
hermes: 'Hermes Agent',
|
|
209
|
+
kimi: 'Kimi CLI',
|
|
210
|
+
codebuddy: 'CodeBuddy',
|
|
211
|
+
cline: 'Cline',
|
|
212
|
+
};
|
|
213
|
+
/**
|
|
214
|
+
* Map a canonical runtime id to its short display label for the
|
|
215
|
+
* install/uninstall console output. Unknown/empty inputs fall back to
|
|
216
|
+
* 'Claude Code'. Sibling to `getDirName`; pure (no I/O).
|
|
217
|
+
*/
|
|
218
|
+
function getRuntimeLabel(runtime) {
|
|
219
|
+
if (!runtime)
|
|
220
|
+
return 'Claude Code';
|
|
221
|
+
const label = RUNTIME_LABELS[runtime];
|
|
222
|
+
return typeof label === 'string' && label.length > 0 ? label : 'Claude Code';
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Source-string fragments for the runtime → global config-home path, used by
|
|
226
|
+
* `getConfigDirFromHome` in bin/install.js to template `path.join()` calls in
|
|
227
|
+
* generated hook scripts. Each value is a JS-source snippet (embedded quotes /
|
|
228
|
+
* commas are intentional — it is spliced into generated code as path.join args).
|
|
229
|
+
*
|
|
230
|
+
* Collapses the prior 14-branch `if (runtime === 'x') return "'...'"` chain in
|
|
231
|
+
* bin/install.js (ADR-1239 Phase B / #1679, AC2 slice 2) — the add-a-host tax:
|
|
232
|
+
* a new runtime meant remembering to add a branch here. Values are preserved
|
|
233
|
+
* BYTE-FOR-BYTE from the prior chain; golden install parity asserts generated
|
|
234
|
+
* hook output is unchanged across all 16 runtimes.
|
|
235
|
+
*
|
|
236
|
+
* Two runtimes are intentionally absent (handled by the caller, NOT this table):
|
|
237
|
+
* - `claude` → the default; falls through to `DEFAULT_FRAGMENT`.
|
|
238
|
+
* - `antigravity`→ resolved dynamically via resolveAntigravityGlobalDir +
|
|
239
|
+
* path.relative (multi-segment, env-overridable).
|
|
240
|
+
*
|
|
241
|
+
* Unknown/empty ids fall back to the default (`.claude`).
|
|
242
|
+
*/
|
|
243
|
+
const DEFAULT_CONFIG_HOME_FRAGMENT = "'.claude'";
|
|
244
|
+
const GLOBAL_CONFIG_HOME_FRAGMENTS = {
|
|
245
|
+
copilot: "'.copilot'",
|
|
246
|
+
opencode: "'.config', 'opencode'",
|
|
247
|
+
gemini: "'.gemini'",
|
|
248
|
+
kilo: "'.config', 'kilo'",
|
|
249
|
+
codex: "'.codex'",
|
|
250
|
+
cursor: "'.cursor'",
|
|
251
|
+
windsurf: "'.windsurf'",
|
|
252
|
+
augment: "'.augment'",
|
|
253
|
+
trae: "'.trae'",
|
|
254
|
+
qwen: "'.qwen'",
|
|
255
|
+
hermes: "'.hermes'",
|
|
256
|
+
codebuddy: "'.codebuddy'",
|
|
257
|
+
cline: "'.cline'",
|
|
258
|
+
kimi: "'.config', 'agents'",
|
|
259
|
+
};
|
|
260
|
+
/**
|
|
261
|
+
* Return the global config-home path-fragment source snippet for a runtime
|
|
262
|
+
* (for hook path.join() codegen). `claude`/unknown/empty → the default
|
|
263
|
+
* `'.claude'` fragment. `antigravity` is NOT handled here (caller resolves it
|
|
264
|
+
* dynamically). Pure: no I/O. Sibling to `getDirName` / `getRuntimeLabel`.
|
|
265
|
+
*/
|
|
266
|
+
function getGlobalConfigHomeFragment(runtime) {
|
|
267
|
+
if (!runtime)
|
|
268
|
+
return DEFAULT_CONFIG_HOME_FRAGMENT;
|
|
269
|
+
const frag = GLOBAL_CONFIG_HOME_FRAGMENTS[runtime];
|
|
270
|
+
return typeof frag === 'string' && frag.length > 0 ? frag : DEFAULT_CONFIG_HOME_FRAGMENT;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* The runtime ids for which `bin/install.js` needs an `is<Runtime>` boolean
|
|
274
|
+
* predicate (every installed host that takes a non-claude install branch).
|
|
275
|
+
* Single source of truth — adding a runtime is one entry here, not a per-
|
|
276
|
+
* function declaration block (the add-a-host tax ADR-1239 Phase B / #1679 AC2
|
|
277
|
+
* removes).
|
|
278
|
+
*/
|
|
279
|
+
const RUNTIME_FLAG_IDS = Object.freeze([
|
|
280
|
+
'opencode', 'kilo', 'gemini', 'codex', 'copilot', 'antigravity', 'cursor',
|
|
281
|
+
'windsurf', 'augment', 'trae', 'qwen', 'hermes', 'codebuddy', 'cline', 'kimi',
|
|
282
|
+
]);
|
|
283
|
+
/**
|
|
284
|
+
* Return a frozen map of `is<Runtime>` boolean predicates for the given runtime
|
|
285
|
+
* id (e.g. `flags.isOpencode`). Collapses the four duplicated `const isX =
|
|
286
|
+
* runtime === 'x'` declaration blocks that lived in `bin/install.js`'s
|
|
287
|
+
* `uninstall`/`writeManifest`/`install`/etc. into one helper (sibling to
|
|
288
|
+
* `getDirName`/`getRuntimeLabel`). Pure: no I/O.
|
|
289
|
+
*/
|
|
290
|
+
function runtimeFlags(runtime) {
|
|
291
|
+
const flags = {};
|
|
292
|
+
for (const id of RUNTIME_FLAG_IDS) {
|
|
293
|
+
flags['is' + id.charAt(0).toUpperCase() + id.slice(1)] = runtime === id;
|
|
294
|
+
}
|
|
295
|
+
return Object.freeze(flags);
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* The `/gsd-new-project` invocation syntax per runtime — the post-install
|
|
299
|
+
* "next step" command string. Most runtimes use the default `/gsd-new-project`;
|
|
300
|
+
* a few hosts need a different surface syntax. Collapses the 14-line
|
|
301
|
+
* `if (runtime === 'x') command = ...` chain in bin/install.js's next-step
|
|
302
|
+
* message (ADR-1239 Phase B / #1679 AC2). Pure: no I/O.
|
|
303
|
+
*/
|
|
304
|
+
const DEFAULT_NEW_PROJECT_COMMAND = '/gsd-new-project';
|
|
305
|
+
const RUNTIME_NEW_PROJECT_COMMANDS = {
|
|
306
|
+
gemini: '/gsd:new-project',
|
|
307
|
+
codex: '$gsd-new-project',
|
|
308
|
+
cursor: 'gsd-new-project (mention the skill name)',
|
|
309
|
+
kimi: '/skill:gsd-new-project',
|
|
310
|
+
};
|
|
311
|
+
function getRuntimeNewProjectCommand(runtime) {
|
|
312
|
+
if (!runtime)
|
|
313
|
+
return DEFAULT_NEW_PROJECT_COMMAND;
|
|
314
|
+
const c = RUNTIME_NEW_PROJECT_COMMANDS[runtime];
|
|
315
|
+
return typeof c === 'string' && c.length > 0 ? c : DEFAULT_NEW_PROJECT_COMMAND;
|
|
316
|
+
}
|
|
@@ -40,6 +40,7 @@ exports.execNpm = execNpm;
|
|
|
40
40
|
exports.execTool = execTool;
|
|
41
41
|
exports.probeTty = probeTty;
|
|
42
42
|
exports.normalizeContent = normalizeContent;
|
|
43
|
+
exports.retryRenameSync = retryRenameSync;
|
|
43
44
|
exports.platformWriteSync = platformWriteSync;
|
|
44
45
|
exports.platformReadSync = platformReadSync;
|
|
45
46
|
exports.platformEnsureDir = platformEnsureDir;
|
|
@@ -575,6 +576,21 @@ function atomicRenameWithRetry(tmpPath, filePath) {
|
|
|
575
576
|
}
|
|
576
577
|
return renameErr;
|
|
577
578
|
}
|
|
579
|
+
/**
|
|
580
|
+
* Drop-in replacement for `fs.renameSync(from, to)` that retries the transient
|
|
581
|
+
* Windows lock errnos (EPERM/EBUSY/EACCES — see DEFECT.WINDOWS-FS-OPS) a bounded
|
|
582
|
+
* number of times with a short backoff before rethrowing the final error.
|
|
583
|
+
*
|
|
584
|
+
* Idempotent on POSIX (the transient errnos do not occur), so callers retain
|
|
585
|
+
* identical semantics on macOS/Linux while gaining resilience on Windows where
|
|
586
|
+
* an antivirus scanner, indexer, or concurrent reader may briefly hold the
|
|
587
|
+
* target open. Enforced by local/require-fs-op-fallback (ADR-1703 Phase 6).
|
|
588
|
+
*/
|
|
589
|
+
function retryRenameSync(fromPath, toPath) {
|
|
590
|
+
const err = atomicRenameWithRetry(fromPath, toPath);
|
|
591
|
+
if (err !== null)
|
|
592
|
+
throw err;
|
|
593
|
+
}
|
|
578
594
|
function platformWriteSync(filePath, content, opts = {}) {
|
|
579
595
|
const { content: normalized, encoding } = normalizeContent(filePath, content, opts);
|
|
580
596
|
node_fs_1.default.mkdirSync(node_path_1.default.dirname(filePath), { recursive: true });
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stale-bake guard for static-frontmatter runtimes (#1688, follow-up to #1650).
|
|
5
|
+
*
|
|
6
|
+
* Runtimes `codex` and `opencode` bake the resolved model ID into each agent's
|
|
7
|
+
* static config at install time (bin/install.js ~5667-5767 for codex,
|
|
8
|
+
* ~10008-10026 for opencode). Their task/spawn_agent interfaces do not accept
|
|
9
|
+
* an inline `model` parameter, so editing `model_overrides` in
|
|
10
|
+
* `.planning/config.json` or `~/.gsd/defaults.json` has NO effect until the
|
|
11
|
+
* user re-runs `gsd install <runtime>` (or `gsd update`). The failure is
|
|
12
|
+
* silent — the sub-agent just uses the prior base model. This module detects
|
|
13
|
+
* that staleness at workflow entry and emits a single stderr warning.
|
|
14
|
+
*
|
|
15
|
+
* Design: pure decision + formatter (testable, no I/O) backed by fs probes
|
|
16
|
+
* that swallow every error (the guard must never break the CLI). Dedup'd per
|
|
17
|
+
* (runtime, cwd) within a process so a single `gsd-tools init *` invocation
|
|
18
|
+
* warns at most once even though multiple agents resolve models underneath.
|
|
19
|
+
*/
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
const os = require('os');
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Runtimes whose agent config is static frontmatter/TOML baked at install time.
|
|
26
|
+
* MUST stay in sync with the bake paths in bin/install.js. The parity test in
|
|
27
|
+
* tests/stale-bake-guard.test.cjs asserts this matches the runtimes that
|
|
28
|
+
* actually emit a baked model: line id #2256 (opencode) and #49/#2256 (codex).
|
|
29
|
+
*/
|
|
30
|
+
const STATIC_FRONTMATTER_RUNTIMES = Object.freeze(['codex', 'opencode']);
|
|
31
|
+
|
|
32
|
+
const _warnedKeys = new Set();
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Pure: decide whether a stale-bake condition exists.
|
|
36
|
+
*
|
|
37
|
+
* Returns `{ stale: true, deltaMs }` when `configMtimeMs` is strictly newer
|
|
38
|
+
* than `agentMtimeMs` on a static-frontmatter runtime. Returns `null` when the
|
|
39
|
+
* guard does not apply (claude / other spawn-time runtime, missing or
|
|
40
|
+
* non-finite mtimes, or agents already at least as new as config).
|
|
41
|
+
*/
|
|
42
|
+
function detectStaleBake({ runtime, configMtimeMs, agentMtimeMs }) {
|
|
43
|
+
if (!runtime || !STATIC_FRONTMATTER_RUNTIMES.includes(runtime)) return null;
|
|
44
|
+
if (typeof configMtimeMs !== 'number' || typeof agentMtimeMs !== 'number') return null;
|
|
45
|
+
if (!Number.isFinite(configMtimeMs) || !Number.isFinite(agentMtimeMs)) return null;
|
|
46
|
+
if (configMtimeMs <= agentMtimeMs) return null;
|
|
47
|
+
return { stale: true, deltaMs: configMtimeMs - agentMtimeMs };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Pure: format the warning string. Returns `''` when no warning is warranted
|
|
52
|
+
* (delegates to detectStaleBake so the decision and the message cannot drift).
|
|
53
|
+
*/
|
|
54
|
+
function formatStaleBakeWarning({ runtime, configPath, configMtimeMs, agentMtimeMs }) {
|
|
55
|
+
const signal = detectStaleBake({ runtime, configMtimeMs, agentMtimeMs });
|
|
56
|
+
if (!signal) return '';
|
|
57
|
+
const configDate = new Date(configMtimeMs).toISOString();
|
|
58
|
+
const installFlag = runtime === 'opencode' ? '--opencode' : '--codex';
|
|
59
|
+
return [
|
|
60
|
+
`gsd: model config in ${configPath} changed since agents were last baked (${configDate}).`,
|
|
61
|
+
` Static-frontmatter runtime '${runtime}' ignores the new model_overrides`,
|
|
62
|
+
` until you re-run: gsd install ${installFlag}`,
|
|
63
|
+
` (or 'gsd update')`,
|
|
64
|
+
].join('\n');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Pure: resolve the active runtime id from a parsed config object.
|
|
69
|
+
* Returns the runtime string, or `'claude'` when unset (the spawn-time default
|
|
70
|
+
* for which the guard is a no-op).
|
|
71
|
+
*/
|
|
72
|
+
function resolveRuntimeFromConfig(config) {
|
|
73
|
+
if (config && typeof config === 'object'
|
|
74
|
+
&& typeof config.runtime === 'string' && config.runtime) {
|
|
75
|
+
return config.runtime;
|
|
76
|
+
}
|
|
77
|
+
return 'claude';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Resolve the install root for a runtime's agent files, honoring the same env
|
|
82
|
+
* vars the installer does (CODEX_HOME, OPENCODE_CONFIG_DIR). Returns the
|
|
83
|
+
* absolute directory or `null` for unsupported runtimes.
|
|
84
|
+
*/
|
|
85
|
+
function resolveAgentDir(runtime, { env = process.env, homedir = os.homedir } = {}) {
|
|
86
|
+
if (runtime === 'opencode') {
|
|
87
|
+
const base = (env.OPENCODE_CONFIG_DIR && String(env.OPENCODE_CONFIG_DIR).trim()) || path.join(homedir(), '.config', 'opencode');
|
|
88
|
+
return path.join(base, 'agent');
|
|
89
|
+
}
|
|
90
|
+
if (runtime === 'codex') {
|
|
91
|
+
const base = (env.CODEX_HOME && String(env.CODEX_HOME).trim()) || path.join(homedir(), '.codex');
|
|
92
|
+
return path.join(base, 'agents');
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Find the newest mtime across config sources that exist. Mirrors the
|
|
99
|
+
* up-to-8-levels-up walk in readGsdEffectiveModelOverrides (bin/install.js)
|
|
100
|
+
* and includes the global ~/.gsd/defaults.json. Returns
|
|
101
|
+
* `{ mtimeMs, path }` of the newest existing config, or `null` if none exist.
|
|
102
|
+
*
|
|
103
|
+
* `homedir` is injectable so tests can point the global lookup at a fixture
|
|
104
|
+
* dir (otherwise the real ~/.gsd/defaults.json on the CI runner leaks in and
|
|
105
|
+
* skews the newest-config calculation — see warnIfStaleBake orchestrator).
|
|
106
|
+
*/
|
|
107
|
+
function findNewestConfigMtime(cwd, { fsStatSync = fs.statSync, homedir = os.homedir } = {}) {
|
|
108
|
+
const candidates = [];
|
|
109
|
+
let probe = path.resolve(cwd || '.');
|
|
110
|
+
for (let i = 0; i < 8; i += 1) {
|
|
111
|
+
candidates.push(path.join(probe, '.planning', 'config.json'));
|
|
112
|
+
const parent = path.dirname(probe);
|
|
113
|
+
if (parent === probe) break;
|
|
114
|
+
probe = parent;
|
|
115
|
+
}
|
|
116
|
+
candidates.push(path.join(homedir(), '.gsd', 'defaults.json'));
|
|
117
|
+
|
|
118
|
+
let newest = null;
|
|
119
|
+
for (const p of candidates) {
|
|
120
|
+
try {
|
|
121
|
+
const st = fsStatSync(p);
|
|
122
|
+
if (st && typeof st.mtimeMs === 'number' && Number.isFinite(st.mtimeMs)
|
|
123
|
+
&& (!newest || st.mtimeMs > newest.mtimeMs)) {
|
|
124
|
+
newest = { mtimeMs: st.mtimeMs, path: p };
|
|
125
|
+
}
|
|
126
|
+
} catch {
|
|
127
|
+
// not present / unreadable — skip
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return newest;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Find the oldest mtime across installed gsd-* agent files for the runtime.
|
|
135
|
+
* Returns `{ mtimeMs, dir }` or `null` if the agent dir is absent or holds no
|
|
136
|
+
* gsd-* files (e.g. not yet installed, or uninstalled).
|
|
137
|
+
*/
|
|
138
|
+
function findOldestAgentMtime(runtime, { env = process.env, homedir = os.homedir, fsStatSync = fs.statSync, fsReaddirSync = fs.readdirSync } = {}) {
|
|
139
|
+
const dir = resolveAgentDir(runtime, { env, homedir });
|
|
140
|
+
if (!dir) return null;
|
|
141
|
+
let entries;
|
|
142
|
+
try {
|
|
143
|
+
entries = fsReaddirSync(dir, { withFileTypes: true });
|
|
144
|
+
} catch {
|
|
145
|
+
return null; // dir missing — runtime not installed for this user
|
|
146
|
+
}
|
|
147
|
+
let oldest = null;
|
|
148
|
+
for (const entry of entries) {
|
|
149
|
+
if (!entry.isFile()) continue;
|
|
150
|
+
if (!entry.name.startsWith('gsd-')) continue;
|
|
151
|
+
const isAgentFile = (runtime === 'opencode' && entry.name.endsWith('.md'))
|
|
152
|
+
|| (runtime === 'codex' && (entry.name.endsWith('.toml') || entry.name.endsWith('.md')));
|
|
153
|
+
if (!isAgentFile) continue;
|
|
154
|
+
try {
|
|
155
|
+
const st = fsStatSync(path.join(dir, entry.name));
|
|
156
|
+
if (st && typeof st.mtimeMs === 'number' && Number.isFinite(st.mtimeMs)
|
|
157
|
+
&& (!oldest || st.mtimeMs < oldest.mtimeMs)) {
|
|
158
|
+
oldest = { mtimeMs: st.mtimeMs, dir };
|
|
159
|
+
}
|
|
160
|
+
} catch {
|
|
161
|
+
// unreadable — skip
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return oldest;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Orchestrator (side-effecting): probe fs, decide, write warning to stderr.
|
|
169
|
+
*
|
|
170
|
+
* - Silent on claude / other spawn-time runtimes (returns false).
|
|
171
|
+
* - Silent when agents are already at least as new as config.
|
|
172
|
+
* - Silent when config or agent dir is absent (nothing to compare).
|
|
173
|
+
* - Dedup'd per (runtime, cwd): a single process warns at most once per pair,
|
|
174
|
+
* so repeated `resolveModelInternal` calls under one `gsd-tools init *` do
|
|
175
|
+
* not repeat the warning.
|
|
176
|
+
* - Swallows every error: a warning helper must never break the CLI.
|
|
177
|
+
*
|
|
178
|
+
* Pass `config` to skip the internal JSON read (caller already loaded it).
|
|
179
|
+
* Returns `true` if a warning was written, `false` otherwise.
|
|
180
|
+
*/
|
|
181
|
+
function warnIfStaleBake(cwd, options = {}) {
|
|
182
|
+
const {
|
|
183
|
+
stderr = process.stderr,
|
|
184
|
+
config = null,
|
|
185
|
+
env = process.env,
|
|
186
|
+
homedir = os.homedir,
|
|
187
|
+
fsStatSync = fs.statSync,
|
|
188
|
+
fsReaddirSync = fs.readdirSync,
|
|
189
|
+
} = options;
|
|
190
|
+
try {
|
|
191
|
+
const resolvedConfig = config || _readRuntimeConfig(cwd, { fsStatSync });
|
|
192
|
+
const runtime = resolveRuntimeFromConfig(resolvedConfig);
|
|
193
|
+
if (!STATIC_FRONTMATTER_RUNTIMES.includes(runtime)) return false;
|
|
194
|
+
|
|
195
|
+
const dedupKey = `${runtime}::${path.resolve(cwd || '.')}`;
|
|
196
|
+
if (_warnedKeys.has(dedupKey)) return false;
|
|
197
|
+
|
|
198
|
+
const newest = findNewestConfigMtime(cwd, { fsStatSync, homedir });
|
|
199
|
+
const oldest = findOldestAgentMtime(runtime, { env, homedir, fsStatSync, fsReaddirSync });
|
|
200
|
+
if (!newest || !oldest) return false;
|
|
201
|
+
|
|
202
|
+
const warning = formatStaleBakeWarning({
|
|
203
|
+
runtime,
|
|
204
|
+
configPath: newest.path,
|
|
205
|
+
configMtimeMs: newest.mtimeMs,
|
|
206
|
+
agentMtimeMs: oldest.mtimeMs,
|
|
207
|
+
});
|
|
208
|
+
if (!warning) return false;
|
|
209
|
+
|
|
210
|
+
stderr.write(warning + '\n');
|
|
211
|
+
_warnedKeys.add(dedupKey);
|
|
212
|
+
return true;
|
|
213
|
+
} catch {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Best-effort minimal read of `.planning/config.json` for the `runtime` key. */
|
|
219
|
+
function _readRuntimeConfig(cwd, { fsStatSync = fs.statSync } = {}) {
|
|
220
|
+
let probe = path.resolve(cwd || '.');
|
|
221
|
+
for (let i = 0; i < 8; i += 1) {
|
|
222
|
+
const candidate = path.join(probe, '.planning', 'config.json');
|
|
223
|
+
try {
|
|
224
|
+
fsStatSync(candidate);
|
|
225
|
+
const raw = fs.readFileSync(candidate, 'utf8');
|
|
226
|
+
const parsed = JSON.parse(raw);
|
|
227
|
+
if (parsed && typeof parsed === 'object') return parsed;
|
|
228
|
+
return {};
|
|
229
|
+
} catch {
|
|
230
|
+
// not present / unreadable / malformed — walk up
|
|
231
|
+
}
|
|
232
|
+
const parent = path.dirname(probe);
|
|
233
|
+
if (parent === probe) break;
|
|
234
|
+
probe = parent;
|
|
235
|
+
}
|
|
236
|
+
return {};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Test-only: reset the in-process dedup set between cases. */
|
|
240
|
+
function _resetWarnedForTests() {
|
|
241
|
+
_warnedKeys.clear();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
module.exports = {
|
|
245
|
+
STATIC_FRONTMATTER_RUNTIMES,
|
|
246
|
+
detectStaleBake,
|
|
247
|
+
formatStaleBakeWarning,
|
|
248
|
+
resolveRuntimeFromConfig,
|
|
249
|
+
resolveAgentDir,
|
|
250
|
+
findNewestConfigMtime,
|
|
251
|
+
findOldestAgentMtime,
|
|
252
|
+
warnIfStaleBake,
|
|
253
|
+
_resetWarnedForTests,
|
|
254
|
+
};
|
|
@@ -155,6 +155,10 @@ function routeStateCommand({ state, args, cwd, raw, error }) {
|
|
|
155
155
|
const a = (0, command_arg_projection_cjs_1.parseNamedArgs)(args, ['keep-recent'], ['dry-run']);
|
|
156
156
|
state.cmdStatePrune(cwd, { keepRecent: strArg(a, 'keep-recent') || '3', dryRun: a['dry-run'] === true }, raw);
|
|
157
157
|
},
|
|
158
|
+
rebuild: () => {
|
|
159
|
+
const a = (0, command_arg_projection_cjs_1.parseNamedArgs)(args, [], ['dry-run', 'verbose']);
|
|
160
|
+
state.cmdStateRebuild(cwd, { dryRun: a['dry-run'] === true, verbose: a['verbose'] === true }, raw);
|
|
161
|
+
},
|
|
158
162
|
// complete-phase: CJS-only — no SDK counterpart.
|
|
159
163
|
'complete-phase': () => {
|
|
160
164
|
const a = (0, command_arg_projection_cjs_1.parseNamedArgs)(args, ['phase']);
|