@opengsd/gsd-core 1.18.0 → 1.20.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 +7 -7
- package/agents/gsd-code-fixer.md +13 -7
- package/agents/gsd-code-reviewer.compact.md +49 -8
- package/agents/gsd-code-reviewer.md +53 -14
- package/agents/gsd-executor.md +8 -4
- package/agents/gsd-verifier.md +1 -1
- package/bin/install.js +422 -103
- package/commands/gsd/update.md +5 -2
- package/gsd-core/bin/gsd-tools.cjs +179 -30
- 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-lifecycle.cjs +1 -12
- package/gsd-core/bin/lib/capability-lock.cjs +13 -2
- package/gsd-core/bin/lib/capability-registry.cjs +376 -103
- package/gsd-core/bin/lib/capability-state.cjs +14 -8
- package/gsd-core/bin/lib/capability-validator.cjs +32 -0
- package/gsd-core/bin/lib/commands.cjs +27 -17
- package/gsd-core/bin/lib/config-loader.cjs +13 -3
- package/gsd-core/bin/lib/config.cjs +256 -58
- package/gsd-core/bin/lib/core-utils.cjs +45 -0
- package/gsd-core/bin/lib/decision-coverage-support.cjs +74 -0
- package/gsd-core/bin/lib/frontmatter.cjs +21 -3
- package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +2 -2
- package/gsd-core/bin/lib/gate-context-drift.cjs +9 -4
- 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-phase-context.cjs +7 -8
- package/gsd-core/bin/lib/gate-schema-drift.cjs +1 -1
- 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 +52 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +31 -12
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +9 -7
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +3 -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 +245 -68
- 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/io.cjs +6 -0
- package/gsd-core/bin/lib/markdown-line-diff.cjs +147 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +147 -1
- package/gsd-core/bin/lib/markdown-table.cjs +23 -0
- package/gsd-core/bin/lib/mcp-server.cjs +8 -5
- 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 +61 -51
- 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-entry-grammar.cjs +119 -0
- package/gsd-core/bin/lib/phase-id-display.cjs +2 -0
- package/gsd-core/bin/lib/phase-id.cjs +629 -38
- package/gsd-core/bin/lib/phase-lifecycle.cjs +12 -44
- package/gsd-core/bin/lib/phase-locator.cjs +328 -19
- package/gsd-core/bin/lib/phase.cjs +3066 -653
- package/gsd-core/bin/lib/planning-document.cjs +26 -5
- package/gsd-core/bin/lib/planning-inspect.cjs +27 -35
- package/gsd-core/bin/lib/planning-snapshot.cjs +15 -8
- package/gsd-core/bin/lib/planning-workspace.cjs +21 -3
- package/gsd-core/bin/lib/pristine-baseline.cjs +11 -3
- package/gsd-core/bin/lib/read-only-git-env.cjs +79 -0
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +2 -2
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +57 -2
- package/gsd-core/bin/lib/review-lane-invocation.cjs +125 -18
- package/gsd-core/bin/lib/review-lane-runner.cjs +33 -4
- package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +11 -9
- package/gsd-core/bin/lib/roadmap-parser.cjs +735 -137
- package/gsd-core/bin/lib/roadmap-progress.cjs +136 -0
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +47 -7
- package/gsd-core/bin/lib/roadmap.cjs +383 -123
- 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 +151 -19
- package/gsd-core/bin/lib/smart-entry.cjs +35 -11
- package/gsd-core/bin/lib/state-contract.cjs +39 -15
- package/gsd-core/bin/lib/state-document.cjs +344 -27
- package/gsd-core/bin/lib/state-transition.cjs +156 -32
- package/gsd-core/bin/lib/state.cjs +527 -186
- 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/template.cjs +2 -2
- package/gsd-core/bin/lib/validate.cjs +3 -2
- package/gsd-core/bin/lib/vendor/re2js.cjs +863 -577
- package/gsd-core/bin/lib/vendor/re2js.d.cts +284 -219
- package/gsd-core/bin/lib/verification.cjs +96 -9
- package/gsd-core/bin/lib/verify.cjs +4 -2
- package/gsd-core/bin/lib/workstream-inventory.cjs +26 -20
- 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/bin/verify-reapply-patches.cjs +411 -10
- package/gsd-core/references/cicd-review-patterns.md +45 -0
- package/gsd-core/references/git-integration.md +11 -0
- package/gsd-core/references/loop-hook-dispatch.md +13 -6
- package/gsd-core/references/necessity-review.md +48 -0
- 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-fix.md +46 -5
- package/gsd-core/workflows/code-review.md +64 -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 +10 -6
- package/gsd-core/workflows/execute-plan.md +1 -1
- 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/steps/stall-detection-helpers.md +13 -9
- package/gsd-core/workflows/plan-phase.md +14 -14
- package/gsd-core/workflows/pr-branch.md +2 -0
- 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/reapply-patches.md +104 -0
- package/gsd-core/workflows/review.md +70 -154
- package/gsd-core/workflows/section-manifest.json +5 -0
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/settings-advanced.md +57 -9
- package/gsd-core/workflows/settings.md +1 -0
- package/gsd-core/workflows/ship.md +15 -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-graphify-update.sh +6 -0
- 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 +37 -3
- package/hooks/dist/gsd-windsurf-pre-write.js +29 -2
- package/hooks/dist/gsd-workflow-guard.js +3 -2
- package/hooks/dist/gsd-worktree-path-guard.js +29 -2
- package/hooks/dist/lib/git-probe.js +16 -0
- 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-graphify-update.sh +6 -0
- package/hooks/gsd-path-export.js +95 -0
- package/hooks/gsd-secret-read-guard.js +172 -39
- package/hooks/gsd-statusline.js +37 -3
- package/hooks/gsd-windsurf-pre-write.js +29 -2
- package/hooks/gsd-workflow-guard.js +3 -2
- package/hooks/gsd-worktree-path-guard.js +29 -2
- package/hooks/hooks.json +1 -0
- package/hooks/lib/git-probe.js +16 -0
- package/hooks/lib/isolation-sentinel.js +44 -0
- package/hooks/managed-hooks-registry.cjs +1 -0
- package/package.json +15 -8
- 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 +774 -0
- package/pi/package-agents/gsd-code-reviewer.md +441 -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 +898 -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 +54 -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/ci-next-health.cjs +246 -23
- package/scripts/command-contract-helpers.cjs +1 -1
- package/scripts/docs-guard-registry.cjs +22 -7
- package/scripts/gen-features.cjs +14 -2
- package/scripts/gen-health-docs.cjs +13 -1
- package/scripts/gen-pi-package-skills.cjs +217 -0
- package/scripts/lib/drift-scan.cjs +27 -0
- package/scripts/lib/macos-conformance-tier.generated.cjs +17 -1
- package/scripts/lib/platform-conformance-tier.generated.cjs +37 -1
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +3 -3
- package/scripts/lint-phase-enumeration-drift.cjs +8 -4
- package/scripts/lint-retired-runtime-name.cjs +5 -0
- package/scripts/lint-roadmap-progress-drift.cjs +421 -0
- package/scripts/mutation-matrix.cjs +23 -1
- 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/scripts/sync-next-version.cjs +38 -6
- package/skills/gsd-update/SKILL.md +5 -2
- 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
|
@@ -30,7 +30,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
30
30
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
31
31
|
};
|
|
32
32
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
|
-
exports.PLANNING_ARTIFACTS = void 0;
|
|
33
|
+
exports.PLANNING_ARTIFACTS = exports.FIELD_LINE_BREAK_RE = void 0;
|
|
34
34
|
exports.parsePlanningDoc = parsePlanningDoc;
|
|
35
35
|
exports.findField = findField;
|
|
36
36
|
exports.readNode = readNode;
|
|
@@ -48,6 +48,15 @@ const artifacts_cjs_1 = require("./artifacts.cjs");
|
|
|
48
48
|
// is imported as a default import (esModuleInterop), not a named import.
|
|
49
49
|
const frontmatter_cjs_1 = __importDefault(require("./frontmatter.cjs"));
|
|
50
50
|
const frontmatter_fence_cjs_1 = require("./frontmatter-fence.cjs");
|
|
51
|
+
/**
|
|
52
|
+
* #4998: every character that ends a line for the planning-document readers.
|
|
53
|
+
* Their `^`/`$` run with the `m` flag, and a JavaScript multiline anchor
|
|
54
|
+
* matches at U+2028 LINE SEPARATOR and U+2029 PARAGRAPH SEPARATOR as well as
|
|
55
|
+
* at CR and LF — so a single-line value carrying either one forges a sibling
|
|
56
|
+
* line just as `\n` does. Every single-line writer, here (`setFieldValue`,
|
|
57
|
+
* `replaceProse`) and in `state-document.cts`, refuses a value matching this.
|
|
58
|
+
*/
|
|
59
|
+
exports.FIELD_LINE_BREAK_RE = /[\r\n\u2028\u2029]/;
|
|
51
60
|
const { extractFrontmatter, FRONTMATTER_UNPARSEABLE } = frontmatter_cjs_1.default;
|
|
52
61
|
/**
|
|
53
62
|
* Canonical `.planning/` root artifact basenames this seam recognises,
|
|
@@ -562,13 +571,25 @@ function setFieldValue(doc, id, value) {
|
|
|
562
571
|
return { ok: false, reason: `node kind '${node.kind}' is not writable this phase` };
|
|
563
572
|
}
|
|
564
573
|
// #4917 / ADR-4910 Decision 2 & 4: a boldField's token boundary is a LINE
|
|
565
|
-
// boundary, not just an offset range — a value containing
|
|
574
|
+
// boundary, not just an offset range — a value containing any
|
|
575
|
+
// FIELD_LINE_BREAK_RE character (#4998: U+2028/U+2029 too) escapes
|
|
566
576
|
// the field's own span and reparses as sibling structure (a forged field)
|
|
567
577
|
// once spliced back into the source. Decision 4 licenses refusal for any
|
|
568
578
|
// value the grammar cannot represent; Phase 3 may widen this to escaping,
|
|
569
579
|
// but Phase 1 refuses outright. Do not remove this as an over-restriction.
|
|
570
|
-
|
|
571
|
-
|
|
580
|
+
// One carve-out (#4998, Codex review): a U+2028/U+2029 the field ALREADY
|
|
581
|
+
// carries may stay when everything from it on is byte-identical — a rewrite
|
|
582
|
+
// of the text before it (phase renumbering a `Depends on`) adds no line, and
|
|
583
|
+
// refusing it left the field silently unrenumbered.
|
|
584
|
+
const breakAt = value.search(exports.FIELD_LINE_BREAK_RE);
|
|
585
|
+
if (breakAt !== -1) {
|
|
586
|
+
const current = doc.source.slice(node.valueSpan.start, node.valueSpan.end);
|
|
587
|
+
const currentBreak = current.search(exports.FIELD_LINE_BREAK_RE);
|
|
588
|
+
const keepsExistingTail = !/[\r\n]/.test(value) && currentBreak !== -1
|
|
589
|
+
&& value.slice(breakAt) === current.slice(currentBreak);
|
|
590
|
+
if (!keepsExistingTail) {
|
|
591
|
+
return { ok: false, reason: 'field value must not contain a line break (\\r, \\n, U+2028 or U+2029)' };
|
|
592
|
+
}
|
|
572
593
|
}
|
|
573
594
|
// #4917 / ADR-4910 Decision 4: "a value that cannot be represented in the
|
|
574
595
|
// grammar is refused by the writer, with a report." This is a GENERAL
|
|
@@ -664,7 +685,7 @@ function replaceProse(doc, from, to) {
|
|
|
664
685
|
if (typeof from !== 'string' || from.length === 0) {
|
|
665
686
|
return { ok: false, reason: 'replaceProse: `from` must be a non-empty string' };
|
|
666
687
|
}
|
|
667
|
-
if (typeof to !== 'string' ||
|
|
688
|
+
if (typeof to !== 'string' || exports.FIELD_LINE_BREAK_RE.test(from) || exports.FIELD_LINE_BREAK_RE.test(to)) {
|
|
668
689
|
return { ok: false, reason: 'replaceProse: `from` and `to` must be single-line strings' };
|
|
669
690
|
}
|
|
670
691
|
if (doc.staged.size > 0) {
|
|
@@ -51,7 +51,7 @@ const planningSnapshotMod = require("./planning-snapshot.cjs");
|
|
|
51
51
|
const { buildPlanningSnapshot, worstScope } = planningSnapshotMod;
|
|
52
52
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
53
53
|
const planningWorkspaceMod = require("./planning-workspace.cjs");
|
|
54
|
-
const { planningPaths } = planningWorkspaceMod;
|
|
54
|
+
const { planningPaths, resolvePhaseIdConvention } = planningWorkspaceMod;
|
|
55
55
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
56
56
|
const planScan = require("./plan-scan.cjs");
|
|
57
57
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
@@ -74,7 +74,7 @@ const verificationMod = require("./verification.cjs");
|
|
|
74
74
|
const { readVerificationStatus, VerificationStatusError, failOnVerificationStatusError } = verificationMod;
|
|
75
75
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
76
76
|
const phaseIdMod = require("./phase-id.cjs");
|
|
77
|
-
const { phaseKeyFromDir, phaseKeyFromToken, phaseMarkdownRegexSource } = phaseIdMod;
|
|
77
|
+
const { phaseKeyFromDir, phaseKeyFromToken, phaseMarkdownRegexSource, extractPhaseDependencyTokens } = phaseIdMod;
|
|
78
78
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
79
79
|
const roadmapParserMod = require("./roadmap-parser.cjs");
|
|
80
80
|
const { extractCurrentMilestone } = roadmapParserMod;
|
|
@@ -407,7 +407,7 @@ function findDuplicateIds(reqMd) {
|
|
|
407
407
|
}
|
|
408
408
|
return dupes;
|
|
409
409
|
}
|
|
410
|
-
function buildRequirements(requirementsPath, knownPhaseKeys, diagnostics, planningRoot) {
|
|
410
|
+
function buildRequirements(requirementsPath, knownPhaseKeys, diagnostics, planningRoot, directoryContext) {
|
|
411
411
|
const doc = readDocument(requirementsPath, planningRoot);
|
|
412
412
|
if (!doc.exists) {
|
|
413
413
|
diagnostics.push({
|
|
@@ -464,7 +464,7 @@ function buildRequirements(requirementsPath, knownPhaseKeys, diagnostics, planni
|
|
|
464
464
|
// here too — it is a FILE-MEMBERSHIP predicate (#3511) for aggregate
|
|
465
465
|
// `*-UAT.md` / `*-VERIFICATION.md` scans, not a phase-identity equality
|
|
466
466
|
// test, and is unreliable for decimal phases.
|
|
467
|
-
if (!knownPhaseKeys.has(phaseKeyFromToken(token))) {
|
|
467
|
+
if (!knownPhaseKeys.has(phaseKeyFromToken(token, directoryContext))) {
|
|
468
468
|
diagnostics.push({
|
|
469
469
|
code: INSPECT_DIAGNOSTIC.REQUIREMENT_PHASE_UNKNOWN,
|
|
470
470
|
subject: `${item.id}->${token}`,
|
|
@@ -915,28 +915,16 @@ function extractGoalProse(sectionBody) {
|
|
|
915
915
|
* token scan above), never a document-wide scan. `[]` when the line is
|
|
916
916
|
* absent.
|
|
917
917
|
*/
|
|
918
|
-
function extractDependencyTokens(sectionBody) {
|
|
918
|
+
function extractDependencyTokens(sectionBody, phaseIdConvention) {
|
|
919
919
|
const m = DEPENDS_ON_LINE_RE.exec(sectionBody);
|
|
920
920
|
if (!m)
|
|
921
921
|
return [];
|
|
922
|
-
// #4764: phase REFERENCES, not digit runs
|
|
923
|
-
// init.manager
|
|
924
|
-
//
|
|
925
|
-
//
|
|
926
|
-
//
|
|
927
|
-
|
|
928
|
-
// the row's own number) is deliberately NOT applied: this reader has no row
|
|
929
|
-
// context at the extraction site and reports informationally, it does not
|
|
930
|
-
// gate.
|
|
931
|
-
const refRe = new RegExp(phaseIdMod.PHASE_DEP_REF_SOURCE, 'gi');
|
|
932
|
-
const tokenRe = new RegExp(phaseIdMod.PHASE_NUMBER_TOKEN_SOURCE, 'g');
|
|
933
|
-
const tokens = [];
|
|
934
|
-
let refMatch;
|
|
935
|
-
while ((refMatch = refRe.exec(m[1])) !== null) {
|
|
936
|
-
for (const t of refMatch[1].matchAll(tokenRe))
|
|
937
|
-
tokens.push(t[0]);
|
|
938
|
-
}
|
|
939
|
-
return sortedUnique(tokens);
|
|
922
|
+
// #4764/#4304: phase REFERENCES, not digit runs. Both this informational
|
|
923
|
+
// reader and init.manager consume phase-id.cts's convention-aware owner, so
|
|
924
|
+
// bracket milestone digits never surface as dependencies. Self-exclusion is
|
|
925
|
+
// deliberately NOT applied here: this site has no row context and does not
|
|
926
|
+
// gate readiness.
|
|
927
|
+
return sortedUnique(extractPhaseDependencyTokens(m[1], phaseIdConvention));
|
|
940
928
|
}
|
|
941
929
|
/**
|
|
942
930
|
* This phase's own ROADMAP.md section body — milestone-scoped via the SAME
|
|
@@ -949,9 +937,9 @@ function extractDependencyTokens(sectionBody) {
|
|
|
949
937
|
* sibling phase whose TITLE merely mentions this phase's number is never
|
|
950
938
|
* hijacked.
|
|
951
939
|
*/
|
|
952
|
-
function findPhaseRoadmapSection(cwd, roadmapText, phaseId) {
|
|
940
|
+
function findPhaseRoadmapSection(cwd, roadmapText, phaseId, directoryContext) {
|
|
953
941
|
const scoped = extractCurrentMilestone(roadmapText, cwd);
|
|
954
|
-
const headingRe = new RegExp(`^(?:\\[[^\\]]{1,200}\\]\\s*)?Phase\\s+${phaseMarkdownRegexSource(phaseId)}(?=[\\s:(]|$)`, 'i');
|
|
942
|
+
const headingRe = new RegExp(`^(?:\\[[^\\]]{1,200}\\]\\s*)?Phase\\s+${phaseMarkdownRegexSource(phaseId, directoryContext)}(?=[\\s:(]|$)`, 'i');
|
|
955
943
|
const section = collectSection(scoped, (h) => headingRe.test(h.text));
|
|
956
944
|
return section ? section.body : null;
|
|
957
945
|
}
|
|
@@ -976,7 +964,7 @@ function findPhaseRoadmapSection(cwd, roadmapText, phaseId) {
|
|
|
976
964
|
* `readDocument(paths.roadmap)` read, keeps both the read path and the
|
|
977
965
|
* "found vs missing vs unreadable" distinction singular.
|
|
978
966
|
*/
|
|
979
|
-
function buildPhaseGoalAndDependencies(cwd, roadmapDoc, phaseId, phaseDirLabel, diagnostics) {
|
|
967
|
+
function buildPhaseGoalAndDependencies(cwd, roadmapDoc, phaseId, phaseDirLabel, phaseIdConvention, diagnostics, directoryContext) {
|
|
980
968
|
if (!roadmapDoc.readable || roadmapDoc.text === null) {
|
|
981
969
|
diagnostics.push({
|
|
982
970
|
code: INSPECT_DIAGNOSTIC.ROADMAP_UNSCOPED,
|
|
@@ -999,7 +987,7 @@ function buildPhaseGoalAndDependencies(cwd, roadmapDoc, phaseId, phaseDirLabel,
|
|
|
999
987
|
dependencies: { value: [], scope: SCOPE.UNSCOPED },
|
|
1000
988
|
};
|
|
1001
989
|
}
|
|
1002
|
-
const sectionBody = findPhaseRoadmapSection(cwd, roadmapDoc.text, phaseId);
|
|
990
|
+
const sectionBody = findPhaseRoadmapSection(cwd, roadmapDoc.text, phaseId, directoryContext);
|
|
1003
991
|
if (sectionBody === null) {
|
|
1004
992
|
diagnostics.push({
|
|
1005
993
|
code: INSPECT_DIAGNOSTIC.ROADMAP_UNSCOPED,
|
|
@@ -1013,7 +1001,7 @@ function buildPhaseGoalAndDependencies(cwd, roadmapDoc, phaseId, phaseDirLabel,
|
|
|
1013
1001
|
}
|
|
1014
1002
|
return {
|
|
1015
1003
|
goal: { value: extractGoalProse(sectionBody), scope: SCOPE.COMPLETE },
|
|
1016
|
-
dependencies: { value: extractDependencyTokens(sectionBody), scope: SCOPE.COMPLETE },
|
|
1004
|
+
dependencies: { value: extractDependencyTokens(sectionBody, phaseIdConvention), scope: SCOPE.COMPLETE },
|
|
1017
1005
|
};
|
|
1018
1006
|
}
|
|
1019
1007
|
// ─── Entry points ─────────────────────────────────────────────────────────────
|
|
@@ -1031,6 +1019,7 @@ function buildPlanningInspectResult(cwd) {
|
|
|
1031
1019
|
let statusError = null;
|
|
1032
1020
|
const diagnostics = [];
|
|
1033
1021
|
const paths = planningPaths(cwd);
|
|
1022
|
+
const phaseIdConvention = resolvePhaseIdConvention(cwd);
|
|
1034
1023
|
const planningExists = node_fs_1.default.existsSync(paths.planning);
|
|
1035
1024
|
if (!planningExists) {
|
|
1036
1025
|
diagnostics.push({
|
|
@@ -1040,6 +1029,7 @@ function buildPlanningInspectResult(cwd) {
|
|
|
1040
1029
|
});
|
|
1041
1030
|
}
|
|
1042
1031
|
const snapshot = buildPlanningSnapshot(cwd);
|
|
1032
|
+
const directoryContext = snapshot.directoryContext;
|
|
1043
1033
|
if (snapshot.milestone.scope !== SCOPE.COMPLETE) {
|
|
1044
1034
|
diagnostics.push({
|
|
1045
1035
|
code: INSPECT_DIAGNOSTIC.ROADMAP_UNSCOPED,
|
|
@@ -1072,14 +1062,14 @@ function buildPlanningInspectResult(cwd) {
|
|
|
1072
1062
|
// one phase.
|
|
1073
1063
|
const checkboxByPhaseKey = new Map();
|
|
1074
1064
|
for (const [token, ticked] of Object.entries(checkboxes)) {
|
|
1075
|
-
checkboxByPhaseKey.set(phaseKeyFromToken(token), ticked);
|
|
1065
|
+
checkboxByPhaseKey.set(phaseKeyFromToken(token, directoryContext), ticked);
|
|
1076
1066
|
}
|
|
1077
1067
|
const phaseSnapshots = snapshot.phases.value;
|
|
1078
1068
|
// Canonical per-directory identity keys (`phase-id.cts`'s
|
|
1079
1069
|
// `phaseKeyFromDir`), not a raw regex scrape — this is the set
|
|
1080
1070
|
// `buildRequirements` below matches Traceability tokens against via
|
|
1081
1071
|
// `phaseKeyFromToken`.
|
|
1082
|
-
const knownPhaseKeys = new Set(windowed.map((dir) => phaseKeyFromDir(dir)));
|
|
1072
|
+
const knownPhaseKeys = new Set(windowed.map((dir) => phaseKeyFromDir(dir, undefined, directoryContext)));
|
|
1083
1073
|
// Read once, shared across every phase row's goal/dependencies lookup —
|
|
1084
1074
|
// the same `readDocument` seam every other document read in this module
|
|
1085
1075
|
// uses, never a second file-reading path.
|
|
@@ -1124,8 +1114,10 @@ function buildPlanningInspectResult(cwd) {
|
|
|
1124
1114
|
verification = { status: null, next_action: err.message, route: '' };
|
|
1125
1115
|
}
|
|
1126
1116
|
const token = /^(\d+(?:\.\d+)*)/.exec(phase.dir);
|
|
1127
|
-
const
|
|
1128
|
-
const
|
|
1117
|
+
const custom = phaseIdMod.customPhaseQuery(phase.dir, directoryContext);
|
|
1118
|
+
const owner = custom && phaseIdMod.customPhaseDirectoryParts(phase.dir, custom);
|
|
1119
|
+
const phaseId = owner ? owner.id : token ? token[1] : null;
|
|
1120
|
+
const { goal, dependencies } = buildPhaseGoalAndDependencies(cwd, roadmapDoc, phaseId, phase.dir, phaseIdConvention, diagnostics, directoryContext);
|
|
1129
1121
|
// `uat.foldScope`, NOT `uat.scope` (#3078 round-8). The two currently
|
|
1130
1122
|
// agree at every call site — see `buildUatRows` for why the field is
|
|
1131
1123
|
// still kept separate — so folding either one here produces the same
|
|
@@ -1159,8 +1151,8 @@ function buildPlanningInspectResult(cwd) {
|
|
|
1159
1151
|
route: verification.route,
|
|
1160
1152
|
},
|
|
1161
1153
|
roadmap_acceptance: {
|
|
1162
|
-
checkbox: checkboxByPhaseKey.has(phaseKeyFromDir(phase.dir))
|
|
1163
|
-
? checkboxByPhaseKey.get(phaseKeyFromDir(phase.dir))
|
|
1154
|
+
checkbox: checkboxByPhaseKey.has(phaseKeyFromDir(phase.dir, undefined, directoryContext))
|
|
1155
|
+
? checkboxByPhaseKey.get(phaseKeyFromDir(phase.dir, undefined, directoryContext))
|
|
1164
1156
|
: null,
|
|
1165
1157
|
// ADR-3180 §7.4: a ticked checkbox is a human annotation with no
|
|
1166
1158
|
// machine authority. Stated in the payload so a consumer cannot
|
|
@@ -1174,7 +1166,7 @@ function buildPlanningInspectResult(cwd) {
|
|
|
1174
1166
|
scope: folded,
|
|
1175
1167
|
};
|
|
1176
1168
|
});
|
|
1177
|
-
const requirements = buildRequirements(paths.requirements, knownPhaseKeys, diagnostics, paths.planning);
|
|
1169
|
+
const requirements = buildRequirements(paths.requirements, knownPhaseKeys, diagnostics, paths.planning, directoryContext);
|
|
1178
1170
|
const phaseScope = worstScope(snapshot.phaseDirs.scope, snapshot.phases.scope, ...phaseRows.map((p) => p.scope));
|
|
1179
1171
|
const acceptedPhases = makeFraction(phaseRows.filter((p) => p.complete).length, phaseRows.length, phaseScope, 'progress.accepted_phases', diagnostics);
|
|
1180
1172
|
const completedPlans = makeFraction(phaseRows.reduce((sum, p) => sum + p.summary_count, 0), phaseRows.reduce((sum, p) => sum + p.plan_count, 0), phaseScope, 'progress.completed_plans', diagnostics);
|
|
@@ -29,7 +29,7 @@ const roadmapParserMod = require("./roadmap-parser.cjs");
|
|
|
29
29
|
const { getMilestoneInfo, extractCurrentMilestone } = roadmapParserMod;
|
|
30
30
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
31
31
|
const phaseLocatorMod = require("./phase-locator.cjs");
|
|
32
|
-
const { listMilestonePhaseDirs, listAllPhaseDirs } = phaseLocatorMod;
|
|
32
|
+
const { phaseDirectoryOptions, listMilestonePhaseDirs, listAllPhaseDirs } = phaseLocatorMod;
|
|
33
33
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
34
34
|
const verificationMod = require("./verification.cjs");
|
|
35
35
|
const { isPhaseComplete } = verificationMod;
|
|
@@ -51,6 +51,7 @@ const { planningPaths, planningRoot, resolvePhaseIdConvention, resolveEnvWorkstr
|
|
|
51
51
|
// PROSE, not quoted literals.
|
|
52
52
|
const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
|
|
53
53
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
54
|
+
const phase_entry_grammar_cjs_1 = require("./phase-entry-grammar.cjs");
|
|
54
55
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
55
56
|
const frontmatterMod = require("./frontmatter.cjs");
|
|
56
57
|
const { extractFrontmatter, stripFrontmatter } = frontmatterMod;
|
|
@@ -129,7 +130,9 @@ function buildPhaseSnapshot(phasesDir, dir, convention) {
|
|
|
129
130
|
dir,
|
|
130
131
|
complete: completionResult.value.complete,
|
|
131
132
|
verificationStatus: completionResult.value.verification.status,
|
|
132
|
-
verificationStatusError: statusError
|
|
133
|
+
verificationStatusError: statusError
|
|
134
|
+
? { file: statusError.file, message: statusError.message, rawStatus: statusError.rawStatus }
|
|
135
|
+
: null,
|
|
133
136
|
planCount: scanResult.planCount,
|
|
134
137
|
summaryCount: scanResult.summaryCount,
|
|
135
138
|
scope: worstScope(completionResult.scope, scanResult.scope),
|
|
@@ -584,7 +587,7 @@ function buildRoadmapDeclaredPhasesField(roadmapPath, convention) {
|
|
|
584
587
|
* diagnostic (W011) whose entire purpose is flagging when the two DISAGREE —
|
|
585
588
|
* reading the data is not re-litigating who is authoritative.
|
|
586
589
|
*/
|
|
587
|
-
function buildRoadmapPhaseCheckboxesField(roadmapPath, convention) {
|
|
590
|
+
function buildRoadmapPhaseCheckboxesField(roadmapPath, convention, directoryContext) {
|
|
588
591
|
if (!node_fs_1.default.existsSync(roadmapPath)) {
|
|
589
592
|
return { value: {}, scope: SCOPE.UNREADABLE };
|
|
590
593
|
}
|
|
@@ -603,7 +606,9 @@ function buildRoadmapPhaseCheckboxesField(roadmapPath, convention) {
|
|
|
603
606
|
// vanish from the exclusion set and every unstarted bracket phase gains a
|
|
604
607
|
// W006. NON-capturing (`capturing` defaults false), so the phase token stays
|
|
605
608
|
// group 2 and the legacy repo compiles a byte-identical source.
|
|
606
|
-
|
|
609
|
+
// The entry prefix (marker, box, bold token) is the phase-entry grammar's (#5764),
|
|
610
|
+
// the same one `buildNotStartedPhaseVariants` composes; group 1 is the box.
|
|
611
|
+
const checkboxRe = (0, phase_entry_grammar_cjs_1.phaseEntryRegExp)(`${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY, convention)}0*(${convention === 'bracket' ? PHASE_NUMBER_TOKEN_SOURCE : phaseIdMod.phaseScanTokenSource(directoryContext)})${OPTIONAL_PHASE_TAG_SOURCE}[:\\s]`, 'gi', { state: 'captured' });
|
|
607
612
|
const value = {};
|
|
608
613
|
let m;
|
|
609
614
|
while ((m = checkboxRe.exec(content)) !== null) {
|
|
@@ -862,7 +867,7 @@ function buildArchivedPhaseTokensField(planBase, convention) {
|
|
|
862
867
|
* ROADMAP.md degrades to an empty list, mirroring every other
|
|
863
868
|
* ROADMAP-sourced field's absent-file handling.
|
|
864
869
|
*/
|
|
865
|
-
function buildCurrentMilestoneRoadmapPhaseIdsField(cwd, roadmapPath, convention) {
|
|
870
|
+
function buildCurrentMilestoneRoadmapPhaseIdsField(cwd, roadmapPath, convention, directoryContext) {
|
|
866
871
|
if (!node_fs_1.default.existsSync(roadmapPath))
|
|
867
872
|
return { value: [], scope: SCOPE.UNREADABLE };
|
|
868
873
|
let content;
|
|
@@ -900,7 +905,7 @@ function buildCurrentMilestoneRoadmapPhaseIdsField(cwd, roadmapPath, convention)
|
|
|
900
905
|
// pinned to this site by name) would catch that narrowing, since the census
|
|
901
906
|
// only pins `phaseHeadingPrefixSrcFor`'s own output, not this regex's tail.
|
|
902
907
|
// Kept as its own composition to preserve this exact byte-for-behavior.
|
|
903
|
-
const phasePattern = new RegExp(`#{2,4}\\s*${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY, convention, Boolean(bracketGroup))}(${PHASE_NUMBER_TOKEN_SOURCE})(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:`, 'gi');
|
|
908
|
+
const phasePattern = new RegExp(`#{2,4}\\s*${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY, convention, Boolean(bracketGroup))}(${convention !== 'bracket' ? phaseIdMod.phaseScanTokenSource(directoryContext) : PHASE_NUMBER_TOKEN_SOURCE})(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:`, 'gi');
|
|
904
909
|
const value = [];
|
|
905
910
|
for (const m of scoped.matchAll(phasePattern)) {
|
|
906
911
|
const bracketId = bracketGroup ? m[1] : undefined;
|
|
@@ -1019,6 +1024,7 @@ function buildPlanningSnapshot(cwd) {
|
|
|
1019
1024
|
// See the `phaseIdConvention` field's comment for why one resolution point is
|
|
1020
1025
|
// load-bearing rather than a micro-optimisation.
|
|
1021
1026
|
const phaseIdConvention = resolvePhaseIdConvention(cwd) ?? null;
|
|
1027
|
+
const directoryContext = phaseDirectoryOptions(cwd);
|
|
1022
1028
|
// #4257: the workstream `planningPaths(cwd)` just scoped every read to
|
|
1023
1029
|
// (its `planningDir` call applies this exact discriminator when handed no
|
|
1024
1030
|
// `ws`), resolved through the same owner so W002's scope clause names the
|
|
@@ -1066,17 +1072,18 @@ function buildPlanningSnapshot(cwd) {
|
|
|
1066
1072
|
statePhaseTokens: stateFields.statePhaseTokens,
|
|
1067
1073
|
stateStatus: stateFields.stateStatus,
|
|
1068
1074
|
roadmapDeclaredPhases: roadmapDeclared.declared,
|
|
1069
|
-
roadmapPhaseCheckboxes: buildRoadmapPhaseCheckboxesField(paths.roadmap, phaseIdConvention),
|
|
1075
|
+
roadmapPhaseCheckboxes: buildRoadmapPhaseCheckboxesField(paths.roadmap, phaseIdConvention, directoryContext),
|
|
1070
1076
|
researchValidationStatus: buildResearchValidationStatusField(paths.phases, phaseDirs.value, phaseDirs.scope, phaseIdConvention),
|
|
1071
1077
|
milestoneArchiveStatus: buildMilestoneArchiveStatusField(cwd),
|
|
1072
1078
|
planningRootFiles: buildPlanningRootFilesField(cwd),
|
|
1073
1079
|
allPhaseDirNames,
|
|
1074
1080
|
archivedPhaseTokens: buildArchivedPhaseTokensField(paths.planning, phaseIdConvention),
|
|
1075
|
-
currentMilestoneRoadmapPhaseIds: buildCurrentMilestoneRoadmapPhaseIdsField(cwd, paths.roadmap, phaseIdConvention),
|
|
1081
|
+
currentMilestoneRoadmapPhaseIds: buildCurrentMilestoneRoadmapPhaseIdsField(cwd, paths.roadmap, phaseIdConvention, directoryContext),
|
|
1076
1082
|
perPhasePlanNumbering: perPhasePlanScanFields.perPhasePlanNumbering,
|
|
1077
1083
|
perPhaseOrphanSummaries: perPhasePlanScanFields.perPhaseOrphanSummaries,
|
|
1078
1084
|
perPhaseWaveMissingPlans: perPhasePlanScanFields.perPhaseWaveMissingPlans,
|
|
1079
1085
|
phaseIdConvention,
|
|
1086
|
+
directoryContext,
|
|
1080
1087
|
roadmapSentinelPhaseTokens: roadmapDeclared.sentinelTokens,
|
|
1081
1088
|
roadmapBracketIncoherences: buildRoadmapBracketIncoherencesField(paths.roadmap, phaseIdConvention),
|
|
1082
1089
|
workstream,
|
|
@@ -186,12 +186,12 @@ function ownConfigValue(configPath, keyPath) {
|
|
|
186
186
|
* GSD_PROJECT/GSD_WORKSTREAM value containing path separators or `..`, and
|
|
187
187
|
* that shape resolves to "not present".
|
|
188
188
|
*/
|
|
189
|
-
function readScopedConfigValue(cwd, keyPath) {
|
|
189
|
+
function readScopedConfigValue(cwd, keyPath, workstream) {
|
|
190
190
|
try {
|
|
191
|
-
const scoped = ownConfigValue(node_path_1.default.join(planningDir(cwd), 'config.json'), keyPath);
|
|
191
|
+
const scoped = ownConfigValue(node_path_1.default.join(planningDir(cwd, workstream), 'config.json'), keyPath);
|
|
192
192
|
if (scoped.present)
|
|
193
193
|
return scoped;
|
|
194
|
-
if (resolveEnvWorkstream() !== null) {
|
|
194
|
+
if ((workstream === undefined ? resolveEnvWorkstream() : workstream) !== null) {
|
|
195
195
|
return ownConfigValue(node_path_1.default.join(planningRoot(cwd), 'config.json'), keyPath);
|
|
196
196
|
}
|
|
197
197
|
}
|
|
@@ -630,6 +630,22 @@ function findContextMdIn(absDirOrFiles) {
|
|
|
630
630
|
return { file: null, files: [], scope: SCOPE.UNREADABLE };
|
|
631
631
|
}
|
|
632
632
|
}
|
|
633
|
+
/**
|
|
634
|
+
* Is `name` the phase's requirements SPEC (#5629)? A plain `NN-SPEC.md`: the `-AI-SPEC.md` (AI design
|
|
635
|
+
* contract) and `-UI-SPEC.md` (UI design contract) variants have other purposes and never lock requirements.
|
|
636
|
+
* The one definition behind the init payloads' `has_spec` / `spec_path`, the discuss and plan-phase workflows
|
|
637
|
+
* (which read those fields) and the context-drift gate, so they cannot disagree about which file is "the SPEC".
|
|
638
|
+
*/
|
|
639
|
+
function isPhaseSpecFile(name) {
|
|
640
|
+
return name.endsWith('-SPEC.md') && !name.endsWith('-AI-SPEC.md') && !name.endsWith('-UI-SPEC.md');
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* The phase SPEC in an already-read directory listing, or `null`. The lexically first match, so the answer
|
|
644
|
+
* does not depend on the filesystem's readdir order. The caller's array is not reordered.
|
|
645
|
+
*/
|
|
646
|
+
function findPhaseSpecIn(files) {
|
|
647
|
+
return files.filter(isPhaseSpecFile).sort()[0] ?? null;
|
|
648
|
+
}
|
|
633
649
|
module.exports = {
|
|
634
650
|
worktreesOptedOut,
|
|
635
651
|
readScopedConfigValue,
|
|
@@ -655,6 +671,8 @@ module.exports = {
|
|
|
655
671
|
diagnoseUnresolvedActiveWorkstream,
|
|
656
672
|
describeUnresolvedWorkstreamReason,
|
|
657
673
|
findContextMdIn,
|
|
674
|
+
isPhaseSpecFile,
|
|
675
|
+
findPhaseSpecIn,
|
|
658
676
|
// Test seam (audit M1): inject a deterministic isPidAlive so the liveness-gated
|
|
659
677
|
// steal decision is exercised without real pids. Mirrors capability-lock.cts.
|
|
660
678
|
_setLockProbes(probes) {
|
|
@@ -34,6 +34,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
34
34
|
const node_path_1 = __importDefault(require("node:path"));
|
|
35
35
|
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
36
36
|
const node_child_process_1 = require("node:child_process");
|
|
37
|
+
const read_only_git_env_cjs_1 = require("./read-only-git-env.cjs");
|
|
37
38
|
/**
|
|
38
39
|
* SHA-256 hex digest of a file's raw bytes. Byte-for-byte the same digest
|
|
39
40
|
* install.js fileHash() records into manifests and backup-meta.json.
|
|
@@ -144,13 +145,20 @@ function isCleanRelativePosixPath(relPath) {
|
|
|
144
145
|
}
|
|
145
146
|
function gitExec(gitDir, args) {
|
|
146
147
|
return (0, node_child_process_1.execFileSync)('git', args, {
|
|
148
|
+
// windowsHide (#685): a console-window flash per git call would spam the
|
|
149
|
+
// user on Windows for what is a background, read-only history walk. Kept
|
|
150
|
+
// high in the options so the completeness guard's 400-char scan finds it.
|
|
151
|
+
windowsHide: true,
|
|
147
152
|
cwd: gitDir,
|
|
148
153
|
encoding: 'utf8',
|
|
154
|
+
// #5048: every call here is a history/objects walk that never opens the
|
|
155
|
+
// index, so the variable is inert today — set anyway so a future `status` /
|
|
156
|
+
// `diff` caller inherits it. The block is owned by
|
|
157
|
+
// src/read-only-git-env.cts and shared with every other read-only seam.
|
|
158
|
+
// Parity: tests/git-optional-locks-parity.test.cjs.
|
|
159
|
+
env: (0, read_only_git_env_cjs_1.readOnlyGitEnv)(),
|
|
149
160
|
timeout: GIT_SUBPROCESS_TIMEOUT_MS,
|
|
150
161
|
maxBuffer: GIT_MAX_BUFFER_BYTES,
|
|
151
|
-
// windowsHide (#685): a console-window flash per git call would spam the
|
|
152
|
-
// user on Windows for what is a background, read-only history walk.
|
|
153
|
-
windowsHide: true,
|
|
154
162
|
// stderr is discarded: "file absent in commit" is an expected walk outcome,
|
|
155
163
|
// not operator-visible diagnostics.
|
|
156
164
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* read-only-git-env.cts — the single owner of the read-only git env block
|
|
4
|
+
* (#5048). CONTEXT.md / OS Shell Projection seam.
|
|
5
|
+
*
|
|
6
|
+
* By default `git status` refreshes the index and takes an *optional*
|
|
7
|
+
* `.git/index.lock` to write the refreshed copy back. That is a write the caller
|
|
8
|
+
* never asked for, and it turns a pure read into a contender for the lock a
|
|
9
|
+
* real `git add` / `git commit` needs — so a read-only statusline render can
|
|
10
|
+
* make a concurrent commit fail with
|
|
11
|
+
* `Unable to create '.git/index.lock': File exists`. `GIT_OPTIONAL_LOCKS=0`
|
|
12
|
+
* tells git to skip only those *optional* index operations. Nothing a read
|
|
13
|
+
* needs is lost: `git status` reads the index, it just doesn't need to rewrite
|
|
14
|
+
* it.
|
|
15
|
+
*
|
|
16
|
+
* ## Why this module exists
|
|
17
|
+
*
|
|
18
|
+
* The block used to be hand-written at every seam that spawns git for a read.
|
|
19
|
+
* Six copies of `{ ...process.env, GIT_OPTIONAL_LOCKS: '0' }` is six places to
|
|
20
|
+
* forget when a seam is added, and a probe that checks each seam for the literal
|
|
21
|
+
* value passes even when a seam stopped consulting the owner. One owner makes
|
|
22
|
+
* the drift structural instead: the parity test patches THIS function to a
|
|
23
|
+
* sentinel and asserts every seam's spawn carries the sentinel, so a seam that
|
|
24
|
+
* hand-writes the variable again fails red rather than passing quietly.
|
|
25
|
+
* (tests/git-optional-locks-parity.test.cjs, "#5048 every seam reads the shared
|
|
26
|
+
* owner".)
|
|
27
|
+
*
|
|
28
|
+
* ## Why the hook scripts require the built artifact
|
|
29
|
+
*
|
|
30
|
+
* `hooks/*.js` are staged as standalone files at install time, so they reach
|
|
31
|
+
* compiled `src/` modules through `../gsd-core/bin/lib/*.cjs` rather than a
|
|
32
|
+
* sibling hooks/-tree file — the same rule hooks/lib/git-cmd.js follows for
|
|
33
|
+
* token-scanner. They call `readOnlyGitEnv()` per spawn rather than snapshotting
|
|
34
|
+
* the result at module load, so a later `process.env` mutation is visible and the
|
|
35
|
+
* two behave identically.
|
|
36
|
+
*/
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
exports.GIT_LOCATION_ENV_VARS = void 0;
|
|
39
|
+
exports.scrubGitLocationEnv = scrubGitLocationEnv;
|
|
40
|
+
exports.readOnlyGitEnv = readOnlyGitEnv;
|
|
41
|
+
/**
|
|
42
|
+
* #5548: env vars that make git act on a repository other than the one `cwd`
|
|
43
|
+
* names (git sets them inside hooks; wrappers export them). This is
|
|
44
|
+
* `git rev-parse --local-env-vars` minus GIT_CONFIG, GIT_CONFIG_PARAMETERS and
|
|
45
|
+
* GIT_CONFIG_COUNT, which inject config (e.g. CI `safe.directory`) rather than
|
|
46
|
+
* select a repository. (They are not repository-neutral: `GIT_CONFIG_COUNT` with a
|
|
47
|
+
* `core.worktree` pair can still redirect git, but a caller who controls the
|
|
48
|
+
* environment can already inject `core.sshCommand` or `core.fsmonitor`.)
|
|
49
|
+
* hooks/lib/git-probe.js carries a parity-tested copy.
|
|
50
|
+
*/
|
|
51
|
+
exports.GIT_LOCATION_ENV_VARS = [
|
|
52
|
+
'GIT_ALTERNATE_OBJECT_DIRECTORIES', 'GIT_OBJECT_DIRECTORY', 'GIT_DIR', 'GIT_WORK_TREE',
|
|
53
|
+
'GIT_IMPLICIT_WORK_TREE', 'GIT_GRAFT_FILE', 'GIT_INDEX_FILE', 'GIT_NO_REPLACE_OBJECTS',
|
|
54
|
+
'GIT_REPLACE_REF_BASE', 'GIT_PREFIX', 'GIT_SHALLOW_FILE', 'GIT_COMMON_DIR',
|
|
55
|
+
];
|
|
56
|
+
/** Copy of `env` without the repo-location vars; case-insensitive (Windows keeps `Git_Dir` casing). */
|
|
57
|
+
function scrubGitLocationEnv(env) {
|
|
58
|
+
return Object.fromEntries(Object.entries(env).filter(([k]) => !exports.GIT_LOCATION_ENV_VARS.includes(k.toUpperCase())));
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Build the env for a read-only git spawn.
|
|
62
|
+
*
|
|
63
|
+
* @param base Environment to spread, `process.env` minus the repo-location vars by default (#5548). Callers that
|
|
64
|
+
* layer extra git variables on top (GIT_TERMINAL_PROMPT, a caller's own
|
|
65
|
+
* overrides) pass them in `base`; callers that must let a caller-supplied
|
|
66
|
+
* override win spread the result and apply their own keys afterwards.
|
|
67
|
+
* @returns A fresh object each call — never the cached `process.env`.
|
|
68
|
+
*/
|
|
69
|
+
function readOnlyGitEnv(base = scrubGitLocationEnv(process.env)) {
|
|
70
|
+
const env = {};
|
|
71
|
+
for (const [key, value] of Object.entries(base)) {
|
|
72
|
+
// process.env carries undefined-valued keys on some platforms; spawn()
|
|
73
|
+
// rejects a non-string value rather than ignoring it.
|
|
74
|
+
if (value !== undefined)
|
|
75
|
+
env[key] = value;
|
|
76
|
+
}
|
|
77
|
+
env.GIT_OPTIONAL_LOCKS = '0';
|
|
78
|
+
return env;
|
|
79
|
+
}
|
|
@@ -58,7 +58,7 @@ const { routeHubCommandFamily } = cjsCommandRouterAdapter;
|
|
|
58
58
|
const { isCapabilityActive } = capabilityStateMod;
|
|
59
59
|
const { resolveConfigKey } = capabilityActivationMod;
|
|
60
60
|
const { loadConfig } = configLoaderMod;
|
|
61
|
-
const { findPhaseInternal, listMilestonePhaseDirs } = phaseLocatorMod;
|
|
61
|
+
const { phaseDirectoryOptions, findPhaseInternal, listMilestonePhaseDirs } = phaseLocatorMod;
|
|
62
62
|
const { planningDir } = planningWorkspaceMod;
|
|
63
63
|
const CAPABILITY_ID = 'refactor-trigger';
|
|
64
64
|
// Default CoreModule implementation. `_core` seam overrides this entirely
|
|
@@ -148,7 +148,7 @@ function requirePhaseArg(args, complexity, usage) {
|
|
|
148
148
|
* REFACTOR_GIT_UNAVAILABLE, for this case.
|
|
149
149
|
*/
|
|
150
150
|
function resolvePhaseDirForArg(cwd, phaseArg) {
|
|
151
|
-
const search = findPhaseInternal(cwd, phaseArg);
|
|
151
|
+
const search = findPhaseInternal(cwd, phaseArg, undefined, phaseDirectoryOptions(cwd));
|
|
152
152
|
if (!search || search.found !== true)
|
|
153
153
|
return null;
|
|
154
154
|
return { phaseDir: search.directory, padded: search.phase_number };
|