@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
|
@@ -677,8 +677,25 @@ function stageValidated(opts) {
|
|
|
677
677
|
throw new Error(`Hook fragment validation failed: ${fragErrs.join('; ')}`);
|
|
678
678
|
}
|
|
679
679
|
// Cross-capability validations (contract, consumes, cross-capability).
|
|
680
|
-
|
|
681
|
-
|
|
680
|
+
//
|
|
681
|
+
// #3929: seed the validation set the way the loader builds its accepted
|
|
682
|
+
// map — frozen first-party registry, then each committed overlay of the
|
|
683
|
+
// TARGET (global) install scope accepted incrementally (structural +
|
|
684
|
+
// engines + full-suite-clean), then the candidate LAST (mirroring
|
|
685
|
+
// `acceptedMap.set(id, cap)`). The singleton seed
|
|
686
|
+
// `new Map([[id, cap]])` this replaced made every non-empty `requires`
|
|
687
|
+
// unsatisfiable (membership is checked against the map) and left cycles,
|
|
688
|
+
// tier-monotone and central config-key exclusivity vacuous at install.
|
|
689
|
+
// The seed builder lives in capability-loader so the overlay semantics
|
|
690
|
+
// have one owner and are clean by construction — pre-existing junk in the
|
|
691
|
+
// install scope is skipped, never attributed to the candidate. No swallow:
|
|
692
|
+
// if the loader cannot build the seed the install fails loudly — silently
|
|
693
|
+
// degrading to the singleton map would re-hide #3929.
|
|
694
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
695
|
+
const seedLoader = require('./capability-loader.cjs');
|
|
696
|
+
/* eslint-enable @typescript-eslint/no-require-imports */
|
|
697
|
+
const { capMap, centralKeys } = seedLoader.crossValidationSeed(process.cwd(), gsdHome, hostVersion, capValidator, semverMod);
|
|
698
|
+
capMap.set(id, cap);
|
|
682
699
|
const crossErrs = [
|
|
683
700
|
...capValidator.validateAgainstContract(cap, id),
|
|
684
701
|
...capValidator.validateConsumesGlobal(capMap),
|
|
@@ -1956,6 +1956,11 @@ const KNOWN_HOST_BEHAVIORS = new Set([
|
|
|
1956
1956
|
'sourceMarkerFile',
|
|
1957
1957
|
'tomlConfigInstall',
|
|
1958
1958
|
'trackCategoryDescription',
|
|
1959
|
+
// #2586: declares runtime-level feature axes GSD does not/cannot support on
|
|
1960
|
+
// this host (e.g. Codex's `["context-warnings","phase-lifecycle-display"]`)
|
|
1961
|
+
// — present-and-populated / absent-is-unsupported-empty convention, so
|
|
1962
|
+
// omitting the key on every other runtime carries no inverted meaning.
|
|
1963
|
+
'unsupportedFeatures',
|
|
1959
1964
|
'verificationStyle',
|
|
1960
1965
|
'writeCategoryDescription',
|
|
1961
1966
|
]);
|
|
@@ -2796,7 +2801,7 @@ function materializeHookFragments(cap, capDir) {
|
|
|
2796
2801
|
|
|
2797
2802
|
const abs = path.resolve(capDir, fragment.path);
|
|
2798
2803
|
const capRoot = path.resolve(capDir);
|
|
2799
|
-
if (abs !== capRoot && !abs.startsWith(capRoot + path.sep)) {
|
|
2804
|
+
if (abs !== capRoot && !abs.startsWith(capRoot + path.sep)) { // allow-handrolled-containment: committed pre-build .cjs; compiled security.cjs is untracked build output
|
|
2800
2805
|
errors.push(
|
|
2801
2806
|
cap.id + '/' + groupName + '[' + i + '].fragment.path escapes capability directory: ' +
|
|
2802
2807
|
fragment.path,
|
|
@@ -2948,6 +2953,14 @@ function validateStep(step, prefix, declaredSkills, declaredAgents) {
|
|
|
2948
2953
|
errors.push(prefix + '.pointFrom must be a string if present');
|
|
2949
2954
|
}
|
|
2950
2955
|
|
|
2956
|
+
// #4209 DISP-02: strict optional boolean opt-in trait. Absent or false is
|
|
2957
|
+
// inert; only a literal `true` reaches the projected active hook. Reject
|
|
2958
|
+
// every other type (including truthy non-boolean values) so a typo can
|
|
2959
|
+
// never silently opt a step into reviewer-lane dispatch.
|
|
2960
|
+
if (step.supportsReviewerLanes !== undefined && typeof step.supportsReviewerLanes !== 'boolean') {
|
|
2961
|
+
errors.push(prefix + '.supportsReviewerLanes must be a boolean if present');
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2951
2964
|
if (step.fragment !== undefined) {
|
|
2952
2965
|
errors.push(...validateFragment(step.fragment, prefix + '.fragment'));
|
|
2953
2966
|
}
|
|
@@ -14,7 +14,13 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
14
14
|
const node_child_process_1 = require("node:child_process");
|
|
15
15
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
16
16
|
const io = require("./io.cjs");
|
|
17
|
-
const { output,
|
|
17
|
+
const { output, ERROR_REASON } = io;
|
|
18
|
+
// Explicitly annotated so TypeScript applies never-return control-flow narrowing.
|
|
19
|
+
// A destructured `const { error } = io` is a const WITHOUT a type annotation, and TS
|
|
20
|
+
// only narrows after a never-returning call when the callee is a function declaration
|
|
21
|
+
// or an annotated const. Without the annotation every `error(...)` guard below would
|
|
22
|
+
// need a dead `throw` after it to convince the checker that the value is non-null.
|
|
23
|
+
const error = io.error;
|
|
18
24
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
19
25
|
const planningWorkspaceMod = require("./planning-workspace.cjs");
|
|
20
26
|
const { planningDir } = planningWorkspaceMod;
|
|
@@ -89,7 +95,12 @@ function readIfExists(filePath) {
|
|
|
89
95
|
}
|
|
90
96
|
}
|
|
91
97
|
function resolvePath(inputPath, projectDir) {
|
|
92
|
-
|
|
98
|
+
const candidate = node_path_1.default.isAbsolute(inputPath) ? inputPath : node_path_1.default.join(projectDir, inputPath);
|
|
99
|
+
const contained = (0, security_cjs_1.tryWithinRoot)(candidate, projectDir, security_cjs_1.PathAcceptance.AbsoluteInsideRoot);
|
|
100
|
+
if (contained === null) {
|
|
101
|
+
error(`path escapes its allowed directory: ${inputPath}`, ERROR_REASON.USAGE);
|
|
102
|
+
}
|
|
103
|
+
return contained;
|
|
93
104
|
}
|
|
94
105
|
function readWorkflowConfig(projectDir) {
|
|
95
106
|
const configPath = node_path_1.default.join(projectDir, '.planning', 'config.json');
|
|
@@ -272,9 +283,34 @@ function loadDecisionExtraction(contextPath) {
|
|
|
272
283
|
outcome: extraction.outcome,
|
|
273
284
|
};
|
|
274
285
|
}
|
|
286
|
+
/**
|
|
287
|
+
* `check decision-coverage-plan` — blocking plan-phase decision-coverage gate
|
|
288
|
+
* (#2492, #1365 fail-loud, #2770 empty-arg fail-closed).
|
|
289
|
+
*
|
|
290
|
+
* Invocation (the context path may be supplied EITHER way; #4130 follow-up):
|
|
291
|
+
* gsd_run check decision-coverage-plan <phase-dir> <context-path> (positional, the workflow caller's form)
|
|
292
|
+
* gsd_run check decision-coverage-plan --context <path> [<phase-dir>]
|
|
293
|
+
*
|
|
294
|
+
* `--context <path>` follows the sibling flag convention (`check predicate`,
|
|
295
|
+
* #2008): `--flag value` pairs parsed by the shared partitionPredicateArgs
|
|
296
|
+
* pass, the flag WINNING over a same-purpose positional when both appear,
|
|
297
|
+
* and a valueless `--context` counting as no context at all (it falls
|
|
298
|
+
* through to the #2770 caller-error branch, not to the "CONTEXT.md missing"
|
|
299
|
+
* green skip). The positional form keeps working unchanged — no sibling
|
|
300
|
+
* check verb deprecates positionals and the plan-phase workflow passes them.
|
|
301
|
+
*/
|
|
275
302
|
function cmdDecisionCoveragePlan(projectDir, args, raw) {
|
|
276
|
-
|
|
277
|
-
|
|
303
|
+
// args[0]='check', args[1]=subcommand — partition the REST so flag tokens
|
|
304
|
+
// and their values never land in a positional slot.
|
|
305
|
+
const { flags, positionals } = partitionPredicateArgs(args.slice(2));
|
|
306
|
+
const phaseDir = positionals[0] ? resolvePath(positionals[0], projectDir) : '';
|
|
307
|
+
// A VALUELESS `--context` stays a bare token in the positionals (sibling
|
|
308
|
+
// parser semantics); it must not then be read as the context PATH — a
|
|
309
|
+
// `--`-prefixed "path" is a caller mistake, and #2770's law says a missing
|
|
310
|
+
// context argument fails CLOSED, never a silent "CONTEXT.md missing" green
|
|
311
|
+
// skip. So only a non-flag positional may serve as the context.
|
|
312
|
+
const positionalContext = positionals[1] && !positionals[1].startsWith('--') ? positionals[1] : '';
|
|
313
|
+
const contextArg = flags['context'] ?? positionalContext ?? '';
|
|
278
314
|
const contextPath = contextArg ? resolvePath(contextArg, projectDir) : '';
|
|
279
315
|
if (!gateEnabled(projectDir)) {
|
|
280
316
|
output({ passed: true, skipped: true, reason: 'workflow.context_coverage_gate is false', total: 0, covered: 0, uncovered: [], message: 'Decision coverage gate disabled by config.' }, raw, undefined);
|
|
@@ -309,12 +345,15 @@ function cmdDecisionCoveragePlan(projectDir, args, raw) {
|
|
|
309
345
|
uncovered: [],
|
|
310
346
|
message: partialParse
|
|
311
347
|
? 'Decision coverage gate: decisions could not be fully parsed — one or more ' +
|
|
312
|
-
'`- **D-NN ...**` bullets appear malformed (missing `:` or ` — ` separator
|
|
313
|
-
'
|
|
348
|
+
'`- **D-NN ...**` bullets appear malformed (missing `:` or ` — ` separator, or a phase ' +
|
|
349
|
+
'prefix that is not a digit run, e.g. `D4x-01`). Fix the bullet format so all decisions ' +
|
|
350
|
+
'can be read before re-running the gate.'
|
|
314
351
|
: 'Decision coverage gate: could not parse decisions — possible format mismatch. ' +
|
|
315
352
|
'The CONTEXT.md appears to be decision-shaped (has a <decisions> block, a decisions heading, ' +
|
|
316
|
-
'or D- tokens) but no
|
|
317
|
-
'block and ensure bullets follow the `- **D-NN:** text
|
|
353
|
+
'or D- tokens) but no decision bullets could be extracted. Check the formatting of the decisions ' +
|
|
354
|
+
'block and ensure bullets follow the `- **D-NN:** text`, `- **D4-NN:** text` (phase-prefixed), ' +
|
|
355
|
+
'or `- **D-NN — title** body` form. An ID grammar the parser does not support (e.g. `DEC-01`) ' +
|
|
356
|
+
'also lands here.',
|
|
318
357
|
}, raw, undefined);
|
|
319
358
|
return;
|
|
320
359
|
}
|
|
@@ -354,11 +393,6 @@ function recentCommitMessages(projectDir) {
|
|
|
354
393
|
return '';
|
|
355
394
|
}
|
|
356
395
|
}
|
|
357
|
-
function isInsideRoot(candidatePath, rootDir) {
|
|
358
|
-
const root = node_path_1.default.resolve(rootDir);
|
|
359
|
-
const target = node_path_1.default.resolve(root, candidatePath);
|
|
360
|
-
return target === root || target.startsWith(`${root}${node_path_1.default.sep}`);
|
|
361
|
-
}
|
|
362
396
|
function readModifiedFilesContent(projectDir, summaries) {
|
|
363
397
|
const out = [];
|
|
364
398
|
let total = 0;
|
|
@@ -371,9 +405,17 @@ function readModifiedFilesContent(projectDir, summaries) {
|
|
|
371
405
|
for (const file of files) {
|
|
372
406
|
if (total >= 50)
|
|
373
407
|
break;
|
|
374
|
-
if (!file
|
|
408
|
+
if (!file)
|
|
409
|
+
continue;
|
|
410
|
+
// Migrated off the hand-rolled prefix check (ADR-4650): resolve+contain in one
|
|
411
|
+
// step via the canonical realpath predicate — the eventual read below follows
|
|
412
|
+
// symlinks, so containment must be decided on the resolved target, not a lexical
|
|
413
|
+
// prefix. Read the value the predicate RETURNED; do not re-derive the path.
|
|
414
|
+
const candidate = node_path_1.default.isAbsolute(file) ? file : node_path_1.default.join(projectDir, file);
|
|
415
|
+
const contained = (0, security_cjs_1.tryWithinRoot)(candidate, projectDir, security_cjs_1.PathAcceptance.AbsoluteInsideRoot);
|
|
416
|
+
if (contained === null)
|
|
375
417
|
continue;
|
|
376
|
-
const raw = readIfExists(
|
|
418
|
+
const raw = readIfExists(contained);
|
|
377
419
|
out.push(raw.length > 256 * 1024 ? raw.slice(0, 256 * 1024) : raw);
|
|
378
420
|
total++;
|
|
379
421
|
}
|
|
@@ -411,9 +453,11 @@ function cmdDecisionCoverageVerify(projectDir, args, raw) {
|
|
|
411
453
|
not_honored: [],
|
|
412
454
|
message: partialParse
|
|
413
455
|
? 'Decision coverage verify (warning): decisions could not be fully parsed — one or more ' +
|
|
414
|
-
'`- **D-NN ...**` bullets appear malformed
|
|
456
|
+
'`- **D-NN ...**` bullets appear malformed (missing `:` or ` — ` separator, or a phase ' +
|
|
457
|
+
'prefix that is not a digit run). Fix the bullet format in the CONTEXT.md decisions block.'
|
|
415
458
|
: 'Decision coverage verify (warning): could not parse decisions — possible format mismatch. ' +
|
|
416
|
-
'Check the formatting of the CONTEXT.md decisions block
|
|
459
|
+
'Check the formatting of the CONTEXT.md decisions block (accepted forms: `- **D-NN:** text`, ' +
|
|
460
|
+
'`- **D4-NN:** text` (phase-prefixed), `- **D-NN — title** body`).',
|
|
417
461
|
}, raw, undefined);
|
|
418
462
|
return;
|
|
419
463
|
}
|
|
@@ -1019,8 +1063,9 @@ function cmdGapAnalysisPlanPost(projectDir, args, raw) {
|
|
|
1019
1063
|
error('gap-analysis.plan-post requires a phase-dir argument: check gap-analysis.plan-post <phase-dir> [phase-req-ids]', ERROR_REASON.SDK_MISSING_ARG);
|
|
1020
1064
|
return;
|
|
1021
1065
|
}
|
|
1066
|
+
const resolvedPhaseDir = resolvePath(phaseDir, projectDir);
|
|
1022
1067
|
const phaseReqIds = args[3] ?? undefined;
|
|
1023
|
-
const result = runGapAnalysis(projectDir,
|
|
1068
|
+
const result = runGapAnalysis(projectDir, resolvedPhaseDir, { phaseReqIds });
|
|
1024
1069
|
// Uniform gate contract: block = false (gap-analysis is always advisory, never blocks).
|
|
1025
1070
|
// `message` carries the human-readable gap analysis report so the dispatch's
|
|
1026
1071
|
// advisory branch can surface it. --raw emits JSON (rawValue=undefined), not
|
|
@@ -1071,21 +1116,21 @@ function buildPredicateDeps() {
|
|
|
1071
1116
|
node_path_1.default.win32.basename(artifactSuffix) !== artifactSuffix) {
|
|
1072
1117
|
return null;
|
|
1073
1118
|
}
|
|
1074
|
-
const
|
|
1075
|
-
if (
|
|
1076
|
-
return
|
|
1119
|
+
const directContained = (0, security_cjs_1.tryWithinRoot)(artifactSuffix, phaseDir);
|
|
1120
|
+
if (directContained !== null && node_fs_1.default.existsSync(directContained) && node_fs_1.default.statSync(directContained).isFile()) {
|
|
1121
|
+
return directContained;
|
|
1077
1122
|
}
|
|
1078
|
-
const
|
|
1079
|
-
if (
|
|
1080
|
-
return
|
|
1123
|
+
const planningContained = (0, security_cjs_1.tryWithinRoot)(node_path_1.default.join('.planning', artifactSuffix), phaseDir);
|
|
1124
|
+
if (planningContained !== null && node_fs_1.default.existsSync(planningContained) && node_fs_1.default.statSync(planningContained).isFile()) {
|
|
1125
|
+
return planningContained;
|
|
1081
1126
|
}
|
|
1082
1127
|
try {
|
|
1083
1128
|
const files = node_fs_1.default.readdirSync(phaseDir);
|
|
1084
1129
|
for (const f of files) {
|
|
1085
1130
|
if (f.endsWith('-' + artifactSuffix) || f === artifactSuffix) {
|
|
1086
|
-
const
|
|
1087
|
-
if (
|
|
1088
|
-
return
|
|
1131
|
+
const candidateContained = (0, security_cjs_1.tryWithinRoot)(f, phaseDir);
|
|
1132
|
+
if (candidateContained !== null && node_fs_1.default.statSync(candidateContained).isFile())
|
|
1133
|
+
return candidateContained;
|
|
1089
1134
|
}
|
|
1090
1135
|
}
|
|
1091
1136
|
}
|
|
@@ -1101,23 +1146,42 @@ function buildPredicateDeps() {
|
|
|
1101
1146
|
}
|
|
1102
1147
|
};
|
|
1103
1148
|
}
|
|
1104
|
-
/**
|
|
1105
|
-
|
|
1106
|
-
|
|
1149
|
+
/**
|
|
1150
|
+
* Split an args array into `--flag value` pairs and the leftover positional
|
|
1151
|
+
* tokens, in ONE pass, with the semantics `check predicate` established
|
|
1152
|
+
* (#2008): a `--flag` followed by a non-`--` token consumes it as the value
|
|
1153
|
+
* (last write wins); a `--flag` with no value stays a bare token and moves to
|
|
1154
|
+
* the positionals; everything else is positional. `parsePredicateFlags` is
|
|
1155
|
+
* the flags half of this same pass — there is exactly one parser, so the
|
|
1156
|
+
* flag-taking check verbs cannot drift apart (#4130 follow-up: `check
|
|
1157
|
+
* decision-coverage-plan --context <path>` shares it).
|
|
1158
|
+
*/
|
|
1159
|
+
function partitionPredicateArgs(args) {
|
|
1160
|
+
const flags = {};
|
|
1161
|
+
const positionals = [];
|
|
1107
1162
|
for (let i = 0; i < args.length; i++) {
|
|
1108
1163
|
const a = args[i];
|
|
1109
1164
|
if (typeof a !== 'string')
|
|
1110
1165
|
continue;
|
|
1111
|
-
if (!a.startsWith('--'))
|
|
1166
|
+
if (!a.startsWith('--')) {
|
|
1167
|
+
positionals.push(a);
|
|
1112
1168
|
continue;
|
|
1169
|
+
}
|
|
1113
1170
|
const key = a.slice(2);
|
|
1114
1171
|
const next = args[i + 1];
|
|
1115
1172
|
if (key.length > 0 && typeof next === 'string' && !next.startsWith('--')) {
|
|
1116
|
-
|
|
1173
|
+
flags[key] = next;
|
|
1117
1174
|
i++;
|
|
1118
1175
|
}
|
|
1176
|
+
else {
|
|
1177
|
+
positionals.push(a);
|
|
1178
|
+
}
|
|
1119
1179
|
}
|
|
1120
|
-
return
|
|
1180
|
+
return { flags, positionals };
|
|
1181
|
+
}
|
|
1182
|
+
/** Parse `--flag value` pairs from an args array into a map (last write wins). */
|
|
1183
|
+
function parsePredicateFlags(args) {
|
|
1184
|
+
return partitionPredicateArgs(args).flags;
|
|
1121
1185
|
}
|
|
1122
1186
|
/**
|
|
1123
1187
|
* `check predicate` — generic evaluator for capability gate `check.predicate`
|
|
@@ -1152,10 +1216,15 @@ function cmdCheckPredicate(projectDir, args, raw) {
|
|
|
1152
1216
|
error('predicate --predicate value must be valid JSON', ERROR_REASON.USAGE);
|
|
1153
1217
|
return;
|
|
1154
1218
|
}
|
|
1219
|
+
const rawPhaseDir = flags['phase-dir'];
|
|
1220
|
+
let resolvedPhaseDir = rawPhaseDir;
|
|
1221
|
+
if (typeof rawPhaseDir === 'string' && rawPhaseDir !== '') {
|
|
1222
|
+
resolvedPhaseDir = resolvePath(rawPhaseDir, projectDir);
|
|
1223
|
+
}
|
|
1155
1224
|
const ctx = {
|
|
1156
1225
|
cwd: projectDir,
|
|
1157
1226
|
phaseNumber: flags['phase-number'],
|
|
1158
|
-
phaseDir:
|
|
1227
|
+
phaseDir: resolvedPhaseDir,
|
|
1159
1228
|
phaseReqIds: flags['phase-req-ids'],
|
|
1160
1229
|
};
|
|
1161
1230
|
let result;
|
|
@@ -1249,7 +1318,12 @@ function cmdApiCoverageVerifyPre(projectDir, args, raw) {
|
|
|
1249
1318
|
// Defense-in-depth: the resolved dir must be inside the phases root (or a
|
|
1250
1319
|
// milestone archive under .planning/milestones).
|
|
1251
1320
|
const milestonesRoot = node_path_1.default.join(pDir, 'milestones');
|
|
1252
|
-
|
|
1321
|
+
// Lexical containment (ADR-4650): resolvedDir is a directory path, not read
|
|
1322
|
+
// through here — mirrors the prior path.resolve(root, candidate)-based check
|
|
1323
|
+
// without introducing a filesystem/realpath dependency this defense-in-depth
|
|
1324
|
+
// recheck never had.
|
|
1325
|
+
if ((0, security_cjs_1.tryWithinRootLexical)(resolvedDir, phasesRoot) === null &&
|
|
1326
|
+
(0, security_cjs_1.tryWithinRootLexical)(resolvedDir, milestonesRoot) === null) {
|
|
1253
1327
|
output({
|
|
1254
1328
|
block: true,
|
|
1255
1329
|
passed: false,
|
|
@@ -1578,7 +1652,9 @@ function routeCheckCommand({ args, cwd, raw }) {
|
|
|
1578
1652
|
// this for any gate whose `check` carries a `predicate` (instead of a `query`),
|
|
1579
1653
|
// passing the predicate object as --predicate '<json>'. NOTE: unlike the
|
|
1580
1654
|
// `check.query` subcommands above (which take positional phase args), this
|
|
1581
|
-
// subcommand
|
|
1655
|
+
// subcommand is flag-driven. `decision-coverage-plan` above now ALSO accepts
|
|
1656
|
+
// `--context <path>` (its positionals still work) — both share
|
|
1657
|
+
// partitionPredicateArgs, the one flag parser.
|
|
1582
1658
|
cmdCheckPredicate(cwd, args, raw);
|
|
1583
1659
|
return;
|
|
1584
1660
|
}
|
|
@@ -1606,6 +1682,7 @@ module.exports = {
|
|
|
1606
1682
|
cmdCheckPredicate,
|
|
1607
1683
|
buildPredicateDeps,
|
|
1608
1684
|
parsePredicateFlags,
|
|
1685
|
+
partitionPredicateArgs,
|
|
1609
1686
|
// Fail-closed phase-scope reader for the api-coverage gate — exported for
|
|
1610
1687
|
// in-process failure-injection tests (#2365 review).
|
|
1611
1688
|
readPhaseScope,
|
|
@@ -61,7 +61,7 @@ function normalizeRelPath(p, repoRoot) {
|
|
|
61
61
|
let relativized = false;
|
|
62
62
|
if (typeof repoRoot === 'string' && repoRoot !== '') {
|
|
63
63
|
const rootNormalized = repoRoot.trim().replace(/\\/g, '/').replace(/\/+$/, '');
|
|
64
|
-
if (rootNormalized !== '' && value.startsWith(`${rootNormalized}/`)) {
|
|
64
|
+
if (rootNormalized !== '' && value.startsWith(`${rootNormalized}/`)) { // allow-handrolled-containment: display-path normalization — strips a caller-declared repoRoot prefix so a path renders repo-relative, not a security root-confinement decision
|
|
65
65
|
value = value.slice(rootNormalized.length + 1);
|
|
66
66
|
relativized = true;
|
|
67
67
|
}
|
|
@@ -86,7 +86,7 @@ function normalizeRelPath(p, repoRoot) {
|
|
|
86
86
|
* with `rulePath + '/'`. Both arguments must already be normalized. Case-sensitive.
|
|
87
87
|
*/
|
|
88
88
|
function ruleMatchesFile(rulePath, filePath) {
|
|
89
|
-
return filePath === rulePath || filePath.startsWith(`${rulePath}/`);
|
|
89
|
+
return filePath === rulePath || filePath.startsWith(`${rulePath}/`); // allow-handrolled-containment: rule-to-file segment match for selecting which review-depth rule applies — not a filesystem root-confinement gate
|
|
90
90
|
}
|
|
91
91
|
/**
|
|
92
92
|
* Validate + normalize a single rule path (not yet matched against files).
|