@opengsd/gsd-core 1.13.0 → 1.14.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 +1 -1
- package/agents/gsd-advisor-researcher.compact.md +85 -0
- package/agents/gsd-ai-researcher.compact.md +96 -0
- package/agents/gsd-assumptions-analyzer.compact.md +81 -0
- package/agents/gsd-code-fixer.compact.md +458 -0
- package/agents/gsd-code-fixer.md +5 -5
- package/agents/gsd-code-reviewer.compact.md +269 -0
- package/agents/gsd-code-reviewer.md +15 -3
- package/agents/gsd-codebase-mapper.compact.md +760 -0
- package/agents/gsd-debug-session-manager.compact.md +345 -0
- package/agents/gsd-doc-classifier.compact.md +192 -0
- package/agents/gsd-doc-synthesizer.compact.md +200 -0
- package/agents/gsd-doc-verifier.compact.md +143 -0
- package/agents/gsd-doc-writer.compact.md +440 -0
- package/agents/gsd-dom-verifier.compact.md +138 -0
- package/agents/gsd-domain-researcher.compact.md +141 -0
- package/agents/gsd-eval-auditor.compact.md +160 -0
- package/agents/gsd-eval-planner.compact.md +137 -0
- package/agents/gsd-framework-selector.compact.md +82 -0
- package/agents/gsd-integration-checker.compact.md +245 -0
- package/agents/gsd-intel-updater.compact.md +226 -0
- package/agents/gsd-mempalace-curator.compact.md +45 -0
- package/agents/gsd-nyquist-auditor.compact.md +179 -0
- package/agents/gsd-pattern-mapper.compact.md +275 -0
- package/agents/gsd-project-researcher.compact.md +587 -0
- package/agents/gsd-research-synthesizer.compact.md +212 -0
- package/agents/gsd-roadmapper.compact.md +454 -0
- package/agents/gsd-roadmapper.md +13 -0
- package/agents/gsd-security-auditor.compact.md +162 -0
- package/agents/gsd-ui-auditor.compact.md +404 -0
- package/agents/gsd-ui-checker.compact.md +277 -0
- package/agents/gsd-ui-researcher.compact.md +282 -0
- package/agents/gsd-user-profiler.compact.md +108 -0
- package/bin/install.js +206 -68
- package/commands/gsd/cleanup.md +1 -0
- package/commands/gsd/code-review.md +2 -1
- package/commands/gsd/complete-milestone.md +1 -0
- package/commands/gsd/config.md +1 -0
- package/commands/gsd/debug.md +1 -0
- package/commands/gsd/graphify.md +1 -0
- package/commands/gsd/health.md +1 -0
- package/commands/gsd/mempalace-capture.md +1 -0
- package/commands/gsd/mempalace-recall.md +1 -0
- package/commands/gsd/new-milestone.md +1 -0
- package/commands/gsd/new-project.md +1 -0
- package/commands/gsd/next.md +1 -0
- package/commands/gsd/pause-work.md +1 -0
- package/commands/gsd/phase.md +1 -0
- package/commands/gsd/pr-branch.md +1 -0
- package/commands/gsd/resume-work.md +1 -0
- package/commands/gsd/review-backlog.md +1 -0
- package/commands/gsd/settings.md +2 -1
- package/commands/gsd/stats.md +1 -0
- package/commands/gsd/thread.md +1 -0
- package/commands/gsd/workspace.md +1 -0
- package/commands/gsd/workstreams.md +1 -0
- package/gsd-core/bin/check-latest-version.cjs +8 -3
- package/gsd-core/bin/gsd-tools.cjs +338 -125
- package/gsd-core/bin/lib/adr-parser.cjs +1 -1
- package/gsd-core/bin/lib/artifacts.cjs +2 -1
- package/gsd-core/bin/lib/audit.cjs +39 -22
- package/gsd-core/bin/lib/broken-windows.cjs +168 -49
- package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
- package/gsd-core/bin/lib/capability-loader.cjs +135 -1
- package/gsd-core/bin/lib/capability-registry.cjs +79 -67
- package/gsd-core/bin/lib/capability-source.cjs +19 -2
- package/gsd-core/bin/lib/capability-validator.cjs +14 -1
- package/gsd-core/bin/lib/check-command-router.cjs +113 -36
- package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
- package/gsd-core/bin/lib/commands.cjs +650 -72
- package/gsd-core/bin/lib/config-loader.cjs +1 -0
- package/gsd-core/bin/lib/config.cjs +153 -38
- package/gsd-core/bin/lib/coverage.cjs +1 -1
- package/gsd-core/bin/lib/decisions.cjs +137 -34
- package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
- package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +12 -1
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
- package/gsd-core/bin/lib/init.cjs +409 -47
- package/gsd-core/bin/lib/install-engine.cjs +16 -3
- package/gsd-core/bin/lib/install-profiles.cjs +14 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +33 -4
- package/gsd-core/bin/lib/loop-resolver.cjs +50 -31
- package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
- package/gsd-core/bin/lib/milestone.cjs +19 -8
- package/gsd-core/bin/lib/model-resolver.cjs +101 -10
- package/gsd-core/bin/lib/phase-command-router.cjs +7 -1
- package/gsd-core/bin/lib/phase-id.cjs +161 -22
- package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
- package/gsd-core/bin/lib/phase.cjs +167 -63
- package/gsd-core/bin/lib/planning-inspect.cjs +34 -18
- package/gsd-core/bin/lib/planning-snapshot.cjs +61 -12
- package/gsd-core/bin/lib/planning-workspace.cjs +50 -1
- package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
- package/gsd-core/bin/lib/quick-batch.cjs +1 -1
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
- package/gsd-core/bin/lib/research-store.cjs +11 -12
- package/gsd-core/bin/lib/review-lane-invocation.cjs +23 -0
- package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
- package/gsd-core/bin/lib/roadmap-parser.cjs +56 -15
- package/gsd-core/bin/lib/roadmap.cjs +108 -14
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +27 -10
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +12 -3
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +13 -5
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +193 -4
- package/gsd-core/bin/lib/security.cjs +126 -7
- package/gsd-core/bin/lib/state-document.cjs +130 -28
- package/gsd-core/bin/lib/state-md-schema.cjs +21 -14
- package/gsd-core/bin/lib/state-transition.cjs +142 -28
- package/gsd-core/bin/lib/state.cjs +223 -27
- package/gsd-core/bin/lib/surface.cjs +60 -2
- package/gsd-core/bin/lib/task-command-router.cjs +12 -6
- package/gsd-core/bin/lib/uat.cjs +1 -1
- package/gsd-core/bin/lib/update-context.cjs +30 -24
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
- package/gsd-core/bin/lib/verification.cjs +47 -15
- package/gsd-core/bin/lib/verify-command-grounding.cjs +1 -1
- package/gsd-core/bin/lib/verify.cjs +188 -23
- package/gsd-core/bin/lib/workstream-inventory.cjs +1 -0
- package/gsd-core/bin/lib/worktree-safety.cjs +13 -7
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +5 -0
- package/gsd-core/bin/verify-reapply-patches.cjs +439 -80
- package/gsd-core/references/compact-content-gate.md +66 -0
- package/gsd-core/references/loop-hook-dispatch.md +18 -0
- package/gsd-core/references/model-profiles.md +12 -3
- package/gsd-core/references/planning-config.md +3 -0
- package/gsd-core/references/tdd.md +5 -2
- package/gsd-core/references/thinking-models-planning.md +18 -2
- package/gsd-core/references/verification-patterns.md +17 -4
- package/gsd-core/references/worktree-path-safety.md +112 -2
- package/gsd-core/templates/README.md +7 -1
- package/gsd-core/templates/state.md +6 -3
- package/gsd-core/templates/summary.compact.md +212 -0
- package/gsd-core/templates/user-setup.compact.md +199 -0
- package/gsd-core/templates/user-setup.md +0 -9
- package/gsd-core/workflows/add-todo.md +3 -2
- package/gsd-core/workflows/autonomous.md +13 -10
- package/gsd-core/workflows/check-todos.md +4 -2
- package/gsd-core/workflows/cleanup.md +3 -1
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +7 -0
- package/gsd-core/workflows/code-review-fix.md +3 -3
- package/gsd-core/workflows/code-review.md +156 -30
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
- package/gsd-core/workflows/complete-milestone.md +39 -262
- package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
- package/gsd-core/workflows/docs-update.md +14 -155
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +18 -3
- package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +7 -2
- package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
- package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
- package/gsd-core/workflows/execute-phase.md +53 -152
- package/gsd-core/workflows/execute-plan.md +20 -7
- package/gsd-core/workflows/help/modes/full.compact.md +398 -0
- package/gsd-core/workflows/help.md +1 -1
- package/gsd-core/workflows/map-codebase.md +50 -3
- package/gsd-core/workflows/new-milestone.md +54 -12
- package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
- package/gsd-core/workflows/new-project.md +32 -202
- package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
- package/gsd-core/workflows/plan-phase.md +22 -181
- package/gsd-core/workflows/pr-branch.md +19 -7
- package/gsd-core/workflows/quick.md +8 -1
- package/gsd-core/workflows/reapply-patches.md +77 -3
- package/gsd-core/workflows/settings.md +18 -5
- package/gsd-core/workflows/update.md +7 -5
- package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
- package/gsd-core/workflows/verify-work.md +20 -180
- package/hooks/dist/gsd-agent-isolation-guard.js +42 -16
- package/hooks/dist/gsd-context-monitor.js +88 -15
- package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
- package/hooks/dist/gsd-secret-read-guard.js +44 -18
- package/hooks/dist/gsd-statusline.js +11 -7
- package/hooks/dist/gsd-validate-commit.sh +34 -4
- package/hooks/dist/gsd-worktree-path-guard.js +25 -14
- package/hooks/dist/gsd-write-guard.js +46 -1
- package/hooks/dist/lib/dispatch-identity.js +187 -0
- package/hooks/dist/lib/filename-classification.js +64 -0
- package/hooks/dist/lib/isolation-deny-reason.js +53 -1
- package/hooks/dist/lib/isolation-sentinel.js +58 -19
- package/hooks/gsd-agent-isolation-guard.js +42 -16
- package/hooks/gsd-context-monitor.js +88 -15
- package/hooks/gsd-cursor-subagent-start.js +34 -14
- package/hooks/gsd-secret-read-guard.js +44 -18
- package/hooks/gsd-statusline.js +11 -7
- package/hooks/gsd-validate-commit.sh +34 -4
- package/hooks/gsd-worktree-path-guard.js +25 -14
- package/hooks/gsd-write-guard.js +46 -1
- package/hooks/lib/dispatch-identity.js +187 -0
- package/hooks/lib/filename-classification.js +64 -0
- package/hooks/lib/isolation-deny-reason.js +53 -1
- package/hooks/lib/isolation-sentinel.js +58 -19
- package/package.json +10 -6
- package/scripts/benchmark-compact-content-variants.cjs +298 -0
- package/scripts/benchmark-compact-content.cjs +368 -0
- package/scripts/check-contract-drift.cjs +4 -1
- package/scripts/check-env.cjs +36 -8
- package/scripts/check-glossary-refs.cjs +25 -21
- package/scripts/ci-next-health.cjs +271 -0
- package/scripts/ci-prepare-test-scope.cjs +7 -7
- package/scripts/ci-test-scope.cjs +126 -20
- package/scripts/ci-timeout-report.cjs +1 -1
- package/scripts/diff-touches-shipped-paths.cjs +1 -1
- package/scripts/docs-guard-registry.cjs +7 -2
- package/scripts/gen-adr-index.cjs +8 -2
- package/scripts/gen-inventory-manifest.cjs +12 -0
- package/scripts/gen-platform-conformance-tier.cjs +557 -0
- package/scripts/lib/drift-scan.cjs +1 -1
- package/scripts/lib/macos-conformance-tier.generated.cjs +210 -0
- package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
- package/scripts/lib/platform-conformance-tier.generated.cjs +276 -0
- package/scripts/lib/suite-detection.cjs +32 -0
- package/scripts/lint-allowed-tools-parity.cjs +221 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +19 -2
- package/scripts/lint-phase-id-drift.cjs +338 -13
- package/scripts/lint-response-language-coverage.cjs +9 -3
- package/scripts/lint-source-test-name-collision.cjs +1 -1
- package/scripts/lint-test-file-count.allowlist.json +1 -0
- package/scripts/lint-vendored-deps.cjs +128 -17
- package/scripts/lint-workflow-shellcheck-baseline.json +85 -0
- package/scripts/prompt-injection-scan.sh +14 -0
- package/scripts/workflow-size.cjs +139 -0
- package/skills/gsd-cleanup/SKILL.md +1 -0
- package/skills/gsd-code-review/SKILL.md +2 -1
- package/skills/gsd-complete-milestone/SKILL.md +1 -0
- package/skills/gsd-config/SKILL.md +1 -0
- package/skills/gsd-debug/SKILL.md +1 -0
- package/skills/gsd-graphify/SKILL.md +1 -0
- package/skills/gsd-health/SKILL.md +1 -0
- package/skills/gsd-mempalace-capture/SKILL.md +1 -0
- package/skills/gsd-mempalace-recall/SKILL.md +1 -0
- package/skills/gsd-new-milestone/SKILL.md +1 -0
- package/skills/gsd-new-project/SKILL.md +1 -0
- package/skills/gsd-next/SKILL.md +1 -0
- package/skills/gsd-pause-work/SKILL.md +1 -0
- package/skills/gsd-phase/SKILL.md +1 -0
- package/skills/gsd-pr-branch/SKILL.md +1 -0
- package/skills/gsd-resume-work/SKILL.md +1 -0
- package/skills/gsd-review-backlog/SKILL.md +1 -0
- package/skills/gsd-settings/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +1 -0
- package/skills/gsd-thread/SKILL.md +1 -0
- package/skills/gsd-workspace/SKILL.md +1 -0
- package/skills/gsd-workstreams/SKILL.md +1 -0
- package/vscode/package.json +1 -1
- package/gsd-core/templates/claude-md.md +0 -145
- package/gsd-core/templates/codebase/concerns.md +0 -310
- package/gsd-core/templates/codebase/conventions.md +0 -307
- package/gsd-core/templates/codebase/integrations.md +0 -280
- package/gsd-core/templates/codebase/structure.md +0 -285
- package/gsd-core/templates/codebase/testing.md +0 -480
- package/gsd-core/templates/debug-subagent-prompt.md +0 -91
- package/gsd-core/templates/discovery.md +0 -146
|
@@ -48,6 +48,7 @@ const coreUtilsMod = require("./core-utils.cjs");
|
|
|
48
48
|
const planningScopeMod = require("./planning-scope.cjs");
|
|
49
49
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
50
50
|
const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
|
|
51
|
+
const security_cjs_1 = require("./security.cjs");
|
|
51
52
|
const { output, error } = io;
|
|
52
53
|
const { extractPhaseToken, scopeToPhase } = phaseId;
|
|
53
54
|
const { extractFrontmatter } = frontmatterMod;
|
|
@@ -251,8 +252,11 @@ function computeCoveredDigest(projectRoot, coveredFiles) {
|
|
|
251
252
|
// confinement check above is not enough. realpathSync resolves the
|
|
252
253
|
// actual target; re-confining against realRoot closes that gap.
|
|
253
254
|
const real = node_fs_1.default.realpathSync(resolved);
|
|
254
|
-
|
|
255
|
-
|
|
255
|
+
// Both operands are already realpath-resolved (this fn's own realpathSync calls
|
|
256
|
+
// above), so the shared containment comparison applies directly (ADR-4650) —
|
|
257
|
+
// no re-resolution through assertWithinRoot/tryWithinRoot, which would redo work
|
|
258
|
+
// this function already owns for its exists-vs-escaped tri-state.
|
|
259
|
+
if (!(0, security_cjs_1.isContainedIn)(real, realRoot)) {
|
|
256
260
|
return null;
|
|
257
261
|
}
|
|
258
262
|
const st = node_fs_1.default.statSync(real);
|
|
@@ -471,7 +475,7 @@ function resolvePhaseArtifactFile(entries, bareName, options = {}) {
|
|
|
471
475
|
// filtered out, and if that leaves nothing the code falls through to
|
|
472
476
|
// `allowBare`/`null` deliberately.
|
|
473
477
|
const scoped = options.phaseDirName
|
|
474
|
-
? scopeToPhase(candidates, options.phaseDirName)
|
|
478
|
+
? scopeToPhase(candidates, options.phaseDirName, options.convention)
|
|
475
479
|
: candidates;
|
|
476
480
|
if (scoped.length > 0)
|
|
477
481
|
return scoped[0];
|
|
@@ -528,7 +532,7 @@ function resolveVerificationFile(entries, options = {}) {
|
|
|
528
532
|
function resolveUatFile(entries, options = {}) {
|
|
529
533
|
return resolvePhaseArtifactFile(entries, 'UAT.md', options);
|
|
530
534
|
}
|
|
531
|
-
function findStaleVerificationSummary(phaseDir, fsImpl = defaultFsImpl, phaseCleanCommitTimesMs = defaultPhaseCleanCommitTimesMs) {
|
|
535
|
+
function findStaleVerificationSummary(phaseDir, fsImpl = defaultFsImpl, phaseCleanCommitTimesMs = defaultPhaseCleanCommitTimesMs, convention) {
|
|
532
536
|
// FS errors (TOCTOU: a SUMMARY listed by scanPhasePlans then removed before statSync;
|
|
533
537
|
// unreadable dir; broken symlink; file->dir swap) must degrade rather than throw
|
|
534
538
|
// uncaught into callers that are NOT under the planning lock (init.manager /
|
|
@@ -544,9 +548,20 @@ function findStaleVerificationSummary(phaseDir, fsImpl = defaultFsImpl, phaseCle
|
|
|
544
548
|
// or sentinel-numbered canonically-shaped file cannot outrank this
|
|
545
549
|
// phase's own (possibly non-canonical) report. #3511: phaseDirName scopes
|
|
546
550
|
// the fallback path to this same phase (see resolveVerificationFile docs).
|
|
551
|
+
// #4187: allowBare — this staleness seam must see the same report set the
|
|
552
|
+
// status reader sees, or a bare report could never read `stale` while its
|
|
553
|
+
// dashed twin could (two answers from one verb).
|
|
547
554
|
const phaseDirName = node_path_1.default.basename(phaseDir);
|
|
548
|
-
|
|
549
|
-
|
|
555
|
+
// #612: derive the token with the resolved convention so a bracket dir's
|
|
556
|
+
// own token is read behind its `{CODE}.{MM}-` prefix. #4187: keep the bare
|
|
557
|
+
// report tier aligned with the status reader.
|
|
558
|
+
const phaseToken = extractPhaseToken(phaseDirName, convention);
|
|
559
|
+
const verificationFile = resolveVerificationFile(phaseFiles, {
|
|
560
|
+
allowBare: true,
|
|
561
|
+
phaseToken,
|
|
562
|
+
phaseDirName,
|
|
563
|
+
convention,
|
|
564
|
+
});
|
|
550
565
|
if (!verificationFile)
|
|
551
566
|
return { determined: true, stale: false };
|
|
552
567
|
const summaryFiles = scanPhasePlans(phaseDir).summaryFiles
|
|
@@ -587,7 +602,9 @@ function findStaleVerificationSummary(phaseDir, fsImpl = defaultFsImpl, phaseCle
|
|
|
587
602
|
* 1. Find the phase's verification report via `resolveVerificationFile`
|
|
588
603
|
* (canonical `<phase-token>-VERIFICATION.md` preferred; falls back to the
|
|
589
604
|
* alphabetically-first `*-VERIFICATION.md` that belongs to THIS phase when
|
|
590
|
-
* none is canonical — #3357/#3511
|
|
605
|
+
* none is canonical — #3357/#3511; and, when the directory's only report
|
|
606
|
+
* is a bare `VERIFICATION.md`, that file — #4187, matching
|
|
607
|
+
* `verification.resolve-file`). If none → status 'missing'.
|
|
591
608
|
* 2. Extract `status` from FRONTMATTER ONLY via the shared extractFrontmatter
|
|
592
609
|
* parser (DEFECT.FRONTMATTER-SCALAR-BROAD-GREP fix — parser anchors at byte 0).
|
|
593
610
|
* If no frontmatter block or no `status` key → status 'missing'.
|
|
@@ -608,7 +625,12 @@ function readVerificationStatus(phaseDir, opts = {}) {
|
|
|
608
625
|
const fsImpl = opts.fs ?? defaultFsImpl;
|
|
609
626
|
const phaseCleanCommitTimesMs = opts.phaseCleanCommitTimesMs ?? defaultPhaseCleanCommitTimesMs;
|
|
610
627
|
const runtime = opts.runtime ?? 'claude';
|
|
611
|
-
// Phase token for the gaps_found command
|
|
628
|
+
// Phase token for the gaps_found command — deliberately convention-LESS
|
|
629
|
+
// even when `opts.convention` is present: the token becomes a bare COMMAND
|
|
630
|
+
// ARGUMENT below, and a bare bracket phase number is milestone-ambiguous
|
|
631
|
+
// (`02` cannot tell GSD.01-02 from GSD.02-02), so the argument keeps its
|
|
632
|
+
// pre-#612 shape. The convention-aware token is derived separately for
|
|
633
|
+
// FILE RESOLUTION only (`resolutionToken`, at the readdir below).
|
|
612
634
|
const baseName = node_path_1.default.basename(phaseDir);
|
|
613
635
|
const phaseToken = extractPhaseToken(baseName);
|
|
614
636
|
const derivedPhaseNumber = phaseToken.length > 0 ? phaseToken : baseName;
|
|
@@ -624,12 +646,21 @@ function readVerificationStatus(phaseDir, opts = {}) {
|
|
|
624
646
|
let verificationFile = null;
|
|
625
647
|
try {
|
|
626
648
|
const entries = fsImpl.readdirSync(phaseDir);
|
|
627
|
-
// #3492: pin selection to THIS phase's own token
|
|
628
|
-
//
|
|
629
|
-
//
|
|
630
|
-
//
|
|
631
|
-
//
|
|
632
|
-
|
|
649
|
+
// #3492: pin selection to THIS phase's own token so a stray cross-phase
|
|
650
|
+
// or sentinel-numbered canonically-shaped file cannot outrank this phase's
|
|
651
|
+
// own report. #612: derive a separate convention-aware RESOLUTION token
|
|
652
|
+
// for bracket directories while the routed command argument above stays
|
|
653
|
+
// convention-less and milestone-unambiguous. #4187: keep the bare report
|
|
654
|
+
// tier aligned with every other verification reader.
|
|
655
|
+
const resolutionToken = opts.convention === 'bracket'
|
|
656
|
+
? extractPhaseToken(baseName, opts.convention)
|
|
657
|
+
: phaseToken;
|
|
658
|
+
verificationFile = resolveVerificationFile(entries, {
|
|
659
|
+
allowBare: true,
|
|
660
|
+
phaseToken: resolutionToken,
|
|
661
|
+
phaseDirName: baseName,
|
|
662
|
+
convention: opts.convention,
|
|
663
|
+
});
|
|
633
664
|
}
|
|
634
665
|
catch {
|
|
635
666
|
// Directory unreadable → treat as missing
|
|
@@ -716,7 +747,7 @@ function readVerificationStatus(phaseDir, opts = {}) {
|
|
|
716
747
|
!allCurrentArtifactsCovered(phaseDir, coveredFilesVal);
|
|
717
748
|
}
|
|
718
749
|
else {
|
|
719
|
-
const staleCheck = findStaleVerificationSummary(phaseDir, fsImpl, phaseCleanCommitTimesMs);
|
|
750
|
+
const staleCheck = findStaleVerificationSummary(phaseDir, fsImpl, phaseCleanCommitTimesMs, opts.convention);
|
|
720
751
|
isStale = staleCheck.determined && staleCheck.stale;
|
|
721
752
|
// staleCheck is either {determined:true, stale:false} (checked; nothing
|
|
722
753
|
// stale) or {determined:false} (could not check — fs/scan/clock failure).
|
|
@@ -800,6 +831,7 @@ function isPhaseComplete(phaseDir, deps = {}) {
|
|
|
800
831
|
phaseCleanCommitTimesMs: deps.phaseCleanCommitTimesMs,
|
|
801
832
|
runtime: deps.runtime,
|
|
802
833
|
phaseNumber: deps.phaseNumber,
|
|
834
|
+
convention: deps.convention,
|
|
803
835
|
});
|
|
804
836
|
return {
|
|
805
837
|
value: {
|
|
@@ -453,7 +453,7 @@ function declaredPathCovers(declaredPaths, norm) {
|
|
|
453
453
|
if (typeof p !== 'string')
|
|
454
454
|
return false;
|
|
455
455
|
const dp = stripLeadingDotSlash(toSlash(p));
|
|
456
|
-
return dp === target || dp.startsWith(target + '/');
|
|
456
|
+
return dp === target || dp.startsWith(target + '/'); // allow-handrolled-containment: declared-path coverage for pending-creation detection, not containment
|
|
457
457
|
});
|
|
458
458
|
}
|
|
459
459
|
/**
|
|
@@ -13,6 +13,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
13
13
|
const node_path_1 = __importDefault(require("node:path"));
|
|
14
14
|
const node_os_1 = __importDefault(require("node:os"));
|
|
15
15
|
const validate_cjs_1 = require("./validate.cjs");
|
|
16
|
+
const security_cjs_1 = require("./security.cjs");
|
|
16
17
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-workspace.cjs is an export= CommonJS module
|
|
17
18
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
18
19
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- frontmatter.cjs is an export= CommonJS module
|
|
@@ -37,7 +38,7 @@ const worktreeSafetyMod = require("./worktree-safety.cjs");
|
|
|
37
38
|
// codebase-drift --name-status parse loop).
|
|
38
39
|
const { decodeGitQuotedPath } = worktreeSafetyMod;
|
|
39
40
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
40
|
-
const
|
|
41
|
+
const security_cjs_2 = require("./security.cjs");
|
|
41
42
|
const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
|
|
42
43
|
const schema_detect_cjs_1 = require("./schema-detect.cjs");
|
|
43
44
|
const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
|
|
@@ -63,7 +64,11 @@ const { SEVERITY: HEALTH_SEVERITY, REMEDY_ACTION, REMEDY_RISK, evaluateRules, ev
|
|
|
63
64
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-snapshot.cjs is an export= CommonJS module
|
|
64
65
|
const planningSnapshotMod = require("./planning-snapshot.cjs");
|
|
65
66
|
const { buildPlanningSnapshot } = planningSnapshotMod;
|
|
66
|
-
|
|
67
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- onboard-projection.cjs is an export= CommonJS module
|
|
68
|
+
const onboardProjectionMod = require("./onboard-projection.cjs");
|
|
69
|
+
const { REQUIRED_CODEBASE_MAP_FILES } = onboardProjectionMod;
|
|
70
|
+
const clock_cjs_1 = require("./clock.cjs");
|
|
71
|
+
const { planningDir, planningRoot, withPlanningLock } = planningWorkspace;
|
|
67
72
|
const { defaultPhaseCleanCommitTimesMs } = verificationMod;
|
|
68
73
|
const { extractFrontmatter, parseMustHavesBlock } = frontmatterMod;
|
|
69
74
|
const { readStateHeadFreshness } = stateMod;
|
|
@@ -152,9 +157,12 @@ function verifySummaryCore(cwd, summaryPath, checkFileCount, opts) {
|
|
|
152
157
|
if (firstSegment.indexOf('.') > 0)
|
|
153
158
|
return false;
|
|
154
159
|
// Containment guard: a `../`-bearing reference must not turn this advisory
|
|
155
|
-
// into a filesystem existence probe outside the project.
|
|
156
|
-
|
|
157
|
-
|
|
160
|
+
// into a filesystem existence probe outside the project. Lexical (ADR-4650
|
|
161
|
+
// decision 6): the candidate is a string pulled from a SUMMARY document and
|
|
162
|
+
// by construction may not exist yet — existence is what gets probed
|
|
163
|
+
// downstream — and this is a pure string-heuristic filter with no other fs
|
|
164
|
+
// access, so a realpath call would also change its cost profile.
|
|
165
|
+
if ((0, security_cjs_1.tryWithinRootLexical)(candidate, projectRoot) === null)
|
|
158
166
|
return false;
|
|
159
167
|
return true;
|
|
160
168
|
};
|
|
@@ -1378,11 +1386,11 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
|
|
|
1378
1386
|
// project. Leave sourceContent as null so the existing not-found /
|
|
1379
1387
|
// pending classification below runs unchanged. Note this guard is
|
|
1380
1388
|
// narrower than it may look: `from: "."` is a non-empty string, so it
|
|
1381
|
-
// still reaches
|
|
1389
|
+
// still reaches tryWithinRoot and safeReadFile below, and DOES read the
|
|
1382
1390
|
// cwd directory (yielding "Source read failed: EISDIR") — this branch
|
|
1383
1391
|
// only short-circuits the true empty-string case.
|
|
1384
|
-
const
|
|
1385
|
-
if (
|
|
1392
|
+
const fromContained = (0, security_cjs_2.tryWithinRoot)(fromPath, cwd);
|
|
1393
|
+
if (fromContained === null) {
|
|
1386
1394
|
// Do not echo result.error — it embeds absolute host paths.
|
|
1387
1395
|
check['path_rejected'] = 'from';
|
|
1388
1396
|
check['detail'] = 'Source path rejected — resolves outside the project directory';
|
|
@@ -1390,7 +1398,7 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
|
|
|
1390
1398
|
continue;
|
|
1391
1399
|
}
|
|
1392
1400
|
try {
|
|
1393
|
-
sourceContent = (0, shell_command_projection_cjs_1.platformReadSync)(
|
|
1401
|
+
sourceContent = (0, shell_command_projection_cjs_1.platformReadSync)(fromContained);
|
|
1394
1402
|
}
|
|
1395
1403
|
catch (err) {
|
|
1396
1404
|
// Report the errno only — never the message or path (untrusted `from:`
|
|
@@ -1458,8 +1466,8 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
|
|
|
1458
1466
|
// An empty/missing `to:` is a malformed plan, not a
|
|
1459
1467
|
// path-confinement violation — only a non-empty path that
|
|
1460
1468
|
// actually resolves outside the project is path_rejected.
|
|
1461
|
-
const
|
|
1462
|
-
if (
|
|
1469
|
+
const toContained = (0, security_cjs_2.tryWithinRoot)(toPath, cwd);
|
|
1470
|
+
if (toContained === null) {
|
|
1463
1471
|
// Do not read a rejected `to:` — treat as no target content
|
|
1464
1472
|
// and do not echo result.error, which embeds absolute host
|
|
1465
1473
|
// paths.
|
|
@@ -1467,7 +1475,7 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
|
|
|
1467
1475
|
check['detail'] = `Pattern "${link['pattern']}" not found in source; target path rejected — resolves outside the project directory`;
|
|
1468
1476
|
}
|
|
1469
1477
|
else {
|
|
1470
|
-
targetContent = (0, shell_command_projection_cjs_1.platformReadSync)(
|
|
1478
|
+
targetContent = (0, shell_command_projection_cjs_1.platformReadSync)(toContained);
|
|
1471
1479
|
}
|
|
1472
1480
|
}
|
|
1473
1481
|
if (targetContent && pat.test(targetContent)) {
|
|
@@ -1823,9 +1831,9 @@ function resolvePhaseDirByToken(phasesDir, phaseArg) {
|
|
|
1823
1831
|
const matched = matchPhaseDirs(dirNames, normalizedPhase).matches[0];
|
|
1824
1832
|
if (matched)
|
|
1825
1833
|
return node_path_1.default.join(phasesDir, matched);
|
|
1826
|
-
const
|
|
1827
|
-
if (
|
|
1828
|
-
return
|
|
1834
|
+
const contained = (0, security_cjs_2.tryWithinRoot)(phaseArg, phasesDir);
|
|
1835
|
+
if (contained !== null && node_fs_1.default.existsSync(contained))
|
|
1836
|
+
return contained;
|
|
1829
1837
|
return null;
|
|
1830
1838
|
}
|
|
1831
1839
|
/**
|
|
@@ -1986,6 +1994,110 @@ function cmdVerifySchemaDrift(cwd, phaseArg, skipFlag, raw) {
|
|
|
1986
1994
|
skipped: isSkipped,
|
|
1987
1995
|
}, raw);
|
|
1988
1996
|
}
|
|
1997
|
+
/**
|
|
1998
|
+
* Stamp `last_mapped_commit` (plus `last_mapped_at`) into the frontmatter of
|
|
1999
|
+
* every codebase-map document that exists on disk, using the current HEAD sha.
|
|
2000
|
+
*
|
|
2001
|
+
* #3418: `drift.cjs` shipped a correct `writeMappedCommit` with no production
|
|
2002
|
+
* caller, so no full `/gsd:map-codebase` run ever wrote the machine-readable
|
|
2003
|
+
* baseline that `cmdVerifyCodebaseDrift` reads. The stamp lives in CODE rather
|
|
2004
|
+
* than in a prose instruction to the mapper agent on purpose: an agent that
|
|
2005
|
+
* decides its work is already done skips a prose step silently, which is the
|
|
2006
|
+
* exact class of failure the stamp exists to detect.
|
|
2007
|
+
*
|
|
2008
|
+
* Only documents that already exist are stamped -- a `--fast` map produces four
|
|
2009
|
+
* of the seven, and this must not conjure the missing three as frontmatter-only
|
|
2010
|
+
* stubs that would then satisfy the seven-file completeness probe. `only`
|
|
2011
|
+
* (`--files a.md,b.md`) narrows further, for a caller that refreshed a subset.
|
|
2012
|
+
*
|
|
2013
|
+
* Non-blocking by contract, like every other drift surface: any failure emits
|
|
2014
|
+
* `skipped` with a reason and exits 0.
|
|
2015
|
+
*/
|
|
2016
|
+
function cmdStampCodebaseMap(cwd, raw, only) {
|
|
2017
|
+
// Non-hoisted: load-order matters for circular dep guard
|
|
2018
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- drift.cjs is an export= CommonJS module
|
|
2019
|
+
const drift = require('./drift.cjs');
|
|
2020
|
+
const emit = (payload) => output(payload, raw);
|
|
2021
|
+
const skip = (reason) => ({
|
|
2022
|
+
stamped: [],
|
|
2023
|
+
commit: null,
|
|
2024
|
+
stamped_at: null,
|
|
2025
|
+
skipped: true,
|
|
2026
|
+
reason,
|
|
2027
|
+
});
|
|
2028
|
+
try {
|
|
2029
|
+
// Probed outside the lock on purpose: taking the lock creates `.planning/`
|
|
2030
|
+
// if absent, and a stamp against a project that has no codebase map at all
|
|
2031
|
+
// must not conjure the directory. The per-document existence check inside
|
|
2032
|
+
// the lock is the one that matters -- a map deleted after this probe lands
|
|
2033
|
+
// on `no-codebase-map` there rather than being recreated as stubs.
|
|
2034
|
+
const codebaseDir = node_path_1.default.join(planningDir(cwd), 'codebase');
|
|
2035
|
+
if (!node_fs_1.default.existsSync(codebaseDir)) {
|
|
2036
|
+
emit(skip('no-codebase-dir'));
|
|
2037
|
+
return;
|
|
2038
|
+
}
|
|
2039
|
+
// `--files` restricts the stamp to the documents a caller actually
|
|
2040
|
+
// refreshed. The execute-phase auto-remap path rewrites STRUCTURE.md and
|
|
2041
|
+
// ARCHITECTURE.md only; stamping the other five at HEAD there would claim a
|
|
2042
|
+
// currency they do not have. Membership is checked against the closed
|
|
2043
|
+
// seven-document set, so an unknown name is a fail-loud non-answer rather
|
|
2044
|
+
// than a path this function tries to resolve. An empty value is refused
|
|
2045
|
+
// rather than read as "no filter": a caller that meant to narrow the scope
|
|
2046
|
+
// must not silently widen it to all seven.
|
|
2047
|
+
let candidates = REQUIRED_CODEBASE_MAP_FILES;
|
|
2048
|
+
if (only) {
|
|
2049
|
+
if (only.length === 0) {
|
|
2050
|
+
emit(skip('empty-codebase-map-file-filter'));
|
|
2051
|
+
return;
|
|
2052
|
+
}
|
|
2053
|
+
const unknown = only.filter((file) => !candidates.includes(file));
|
|
2054
|
+
if (unknown.length > 0) {
|
|
2055
|
+
emit(skip('unknown-codebase-map-file: ' + unknown.join(',')));
|
|
2056
|
+
return;
|
|
2057
|
+
}
|
|
2058
|
+
candidates = candidates.filter((file) => only.includes(file));
|
|
2059
|
+
}
|
|
2060
|
+
// Everything the stamp reads is read under the lock it writes under, the
|
|
2061
|
+
// same lock the rest of the .planning/ writers take. Two stampers can run
|
|
2062
|
+
// at once (the full map-codebase run and the execute-phase auto-remap);
|
|
2063
|
+
// one that resolved HEAD or listed the present documents before waiting on
|
|
2064
|
+
// the lock would write its now-stale sha over the newer one, or recreate a
|
|
2065
|
+
// document deleted while it waited as a frontmatter-only stub.
|
|
2066
|
+
const result = withPlanningLock(cwd, () => {
|
|
2067
|
+
const revProbe = (0, shell_command_projection_cjs_1.execGit)(['rev-parse', 'HEAD'], { cwd });
|
|
2068
|
+
if (revProbe.exitCode !== 0)
|
|
2069
|
+
return skip('not-a-git-repo');
|
|
2070
|
+
const commit = revProbe.stdout.trim();
|
|
2071
|
+
if (!/^[0-9a-f]{7,40}$/.test(commit))
|
|
2072
|
+
return skip('unreadable-head');
|
|
2073
|
+
const present = candidates.filter((file) => node_fs_1.default.existsSync(node_path_1.default.join(codebaseDir, file)));
|
|
2074
|
+
if (present.length === 0)
|
|
2075
|
+
return skip('no-codebase-map');
|
|
2076
|
+
// Host-local calendar day, matching the `**Analysis Date:**` line the
|
|
2077
|
+
// mapper agent writes -- the two freshness markers must not disagree by
|
|
2078
|
+
// a timezone.
|
|
2079
|
+
const stampedAt = clock_cjs_1.realClock.localToday();
|
|
2080
|
+
const write = drift['writeMappedCommit'];
|
|
2081
|
+
const stamped = [];
|
|
2082
|
+
const failed = [];
|
|
2083
|
+
for (const file of present) {
|
|
2084
|
+
try {
|
|
2085
|
+
write(node_path_1.default.join(codebaseDir, file), commit, stampedAt);
|
|
2086
|
+
stamped.push(file);
|
|
2087
|
+
}
|
|
2088
|
+
catch (err) {
|
|
2089
|
+
// One unwritable document must not cost the stamp on the other six.
|
|
2090
|
+
failed.push({ file, reason: err instanceof Error ? err.message : String(err) });
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
return { stamped, failed, commit, stamped_at: stampedAt, skipped: false, reason: null };
|
|
2094
|
+
});
|
|
2095
|
+
emit(result);
|
|
2096
|
+
}
|
|
2097
|
+
catch (err) {
|
|
2098
|
+
emit(skip('exception: ' + (err instanceof Error ? err.message : String(err))));
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
1989
2101
|
function cmdVerifyCodebaseDrift(cwd, raw) {
|
|
1990
2102
|
// Non-hoisted: load-order matters for circular dep guard
|
|
1991
2103
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- drift.cjs is an export= CommonJS module
|
|
@@ -2034,16 +2146,45 @@ function cmdVerifyCodebaseDrift(cwd, raw) {
|
|
|
2034
2146
|
});
|
|
2035
2147
|
return;
|
|
2036
2148
|
}
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2149
|
+
// #3418: an absent or unresolvable baseline means NO COMPARISON IS POSSIBLE.
|
|
2150
|
+
// It is neither zero drift nor total drift, and reporting it as either is a
|
|
2151
|
+
// lie the consumer cannot detect. The former fallback diffed HEAD against
|
|
2152
|
+
// the empty tree, so every tracked file read as newly added and the gate
|
|
2153
|
+
// reported maximum drift identically on every run -- which made a genuinely
|
|
2154
|
+
// stale map indistinguishable from a fresh one, and let `spawn_mapper` fire
|
|
2155
|
+
// a whole-repo remap while presenting itself as an incremental one.
|
|
2156
|
+
if (!lastMapped) {
|
|
2157
|
+
emit({
|
|
2158
|
+
block: false,
|
|
2159
|
+
skipped: true,
|
|
2160
|
+
reason: 'no-mapped-commit',
|
|
2161
|
+
action_required: false,
|
|
2162
|
+
directive: 'none',
|
|
2163
|
+
elements: [],
|
|
2164
|
+
last_mapped_commit: null,
|
|
2165
|
+
});
|
|
2166
|
+
return;
|
|
2041
2167
|
}
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2168
|
+
const baseProbe = (0, shell_command_projection_cjs_1.execGit)(['cat-file', '-t', lastMapped], { cwd });
|
|
2169
|
+
if (baseProbe.exitCode !== 0 || baseProbe.stdout.trim() !== 'commit') {
|
|
2170
|
+
// A stamp git cannot resolve: history rewrite, GC, or a shallow clone.
|
|
2171
|
+
// Distinct reason from 'no-mapped-commit' -- the map claims a baseline,
|
|
2172
|
+
// this repository just cannot see it, which is an operator-actionable
|
|
2173
|
+
// difference (re-map vs. unshallow). A resolvable non-commit (a tree or
|
|
2174
|
+
// blob sha, a ref name) is the same class of bad baseline: git would
|
|
2175
|
+
// happily diff against it and report drift against the wrong object.
|
|
2176
|
+
emit({
|
|
2177
|
+
block: false,
|
|
2178
|
+
skipped: true,
|
|
2179
|
+
reason: 'unresolvable-mapped-commit',
|
|
2180
|
+
action_required: false,
|
|
2181
|
+
directive: 'none',
|
|
2182
|
+
elements: [],
|
|
2183
|
+
last_mapped_commit: lastMapped,
|
|
2184
|
+
});
|
|
2185
|
+
return;
|
|
2046
2186
|
}
|
|
2187
|
+
const base = lastMapped;
|
|
2047
2188
|
const diff = (0, shell_command_projection_cjs_1.execGit)(['diff', '--name-status', base, 'HEAD'], { cwd });
|
|
2048
2189
|
if (diff.exitCode !== 0) {
|
|
2049
2190
|
emit({
|
|
@@ -2056,6 +2197,27 @@ function cmdVerifyCodebaseDrift(cwd, raw) {
|
|
|
2056
2197
|
});
|
|
2057
2198
|
return;
|
|
2058
2199
|
}
|
|
2200
|
+
// #3418: GSD's own planning artifacts are not codebase structure. A
|
|
2201
|
+
// map-codebase run commits `.planning/codebase/*.md`, so a correctly
|
|
2202
|
+
// stamped baseline would be re-poisoned by the very commit that carries
|
|
2203
|
+
// the stamp -- the next gate invocation would report the map's own seven
|
|
2204
|
+
// documents as seven new directories, back over the default threshold of
|
|
2205
|
+
// three. Derived from planningRoot() rather than a hardcoded literal so a
|
|
2206
|
+
// repoint of the planning root cannot leave this filter behind.
|
|
2207
|
+
//
|
|
2208
|
+
// `git diff --name-status` always prints repo-root-relative paths, so a cwd
|
|
2209
|
+
// below the root needs the `sub/` prefix or the filter matches nothing.
|
|
2210
|
+
// That prefix comes from git (`--show-prefix`: root-relative, forward
|
|
2211
|
+
// slashes, trailing slash, empty at the root). The rejected alternative was
|
|
2212
|
+
// path.relative(`--show-toplevel`, cwd), which mixes two path producers: on
|
|
2213
|
+
// Windows os.tmpdir() hands back the 8.3 short form while git resolves the
|
|
2214
|
+
// long one, so relative() between them yields a `../..` chain that matches
|
|
2215
|
+
// nothing. The `.planning` half below is safe to compute with relative()
|
|
2216
|
+
// because both of its sides are the same cwd string.
|
|
2217
|
+
const prefixProbe = (0, shell_command_projection_cjs_1.execGit)(['rev-parse', '--show-prefix'], { cwd });
|
|
2218
|
+
const repoPrefix = prefixProbe.exitCode === 0 ? prefixProbe.stdout.trim() : '';
|
|
2219
|
+
const planningPrefix = repoPrefix + node_path_1.default.relative(cwd, planningRoot(cwd)).split(node_path_1.default.sep).join('/') + '/';
|
|
2220
|
+
const isPlanningArtifact = (file) => file.split('\\').join('/').startsWith(planningPrefix);
|
|
2059
2221
|
const added = [];
|
|
2060
2222
|
const modified = [];
|
|
2061
2223
|
const deleted = [];
|
|
@@ -2076,6 +2238,8 @@ function cmdVerifyCodebaseDrift(cwd, raw) {
|
|
|
2076
2238
|
// ASCII common case — passes through untouched. Both capture groups are
|
|
2077
2239
|
// decoded: R/C lines carry old AND new paths, either may be quoted.
|
|
2078
2240
|
const file = decodeGitQuotedPath(m[3] || m[2]);
|
|
2241
|
+
if (isPlanningArtifact(file))
|
|
2242
|
+
continue;
|
|
2079
2243
|
if (status === 'A' || status === 'R' || status === 'C')
|
|
2080
2244
|
added.push(file);
|
|
2081
2245
|
else if (status === 'M')
|
|
@@ -2154,5 +2318,6 @@ module.exports = {
|
|
|
2154
2318
|
cmdVerifyCodebaseDrift,
|
|
2155
2319
|
computeContextDrift,
|
|
2156
2320
|
cmdVerifyContextDrift,
|
|
2321
|
+
cmdStampCodebaseMap,
|
|
2157
2322
|
STATE_HEAD_ADVISORY_COMMITS,
|
|
2158
2323
|
};
|
|
@@ -14,6 +14,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
14
14
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
15
15
|
const node_path_1 = __importDefault(require("node:path"));
|
|
16
16
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
17
|
+
const security_cjs_1 = require("./security.cjs");
|
|
17
18
|
// Default timeout for worktree-related git subprocess calls.
|
|
18
19
|
// 10 s is generous enough for normal git operations on large repos while still
|
|
19
20
|
// providing a deterministic failure path when git stalls (locked index, hung
|
|
@@ -578,7 +579,7 @@ function normalizeScopePath(raw) {
|
|
|
578
579
|
*/
|
|
579
580
|
function isSummaryArtifactRelPath(relPath) {
|
|
580
581
|
const normalized = normalizeScopePath(relPath);
|
|
581
|
-
return normalized.startsWith(`${SUMMARY_ARTIFACT_DIR}/`)
|
|
582
|
+
return normalized.startsWith(`${SUMMARY_ARTIFACT_DIR}/`) // allow-handrolled-containment: artifact-type classification by a fixed known subdirectory name, not a filesystem root-confinement gate
|
|
582
583
|
&& normalized.endsWith(SUMMARY_ARTIFACT_SUFFIX);
|
|
583
584
|
}
|
|
584
585
|
/**
|
|
@@ -772,7 +773,8 @@ function planWaveScopeConformance(changedPaths, declaredFiles, branch) {
|
|
|
772
773
|
seen.add(changed);
|
|
773
774
|
if (isSummaryArtifactRelPath(changed))
|
|
774
775
|
continue;
|
|
775
|
-
const covered = prefixes.some((prefix) => (prefix === null || changed === prefix || changed.startsWith(`${prefix}/`))
|
|
776
|
+
const covered = prefixes.some((prefix) => (prefix === null || changed === prefix || changed.startsWith(`${prefix}/`) // allow-handrolled-containment: advisory scope-coverage match against a caller-declared prefix (documented above as deliberately distinct from a security gate) — not a filesystem root-confinement decision
|
|
777
|
+
));
|
|
776
778
|
if (covered)
|
|
777
779
|
continue;
|
|
778
780
|
warnings.push({ code: WAVE_CLEANUP_WARNING.SCOPE_OUT_OF_DECLARED, branch, path: changed });
|
|
@@ -1581,11 +1583,15 @@ function cmdWorktreeCreate(cwd, args = [], deps = {}) {
|
|
|
1581
1583
|
{
|
|
1582
1584
|
const absRoot = node_path_1.default.resolve(cwd, rootFlag);
|
|
1583
1585
|
const absWorktree = node_path_1.default.resolve(cwd, plan.entry.worktree_path);
|
|
1584
|
-
|
|
1585
|
-
//
|
|
1586
|
-
//
|
|
1587
|
-
//
|
|
1588
|
-
|
|
1586
|
+
// Lexical containment (ADR-4650): the worktree does not exist yet, so there is
|
|
1587
|
+
// nothing to realpath. Both operands are already resolved above, so the shared
|
|
1588
|
+
// `isContainedIn` comparison applies directly (mirrors the already-resolved-caller
|
|
1589
|
+
// exception documented on `isContainedIn`) — a different Windows drive/UNC root
|
|
1590
|
+
// fails the prefix comparison the same way an escaping relative path does.
|
|
1591
|
+
// The canonical predicate treats target === root as CONTAINED; this call site
|
|
1592
|
+
// explicitly REJECTS that case (absWorktree === absRoot below), because a worktree
|
|
1593
|
+
// AT the root would clobber the checkout — the inversion this migration preserves.
|
|
1594
|
+
if (absWorktree === absRoot || !(0, security_cjs_1.isContainedIn)(absWorktree, absRoot)) {
|
|
1589
1595
|
const hint = `--path must resolve INSIDE --root (root="${absRoot}", path="${absWorktree}"). A worktree outside the declared root is unreachable by manifest-scoped cleanup and would let a spawned executor write outside the project.`;
|
|
1590
1596
|
writeErr(`[gsd] worktree.create: path_outside_root — ${hint}\n`);
|
|
1591
1597
|
write(`${JSON.stringify({ ok: false, reason: 'path_outside_root', hint }, null, 2)}\n`);
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"workflow.smart_zone_tokens",
|
|
23
23
|
"workflow.human_verify_mode",
|
|
24
24
|
"workflow.text_mode",
|
|
25
|
+
"workflow.compact_content",
|
|
25
26
|
"workflow.research_before_questions",
|
|
26
27
|
"workflow.discuss_mode",
|
|
27
28
|
"workflow.skip_discuss",
|
|
@@ -75,7 +76,11 @@
|
|
|
75
76
|
"planner.stall_threshold_minutes",
|
|
76
77
|
"workflow.inline_plan_threshold",
|
|
77
78
|
"hooks.context_warnings",
|
|
79
|
+
"hooks.context_warning_threshold",
|
|
80
|
+
"hooks.context_critical_threshold",
|
|
78
81
|
"hooks.workflow_guard",
|
|
82
|
+
"hooks.commit_types",
|
|
83
|
+
"hooks.community",
|
|
79
84
|
"workflow.context_coverage_gate",
|
|
80
85
|
"statusline.show_last_command",
|
|
81
86
|
"statusline.context_position",
|