@opengsd/gsd-core 1.18.0 → 1.19.0
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 +2 -1
- package/.opencode/plugins/gsd-core.js +2 -3
- package/agents/gsd-code-fixer.compact.md +2 -2
- package/agents/gsd-code-fixer.md +4 -3
- package/agents/gsd-verifier.md +1 -1
- package/bin/install.js +422 -103
- package/gsd-core/bin/gsd-tools.cjs +106 -7
- package/gsd-core/bin/lib/agent-install-check.cjs +29 -3
- package/gsd-core/bin/lib/args-terminator.cjs +114 -0
- package/gsd-core/bin/lib/broken-windows.cjs +59 -1
- package/gsd-core/bin/lib/capability-lock.cjs +13 -2
- package/gsd-core/bin/lib/capability-registry.cjs +156 -74
- package/gsd-core/bin/lib/capability-state.cjs +14 -8
- package/gsd-core/bin/lib/capability-validator.cjs +17 -0
- package/gsd-core/bin/lib/config-loader.cjs +10 -0
- package/gsd-core/bin/lib/config.cjs +256 -58
- package/gsd-core/bin/lib/core-utils.cjs +34 -0
- package/gsd-core/bin/lib/decision-coverage-support.cjs +74 -0
- package/gsd-core/bin/lib/gate-context-drift.cjs +8 -3
- package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +6 -1
- package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +6 -1
- package/gsd-core/bin/lib/gate-evaluation-scope.cjs +134 -13
- package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +63 -22
- package/gsd-core/bin/lib/gate-ui-safety.cjs +23 -5
- package/gsd-core/bin/lib/git-info-exclude.cjs +57 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +18 -3
- package/gsd-core/bin/lib/health-diagnostic-types.cjs +4 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +52 -1
- package/gsd-core/bin/lib/host-integration.cjs +13 -6
- package/gsd-core/bin/lib/init.cjs +65 -1
- package/gsd-core/bin/lib/install-engine.cjs +246 -32
- package/gsd-core/bin/lib/install-profiles.cjs +9 -4
- package/gsd-core/bin/lib/installer-migration-report.cjs +2 -0
- package/gsd-core/bin/lib/markdown-line-diff.cjs +147 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +113 -0
- package/gsd-core/bin/lib/milestone-lock.cjs +8 -1
- package/gsd-core/bin/lib/milestone-release-map.cjs +244 -0
- package/gsd-core/bin/lib/milestone-release-tags.cjs +440 -0
- package/gsd-core/bin/lib/milestone.cjs +43 -1
- package/gsd-core/bin/lib/model-catalog.cjs +8 -0
- package/gsd-core/bin/lib/normalize-test-command.cjs +67 -0
- package/gsd-core/bin/lib/phase-id.cjs +21 -4
- package/gsd-core/bin/lib/phase-lifecycle.cjs +37 -4
- package/gsd-core/bin/lib/phase.cjs +115 -26
- package/gsd-core/bin/lib/planning-snapshot.cjs +3 -1
- package/gsd-core/bin/lib/planning-workspace.cjs +18 -0
- package/gsd-core/bin/lib/pristine-baseline.cjs +11 -3
- package/gsd-core/bin/lib/read-only-git-env.cjs +58 -0
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +35 -2
- package/gsd-core/bin/lib/review-lane-runner.cjs +33 -4
- package/gsd-core/bin/lib/roadmap-parser.cjs +12 -0
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +39 -2
- package/gsd-core/bin/lib/roadmap.cjs +33 -25
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +381 -14
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +8 -4
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +60 -16
- package/gsd-core/bin/lib/shell-command-projection.cjs +121 -16
- package/gsd-core/bin/lib/smart-entry.cjs +13 -0
- package/gsd-core/bin/lib/state-contract.cjs +29 -9
- package/gsd-core/bin/lib/state-document.cjs +12 -4
- package/gsd-core/bin/lib/state-transition.cjs +119 -24
- package/gsd-core/bin/lib/state.cjs +50 -28
- package/gsd-core/bin/lib/surface.cjs +9 -0
- package/gsd-core/bin/lib/task-command-router.cjs +5 -25
- package/gsd-core/bin/lib/task-content-resolution.cjs +46 -1
- package/gsd-core/bin/lib/verification.cjs +96 -9
- package/gsd-core/bin/lib/verify.cjs +2 -0
- package/gsd-core/bin/lib/worktree-safety.cjs +22 -1
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
- package/gsd-core/references/git-integration.md +11 -0
- package/gsd-core/references/loop-hook-dispatch.md +13 -6
- package/gsd-core/references/planning-config.md +2 -0
- package/gsd-core/references/tdd.md +2 -2
- package/gsd-core/templates/DEBUG.md +16 -0
- package/gsd-core/templates/config.json +2 -1
- package/gsd-core/templates/verification-report.md +1 -1
- package/gsd-core/workflows/audit-fix.md +2 -1
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +10 -1
- package/gsd-core/workflows/code-review.md +61 -7
- package/gsd-core/workflows/complete-milestone/steps/project-md-evolution-review.md +145 -0
- package/gsd-core/workflows/complete-milestone/steps/release-map.md +80 -0
- package/gsd-core/workflows/complete-milestone.md +37 -150
- package/gsd-core/workflows/diagnose-issues.md +29 -5
- package/gsd-core/workflows/discuss-phase/modes/power.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +33 -2
- package/gsd-core/workflows/discuss-phase-power.md +19 -2
- package/gsd-core/workflows/discuss-phase.md +6 -10
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +13 -7
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +7 -2
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -1
- package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +11 -2
- package/gsd-core/workflows/execute-phase.md +9 -5
- package/gsd-core/workflows/health.md +2 -1
- package/gsd-core/workflows/import.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +2 -2
- package/gsd-core/workflows/plan-phase.md +13 -13
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +1 -1
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +8 -1
- package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +2 -1
- package/gsd-core/workflows/quick.md +1 -1
- package/gsd-core/workflows/review.md +16 -13
- package/gsd-core/workflows/section-manifest.json +5 -0
- package/gsd-core/workflows/settings.md +1 -0
- package/gsd-core/workflows/ship.md +3 -4
- package/gsd-core/workflows/spec-phase.md +3 -6
- package/gsd-core/workflows/verify-work.md +4 -3
- package/hooks/dist/gsd-agent-isolation-guard.js +11 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +6 -2
- package/hooks/dist/gsd-path-export.js +95 -0
- package/hooks/dist/gsd-secret-read-guard.js +172 -39
- package/hooks/dist/gsd-statusline.js +35 -2
- package/hooks/dist/gsd-windsurf-pre-write.js +28 -1
- package/hooks/dist/gsd-workflow-guard.js +1 -1
- package/hooks/dist/gsd-worktree-path-guard.js +28 -1
- package/hooks/dist/lib/isolation-sentinel.js +44 -0
- package/hooks/dist/managed-hooks-registry.cjs +1 -0
- package/hooks/gsd-agent-isolation-guard.js +11 -1
- package/hooks/gsd-cursor-subagent-start.js +6 -2
- package/hooks/gsd-path-export.js +95 -0
- package/hooks/gsd-secret-read-guard.js +172 -39
- package/hooks/gsd-statusline.js +35 -2
- package/hooks/gsd-windsurf-pre-write.js +28 -1
- package/hooks/gsd-workflow-guard.js +1 -1
- package/hooks/gsd-worktree-path-guard.js +28 -1
- package/hooks/hooks.json +1 -0
- package/hooks/lib/isolation-sentinel.js +44 -0
- package/hooks/managed-hooks-registry.cjs +1 -0
- package/package.json +13 -6
- package/pi/gsd.cjs +1047 -10
- package/pi/package-agents/gsd-advisor-researcher.md +113 -0
- package/pi/package-agents/gsd-ai-researcher.md +111 -0
- package/pi/package-agents/gsd-assumptions-analyzer.md +110 -0
- package/pi/package-agents/gsd-code-fixer.md +769 -0
- package/pi/package-agents/gsd-code-reviewer.md +402 -0
- package/pi/package-agents/gsd-codebase-mapper.md +850 -0
- package/pi/package-agents/gsd-debug-session-manager.md +416 -0
- package/pi/package-agents/gsd-debugger.md +1274 -0
- package/pi/package-agents/gsd-doc-classifier.md +269 -0
- package/pi/package-agents/gsd-doc-synthesizer.md +259 -0
- package/pi/package-agents/gsd-doc-verifier.md +212 -0
- package/pi/package-agents/gsd-doc-writer.md +626 -0
- package/pi/package-agents/gsd-dom-verifier.md +164 -0
- package/pi/package-agents/gsd-domain-researcher.md +144 -0
- package/pi/package-agents/gsd-eval-auditor.md +185 -0
- package/pi/package-agents/gsd-eval-planner.md +149 -0
- package/pi/package-agents/gsd-executor.md +894 -0
- package/pi/package-agents/gsd-framework-selector.md +159 -0
- package/pi/package-agents/gsd-integration-checker.md +476 -0
- package/pi/package-agents/gsd-intel-updater.md +338 -0
- package/pi/package-agents/gsd-mempalace-curator.md +49 -0
- package/pi/package-agents/gsd-nyquist-auditor.md +200 -0
- package/pi/package-agents/gsd-pattern-mapper.md +340 -0
- package/pi/package-agents/gsd-phase-researcher.md +897 -0
- package/pi/package-agents/gsd-plan-checker.md +1077 -0
- package/pi/package-agents/gsd-planner.md +1003 -0
- package/pi/package-agents/gsd-project-researcher.md +611 -0
- package/pi/package-agents/gsd-research-synthesizer.md +259 -0
- package/pi/package-agents/gsd-roadmapper.md +779 -0
- package/pi/package-agents/gsd-security-auditor.md +171 -0
- package/pi/package-agents/gsd-ui-auditor.md +581 -0
- package/pi/package-agents/gsd-ui-checker.md +417 -0
- package/pi/package-agents/gsd-ui-researcher.md +469 -0
- package/pi/package-agents/gsd-user-profiler.md +174 -0
- package/pi/package-agents/gsd-verifier.md +963 -0
- package/pi/package-agents/manifest.json +39 -0
- package/pi/package-skills/gsd-add-tests/SKILL.md +39 -0
- package/pi/package-skills/gsd-ai-integration-phase/SKILL.md +36 -0
- package/pi/package-skills/gsd-audit-fix/SKILL.md +34 -0
- package/pi/package-skills/gsd-audit-milestone/SKILL.md +40 -0
- package/pi/package-skills/gsd-audit-uat/SKILL.md +26 -0
- package/pi/package-skills/gsd-autonomous/SKILL.md +52 -0
- package/pi/package-skills/gsd-capture/SKILL.md +68 -0
- package/pi/package-skills/gsd-cleanup/SKILL.md +25 -0
- package/pi/package-skills/gsd-code-review/SKILL.md +71 -0
- package/pi/package-skills/gsd-complete-milestone/SKILL.md +147 -0
- package/pi/package-skills/gsd-config/SKILL.md +60 -0
- package/pi/package-skills/gsd-debug/SKILL.md +64 -0
- package/pi/package-skills/gsd-discuss-phase/SKILL.md +74 -0
- package/pi/package-skills/gsd-docs-update/SKILL.md +50 -0
- package/pi/package-skills/gsd-eval-review/SKILL.md +35 -0
- package/pi/package-skills/gsd-execute-phase/SKILL.md +62 -0
- package/pi/package-skills/gsd-explore/SKILL.md +26 -0
- package/pi/package-skills/gsd-extract-learnings/SKILL.md +25 -0
- package/pi/package-skills/gsd-fast/SKILL.md +33 -0
- package/pi/package-skills/gsd-forensics/SKILL.md +59 -0
- package/pi/package-skills/gsd-graphify/SKILL.md +210 -0
- package/pi/package-skills/gsd-health/SKILL.md +36 -0
- package/pi/package-skills/gsd-help/SKILL.md +37 -0
- package/pi/package-skills/gsd-import/SKILL.md +46 -0
- package/pi/package-skills/gsd-inbox/SKILL.md +42 -0
- package/pi/package-skills/gsd-ingest-docs/SKILL.md +43 -0
- package/pi/package-skills/gsd-manager/SKILL.md +46 -0
- package/pi/package-skills/gsd-map-codebase/SKILL.md +85 -0
- package/pi/package-skills/gsd-mempalace-capture/SKILL.md +111 -0
- package/pi/package-skills/gsd-mempalace-recall/SKILL.md +111 -0
- package/pi/package-skills/gsd-milestone-summary/SKILL.md +54 -0
- package/pi/package-skills/gsd-mvp-phase/SKILL.md +45 -0
- package/pi/package-skills/gsd-new-milestone/SKILL.md +49 -0
- package/pi/package-skills/gsd-new-project/SKILL.md +49 -0
- package/pi/package-skills/gsd-next/SKILL.md +34 -0
- package/pi/package-skills/gsd-ns-context/SKILL.md +31 -0
- package/pi/package-skills/gsd-ns-ideate/SKILL.md +30 -0
- package/pi/package-skills/gsd-ns-manage/SKILL.md +42 -0
- package/pi/package-skills/gsd-ns-project/SKILL.md +34 -0
- package/pi/package-skills/gsd-ns-review/SKILL.md +35 -0
- package/pi/package-skills/gsd-ns-workflow/SKILL.md +44 -0
- package/pi/package-skills/gsd-onboard/SKILL.md +46 -0
- package/pi/package-skills/gsd-pause-work/SKILL.md +48 -0
- package/pi/package-skills/gsd-phase/SKILL.md +61 -0
- package/pi/package-skills/gsd-plan-phase/SKILL.md +62 -0
- package/pi/package-skills/gsd-plan-review-convergence/SKILL.md +63 -0
- package/pi/package-skills/gsd-pr-branch/SKILL.md +31 -0
- package/pi/package-skills/gsd-profile-user/SKILL.md +48 -0
- package/pi/package-skills/gsd-progress/SKILL.md +51 -0
- package/pi/package-skills/gsd-quick/SKILL.md +179 -0
- package/pi/package-skills/gsd-quick-batch/SKILL.md +114 -0
- package/pi/package-skills/gsd-resume-work/SKILL.md +31 -0
- package/pi/package-skills/gsd-review/SKILL.md +50 -0
- package/pi/package-skills/gsd-review-backlog/SKILL.md +65 -0
- package/pi/package-skills/gsd-secure-phase/SKILL.md +37 -0
- package/pi/package-skills/gsd-settings/SKILL.md +30 -0
- package/pi/package-skills/gsd-ship/SKILL.md +27 -0
- package/pi/package-skills/gsd-sketch/SKILL.md +56 -0
- package/pi/package-skills/gsd-spec-phase/SKILL.md +63 -0
- package/pi/package-skills/gsd-spike/SKILL.md +53 -0
- package/pi/package-skills/gsd-stats/SKILL.md +22 -0
- package/pi/package-skills/gsd-surface/SKILL.md +177 -0
- package/pi/package-skills/gsd-thread/SKILL.md +29 -0
- package/pi/package-skills/gsd-ui-phase/SKILL.md +35 -0
- package/pi/package-skills/gsd-ui-review/SKILL.md +35 -0
- package/pi/package-skills/gsd-ultraplan-phase/SKILL.md +38 -0
- package/pi/package-skills/gsd-undo/SKILL.md +38 -0
- package/pi/package-skills/gsd-update/SKILL.md +51 -0
- package/pi/package-skills/gsd-validate-phase/SKILL.md +37 -0
- package/pi/package-skills/gsd-verify-work/SKILL.md +41 -0
- package/pi/package-skills/gsd-workspace/SKILL.md +57 -0
- package/pi/package-skills/gsd-workstreams/SKILL.md +73 -0
- package/scripts/build-hooks.js +4 -0
- package/scripts/command-contract-helpers.cjs +1 -1
- package/scripts/docs-guard-registry.cjs +20 -7
- package/scripts/gen-health-docs.cjs +13 -1
- package/scripts/gen-pi-package-skills.cjs +217 -0
- package/scripts/lib/macos-conformance-tier.generated.cjs +11 -1
- package/scripts/lib/platform-conformance-tier.generated.cjs +26 -1
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +3 -3
- package/scripts/lint-retired-runtime-name.cjs +5 -0
- package/scripts/npm-audit-baseline.cjs +41 -7
- package/scripts/reviewed-or-maintainer-policy.cjs +181 -0
- package/scripts/select-docs-guards.cjs +1 -1
- package/scripts/self-test-report-policy.cjs +350 -0
- package/vscode/package.json +1 -1
- package/scripts/diagnose-validate-commit-in-tier.cjs +0 -270
- package/scripts/diagnose-validate-commit-phases.cjs +0 -348
- package/scripts/xtrace-phase-timing.cjs +0 -205
|
@@ -43,6 +43,8 @@ const node_child_process_1 = __importDefault(require("node:child_process"));
|
|
|
43
43
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
44
44
|
const ioMod = require("./io.cjs");
|
|
45
45
|
const { formatDiagnosticToken } = ioMod;
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
47
|
+
const capabilityLoaderMod = require("./capability-loader.cjs");
|
|
46
48
|
// ─── Result taxonomy ──────────────────────────────────────────────────────────
|
|
47
49
|
/**
|
|
48
50
|
* The four non-throwing outcomes of `resolveTaskContent`. Frozen because the
|
|
@@ -102,6 +104,17 @@ class ResolverFailedError extends Error {
|
|
|
102
104
|
this.stderrTail = stderrTail;
|
|
103
105
|
}
|
|
104
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* The tracker-id's id segment cannot safely be one argv element: a leading `-`
|
|
109
|
+
* reads as an option to the resolver binary, and control characters are never
|
|
110
|
+
* legitimate. Thrown before any spawn.
|
|
111
|
+
*/
|
|
112
|
+
class ResolverUnsafeIdError extends Error {
|
|
113
|
+
constructor() {
|
|
114
|
+
super('tracker-id rejected: id must not start with "-" or contain control characters');
|
|
115
|
+
this.name = 'ResolverUnsafeIdError';
|
|
116
|
+
}
|
|
117
|
+
}
|
|
105
118
|
/** The resolver subprocess exceeded its declared `invoke.timeoutMs` bound. */
|
|
106
119
|
class ResolverTimeoutError extends Error {
|
|
107
120
|
timeoutMs;
|
|
@@ -312,6 +325,9 @@ function resolveTaskContent(input) {
|
|
|
312
325
|
const split = splitTrackerId(input.trackerId);
|
|
313
326
|
if (split === null)
|
|
314
327
|
return { kind: TASK_CONTENT_RESULT.NOT_APPLICABLE };
|
|
328
|
+
if (split.id.trimStart().startsWith('-') || /[\u0000-\u001f\u007f]/.test(input.trackerId ?? '')) {
|
|
329
|
+
throw new ResolverUnsafeIdError();
|
|
330
|
+
}
|
|
315
331
|
const resolver = findResolver(split.prefix, input.capabilities ?? []);
|
|
316
332
|
if (resolver === null)
|
|
317
333
|
return { kind: TASK_CONTENT_RESULT.NO_RESOLVER };
|
|
@@ -327,7 +343,7 @@ function resolveTaskContent(input) {
|
|
|
327
343
|
throw new ResolverAmbiguousError(split.prefix, ids);
|
|
328
344
|
}
|
|
329
345
|
const invocation = buildInvocation(resolver, split.id);
|
|
330
|
-
const timeoutMs = input.timeoutOverrideMs ?? invocation.timeoutMs;
|
|
346
|
+
const timeoutMs = Math.floor(Math.min(input.timeoutOverrideMs ?? invocation.timeoutMs, invocation.timeoutMs));
|
|
331
347
|
const execFn = input.execFn ?? realExec;
|
|
332
348
|
const result = execFn(invocation.binary, invocation.args, { timeout: timeoutMs });
|
|
333
349
|
if (isExecTimeout(result)) {
|
|
@@ -354,12 +370,41 @@ function resolveTaskContent(input) {
|
|
|
354
370
|
}
|
|
355
371
|
return { kind: TASK_CONTENT_RESULT.RESOLVED, content };
|
|
356
372
|
}
|
|
373
|
+
/**
|
|
374
|
+
* Production capability loader for tracker-id resolution: the merged
|
|
375
|
+
* first-party + validated-installed-overlay registry (ADR-1244 D2), the
|
|
376
|
+
* established runtime read path for "installed capabilities including
|
|
377
|
+
* third-party" — as opposed to `capability-loader.cts`'s heavier build-time
|
|
378
|
+
* validation entry points or the static `capability-registry.cjs` alone
|
|
379
|
+
* (first-party only, would miss a third-party capability's
|
|
380
|
+
* `taskContentResolver` declaration entirely).
|
|
381
|
+
*/
|
|
382
|
+
function loadResolverCapabilities(cwd) {
|
|
383
|
+
const registry = capabilityLoaderMod.loadRegistry({ includeInstalled: true, cwd });
|
|
384
|
+
return Object.values(registry.capabilities ?? {});
|
|
385
|
+
}
|
|
386
|
+
/** True for every error `resolveTaskContent` throws as a hard halt (the one classifier all consumers share). */
|
|
387
|
+
function isResolverFailure(err) {
|
|
388
|
+
return (err instanceof ResolverAmbiguousError ||
|
|
389
|
+
err instanceof ResolverFailedError ||
|
|
390
|
+
err instanceof ResolverTimeoutError ||
|
|
391
|
+
err instanceof ResolverMalformedOutputError ||
|
|
392
|
+
err instanceof ResolverUnsafeIdError);
|
|
393
|
+
}
|
|
394
|
+
/** Every text field of resolved content, so a consumer scanning for text cannot miss one when the shape grows. */
|
|
395
|
+
function contentTexts(c) {
|
|
396
|
+
return [c.action, c.verify, ...c.acceptanceCriteria, ...c.readFirst, c.done].filter((v) => v !== null);
|
|
397
|
+
}
|
|
357
398
|
const taskContentResolution = {
|
|
358
399
|
TASK_CONTENT_RESULT,
|
|
359
400
|
splitTrackerId,
|
|
360
401
|
findResolver,
|
|
361
402
|
buildInvocation,
|
|
362
403
|
resolveTaskContent,
|
|
404
|
+
loadResolverCapabilities,
|
|
405
|
+
isResolverFailure,
|
|
406
|
+
contentTexts,
|
|
407
|
+
ResolverUnsafeIdError,
|
|
363
408
|
ResolverAmbiguousError,
|
|
364
409
|
ResolverFailedError,
|
|
365
410
|
ResolverTimeoutError,
|
|
@@ -47,6 +47,8 @@ const coreUtilsMod = require("./core-utils.cjs");
|
|
|
47
47
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-scope.cjs is an export= CommonJS module
|
|
48
48
|
const planningScopeMod = require("./planning-scope.cjs");
|
|
49
49
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
50
|
+
const text_lines_cjs_1 = require("./text-lines.cjs");
|
|
51
|
+
const frontmatter_fence_cjs_1 = require("./frontmatter-fence.cjs");
|
|
50
52
|
const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
|
|
51
53
|
const security_cjs_1 = require("./security.cjs");
|
|
52
54
|
const pattern_cjs_1 = require("./pattern.cjs");
|
|
@@ -607,17 +609,19 @@ function phaseArtifactPaths(phaseDir, projectRoot) {
|
|
|
607
609
|
* `.planning/<project>/workstreams/<ws>`), covering a
|
|
608
610
|
* per-project/per-workstream store symlink (amendment 1,
|
|
609
611
|
* revised R7).
|
|
612
|
+
* v4 (#5351) — v3 with each CRLF byte pair hashed as LF. Every other
|
|
613
|
+
* byte, including a lone CR and non-UTF-8 input, is preserved.
|
|
610
614
|
*
|
|
611
615
|
* A stored digest names its own version (`v<N>:sha256:…`), and
|
|
612
616
|
* `readVerificationStatus` recomputes under the STORED version rather than
|
|
613
617
|
* this constant — so bumping it does not flip every already-verified phase
|
|
614
|
-
* to `stale` on upgrade. A legacy v1/v2 report keeps its own
|
|
615
|
-
* (shared documents included for v1; no report filter or artifact
|
|
616
|
-
*
|
|
618
|
+
* to `stale` on upgrade. A legacy v1/v2/v3 report keeps its own raw-byte
|
|
619
|
+
* semantics (shared documents included for v1; no report filter or artifact
|
|
620
|
+
* union for v1/v2) until it is re-fingerprinted; only a version outside
|
|
617
621
|
* `KNOWN_FINGERPRINT_VERSIONS` is unrecomputable and fails closed.
|
|
618
622
|
*/
|
|
619
|
-
const FINGERPRINT_VERSION =
|
|
620
|
-
const KNOWN_FINGERPRINT_VERSIONS = new Set([1, 2, 3]);
|
|
623
|
+
const FINGERPRINT_VERSION = 4;
|
|
624
|
+
const KNOWN_FINGERPRINT_VERSIONS = new Set([1, 2, 3, 4]);
|
|
621
625
|
/**
|
|
622
626
|
* #4623: the planning roots whose DIRECT children are repo-wide planning
|
|
623
627
|
* documents, as project-root-relative posix paths. Always `.planning`; plus
|
|
@@ -815,7 +819,22 @@ function deriveCoveredDigest(projectRoot, coveredFiles, version = FINGERPRINT_VE
|
|
|
815
819
|
catch {
|
|
816
820
|
return { ok: true, digest: null, files: declared, mappedPhaseDir: artifactMappedPhaseDir };
|
|
817
821
|
}
|
|
818
|
-
const
|
|
822
|
+
const hash = node_crypto_1.default.createHash('sha256');
|
|
823
|
+
if (version >= 4) {
|
|
824
|
+
// #5351: drop only the CR in each CRLF pair without decoding the
|
|
825
|
+
// covered input — binary files and lone CR bytes keep their meaning.
|
|
826
|
+
// Hash Buffer slices directly, retaining the LF at each new start.
|
|
827
|
+
let start = 0;
|
|
828
|
+
for (let crlf = bytes.indexOf('\r\n'); crlf !== -1; crlf = bytes.indexOf('\r\n', start)) {
|
|
829
|
+
hash.update(bytes.subarray(start, crlf));
|
|
830
|
+
start = crlf + 1;
|
|
831
|
+
}
|
|
832
|
+
hash.update(bytes.subarray(start));
|
|
833
|
+
}
|
|
834
|
+
else {
|
|
835
|
+
hash.update(bytes);
|
|
836
|
+
}
|
|
837
|
+
const fileHash = hash.digest('hex');
|
|
819
838
|
parts.push(`${rel}\n${fileHash}\n`);
|
|
820
839
|
hashed++;
|
|
821
840
|
}
|
|
@@ -1175,6 +1194,72 @@ function findVerificationStatusError(phaseDirs, deps = {}) {
|
|
|
1175
1194
|
}
|
|
1176
1195
|
return null;
|
|
1177
1196
|
}
|
|
1197
|
+
/**
|
|
1198
|
+
* #5291: legacy report statuses `validate health --repair` may rewrite, and the
|
|
1199
|
+
* closed-set status each becomes. Deliberately minimal — only the value the
|
|
1200
|
+
* maintainer ruled on (`pass_with_caveats` -> `passed`). Every other status
|
|
1201
|
+
* outside the closed set (`pending`, `partial`, …) has NO mapping: it stays
|
|
1202
|
+
* refused and its W030 stays advise-only (fail closed; only `passed` satisfies
|
|
1203
|
+
* the gate). The original value is preserved in `legacy_status`.
|
|
1204
|
+
*/
|
|
1205
|
+
const LEGACY_STATUS_REPAIRS = Object.freeze({
|
|
1206
|
+
pass_with_caveats: VERIFICATION_STATUS.PASSED,
|
|
1207
|
+
});
|
|
1208
|
+
/** The closed-set status a known legacy `raw` status repairs to, or `null` when it has no mapping. */
|
|
1209
|
+
function legacyStatusRepairTarget(raw) {
|
|
1210
|
+
return typeof raw === 'string' && Object.prototype.hasOwnProperty.call(LEGACY_STATUS_REPAIRS, raw)
|
|
1211
|
+
? LEGACY_STATUS_REPAIRS[raw]
|
|
1212
|
+
: null;
|
|
1213
|
+
}
|
|
1214
|
+
/**
|
|
1215
|
+
* #5291: rewrite one report's frontmatter `status:` from a known legacy value
|
|
1216
|
+
* to its closed-set equivalent and record the original as `legacy_status:`
|
|
1217
|
+
* right after it. Touches only those lines — every other byte (body, other
|
|
1218
|
+
* keys, line endings) is preserved — and never overwrites an existing
|
|
1219
|
+
* `legacy_status`. Refuses (writes nothing) for a path that is not a report
|
|
1220
|
+
* contained in its own phase directory, a report with no frontmatter `status`,
|
|
1221
|
+
* and any status without a mapping.
|
|
1222
|
+
*/
|
|
1223
|
+
function repairLegacyVerificationStatus(filePath) {
|
|
1224
|
+
if (!isReportContained(node_path_1.default.dirname(filePath), filePath)) {
|
|
1225
|
+
return { ok: false, reason: 'the report is not contained in its phase directory' };
|
|
1226
|
+
}
|
|
1227
|
+
let content;
|
|
1228
|
+
try {
|
|
1229
|
+
content = node_fs_1.default.readFileSync(filePath, 'utf-8');
|
|
1230
|
+
}
|
|
1231
|
+
catch (err) {
|
|
1232
|
+
return { ok: false, reason: err instanceof Error ? err.message : String(err) };
|
|
1233
|
+
}
|
|
1234
|
+
// The fence owner (`locateFrontmatterFence`) is the one place that knows where
|
|
1235
|
+
// the frontmatter body starts and ends (BOM, CRLF, lenient closing fence).
|
|
1236
|
+
const fence = (0, frontmatter_fence_cjs_1.locateFrontmatterFence)(content);
|
|
1237
|
+
if (!fence || !fence.closed)
|
|
1238
|
+
return { ok: false, reason: 'the report has no closed frontmatter' };
|
|
1239
|
+
const lines = (0, text_lines_cjs_1.splitLines)(content.slice(fence.openEnd, fence.bodyEnd));
|
|
1240
|
+
// The YAML reader keeps the LAST of duplicate keys, so the line the repair
|
|
1241
|
+
// edits is the last `status:` line — the very value the refusal named. A
|
|
1242
|
+
// trailing comment (`status: pass_with_caveats # x`) is not part of the value.
|
|
1243
|
+
let statusAt = -1;
|
|
1244
|
+
lines.forEach((l, i) => { if (/^status:/.test(l))
|
|
1245
|
+
statusAt = i; });
|
|
1246
|
+
if (statusAt === -1)
|
|
1247
|
+
return { ok: false, reason: 'the report frontmatter has no status' };
|
|
1248
|
+
const raw = lines[statusAt]
|
|
1249
|
+
.replace(/^status:\s*/, '')
|
|
1250
|
+
.replace(/\s+#.*$/, '')
|
|
1251
|
+
.replace(/\s+$/, '')
|
|
1252
|
+
.replace(/^(["'])(.*)\1$/, '$2');
|
|
1253
|
+
const to = legacyStatusRepairTarget(raw);
|
|
1254
|
+
if (to === null)
|
|
1255
|
+
return { ok: false, reason: `${describeRawStatus(raw)} has no known repair` };
|
|
1256
|
+
const legacyStatusKept = lines.some((l) => /^legacy_status:/.test(l));
|
|
1257
|
+
lines[statusAt] = `status: ${to}`;
|
|
1258
|
+
if (!legacyStatusKept)
|
|
1259
|
+
lines.splice(statusAt + 1, 0, `legacy_status: ${raw}`);
|
|
1260
|
+
(0, shell_command_projection_cjs_1.platformWriteSync)(filePath, content.slice(0, fence.openEnd) + (0, text_lines_cjs_1.joinLines)(lines, fence.eol) + content.slice(fence.bodyEnd), { verbatim: true });
|
|
1261
|
+
return { ok: true, from: raw, to, legacyStatusKept };
|
|
1262
|
+
}
|
|
1178
1263
|
/**
|
|
1179
1264
|
* #3518: the shared phase-pinned artifact-selection core BOTH single-pick
|
|
1180
1265
|
* resolvers (`resolveVerificationFile` for `*-VERIFICATION.md`,
|
|
@@ -1499,11 +1584,11 @@ function readVerificationStatus(phaseDir, opts = {}) {
|
|
|
1499
1584
|
isStale = true;
|
|
1500
1585
|
}
|
|
1501
1586
|
else if (storedVersion >= 3) {
|
|
1502
|
-
// #5095 (R1): v3
|
|
1587
|
+
// #5095 (R1): the v3+ digest is computed over the declared set UNIONED
|
|
1503
1588
|
// with the phase's own live artifacts — a plan/summary added after
|
|
1504
1589
|
// fingerprinting already moves the digest itself, so the separate
|
|
1505
1590
|
// live-directory re-scan (`uncoveredCurrentArtifacts`) is redundant
|
|
1506
|
-
// for v3 and is not run.
|
|
1591
|
+
// for v3+ and is not run.
|
|
1507
1592
|
isStale =
|
|
1508
1593
|
computeCoveredDigest(projectRoot, coveredFilesVal, storedVersion, { phaseDir }) !== coveredDigestVal;
|
|
1509
1594
|
if (isStale) {
|
|
@@ -1784,7 +1869,7 @@ function parseFingerprintFileArgs(tokens) {
|
|
|
1784
1869
|
* directly assignable. #5095 (ADR-5057 Phase 2): `raw`
|
|
1785
1870
|
* returning only the digest is safe even though the
|
|
1786
1871
|
* emitted `covered_files` is a SUPERSET of the caller's
|
|
1787
|
-
* declared list — the v3 digest is computed over
|
|
1872
|
+
* declared list — the v3+ digest is computed over
|
|
1788
1873
|
* `canonicalize(declared ∪ phaseArtifactPaths)` on BOTH
|
|
1789
1874
|
* the emit side (here) and the check side
|
|
1790
1875
|
* (`readVerificationStatus`), so a `--raw` caller that
|
|
@@ -2134,6 +2219,8 @@ const verificationModule = {
|
|
|
2134
2219
|
isReportContained,
|
|
2135
2220
|
routeResult,
|
|
2136
2221
|
findVerificationStatusError,
|
|
2222
|
+
legacyStatusRepairTarget,
|
|
2223
|
+
repairLegacyVerificationStatus,
|
|
2137
2224
|
defaultPhaseCleanCommitTimesMs,
|
|
2138
2225
|
resolvePhaseArtifactFile,
|
|
2139
2226
|
resolveVerificationFile,
|
|
@@ -1809,6 +1809,8 @@ function repairFixText(slash, action) {
|
|
|
1809
1809
|
return `Run ${slash('health')} --repair to add key`;
|
|
1810
1810
|
case REMEDY_ACTION.BACKFILL_MILESTONES:
|
|
1811
1811
|
return `Run ${slash('health')} --backfill to synthesize missing entries from archive snapshots`;
|
|
1812
|
+
case REMEDY_ACTION.REPAIR_VERIFICATION_STATUS:
|
|
1813
|
+
return `Run ${slash('health')} --repair to rewrite the legacy status (original kept as legacy_status)`;
|
|
1812
1814
|
default:
|
|
1813
1815
|
return '';
|
|
1814
1816
|
}
|
|
@@ -176,7 +176,28 @@ function resolveWorktreeContext(cwd, deps = {}) {
|
|
|
176
176
|
reason: 'has_local_planning',
|
|
177
177
|
};
|
|
178
178
|
}
|
|
179
|
-
|
|
179
|
+
const linkage = resolveWorktreeLinkage(cwd, deps);
|
|
180
|
+
if (linkage.reason !== 'linked_worktree')
|
|
181
|
+
return linkage;
|
|
182
|
+
// #4885: from a SUBDIRECTORY of a LINKED worktree the cwd has no .planning of its own, but the
|
|
183
|
+
// worktree's own top-level may. That checkout is the project the caller means; remapping to the
|
|
184
|
+
// main checkout would make `commit` and every planning write land in the wrong repository. A linked
|
|
185
|
+
// worktree with no .planning anywhere still remaps to the main checkout, and the main checkout's own
|
|
186
|
+
// subdirectories are untouched.
|
|
187
|
+
const execGit = deps.execGit || execGitDefault;
|
|
188
|
+
const top = execGit(['rev-parse', '--show-toplevel'], { cwd });
|
|
189
|
+
const topText = String(top.stdout || '').trim();
|
|
190
|
+
if (!top.timedOut && top.exitCode === 0 && topText !== '') {
|
|
191
|
+
const topLevel = node_path_1.default.resolve(cwd, topText);
|
|
192
|
+
if (existsSync(node_path_1.default.join(topLevel, '.planning'))) {
|
|
193
|
+
return {
|
|
194
|
+
effectiveRoot: topLevel,
|
|
195
|
+
mode: 'current_directory',
|
|
196
|
+
reason: 'has_local_planning',
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return linkage;
|
|
180
201
|
}
|
|
181
202
|
function planWorktreePrune(repoRoot, options = {}, deps = {}) {
|
|
182
203
|
const parsePorcelain = deps.parseWorktreePorcelain || parseWorktreePorcelain;
|
|
@@ -295,4 +295,15 @@ This stages `src/api/users.ts` and `src/types/user.ts` in the `backend/` repo, a
|
|
|
295
295
|
|
|
296
296
|
Files that don't match any configured sub-repo are reported as unmatched.
|
|
297
297
|
|
|
298
|
+
### Milestone Tagging Across Sub-Repos
|
|
299
|
+
|
|
300
|
+
A GSD milestone version names planned work in `.planning/`; it is not any repository's release version. With `git.repo_tagging: "per_repo"` and a non-empty `sub_repos`, `/gsd:complete-milestone` records one release map entry per repository (status, release, reference, environment, tag choice) instead of creating one `v[X.Y]` tag in the current directory:
|
|
301
|
+
|
|
302
|
+
1. `gsd_run query milestone release-tags --map <tags.json> --dry-run` checks every requested tag (repository listed in `sub_repos`, ref resolves, tag name valid, no tag at a different commit) before anything is written.
|
|
303
|
+
2. After one consolidated confirmation, `gsd_run query milestone release-tags --map <tags.json>` creates the annotated tags, each with the target repository as its working directory. Re-running the same map is idempotent (`already_tagged`).
|
|
304
|
+
3. `milestone complete <version> --release-map <file>` archives the map by recorded value.
|
|
305
|
+
4. Nothing is pushed by default. Each repository's tag has its own confirmation naming the repository and its remote.
|
|
306
|
+
|
|
307
|
+
With `git.create_tag: false` the map is still recorded and no tag is created. See [How to tag a multi-repository milestone](../../docs/how-to/tag-a-multi-repo-milestone.md).
|
|
308
|
+
|
|
298
309
|
</sub_repos_support>
|
|
@@ -91,12 +91,19 @@ not one shape of one.
|
|
|
91
91
|
capability manifest, which may be third-party — and `gates[].check` is **not** one of the
|
|
92
92
|
executable surfaces the install consent prompt discloses (`hooks`, command modules,
|
|
93
93
|
`mcpServers`, reviewer lanes), so a capability can be consented to as declarative-only and
|
|
94
|
-
still reach a shell through a gate.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
94
|
+
still reach a shell through a gate. Normalize first, then validate: the registry declares query
|
|
95
|
+
ids in dotted form (`ui.plan-gate`, `verify.codebase-drift`) and `gsd_run check` accepts them by
|
|
96
|
+
turning each `.` into `-`, so replace every `.` with `-` in the query value you read from
|
|
97
|
+
`activeHooks`, then check that normalized value against
|
|
98
|
+
`^[a-z][a-z0-9]*(-[a-z0-9]+)*( [a-z][a-z0-9]*(-[a-z0-9]+)*)*$` yourself, IN-CONTEXT — **never** by
|
|
99
|
+
pasting it into a shell command to be tested there, because a value carrying a quote, `;`, a
|
|
100
|
+
`` ` ``, `$(`, or a newline would terminate the assignment and run as its own statement before
|
|
101
|
+
any shell-side check could execute. The normalized value is the one value you validate and the
|
|
102
|
+
one value you pass: wherever a workflow shows `${hook.check.query}`, write the normalized value.
|
|
103
|
+
A leading dot, a trailing dot or consecutive dots become a leading, trailing or doubled hyphen,
|
|
104
|
+
which the pattern rejects. A value that fails — or that passes but names no known check, which
|
|
105
|
+
`gsd_run check` rejects as an unknown check subcommand — is a malformed manifest: record a
|
|
106
|
+
warning, route it per the gate's `onError`, and do not run it. Pass `check.predicate` as a **single argv
|
|
100
107
|
element** for the same reason — never re-quote it into a shell string, where an apostrophe
|
|
101
108
|
would close the literal. This is the identical requirement `step` → `ref.command` carries
|
|
102
109
|
above; it was stated there and omitted here, which is the gap #3559 closed.
|
|
@@ -35,6 +35,7 @@ Configuration options for `.planning/` directory behavior.
|
|
|
35
35
|
| `git.base_branch` | `null` (auto-detect) | Target branch for PRs and merges (e.g. `"master"`, `"develop"`). When `null`, auto-detects from `git symbolic-ref refs/remotes/origin/HEAD`, falling back to `"main"`. |
|
|
36
36
|
| `git.protected_branches` | (none) | Optional array of non-empty strings naming additional shared branches that should trigger protected-branch warnings |
|
|
37
37
|
| `git.create_tag` | `true` | Create git tags on milestone completion |
|
|
38
|
+
| `git.repo_tagging` | `"single"` | Milestone tagging mode: `"single"` (existing behavior) or `"per_repo"` (record one release per configured repository; each tag is pushed only after a per-repository confirmation) |
|
|
38
39
|
| `git.phase_branch_template` | `"gsd/phase-{phase}-{slug}"` | Branch template for phase strategy |
|
|
39
40
|
| `git.milestone_branch_template` | `"gsd/{milestone}-{slug}"` | Branch template for milestone strategy |
|
|
40
41
|
| `git.quick_branch_template` | `null` | Optional branch template for quick-task runs |
|
|
@@ -340,6 +341,7 @@ Set via `git.*` namespace (e.g., `"git": { "branching_strategy": "phase" }`).
|
|
|
340
341
|
| `git.base_branch` | string\|null | `null` (auto-detect) | Any branch name | Target branch for PRs and merges; auto-detects from `origin/HEAD` when `null` |
|
|
341
342
|
| `git.protected_branches` | array of non-empty strings | (none) | Non-empty branch names | Optional protected names added to the resolved base branch for execute-phase and ship warnings |
|
|
342
343
|
| `git.create_tag` | boolean | `true` | `true`, `false` | Create git tags on milestone completion |
|
|
344
|
+
| `git.repo_tagging` | string | `"single"` | `"single"`, `"per_repo"` | Milestone tagging mode; `"per_repo"` opts a multi-repo project into per-repository release records |
|
|
343
345
|
| `git.phase_branch_template` | string | `"gsd/phase-{phase}-{slug}"` | Template with `{phase}`, `{slug}` | Branch naming template for `phase` strategy |
|
|
344
346
|
| `git.milestone_branch_template` | string | `"gsd/{milestone}-{slug}"` | Template with `{milestone}`, `{slug}` | Branch naming template for `milestone` strategy |
|
|
345
347
|
| `git.quick_branch_template` | string\|null | `null` | Template with `{slug}` | Optional branch template for quick-task runs |
|
|
@@ -316,7 +316,7 @@ git log --oneline -E --grep="^feat\((0*${PHASE_N})-(0*${PLAN_N})\):" | head -1
|
|
|
316
316
|
git log --oneline -E --grep="^refactor\((0*${PHASE_N})-(0*${PLAN_N})\):" | head -1
|
|
317
317
|
```
|
|
318
318
|
|
|
319
|
-
If RED or GREEN gate commits are missing, add a `## TDD Gate Compliance` section to SUMMARY.md with the violation details.
|
|
319
|
+
If RED or GREEN gate commits are missing, or no GREEN commit follows a RED commit (#5275), add a `## TDD Gate Compliance` section to SUMMARY.md with the violation details.
|
|
320
320
|
</gate_enforcement>
|
|
321
321
|
|
|
322
322
|
<end_of_phase_review>
|
|
@@ -342,7 +342,7 @@ TDD Plans: {count} | Gate violations: {count}
|
|
|
342
342
|
|
|
343
343
|
### What the Review Checks
|
|
344
344
|
|
|
345
|
-
1. **Gate sequence:** Each TDD plan has RED → GREEN commits in order. A plan is scored on its own `test(<plan>):` / `feat(<plan>):` commits, with one exception (#4867): a RED-only probe plan is satisfied by a `feat(...)` commit under a plan that declares it in `depends_on`, directly or through a chain. That is the gap-closure shape (probe plan, then the fix plan that depends on it). The plan's own RED is still required, a plan nothing depends on is scored alone, and the report names the plan that supplied the GREEN (`Plan {id} GREEN is supplied by dependent plan {id}.`).
|
|
345
|
+
1. **Gate sequence:** Each TDD plan has RED → GREEN commits in order (#5275). Order is commit ancestry, not log position or commit date: a GREEN counts only when it descends from a RED commit of the plan, so a history merged from worktree branches is judged by what each commit descends from. A plan with both commit kinds but no GREEN after a RED fails with `reason: green_before_red` and the line `Plan {id} GREEN precedes RED: no feat(...) commit descends from a test(...) commit.`, distinct from the `missing` row for a plan without a RED or GREEN commit. If git cannot answer the ancestry question, the GREEN keeps its credit rather than becoming a violation. A plan is scored on its own `test(<plan>):` / `feat(<plan>):` commits, with one exception (#4867): a RED-only probe plan is satisfied by a `feat(...)` commit under a plan that declares it in `depends_on`, directly or through a chain. That is the gap-closure shape (probe plan, then the fix plan that depends on it). The plan's own RED is still required, a plan nothing depends on is scored alone, and the report names the plan that supplied the GREEN (`Plan {id} GREEN is supplied by dependent plan {id}.`).
|
|
346
346
|
2. **Test quality:** RED phase tests fail for the right reason (not import errors or syntax)
|
|
347
347
|
3. **Minimal GREEN:** Implementation is minimal — no premature optimization in GREEN phase
|
|
348
348
|
4. **Refactor discipline:** If REFACTOR commit exists, tests still pass
|
|
@@ -25,6 +25,17 @@ bug_class: null <!-- assigned at Phase 1.75 — bohrbug|heisenbug-mandelbug|con
|
|
|
25
25
|
reasoning_checkpoint: null <!-- populated before every fix attempt — see structured_returns -->
|
|
26
26
|
tdd_checkpoint: null <!-- populated when tdd_mode is active after root cause confirmed -->
|
|
27
27
|
|
|
28
|
+
## Session Loop
|
|
29
|
+
<!-- Read and written only by the checkpoint loop that drives gsd-debugger (on Pi, the top-level session); gsd-debugger never edits it. OVERWRITE on each dispatch and each wait -->
|
|
30
|
+
|
|
31
|
+
goal: find_and_fix | find_root_cause_only
|
|
32
|
+
tdd_mode: false
|
|
33
|
+
tdd_phase: none | green
|
|
34
|
+
resume_count: 0
|
|
35
|
+
awaiting: none | [header of the return whose question is open]
|
|
36
|
+
question: none | [checkpoint type and the full question text shown to the user]
|
|
37
|
+
last_next_action: [next_action seen before the last re-dispatch]
|
|
38
|
+
|
|
28
39
|
## Symptoms
|
|
29
40
|
<!-- Written during gathering, then immutable -->
|
|
30
41
|
|
|
@@ -78,6 +89,11 @@ files_changed: []
|
|
|
78
89
|
- `reasoning_checkpoint`: OVERWRITE before every fix_and_verify — seven-field structured reasoning record (hypothesis, confirming_evidence, falsification_test, fix_rationale, blind_spots, candidate_causes, and_gate) — see `gsd-debugger.md` Structured Reasoning Checkpoint
|
|
79
90
|
- `tdd_checkpoint`: OVERWRITE during TDD red/green phases — test file, name, status, failure output
|
|
80
91
|
|
|
92
|
+
**Session Loop:**
|
|
93
|
+
- Owned by the checkpoint loop that drives `gsd-debugger`; on Pi that loop is the top-level session, which holds no state in memory between turns (#5638)
|
|
94
|
+
- OVERWRITE on each dispatch and each wait; the debugger never edits it and `## Current Focus` rewrites never touch it
|
|
95
|
+
- Fields: goal, tdd_mode, tdd_phase, resume_count, awaiting, question, last_next_action
|
|
96
|
+
|
|
81
97
|
**Symptoms:**
|
|
82
98
|
- Written during initial gathering phase
|
|
83
99
|
- IMMUTABLE after gathering complete
|
|
@@ -16,7 +16,7 @@ covered_files: # #4155 — see agents/gsd-verifier.md's "Create VERIFICATION.md"
|
|
|
16
16
|
- .planning/phases/XX-name/{phase_num}-{plan}-PLAN.md
|
|
17
17
|
- .planning/phases/XX-name/{phase_num}-{plan}-SUMMARY.md
|
|
18
18
|
- src/{changed-file}.cts
|
|
19
|
-
covered_digest: "
|
|
19
|
+
covered_digest: "v4:sha256:{digest from verification.fingerprint}"
|
|
20
20
|
behavior_unverified: 0 # Count of ⚠️ PRESENT_BEHAVIOR_UNVERIFIED truths (present + wired, behavior not exercised)
|
|
21
21
|
behavior_unverified_items: # Only if behavior_unverified > 0 — the truths above as structured items; emitted regardless of overall status
|
|
22
22
|
- truth: "Observable truth whose state transition or cancellation/cleanup/ordering invariant no test exercises"
|
|
@@ -128,7 +128,8 @@ if [ -z "$AUDIT_TEST_CMD" ]; then
|
|
|
128
128
|
elif [ -f "Justfile" ] || [ -f "justfile" ]; then
|
|
129
129
|
AUDIT_TEST_CMD="just test"
|
|
130
130
|
elif [ -f "package.json" ]; then
|
|
131
|
-
AUDIT_TEST_CMD
|
|
131
|
+
AUDIT_TEST_CMD=$(gsd_run query resolve-package-manager test --cwd . 2>/dev/null || echo "npm test")
|
|
132
|
+
[ -n "$AUDIT_TEST_CMD" ] || AUDIT_TEST_CMD="npm test"
|
|
132
133
|
elif [ -f "Cargo.toml" ]; then
|
|
133
134
|
AUDIT_TEST_CMD="cargo test"
|
|
134
135
|
elif [ -f "go.mod" ]; then
|
|
@@ -2,8 +2,17 @@ When `FALLOW_ENABLED=true`:
|
|
|
2
2
|
|
|
3
3
|
1) Resolve binary via `node_modules/.bin/fallow` first, then PATH.
|
|
4
4
|
```bash
|
|
5
|
+
# gsd-resolve-lib begin fallow-runner.cjs (#5640): load helpers from the installed runtime, not the cwd
|
|
6
|
+
_gsd_lib_probe="fallow-runner.cjs"
|
|
7
|
+
_gsd_rt="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
|
8
|
+
GSD_LIB_DIR=""
|
|
9
|
+
_gsd_pick() { for _gsd_d; do if [ -n "$_gsd_d" ] && [ -f "$_gsd_d/$_gsd_lib_probe" ]; then GSD_LIB_DIR="$_gsd_d"; return 0; fi; done; return 1; }
|
|
10
|
+
_gsd_pick "${GSD_TOOLS:+${GSD_TOOLS%/*}/lib}" "$_gsd_rt/gsd-core/bin/lib" "$_gsd_rt/.claude/gsd-core/bin/lib" "$_gsd_rt/.codex/gsd-core/bin/lib" "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/lib" "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/lib" || true
|
|
11
|
+
if [ -z "$GSD_LIB_DIR" ]; then echo "ERROR: fallow-runner.cjs not found in the GSD runtime. Looked in: ${GSD_TOOLS:+${GSD_TOOLS%/*}/lib, }${_gsd_rt}/gsd-core/bin/lib (and its .claude and .codex forms), ${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/lib, ${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/lib. Reinstall: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi
|
|
12
|
+
export GSD_LIB_DIR
|
|
13
|
+
# gsd-resolve-lib end
|
|
5
14
|
FALLOW_BIN=$(FALLOW_CWD="$(pwd)" node -e "
|
|
6
|
-
const { resolveFallowBinary } = require('
|
|
15
|
+
const { resolveFallowBinary } = require(require('path').join(process.env.GSD_LIB_DIR, 'fallow-runner.cjs'));
|
|
7
16
|
const resolved = resolveFallowBinary({ cwd: process.env.FALLOW_CWD });
|
|
8
17
|
if (resolved) process.stdout.write(resolved);
|
|
9
18
|
")
|
|
@@ -32,8 +32,20 @@ PHASE_ARG="${1}"
|
|
|
32
32
|
# logic (e.g., --all and --auto imply --fix). Resolved BEFORE the init call below so
|
|
33
33
|
# the section-manifest gate forwards the RESOLVED (post-implication) fix decision, not
|
|
34
34
|
# just a literal --fix token check.
|
|
35
|
+
# gsd-resolve-lib begin code-review-flags.cjs (#5640): load helpers from the installed runtime, not the cwd
|
|
36
|
+
_gsd_lib_probe="code-review-flags.cjs"
|
|
37
|
+
_gsd_rt="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
|
38
|
+
_gsd_gr="$(command -v gsd_run 2>/dev/null || true)"
|
|
39
|
+
case "$_gsd_gr" in /*) ;; *) _gsd_gr="" ;; esac # a gsd_run shell function prints its bare name: not a path
|
|
40
|
+
[ -n "$_gsd_gr" ] && _gsd_gr="$(dirname "$(node -p "require('fs').realpathSync(process.argv[1])" "$_gsd_gr" 2>/dev/null || echo "$_gsd_gr")")/lib"
|
|
41
|
+
GSD_LIB_DIR=""
|
|
42
|
+
_gsd_pick() { for _gsd_d; do if [ -n "$_gsd_d" ] && [ -f "$_gsd_d/$_gsd_lib_probe" ]; then GSD_LIB_DIR="$_gsd_d"; return 0; fi; done; return 1; }
|
|
43
|
+
_gsd_pick "${GSD_TOOLS:+${GSD_TOOLS%/*}/lib}" "$_gsd_rt/gsd-core/bin/lib" "$_gsd_rt/.claude/gsd-core/bin/lib" "$_gsd_rt/.codex/gsd-core/bin/lib" "$_gsd_gr" "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/lib" "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/lib" || true
|
|
44
|
+
if [ -z "$GSD_LIB_DIR" ]; then echo "ERROR: code-review-flags.cjs not found in the GSD runtime. Looked in: ${GSD_TOOLS:+${GSD_TOOLS%/*}/lib, }${_gsd_rt}/gsd-core/bin/lib (and its .claude and .codex forms), ${_gsd_gr:-next to gsd_run (not on PATH)}, ${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/lib, ${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/lib. Reinstall: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi
|
|
45
|
+
export GSD_LIB_DIR
|
|
46
|
+
# gsd-resolve-lib end
|
|
35
47
|
FLAGS_JSON=$(node -e "
|
|
36
|
-
const { parseCodeReviewFlags } = require('
|
|
48
|
+
const { parseCodeReviewFlags } = require(require('path').join(process.env.GSD_LIB_DIR, 'code-review-flags.cjs'));
|
|
37
49
|
const flags = parseCodeReviewFlags(process.argv.slice(1));
|
|
38
50
|
process.stdout.write(JSON.stringify(flags));
|
|
39
51
|
" -- "$@" 2>/dev/null)
|
|
@@ -125,12 +137,25 @@ If FILES_OVERRIDE is set (from --files flag):
|
|
|
125
137
|
```bash
|
|
126
138
|
if [ -n "$FILES_OVERRIDE" ]; then
|
|
127
139
|
REVIEW_FILES=()
|
|
128
|
-
|
|
140
|
+
# #5519: compare the repository root and each resolved path in ONE form. On Windows Git Bash,
|
|
141
|
+
# `git rev-parse --show-toplevel` (a native program) reports a mixed path (C:/Users/..) while
|
|
142
|
+
# GNU `realpath` reports an MSYS POSIX path (/c/Users/..); the two never share a prefix, so
|
|
143
|
+
# every entry looked "outside". `cygpath -m` exists exactly where the forms differ and maps both
|
|
144
|
+
# to the mixed form; elsewhere the value passes through unchanged.
|
|
145
|
+
_gsd_norm_path() {
|
|
146
|
+
if command -v cygpath >/dev/null 2>&1; then cygpath -m "$1"; else printf '%s' "$1"; fi
|
|
147
|
+
}
|
|
148
|
+
REPO_ROOT=$(_gsd_norm_path "$(git rev-parse --show-toplevel 2>/dev/null)")
|
|
129
149
|
|
|
130
150
|
for file_path in "${FILES_ARRAY[@]}"; do
|
|
131
|
-
# Security: validate path is within repository (prevent path traversal)
|
|
132
|
-
|
|
133
|
-
|
|
151
|
+
# Security: validate path is within repository (prevent path traversal). Compare on a path
|
|
152
|
+
# boundary: the root itself or anything under "<root>/" — a sibling such as "<root>-other"
|
|
153
|
+
# shares the prefix but is not inside.
|
|
154
|
+
ABS_PATH=$(_gsd_norm_path "$(realpath -m "${file_path}" 2>/dev/null || echo "${file_path}")")
|
|
155
|
+
# Fail closed on two shapes that cannot be trusted: an empty root (git failed, so nothing can be
|
|
156
|
+
# "inside" it) and a path that still holds ".." segments (a resolved path never does, so it was
|
|
157
|
+
# not resolved — no GNU realpath).
|
|
158
|
+
if [ -z "$REPO_ROOT" ] || [[ "$ABS_PATH" == */../* || "$ABS_PATH" == */.. ]] || [[ "$ABS_PATH" != "$REPO_ROOT" && "$ABS_PATH" != "$REPO_ROOT"/* ]]; then
|
|
134
159
|
echo "Error: File path outside repository, skipping: ${file_path}"
|
|
135
160
|
continue
|
|
136
161
|
fi
|
|
@@ -437,8 +462,20 @@ DEPTH_PAYLOAD=$(printf '%s\n' "${REVIEW_FILES[@]}" | FLAG_DEPTH="$DEPTH_OVERRIDE
|
|
|
437
462
|
}));
|
|
438
463
|
")
|
|
439
464
|
|
|
465
|
+
# gsd-resolve-lib begin code-review-depth.cjs (#5640): load helpers from the installed runtime, not the cwd
|
|
466
|
+
_gsd_lib_probe="code-review-depth.cjs"
|
|
467
|
+
_gsd_rt="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
|
468
|
+
_gsd_gr="$(command -v gsd_run 2>/dev/null || true)"
|
|
469
|
+
case "$_gsd_gr" in /*) ;; *) _gsd_gr="" ;; esac # a gsd_run shell function prints its bare name: not a path
|
|
470
|
+
[ -n "$_gsd_gr" ] && _gsd_gr="$(dirname "$(node -p "require('fs').realpathSync(process.argv[1])" "$_gsd_gr" 2>/dev/null || echo "$_gsd_gr")")/lib"
|
|
471
|
+
GSD_LIB_DIR=""
|
|
472
|
+
_gsd_pick() { for _gsd_d; do if [ -n "$_gsd_d" ] && [ -f "$_gsd_d/$_gsd_lib_probe" ]; then GSD_LIB_DIR="$_gsd_d"; return 0; fi; done; return 1; }
|
|
473
|
+
_gsd_pick "${GSD_TOOLS:+${GSD_TOOLS%/*}/lib}" "$_gsd_rt/gsd-core/bin/lib" "$_gsd_rt/.claude/gsd-core/bin/lib" "$_gsd_rt/.codex/gsd-core/bin/lib" "$_gsd_gr" "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/lib" "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/lib" || true
|
|
474
|
+
if [ -z "$GSD_LIB_DIR" ]; then echo "ERROR: code-review-depth.cjs not found in the GSD runtime. Looked in: ${GSD_TOOLS:+${GSD_TOOLS%/*}/lib, }${_gsd_rt}/gsd-core/bin/lib (and its .claude and .codex forms), ${_gsd_gr:-next to gsd_run (not on PATH)}, ${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/lib, ${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/lib. Reinstall: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi
|
|
475
|
+
export GSD_LIB_DIR
|
|
476
|
+
# gsd-resolve-lib end
|
|
440
477
|
DEPTH_JSON=$(echo "$DEPTH_PAYLOAD" | node -e "
|
|
441
|
-
const { resolveCodeReviewDepth } = require('
|
|
478
|
+
const { resolveCodeReviewDepth } = require(require('path').join(process.env.GSD_LIB_DIR, 'code-review-depth.cjs'));
|
|
442
479
|
const input = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf-8'));
|
|
443
480
|
process.stdout.write(JSON.stringify(resolveCodeReviewDepth(input)));
|
|
444
481
|
")
|
|
@@ -737,9 +774,21 @@ if [ -n "$FALLOW_JSON_PATH" ] && [ -f "$FALLOW_JSON_PATH" ]; then
|
|
|
737
774
|
# Normalize fallow's raw report into the compact {summary, findings[]} contract
|
|
738
775
|
# the reviewer consumes (real fallow schema -> normalized findings).
|
|
739
776
|
FALLOW_NORMALIZED_PATH="${PHASE_DIR}/FALLOW-normalized.json"
|
|
777
|
+
# gsd-resolve-lib begin fallow-runner.cjs (#5640): load helpers from the installed runtime, not the cwd
|
|
778
|
+
_gsd_lib_probe="fallow-runner.cjs"
|
|
779
|
+
_gsd_rt="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
|
780
|
+
_gsd_gr="$(command -v gsd_run 2>/dev/null || true)"
|
|
781
|
+
case "$_gsd_gr" in /*) ;; *) _gsd_gr="" ;; esac # a gsd_run shell function prints its bare name: not a path
|
|
782
|
+
[ -n "$_gsd_gr" ] && _gsd_gr="$(dirname "$(node -p "require('fs').realpathSync(process.argv[1])" "$_gsd_gr" 2>/dev/null || echo "$_gsd_gr")")/lib"
|
|
783
|
+
GSD_LIB_DIR=""
|
|
784
|
+
_gsd_pick() { for _gsd_d; do if [ -n "$_gsd_d" ] && [ -f "$_gsd_d/$_gsd_lib_probe" ]; then GSD_LIB_DIR="$_gsd_d"; return 0; fi; done; return 1; }
|
|
785
|
+
_gsd_pick "${GSD_TOOLS:+${GSD_TOOLS%/*}/lib}" "$_gsd_rt/gsd-core/bin/lib" "$_gsd_rt/.claude/gsd-core/bin/lib" "$_gsd_rt/.codex/gsd-core/bin/lib" "$_gsd_gr" "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/lib" "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/lib" || true
|
|
786
|
+
if [ -z "$GSD_LIB_DIR" ]; then echo "ERROR: fallow-runner.cjs not found in the GSD runtime (looked in ${_gsd_rt}/gsd-core/bin/lib (and its .claude and .codex forms), ${_gsd_gr:-next to gsd_run (not on PATH)}, ${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/lib, ${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/lib); continuing without it." >&2; fi
|
|
787
|
+
export GSD_LIB_DIR
|
|
788
|
+
# gsd-resolve-lib end
|
|
740
789
|
FALLOW_SRC="$FALLOW_JSON_PATH" FALLOW_OUT="$FALLOW_NORMALIZED_PATH" node -e "
|
|
741
790
|
const fs = require('fs');
|
|
742
|
-
const { normalizeFallowReportFile } = require('
|
|
791
|
+
const { normalizeFallowReportFile } = require(require('path').join(process.env.GSD_LIB_DIR, 'fallow-runner.cjs'));
|
|
743
792
|
const n = normalizeFallowReportFile(process.env.FALLOW_SRC);
|
|
744
793
|
fs.writeFileSync(process.env.FALLOW_OUT, JSON.stringify(n, null, 2));
|
|
745
794
|
" 2>/dev/null && FALLOW_EMBED_PATH="$FALLOW_NORMALIZED_PATH" || FALLOW_EMBED_PATH="$FALLOW_JSON_PATH"
|
|
@@ -943,6 +992,11 @@ grep -A 3 "^### CR-\|^### BL-\|^### WR-" "${REVIEW_PATH}" | head -n 12
|
|
|
943
992
|
**Windows:** This workflow uses bash features (arrays, process substitution). On Windows, it requires
|
|
944
993
|
Git Bash or WSL. Native PowerShell is not supported. The CI matrix (Ubuntu/macOS/Windows)
|
|
945
994
|
runs under Git Bash on Windows runners, which provides bash compatibility.
|
|
995
|
+
The `--files` containment check compares the repository root with each resolved path, and Git Bash
|
|
996
|
+
reports them in different forms (`git rev-parse` returns `C:/Users/...`, GNU `realpath` returns
|
|
997
|
+
`/c/Users/...`), so both are normalized with `cygpath -m` (shipped with Git Bash) before the
|
|
998
|
+
comparison. Relative paths and absolute paths in either form are accepted when they are inside the
|
|
999
|
+
repository (#5519).
|
|
946
1000
|
|
|
947
1001
|
**macOS:** macOS ships with bash 3.2 (GPL licensing). This workflow does NOT use `mapfile` (bash 4+
|
|
948
1002
|
only) — all array construction uses portable `while IFS= read -r` loops compatible with bash 3.2.
|