@opengsd/gsd-core 1.7.0-rc.2 → 1.7.0-rc.3
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 +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/GEMINI.md +25 -23
- package/README.md +3 -3
- package/agents/gsd-advisor-researcher.md +1 -1
- package/agents/gsd-ai-researcher.md +1 -1
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-debug-session-manager.md +1 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-domain-researcher.md +1 -1
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +4 -4
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +1 -1
- package/agents/gsd-phase-researcher.md +2 -2
- package/agents/gsd-plan-checker.md +1 -1
- package/agents/gsd-planner.md +2 -2
- package/agents/gsd-project-researcher.md +2 -2
- package/agents/gsd-research-synthesizer.md +1 -1
- package/agents/gsd-ui-researcher.md +2 -2
- package/agents/gsd-verifier.md +2 -2
- package/bin/install.js +108 -458
- package/commands/gsd/mempalace-capture.md +1 -1
- package/commands/gsd/mempalace-recall.md +9 -5
- package/commands/gsd/next.md +29 -0
- package/commands/gsd/ns-workflow.md +5 -2
- package/gsd-core/bin/ensure-runtime-build.cjs +246 -0
- package/gsd-core/bin/gsd-tools.cjs +25 -1
- package/gsd-core/bin/lib/capability-registry.cjs +207 -205
- package/gsd-core/bin/lib/check-command-router.cjs +111 -1
- package/gsd-core/bin/lib/clusters.cjs +1 -0
- package/gsd-core/bin/lib/config.cjs +19 -1
- package/gsd-core/bin/lib/core-utils.cjs +41 -0
- package/gsd-core/bin/lib/external-job.cjs +287 -0
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +151 -0
- package/gsd-core/bin/lib/graphify.cjs +41 -9
- package/gsd-core/bin/lib/milestone.cjs +7 -1
- package/gsd-core/bin/lib/phase.cjs +14 -2
- package/gsd-core/bin/lib/plan-scan.cjs +9 -1
- package/gsd-core/bin/lib/profile-output.cjs +1 -1
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +21 -19
- package/gsd-core/bin/lib/roadmap.cjs +17 -2
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +18 -13
- package/gsd-core/bin/lib/runtime-name-policy.cjs +8 -10
- package/gsd-core/bin/lib/shell-command-projection.cjs +12 -8
- package/gsd-core/bin/lib/smart-entry.cjs +521 -0
- package/gsd-core/bin/lib/surface.cjs +18 -9
- package/gsd-core/bin/lib/update-context.cjs +0 -5
- package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
- package/gsd-core/bin/shared/model-catalog.json +0 -5
- package/gsd-core/bin/shared/runtime-aliases.manifest.json +0 -5
- package/gsd-core/references/gates.md +1 -1
- package/gsd-core/references/planning-config.md +1 -1
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +1 -1
- package/gsd-core/workflows/add-phase.md +1 -1
- package/gsd-core/workflows/add-tests.md +1 -1
- package/gsd-core/workflows/add-todo.md +1 -1
- package/gsd-core/workflows/ai-integration-phase.md +1 -1
- package/gsd-core/workflows/audit-fix.md +1 -1
- package/gsd-core/workflows/audit-milestone.md +1 -1
- package/gsd-core/workflows/audit-uat.md +1 -1
- package/gsd-core/workflows/autonomous.md +1 -1
- package/gsd-core/workflows/check-todos.md +1 -1
- package/gsd-core/workflows/cleanup.md +1 -1
- package/gsd-core/workflows/code-review-fix.md +1 -1
- package/gsd-core/workflows/code-review.md +1 -1
- package/gsd-core/workflows/complete-milestone.md +1 -1
- package/gsd-core/workflows/debug.md +1 -1
- package/gsd-core/workflows/diagnose-issues.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/auto.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
- package/gsd-core/workflows/discuss-phase.md +2 -2
- package/gsd-core/workflows/do.md +1 -1
- package/gsd-core/workflows/docs-update.md +1 -1
- package/gsd-core/workflows/edit-phase.md +1 -1
- package/gsd-core/workflows/eval-review.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +1 -1
- package/gsd-core/workflows/execute-phase.md +4 -5
- package/gsd-core/workflows/execute-plan.md +1 -1
- package/gsd-core/workflows/explore.md +1 -1
- package/gsd-core/workflows/extract-learnings.md +1 -1
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +1 -1
- package/gsd-core/workflows/help/modes/full.md +14 -0
- package/gsd-core/workflows/help/modes/topic.md +1 -0
- package/gsd-core/workflows/import.md +1 -1
- package/gsd-core/workflows/ingest-docs.md +1 -1
- package/gsd-core/workflows/insert-phase.md +1 -1
- package/gsd-core/workflows/list-seeds.md +1 -1
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +1 -1
- package/gsd-core/workflows/map-codebase.md +1 -1
- package/gsd-core/workflows/milestone-summary.md +1 -1
- package/gsd-core/workflows/mvp-phase.md +1 -1
- package/gsd-core/workflows/new-milestone.md +1 -1
- package/gsd-core/workflows/new-project.md +1 -1
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +1 -1
- package/gsd-core/workflows/pause-work.md +1 -1
- package/gsd-core/workflows/plan-milestone-gaps.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase.md +9 -2
- package/gsd-core/workflows/plan-review-convergence.md +1 -1
- package/gsd-core/workflows/plant-seed.md +1 -1
- package/gsd-core/workflows/pr-branch.md +1 -1
- package/gsd-core/workflows/profile-user.md +1 -1
- package/gsd-core/workflows/progress.md +1 -1
- package/gsd-core/workflows/quick.md +1 -1
- package/gsd-core/workflows/reapply-patches.md +1 -1
- package/gsd-core/workflows/remove-phase.md +1 -1
- package/gsd-core/workflows/remove-workspace.md +1 -1
- package/gsd-core/workflows/resume-project.md +1 -1
- package/gsd-core/workflows/review.md +43 -7
- package/gsd-core/workflows/scan.md +1 -1
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/settings-advanced.md +3 -1
- package/gsd-core/workflows/settings-integrations.md +1 -1
- package/gsd-core/workflows/settings.md +1 -1
- package/gsd-core/workflows/ship.md +1 -1
- package/gsd-core/workflows/sketch-wrap-up.md +1 -1
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +120 -0
- package/gsd-core/workflows/spec-phase.md +8 -6
- package/gsd-core/workflows/spike-wrap-up.md +1 -1
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +1 -1
- package/gsd-core/workflows/thread.md +1 -1
- package/gsd-core/workflows/transition.md +1 -1
- package/gsd-core/workflows/ui-phase.md +1 -1
- package/gsd-core/workflows/ui-review.md +1 -1
- package/gsd-core/workflows/ultraplan-phase.md +1 -1
- package/gsd-core/workflows/update.md +3 -6
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-phase.md +1 -1
- package/gsd-core/workflows/verify-work.md +62 -4
- package/package.json +3 -5
- package/scripts/ci-test-scope.cjs +19 -1
- package/scripts/generate-package-identity.cjs +27 -2
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -148
- package/scripts/lint-regression-test-names.allowlist.json +1 -273
- package/scripts/lint-test-file-count.allowlist.json +7 -91
- package/scripts/research-profiles.cjs +6 -6
- package/scripts/run-tests.cjs +16 -3
- package/scripts/slurm-adapter.cjs +269 -0
- package/scripts/sync-manifest-versions.cjs +3 -2
- package/skills/gsd-mempalace-capture/SKILL.md +1 -1
- package/skills/gsd-mempalace-recall/SKILL.md +9 -5
- package/skills/gsd-next/SKILL.md +30 -0
- package/skills/gsd-ns-workflow/SKILL.md +4 -1
- package/gemini-extension.json +0 -6
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Gate Predicate Evaluator — issue #2008 / ADR-2008
|
|
4
|
+
*
|
|
5
|
+
* Pure, deps-injected evaluator for capability gate `check.predicate` blocks.
|
|
6
|
+
*
|
|
7
|
+
* Background: the loop-resolver renders active gate hooks (carrying their
|
|
8
|
+
* `check.predicate` declarations) but, prior to #2008, nothing EVALUATED a
|
|
9
|
+
* declared predicate for non-built-in capabilities — `check.query` was the only
|
|
10
|
+
* enforced shape (dispatched via `gsd_run check <query>`), and `check.predicate`
|
|
11
|
+
* was declaration-only. This module is the generic evaluation path.
|
|
12
|
+
*
|
|
13
|
+
* The workflow gate-dispatch calls this evaluator (via the `gsd_run check predicate`
|
|
14
|
+
* subcommand in check-command-router) for any gate whose `check` carries a
|
|
15
|
+
* `predicate` instead of a `query`. The evaluator dispatches by `predicate.kind`
|
|
16
|
+
* and returns the existing `{ block, message }` gate contract. A THROWN error
|
|
17
|
+
* (malformed predicate / unknown kind) is mapped by the CLI wrapper to a
|
|
18
|
+
* non-zero check-command exit, which the workflow's two-step gate contract treats
|
|
19
|
+
* as a step-1 command failure (routed per the gate's `onError`).
|
|
20
|
+
*
|
|
21
|
+
* Built-in kind (v1): `command-exit-zero` — run a declared command in a bounded
|
|
22
|
+
* `sh -c` subprocess at the project root, inheriting the process env; exit 0 =>
|
|
23
|
+
* pass, non-zero => block, timeout => block. The production runBoundedShell
|
|
24
|
+
* binding is shell-command-projection.execTool (bounded spawnSync). See ADR-2008
|
|
25
|
+
* for the full sandbox contract.
|
|
26
|
+
*
|
|
27
|
+
* This is a leaf pure module: no fs, no child_process, no config — the subprocess
|
|
28
|
+
* seam is injected so the evaluator is trivially testable without spawning.
|
|
29
|
+
*/
|
|
30
|
+
// ─── Public constants ─────────────────────────────────────────────────────────
|
|
31
|
+
/** Default command timeout for `command-exit-zero` (30s). Matches execTool default. */
|
|
32
|
+
const COMMAND_EXIT_ZERO_DEFAULT_TIMEOUT_MS = 30_000;
|
|
33
|
+
/** Hard cap on the stderr/stdout tail embedded in the gate `message`. */
|
|
34
|
+
const COMMAND_MAX_OUTPUT_CHARS = 2000;
|
|
35
|
+
/** Hard cap on the declared command length (defense-in-depth against ARGV overflow / abuse). */
|
|
36
|
+
const COMMAND_MAX_LENGTH = 4096;
|
|
37
|
+
/** Predicate kinds this evaluator recognises (extensible — add to KIND_TABLE). */
|
|
38
|
+
const EVALUATOR_KINDS = Object.freeze(['command-exit-zero']);
|
|
39
|
+
/** Placeholders interpolated into a declared command, in addition to sh's own vars. */
|
|
40
|
+
const INTERPOLATION_VAR_NAMES = Object.freeze(['PHASE_NUMBER', 'PHASE_DIR', 'PHASE_REQ_IDS']);
|
|
41
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
42
|
+
const INTERPOLATION_RE = /\$\{(PHASE_NUMBER|PHASE_DIR|PHASE_REQ_IDS)\}/g;
|
|
43
|
+
/** Replace the three known ${PHASE_*} placeholders with context values (undefined => ''). */
|
|
44
|
+
function interpolate(command, ctx) {
|
|
45
|
+
return command.replace(INTERPOLATION_RE, (_whole, name) => {
|
|
46
|
+
if (name === 'PHASE_NUMBER')
|
|
47
|
+
return ctx.phaseNumber ?? '';
|
|
48
|
+
if (name === 'PHASE_DIR')
|
|
49
|
+
return ctx.phaseDir ?? '';
|
|
50
|
+
if (name === 'PHASE_REQ_IDS')
|
|
51
|
+
return ctx.phaseReqIds ?? '';
|
|
52
|
+
return '';
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
/** Cap a string at COMMAND_MAX_OUTPUT_CHARS so gate messages stay context-bounded. */
|
|
56
|
+
function trimToMax(s) {
|
|
57
|
+
return s.length > COMMAND_MAX_OUTPUT_CHARS ? s.slice(0, COMMAND_MAX_OUTPUT_CHARS) : s;
|
|
58
|
+
}
|
|
59
|
+
function isNonEmptyString(v) {
|
|
60
|
+
return typeof v === 'string' && v.trim().length > 0;
|
|
61
|
+
}
|
|
62
|
+
// ─── Kind: command-exit-zero ──────────────────────────────────────────────────
|
|
63
|
+
function evaluateCommandExitZero(predicate, ctx, deps) {
|
|
64
|
+
const command = predicate['command'];
|
|
65
|
+
if (!isNonEmptyString(command)) {
|
|
66
|
+
throw new Error('command-exit-zero predicate requires a non-empty string "command"');
|
|
67
|
+
}
|
|
68
|
+
if (command.length > COMMAND_MAX_LENGTH) {
|
|
69
|
+
throw new Error(`command-exit-zero predicate "command" exceeds max length ${COMMAND_MAX_LENGTH}`);
|
|
70
|
+
}
|
|
71
|
+
let timeoutMs = COMMAND_EXIT_ZERO_DEFAULT_TIMEOUT_MS;
|
|
72
|
+
const rawTimeout = predicate['timeout'];
|
|
73
|
+
if (rawTimeout !== undefined) {
|
|
74
|
+
if (typeof rawTimeout !== 'number' || !Number.isFinite(rawTimeout) || rawTimeout <= 0) {
|
|
75
|
+
throw new Error('command-exit-zero predicate "timeout" must be a positive finite number (seconds)');
|
|
76
|
+
}
|
|
77
|
+
timeoutMs = Math.floor(rawTimeout * 1000);
|
|
78
|
+
}
|
|
79
|
+
const interpolated = interpolate(command, ctx);
|
|
80
|
+
const res = deps.runBoundedShell({ command: interpolated, cwd: ctx.cwd, timeoutMs });
|
|
81
|
+
if (res.timedOut) {
|
|
82
|
+
return {
|
|
83
|
+
block: true,
|
|
84
|
+
message: trimToMax(`command timed out after ${Math.round(timeoutMs / 1000)}s: ${res.stderr || interpolated}`),
|
|
85
|
+
details: { kind: 'command-exit-zero', timedOut: true, signal: res.signal },
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
if (res.exitCode === 0) {
|
|
89
|
+
return {
|
|
90
|
+
block: false,
|
|
91
|
+
message: 'command exited 0',
|
|
92
|
+
details: { kind: 'command-exit-zero', exitCode: 0 },
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
// Non-zero (incl. null exit from a signal kill) => block. Surface code + stderr/stdout tail.
|
|
96
|
+
const code = res.exitCode === null ? '<none>' : String(res.exitCode);
|
|
97
|
+
const tail = trimToMax(res.stderr || res.stdout || '');
|
|
98
|
+
const message = tail ? `command exited ${code}: ${tail}` : `command exited ${code}`;
|
|
99
|
+
return {
|
|
100
|
+
block: true,
|
|
101
|
+
message: trimToMax(message),
|
|
102
|
+
details: { kind: 'command-exit-zero', exitCode: res.exitCode, signal: res.signal },
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
// ─── Kind dispatch table ──────────────────────────────────────────────────────
|
|
106
|
+
const KIND_TABLE = {
|
|
107
|
+
'command-exit-zero': evaluateCommandExitZero,
|
|
108
|
+
};
|
|
109
|
+
// ─── Public entry point ───────────────────────────────────────────────────────
|
|
110
|
+
/**
|
|
111
|
+
* Evaluate a capability gate `check.predicate`.
|
|
112
|
+
*
|
|
113
|
+
* Returns `{ block, message }` for any successfully-recognised predicate.
|
|
114
|
+
* THROWS for a malformed predicate, missing context/deps, or unknown `kind` —
|
|
115
|
+
* the CLI wrapper converts a throw into a non-zero check-command exit so the
|
|
116
|
+
* workflow's `onError` (step-1) contract applies. This keeps the gate fail-closed
|
|
117
|
+
* without conflating an evaluator bug with a legitimate gate-block decision.
|
|
118
|
+
*/
|
|
119
|
+
function evaluatePredicate(predicate, context, deps) {
|
|
120
|
+
if (!predicate || typeof predicate !== 'object' || Array.isArray(predicate)) {
|
|
121
|
+
throw new Error('predicate must be an object');
|
|
122
|
+
}
|
|
123
|
+
const ctx = context;
|
|
124
|
+
if (!ctx || typeof ctx.cwd !== 'string' || ctx.cwd.length === 0) {
|
|
125
|
+
throw new Error('predicate context requires a non-empty "cwd"');
|
|
126
|
+
}
|
|
127
|
+
const d = deps;
|
|
128
|
+
if (!d || typeof d.runBoundedShell !== 'function') {
|
|
129
|
+
throw new Error('predicate deps require a "runBoundedShell" function');
|
|
130
|
+
}
|
|
131
|
+
const pred = predicate;
|
|
132
|
+
const kind = pred['kind'];
|
|
133
|
+
if (typeof kind !== 'string' || kind.length === 0) {
|
|
134
|
+
throw new Error('predicate.kind must be a non-empty string');
|
|
135
|
+
}
|
|
136
|
+
const handler = KIND_TABLE[kind];
|
|
137
|
+
if (!handler) {
|
|
138
|
+
throw new Error(`Unknown predicate kind: "${kind}". Known kinds: ${EVALUATOR_KINDS.join(', ')}`);
|
|
139
|
+
}
|
|
140
|
+
return handler(pred, ctx, d);
|
|
141
|
+
}
|
|
142
|
+
module.exports = {
|
|
143
|
+
evaluatePredicate,
|
|
144
|
+
evaluateCommandExitZero,
|
|
145
|
+
interpolate,
|
|
146
|
+
COMMAND_EXIT_ZERO_DEFAULT_TIMEOUT_MS,
|
|
147
|
+
COMMAND_MAX_OUTPUT_CHARS,
|
|
148
|
+
COMMAND_MAX_LENGTH,
|
|
149
|
+
EVALUATOR_KINDS,
|
|
150
|
+
INTERPOLATION_VAR_NAMES,
|
|
151
|
+
};
|
|
@@ -261,6 +261,29 @@ function countCommitsBetween(cwd, from, to) {
|
|
|
261
261
|
const n = parseInt(r.stdout.trim(), 10);
|
|
262
262
|
return Number.isFinite(n) ? n : null;
|
|
263
263
|
}
|
|
264
|
+
// ─── Graph location resolution (#1825) ───────────────────────────────────────
|
|
265
|
+
//
|
|
266
|
+
// `graphify.graph_path` (in .planning/config.json) lets one umbrella-level graph
|
|
267
|
+
// serve multiple sibling projects without a per-project mirror. When set, query /
|
|
268
|
+
// status / diff read the configured graph, and the diff snapshot travels with it
|
|
269
|
+
// (alongside graph.json). When unset/blank/non-string, behaviour is byte-identical
|
|
270
|
+
// to the historical `<planningDir>/graphs/graph.json`.
|
|
271
|
+
const GRAPH_FILENAME = 'graph.json';
|
|
272
|
+
const SNAPSHOT_FILENAME = '.last-build-snapshot.json';
|
|
273
|
+
/**
|
|
274
|
+
* Resolve the absolute graph.json location. Honors `graphify.graph_path` in
|
|
275
|
+
* config.json (resolved relative to the project root, `cwd`); falls back to the
|
|
276
|
+
* default `<planningDir>/graphs/graph.json` when unset/blank/non-string.
|
|
277
|
+
*/
|
|
278
|
+
function resolveGraphLocation(cwd, planningDir) {
|
|
279
|
+
const config = safeReadJson(node_path_1.default.join(planningDir, 'config.json'));
|
|
280
|
+
const graphify = (config && config['graphify']);
|
|
281
|
+
const configuredValue = graphify && graphify['graph_path'];
|
|
282
|
+
if (typeof configuredValue === 'string' && configuredValue.trim().length > 0) {
|
|
283
|
+
return { graphPath: node_path_1.default.resolve(cwd, configuredValue), configured: true };
|
|
284
|
+
}
|
|
285
|
+
return { graphPath: node_path_1.default.join(planningDir, 'graphs', GRAPH_FILENAME), configured: false };
|
|
286
|
+
}
|
|
264
287
|
/**
|
|
265
288
|
* Query the knowledge graph for nodes matching a term, with optional budget cap.
|
|
266
289
|
* Uses seed-then-expand BFS traversal (D-01).
|
|
@@ -269,9 +292,11 @@ function graphifyQuery(cwd, term, options = {}) {
|
|
|
269
292
|
const planningDir = node_path_1.default.join(cwd, '.planning');
|
|
270
293
|
if (!isCapabilityActive('graphify', cwd))
|
|
271
294
|
return disabledResponse();
|
|
272
|
-
const graphPath =
|
|
295
|
+
const { graphPath, configured } = resolveGraphLocation(cwd, planningDir);
|
|
273
296
|
if (!node_fs_1.default.existsSync(graphPath)) {
|
|
274
|
-
return { error:
|
|
297
|
+
return { error: configured
|
|
298
|
+
? `Configured graph not found at ${graphPath}. Set graphify.graph_path or run /gsd:graphify build.`
|
|
299
|
+
: 'No graph built yet. Run graphify build first.' };
|
|
275
300
|
}
|
|
276
301
|
const graph = safeReadJson(graphPath);
|
|
277
302
|
if (!graph) {
|
|
@@ -303,9 +328,11 @@ function graphifyStatus(cwd) {
|
|
|
303
328
|
const planningDir = node_path_1.default.join(cwd, '.planning');
|
|
304
329
|
if (!isCapabilityActive('graphify', cwd))
|
|
305
330
|
return disabledResponse();
|
|
306
|
-
const graphPath =
|
|
331
|
+
const { graphPath, configured } = resolveGraphLocation(cwd, planningDir);
|
|
307
332
|
if (!node_fs_1.default.existsSync(graphPath)) {
|
|
308
|
-
return { exists: false, message:
|
|
333
|
+
return { exists: false, message: configured
|
|
334
|
+
? `Configured graph not found at ${graphPath}. Set graphify.graph_path or run /gsd:graphify build.`
|
|
335
|
+
: 'No graph built yet. Run graphify build to create one.' };
|
|
309
336
|
}
|
|
310
337
|
const stat = node_fs_1.default.statSync(graphPath);
|
|
311
338
|
const graph = safeReadJson(graphPath);
|
|
@@ -360,8 +387,8 @@ function graphifyDiff(cwd) {
|
|
|
360
387
|
const planningDir = node_path_1.default.join(cwd, '.planning');
|
|
361
388
|
if (!isCapabilityActive('graphify', cwd))
|
|
362
389
|
return disabledResponse();
|
|
363
|
-
const
|
|
364
|
-
const
|
|
390
|
+
const { graphPath } = resolveGraphLocation(cwd, planningDir);
|
|
391
|
+
const snapshotPath = node_path_1.default.join(node_path_1.default.dirname(graphPath), SNAPSHOT_FILENAME);
|
|
365
392
|
if (!node_fs_1.default.existsSync(snapshotPath)) {
|
|
366
393
|
return { no_baseline: true, message: 'No previous snapshot. Run graphify build first, then build again to generate a diff baseline.' };
|
|
367
394
|
}
|
|
@@ -405,7 +432,11 @@ function graphifyBuild(cwd) {
|
|
|
405
432
|
if (!installed.installed)
|
|
406
433
|
return { error: installed.message };
|
|
407
434
|
const version = checkGraphifyVersion();
|
|
408
|
-
// Ensure output directory exists (D-05)
|
|
435
|
+
// Ensure output directory exists (D-05). Build stays project-scoped: the build
|
|
436
|
+
// skill cp's artifacts into `<planningDir>/graphs/` regardless of graph_path, so
|
|
437
|
+
// graphs_dir reflects that real destination (not the configured read location).
|
|
438
|
+
// A shared umbrella graph is built in the umbrella project; sub-projects only
|
|
439
|
+
// READ it via graphify.graph_path (#1825).
|
|
409
440
|
const graphsDir = node_path_1.default.join(planningDir, 'graphs');
|
|
410
441
|
node_fs_1.default.mkdirSync(graphsDir, { recursive: true });
|
|
411
442
|
// Read build timeout from config -- default 300s per D-02
|
|
@@ -428,7 +459,8 @@ function graphifyBuild(cwd) {
|
|
|
428
459
|
* using platformWriteSync for crash safety.
|
|
429
460
|
*/
|
|
430
461
|
function writeSnapshot(cwd) {
|
|
431
|
-
const
|
|
462
|
+
const planningDir = node_path_1.default.join(cwd, '.planning');
|
|
463
|
+
const { graphPath } = resolveGraphLocation(cwd, planningDir);
|
|
432
464
|
const graph = safeReadJson(graphPath);
|
|
433
465
|
if (!graph)
|
|
434
466
|
return { error: 'Cannot write snapshot: graph.json not parseable' };
|
|
@@ -438,7 +470,7 @@ function writeSnapshot(cwd) {
|
|
|
438
470
|
nodes: graph.nodes || [],
|
|
439
471
|
edges: graph.edges || graph.links || [],
|
|
440
472
|
};
|
|
441
|
-
const snapshotPath = node_path_1.default.join(
|
|
473
|
+
const snapshotPath = node_path_1.default.join(node_path_1.default.dirname(graphPath), SNAPSHOT_FILENAME);
|
|
442
474
|
(0, shell_command_projection_cjs_1.platformWriteSync)(snapshotPath, JSON.stringify(snapshot, null, 2));
|
|
443
475
|
return {
|
|
444
476
|
saved: true,
|
|
@@ -263,7 +263,13 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
263
263
|
// Archive REQUIREMENTS.md
|
|
264
264
|
if (node_fs_1.default.existsSync(reqPath)) {
|
|
265
265
|
const reqContent = node_fs_1.default.readFileSync(reqPath, 'utf-8');
|
|
266
|
-
|
|
266
|
+
// Derive the display path from the same source the writer uses (reqPath), so a
|
|
267
|
+
// workstream archive header points at `.planning/workstreams/<ws>/REQUIREMENTS.md`
|
|
268
|
+
// instead of the hardcoded root path (#1993). Root case is byte-identical.
|
|
269
|
+
// Normalize to POSIX separators so the header is cross-platform (Windows
|
|
270
|
+
// path.relative yields backslashes; the original literal was forward-slash).
|
|
271
|
+
const reqDisplay = node_path_1.default.relative(cwd, reqPath).split(node_path_1.default.sep).join('/');
|
|
272
|
+
const archiveHeader = `# Requirements Archive: ${version} ${milestoneName}\n\n**Archived:** ${today}\n**Status:** SHIPPED\n\nFor current requirements, see \`${reqDisplay}\`.\n\n---\n\n`;
|
|
267
273
|
(0, shell_command_projection_cjs_1.platformWriteSync)(node_path_1.default.join(archiveDir, `${version}-REQUIREMENTS.md`), archiveHeader + reqContent);
|
|
268
274
|
}
|
|
269
275
|
// Archive audit file if exists
|
|
@@ -1135,7 +1135,10 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
1135
1135
|
const checkboxPattern = new RegExp(`(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${phaseEscaped}${OPTIONAL_PHASE_TAG_SOURCE}[:\\s][^\\n]*)`, 'i');
|
|
1136
1136
|
roadmapContent = roadmapContent.replace(checkboxPattern, `$1x$2 (completed ${today})`);
|
|
1137
1137
|
const tableRowPattern = new RegExp(`^(\\|\\s*${phaseEscaped}\\.?\\s[^|]*(?:\\|[^\\n]*))$`, 'im');
|
|
1138
|
-
|
|
1138
|
+
// Scope the Progress-row search to the ## Progress section so the regex
|
|
1139
|
+
// doesn't bind to an earlier table (e.g. | Phase | Requirements | Count |)
|
|
1140
|
+
// whose rows also start with the phase number. (#2012)
|
|
1141
|
+
const updateProgressRow = (fullRow) => {
|
|
1139
1142
|
const cells = fullRow.split('|').slice(1, -1);
|
|
1140
1143
|
const dateShape = /^\d{4}-\d{2}-\d{2}$/;
|
|
1141
1144
|
if (cells.length === 5) {
|
|
@@ -1153,7 +1156,16 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
1153
1156
|
cells[3] = dateShape.test(existingDate4) ? cells[3] : ` ${today} `;
|
|
1154
1157
|
}
|
|
1155
1158
|
return '|' + cells.join('|') + '|';
|
|
1156
|
-
}
|
|
1159
|
+
};
|
|
1160
|
+
const progressIdx = roadmapContent.indexOf('## Progress');
|
|
1161
|
+
if (progressIdx >= 0) {
|
|
1162
|
+
const beforeProgress = roadmapContent.slice(0, progressIdx);
|
|
1163
|
+
const progressSection = roadmapContent.slice(progressIdx);
|
|
1164
|
+
roadmapContent = beforeProgress + progressSection.replace(tableRowPattern, updateProgressRow);
|
|
1165
|
+
}
|
|
1166
|
+
else {
|
|
1167
|
+
roadmapContent = roadmapContent.replace(tableRowPattern, updateProgressRow);
|
|
1168
|
+
}
|
|
1157
1169
|
const planCountPattern = new RegExp(`(#{2,4}\\s*Phase\\s+${phaseEscaped}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`, 'i');
|
|
1158
1170
|
roadmapContent = roadmapContent.replace(planCountPattern, `$1${summaryCount}/${planCount} plans complete`);
|
|
1159
1171
|
const phaseInfoSummaries = phaseInfo['summaries'];
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
*/
|
|
10
10
|
const node_fs_1 = require("node:fs");
|
|
11
11
|
const node_path_1 = require("node:path");
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
13
|
+
const coreUtils = require("./core-utils.cjs");
|
|
14
|
+
const { countMatchedSummaries } = coreUtils;
|
|
12
15
|
// Excluded derivative files
|
|
13
16
|
const PLAN_OUTLINE_RE = /-OUTLINE\.md$/i;
|
|
14
17
|
const PLAN_PRE_BOUNCE_RE = /\.pre-bounce\.md$/i;
|
|
@@ -72,7 +75,12 @@ function scanPhasePlans(phaseDir) {
|
|
|
72
75
|
const planFiles = rootPlanFiles.concat(nestedPlanFiles);
|
|
73
76
|
const summaryFiles = rootSummaryFiles.concat(nestedSummaryFiles);
|
|
74
77
|
const planCount = planFiles.length;
|
|
75
|
-
|
|
78
|
+
// Count only summaries that are the PLAN→SUMMARY partner of an existing plan
|
|
79
|
+
// (#1988): stray non-plan summaries (e.g. 30-FIX-CR02-SUMMARY.md,
|
|
80
|
+
// 30-GAPCLOSURE-SUMMARY.md) must not inflate summary_count or flip a phase to
|
|
81
|
+
// Complete when plans are still missing summaries. summaryFiles (the array)
|
|
82
|
+
// still holds every summary on disk for callers that read/list them.
|
|
83
|
+
const summaryCount = countMatchedSummaries(planFiles, summaryFiles);
|
|
76
84
|
return {
|
|
77
85
|
planCount,
|
|
78
86
|
summaryCount,
|
|
@@ -1024,7 +1024,7 @@ function cmdGenerateClaudeMd(cwd, options, raw) {
|
|
|
1024
1024
|
// project-instruction-file`). Previously this was a codex-only override
|
|
1025
1025
|
// (#3163) that left AGENTS-native runtimes (opencode/kilo/kimi) emitting
|
|
1026
1026
|
// CLAUDE.md; copilot now resolves to .github/copilot-instructions.md, and
|
|
1027
|
-
// antigravity
|
|
1027
|
+
// antigravity to GEMINI.md. GSD_RUNTIME env var takes precedence
|
|
1028
1028
|
// over config.runtime, mirroring detectRuntime().
|
|
1029
1029
|
//
|
|
1030
1030
|
// Non-claude runtimes always win over a stale `claude_md_path` (the #3163
|
|
@@ -515,12 +515,12 @@ function defaultProveFailFirst(check, cwd, timeoutMs) {
|
|
|
515
515
|
// a setup crash, not from the prohibition firing. Requiring the fixture to exist before spawning
|
|
516
516
|
// closes the realistic typo/stale-path case (#1279 review, Major 1).
|
|
517
517
|
//
|
|
518
|
-
// CAUSATION (#1346): existence + a non-vacuous red is necessary but not
|
|
519
|
-
// negative test that reds merely BECAUSE `GSD_PROHIB_SUBJECT` is set
|
|
520
|
-
// subject's CONTENT violates the must-NOT) would otherwise be accepted.
|
|
521
|
-
// control below proves content-dependence
|
|
522
|
-
//
|
|
523
|
-
//
|
|
518
|
+
// CAUSATION (#1346; MANDATORY as of #1906): existence + a non-vacuous red is necessary but not
|
|
519
|
+
// sufficient — a deceptive negative test that reds merely BECAUSE `GSD_PROHIB_SUBJECT` is set
|
|
520
|
+
// (rather than because the subject's CONTENT violates the must-NOT) would otherwise be accepted.
|
|
521
|
+
// The `cleanFixture` control below proves content-dependence (red on bad AND green on clean) and is
|
|
522
|
+
// now REQUIRED for the node-test kind: absent it, the check is un-provable (fail-closed), not
|
|
523
|
+
// accepted under the weaker violation-only proof (#1906 supersedes #1346's opt-in; ADR-1606 D4).
|
|
524
524
|
// Resolve the fixture against `cwd` (NOT the verify process's cwd): the spawned test reads
|
|
525
525
|
// `GSD_PROHIB_SUBJECT` and resolves a relative subject against `cwd`, so the existence check must
|
|
526
526
|
// use the SAME base or it could pass here yet ENOENT in the child (re-opening the fail-open hole).
|
|
@@ -530,20 +530,22 @@ function defaultProveFailFirst(check, cwd, timeoutMs) {
|
|
|
530
530
|
const redOut = runNodeTestWithSubject(check, cwd, fixture, timeoutMs);
|
|
531
531
|
if (!isNonVacuousNodeTestRed(redOut, check.target))
|
|
532
532
|
return { provenFailFirst: false, method: 'violation-fixture' };
|
|
533
|
-
// #
|
|
534
|
-
//
|
|
535
|
-
// CONTENT — a deceptive test that reds merely
|
|
536
|
-
// not content-dependent → not proven.
|
|
533
|
+
// #1906 CAUSATION CONTROL (MANDATORY for node-test — supersedes #1346's opt-in, ADR-1606 D4): the
|
|
534
|
+
// clean control subject is REQUIRED. Run the SAME test against it and require it to stay GREEN,
|
|
535
|
+
// proving the red above was caused by the subject's CONTENT — a deceptive test that reds merely
|
|
536
|
+
// because GSD_PROHIB_SUBJECT is SET reds here too → not content-dependent → not proven. ABSENT →
|
|
537
|
+
// the control cannot run → un-provable → fail-closed (NOT accepted under the weaker violation-only
|
|
538
|
+
// proof). This is the one behavior change vs #1346: absent `cleanFixture` was previously proven.
|
|
537
539
|
const clean = check.cleanFixture;
|
|
538
|
-
if (clean)
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
540
|
+
if (!clean)
|
|
541
|
+
return { provenFailFirst: false, method: 'violation-fixture' };
|
|
542
|
+
// A supplied-but-missing/typo'd control path can't run the control → fail-closed, symmetric
|
|
543
|
+
// with the violation-fixture existence guard (resolve against the SAME `cwd` as the child).
|
|
544
|
+
if (!node_fs_1.default.existsSync(node_path_1.default.resolve(cwd, clean)))
|
|
545
|
+
return { provenFailFirst: false, method: 'violation-fixture' };
|
|
546
|
+
const cleanOut = runNodeTestWithSubject(check, cwd, clean, timeoutMs);
|
|
547
|
+
if (!isNonVacuousNodeTestPass(cleanOut, check.target))
|
|
548
|
+
return { provenFailFirst: false, method: 'violation-fixture' };
|
|
547
549
|
return { provenFailFirst: true, method: 'violation-fixture' };
|
|
548
550
|
}
|
|
549
551
|
// Unknown kind — defensive; the LOCATE guard already rejects it.
|
|
@@ -32,8 +32,14 @@ const { planningPaths, withPlanningLock, findContextMdIn } = planningWorkspace;
|
|
|
32
32
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
33
33
|
const scanPhasePlans = require("./plan-scan.cjs");
|
|
34
34
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
35
|
+
const coreUtils = require("./core-utils.cjs");
|
|
36
|
+
const { countMatchedSummaries } = coreUtils;
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
35
38
|
const frontmatter = require("./frontmatter.cjs");
|
|
36
39
|
const { extractFrontmatter, parseMustHavesBlock } = frontmatter;
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
41
|
+
const verificationMod = require("./verification.cjs");
|
|
42
|
+
const { readVerificationStatus } = verificationMod;
|
|
37
43
|
// ─── coerceTruthToString ──────────────────────────────────────────────────────
|
|
38
44
|
/**
|
|
39
45
|
* Coerce an arbitrary YAML scalar/object into a string for cross-cutting
|
|
@@ -410,12 +416,21 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
|
|
|
410
416
|
error(`Phase ${phaseNum} not found`);
|
|
411
417
|
}
|
|
412
418
|
const planCount = phaseInfo.plans.length;
|
|
413
|
-
|
|
419
|
+
// Count only summaries that pair with a real plan (#1988): stray non-plan
|
|
420
|
+
// summaries (30-FIX-CR02-SUMMARY.md, 30-GAPCLOSURE-SUMMARY.md, …) must not
|
|
421
|
+
// inflate summary_count and silently flip the phase to Complete.
|
|
422
|
+
const summaryCount = countMatchedSummaries(phaseInfo.plans, phaseInfo.summaries);
|
|
414
423
|
if (planCount === 0) {
|
|
415
424
|
output({ updated: false, reason: 'No plans found', plan_count: 0, summary_count: 0 }, raw, 'no plans');
|
|
416
425
|
return;
|
|
417
426
|
}
|
|
418
|
-
|
|
427
|
+
// Verification gate (#2022): do NOT check the phase checkbox or stamp a
|
|
428
|
+
// completion date until the phase's verification status is 'passed', matching
|
|
429
|
+
// cmdPhaseComplete's gate (phase.cts:1436). Previously the checkbox fired the
|
|
430
|
+
// moment the last plan summary landed — before gsd-verifier had verified.
|
|
431
|
+
const phaseDir = node_path_1.default.join(cwd, phaseInfo.directory);
|
|
432
|
+
const verificationPassed = readVerificationStatus(phaseDir).status === 'passed';
|
|
433
|
+
const isComplete = summaryCount >= planCount && verificationPassed;
|
|
419
434
|
const status = isComplete ? 'Complete' : summaryCount > 0 ? 'In Progress' : 'Planned';
|
|
420
435
|
const today = clock_cjs_1.realClock.today();
|
|
421
436
|
if (!node_fs_1.default.existsSync(roadmapPath)) {
|
|
@@ -1039,7 +1039,7 @@ function applySettingsJsonHooks(settings, opts) {
|
|
|
1039
1039
|
// Configure PreToolUse hook for prompt injection detection
|
|
1040
1040
|
// ADR-857 phase 5f-2: drive dialect from opts.hookEvents (registry descriptor).
|
|
1041
1041
|
// hookEvents='gemini' → BeforeTool; all others → PreToolUse.
|
|
1042
|
-
// Equivalence: hookEvents='gemini' iff runtime
|
|
1042
|
+
// Equivalence: hookEvents='gemini' iff runtime===antigravity (same as old check).
|
|
1043
1043
|
const preToolEvent = hookEvents === 'gemini' ? 'BeforeTool' : 'PreToolUse';
|
|
1044
1044
|
if (!settings.hooks[preToolEvent]) {
|
|
1045
1045
|
settings.hooks[preToolEvent] = [];
|
|
@@ -1312,9 +1312,14 @@ function applySettingsJsonHooks(settings, opts) {
|
|
|
1312
1312
|
}
|
|
1313
1313
|
}
|
|
1314
1314
|
// ── end SubagentStop / Stop / PreCompact events ────────────────────────────
|
|
1315
|
-
// ──
|
|
1316
|
-
// Gemini
|
|
1317
|
-
//
|
|
1315
|
+
// ── Extended hook events (#776; Gemini runtime removed #1928) ──────────────
|
|
1316
|
+
// The Gemini-3-backend dialect exposes several hook events beyond
|
|
1317
|
+
// BeforeTool/AfterTool. These were added for the now-removed Gemini CLI
|
|
1318
|
+
// runtime (#776). No currently supported runtime declares them —
|
|
1319
|
+
// Antigravity's descriptor carries `extendedHookEvents: []` — so this loop
|
|
1320
|
+
// is an inert, descriptor-driven seam: it no-ops for every present runtime
|
|
1321
|
+
// and re-activates automatically if a future runtime declares any of them.
|
|
1322
|
+
// Three high-value events would be wired here:
|
|
1318
1323
|
//
|
|
1319
1324
|
// BeforeAgent — fires after user submits a prompt, before the agent
|
|
1320
1325
|
// plans. Wire gsd-context-monitor for context headroom
|
|
@@ -1335,15 +1340,15 @@ function applySettingsJsonHooks(settings, opts) {
|
|
|
1335
1340
|
// gsd hook use case at this time; deferred to a follow-on issue.
|
|
1336
1341
|
//
|
|
1337
1342
|
// Guard is now descriptor-driven: only events present in extendedEvents are wired.
|
|
1338
|
-
for (const
|
|
1339
|
-
if (!extendedEvents.includes(
|
|
1343
|
+
for (const extendedEvent of ['BeforeAgent', 'AfterAgent', 'BeforeModel']) {
|
|
1344
|
+
if (!extendedEvents.includes(extendedEvent))
|
|
1340
1345
|
continue;
|
|
1341
|
-
if (!Array.isArray(settings.hooks[
|
|
1342
|
-
settings.hooks[
|
|
1346
|
+
if (!Array.isArray(settings.hooks[extendedEvent])) {
|
|
1347
|
+
settings.hooks[extendedEvent] = [];
|
|
1343
1348
|
}
|
|
1344
|
-
const alreadyHasContextMonitor = settings.hooks[
|
|
1349
|
+
const alreadyHasContextMonitor = settings.hooks[extendedEvent].some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'gsd-context-monitor')));
|
|
1345
1350
|
if (!alreadyHasContextMonitor && node_fs_1.default.existsSync(contextMonitorFile) && contextMonitorCommand) {
|
|
1346
|
-
settings.hooks[
|
|
1351
|
+
settings.hooks[extendedEvent].push({
|
|
1347
1352
|
hooks: [
|
|
1348
1353
|
{
|
|
1349
1354
|
type: 'command',
|
|
@@ -1352,13 +1357,13 @@ function applySettingsJsonHooks(settings, opts) {
|
|
|
1352
1357
|
}
|
|
1353
1358
|
]
|
|
1354
1359
|
});
|
|
1355
|
-
console.log(` ${green}✓${reset} Configured ${
|
|
1360
|
+
console.log(` ${green}✓${reset} Configured ${extendedEvent} context monitor hook`);
|
|
1356
1361
|
}
|
|
1357
1362
|
else if (!alreadyHasContextMonitor && !node_fs_1.default.existsSync(contextMonitorFile)) {
|
|
1358
|
-
console.warn(` ${yellow}⚠${reset} Skipped ${
|
|
1363
|
+
console.warn(` ${yellow}⚠${reset} Skipped ${extendedEvent} hook — gsd-context-monitor.js not found at target`);
|
|
1359
1364
|
}
|
|
1360
1365
|
}
|
|
1361
|
-
// ── end
|
|
1366
|
+
// ── end Antigravity-only extended hook events ──────────────────────────────
|
|
1362
1367
|
// ── FileChanged hook: hot-reload gsd config on .planning/config.json edits ─
|
|
1363
1368
|
// Claude Code fires FileChanged when a watched file changes on disk. Wire
|
|
1364
1369
|
// gsd-config-reload.js to reload the gsd config context whenever the user
|
|
@@ -29,7 +29,6 @@ const FALLBACK_ALIASES = {
|
|
|
29
29
|
claude: ['claude', 'claude-code', 'claude-cli'],
|
|
30
30
|
opencode: ['opencode', 'open-code', 'opencode-cli'],
|
|
31
31
|
kilo: ['kilo', 'kilo-cli'],
|
|
32
|
-
gemini: ['gemini', 'gemini-cli', 'gemini-code'],
|
|
33
32
|
codex: ['codex', 'codex-app', 'codex-cli', 'codex_desktop', 'codex-desktop'],
|
|
34
33
|
copilot: ['copilot', 'copilot-cli', 'github-copilot'],
|
|
35
34
|
antigravity: ['antigravity', 'antigravity-cli', 'antigravity-agent'],
|
|
@@ -114,7 +113,7 @@ function resolveRuntimeNameFromCandidates(...candidates) {
|
|
|
114
113
|
* claude → .claude/CLAUDE.md
|
|
115
114
|
* codex, opencode, kilo, kimi → AGENTS.md
|
|
116
115
|
* copilot → .github/copilot-instructions.md
|
|
117
|
-
* antigravity
|
|
116
|
+
* antigravity → GEMINI.md
|
|
118
117
|
* unknown / future runtimes → AGENTS.md (safe cross-agent default)
|
|
119
118
|
*
|
|
120
119
|
* Source-of-truth references for each runtime's read path:
|
|
@@ -126,7 +125,9 @@ function resolveRuntimeNameFromCandidates(...candidates) {
|
|
|
126
125
|
* 'copilot-instructions' writes the same `.github/copilot-instructions.md`.)
|
|
127
126
|
* - codex/opencode/kilo/kimi: AGENTS.md is the documented cross-agent
|
|
128
127
|
* instruction file (agentsmd/agents.md convention).
|
|
129
|
-
* - antigravity
|
|
128
|
+
* - antigravity: GEMINI.md is Antigravity CLI's contextFileName (the Gemini
|
|
129
|
+
* CLI runtime that historically shared this file was removed — #1928;
|
|
130
|
+
* Google sunset Gemini CLI 2026-06-18 and Antigravity CLI is its successor).
|
|
130
131
|
*
|
|
131
132
|
* Aliases are normalized via `canonicalizeRuntimeName` first, so inputs like
|
|
132
133
|
* `codex-cli` resolve to `codex` → `AGENTS.md`. Replaces the prior codex-only
|
|
@@ -139,7 +140,7 @@ function getProjectInstructionFile(runtime) {
|
|
|
139
140
|
return '.claude/CLAUDE.md';
|
|
140
141
|
if (canonical === 'copilot')
|
|
141
142
|
return '.github/copilot-instructions.md';
|
|
142
|
-
if (canonical === 'antigravity'
|
|
143
|
+
if (canonical === 'antigravity')
|
|
143
144
|
return 'GEMINI.md';
|
|
144
145
|
// codex, opencode, kilo, kimi, AND unknown/future runtimes all default to
|
|
145
146
|
// root AGENTS.md (the safe cross-agent instruction file).
|
|
@@ -180,7 +181,7 @@ function getDirName(runtime) {
|
|
|
180
181
|
*
|
|
181
182
|
* Voice: these are the SHORT UI labels, intentionally distinct from the
|
|
182
183
|
* descriptor `title` (the long product name — e.g. "OpenAI Codex CLI",
|
|
183
|
-
* "GitHub Copilot"
|
|
184
|
+
* "GitHub Copilot") which serves documentation/registry display,
|
|
184
185
|
* not the install console. A future slice may relocate this to a
|
|
185
186
|
* `runtime.label` descriptor field; until then this table is the source.
|
|
186
187
|
*
|
|
@@ -195,7 +196,6 @@ function getDirName(runtime) {
|
|
|
195
196
|
const RUNTIME_LABELS = {
|
|
196
197
|
claude: 'Claude Code',
|
|
197
198
|
opencode: 'OpenCode',
|
|
198
|
-
gemini: 'Gemini',
|
|
199
199
|
kilo: 'Kilo',
|
|
200
200
|
codex: 'Codex',
|
|
201
201
|
copilot: 'Copilot',
|
|
@@ -231,7 +231,7 @@ function getRuntimeLabel(runtime) {
|
|
|
231
231
|
* bin/install.js (ADR-1239 Phase B / #1679, AC2 slice 2) — the add-a-host tax:
|
|
232
232
|
* a new runtime meant remembering to add a branch here. Values are preserved
|
|
233
233
|
* BYTE-FOR-BYTE from the prior chain; golden install parity asserts generated
|
|
234
|
-
* hook output is unchanged across all
|
|
234
|
+
* hook output is unchanged across all 15 runtimes.
|
|
235
235
|
*
|
|
236
236
|
* Two runtimes are intentionally absent (handled by the caller, NOT this table):
|
|
237
237
|
* - `claude` → the default; falls through to `DEFAULT_FRAGMENT`.
|
|
@@ -244,7 +244,6 @@ const DEFAULT_CONFIG_HOME_FRAGMENT = "'.claude'";
|
|
|
244
244
|
const GLOBAL_CONFIG_HOME_FRAGMENTS = {
|
|
245
245
|
copilot: "'.copilot'",
|
|
246
246
|
opencode: "'.config', 'opencode'",
|
|
247
|
-
gemini: "'.gemini'",
|
|
248
247
|
kilo: "'.config', 'kilo'",
|
|
249
248
|
codex: "'.codex'",
|
|
250
249
|
cursor: "'.cursor'",
|
|
@@ -277,7 +276,7 @@ function getGlobalConfigHomeFragment(runtime) {
|
|
|
277
276
|
* removes).
|
|
278
277
|
*/
|
|
279
278
|
const RUNTIME_FLAG_IDS = Object.freeze([
|
|
280
|
-
'opencode', 'kilo', '
|
|
279
|
+
'opencode', 'kilo', 'codex', 'copilot', 'antigravity', 'cursor',
|
|
281
280
|
'windsurf', 'augment', 'trae', 'qwen', 'hermes', 'codebuddy', 'cline', 'kimi',
|
|
282
281
|
]);
|
|
283
282
|
/**
|
|
@@ -303,7 +302,6 @@ function runtimeFlags(runtime) {
|
|
|
303
302
|
*/
|
|
304
303
|
const DEFAULT_NEW_PROJECT_COMMAND = '/gsd-new-project';
|
|
305
304
|
const RUNTIME_NEW_PROJECT_COMMANDS = {
|
|
306
|
-
gemini: '/gsd:new-project',
|
|
307
305
|
codex: '$gsd-new-project',
|
|
308
306
|
cursor: 'gsd-new-project (mention the skill name)',
|
|
309
307
|
kimi: '/skill:gsd-new-project',
|
|
@@ -56,16 +56,20 @@ const node_child_process_1 = __importDefault(require("node:child_process"));
|
|
|
56
56
|
* runtime/shell combination.
|
|
57
57
|
*
|
|
58
58
|
* Current evidence-backed policy:
|
|
59
|
-
* -
|
|
60
|
-
*
|
|
61
|
-
*
|
|
59
|
+
* - Claude Code on Windows does NOT need it: its hook commands execute under
|
|
60
|
+
* bash/Git Bash and `& ` breaks there (#3413).
|
|
61
|
+
* - #1928: Gemini CLI — the ONLY runtime with a verified need for the `& `
|
|
62
|
+
* prefix on Windows — was removed (Google sunset it 2026-06-18). No currently
|
|
63
|
+
* supported runtime has a verified need, so this seam is now inert. It is
|
|
64
|
+
* retained (not deleted) so a future runtime with a verified need is a
|
|
65
|
+
* one-line re-enable, per the conservative policy below. Note: Antigravity —
|
|
66
|
+
* the Gemini-backend successor — never matched the old `runtime === 'gemini'`
|
|
67
|
+
* check, so its behavior (no prefix) is unchanged.
|
|
62
68
|
*
|
|
63
69
|
* Keep the policy conservative until another runtime has a verified need.
|
|
64
70
|
*/
|
|
65
|
-
function hookCommandNeedsPowerShellCallOperator(
|
|
66
|
-
|
|
67
|
-
const runtime = opts.runtime || 'generic';
|
|
68
|
-
return platform === 'win32' && runtime === 'gemini';
|
|
71
|
+
function hookCommandNeedsPowerShellCallOperator(_opts = {}) {
|
|
72
|
+
return false;
|
|
69
73
|
}
|
|
70
74
|
/**
|
|
71
75
|
* Project a fully-assembled hook command string for the target runtime.
|
|
@@ -121,7 +125,7 @@ function formatManagedHookScriptToken(scriptPath, opts = {}) {
|
|
|
121
125
|
function projectLocalHookPrefix({ runtime = 'claude', dirName }) {
|
|
122
126
|
if (!dirName)
|
|
123
127
|
return dirName;
|
|
124
|
-
return (runtime === '
|
|
128
|
+
return (runtime === 'antigravity')
|
|
125
129
|
? dirName
|
|
126
130
|
: `"$CLAUDE_PROJECT_DIR"/${dirName}`;
|
|
127
131
|
}
|