@opengsd/gsd-core 1.10.0 → 1.12.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-code-fixer.md +1 -1
- package/agents/gsd-debug-session-manager.md +12 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-doc-synthesizer.md +2 -4
- package/agents/gsd-dom-verifier.md +169 -0
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +22 -14
- package/agents/gsd-framework-selector.md +1 -3
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +5 -3
- package/agents/gsd-pattern-mapper.md +11 -0
- package/agents/gsd-phase-researcher.md +23 -2
- package/agents/gsd-plan-checker.md +50 -53
- package/agents/gsd-planner.md +50 -50
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.md +2 -2
- package/agents/gsd-roadmapper.md +15 -11
- package/agents/gsd-ui-checker.md +63 -4
- package/agents/gsd-ui-researcher.md +41 -3
- package/agents/gsd-user-profiler.md +3 -0
- package/agents/gsd-verifier.md +13 -4
- package/bin/install.js +1448 -1103
- package/commands/gsd/code-review.md +1 -1
- package/commands/gsd/discuss-phase.md +1 -1
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/import.md +1 -1
- package/commands/gsd/map-codebase.md +1 -1
- package/commands/gsd/mempalace-capture.md +1 -1
- package/commands/gsd/mempalace-recall.md +1 -1
- package/commands/gsd/new-milestone.md +1 -1
- package/commands/gsd/quick.md +9 -5
- package/commands/gsd/review-backlog.md +2 -1
- package/commands/gsd/verify-work.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +1035 -138
- package/gsd-core/bin/lib/active-workstream-store.cjs +146 -22
- package/gsd-core/bin/lib/adr-parser.cjs +13 -7
- package/gsd-core/bin/lib/agent-install-check.cjs +392 -32
- package/gsd-core/bin/lib/api-coverage.cjs +33 -14
- package/gsd-core/bin/lib/artifacts.cjs +5 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +32 -15
- package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
- package/gsd-core/bin/lib/audit.cjs +1026 -268
- package/gsd-core/bin/lib/broken-windows.cjs +306 -28
- package/gsd-core/bin/lib/capability-consent.cjs +149 -15
- package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
- package/gsd-core/bin/lib/capability-lock.cjs +10 -4
- package/gsd-core/bin/lib/capability-registry.cjs +845 -130
- package/gsd-core/bin/lib/capability-source.cjs +92 -0
- package/gsd-core/bin/lib/capability-state.cjs +18 -3
- package/gsd-core/bin/lib/capability-trust.cjs +444 -25
- package/gsd-core/bin/lib/capability-validator.cjs +700 -40
- package/gsd-core/bin/lib/capability-writer.cjs +3 -2
- package/gsd-core/bin/lib/check-command-router.cjs +216 -42
- package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
- package/gsd-core/bin/lib/cli-exit.cjs +496 -10
- package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
- package/gsd-core/bin/lib/codex-agent-toml.cjs +735 -0
- package/gsd-core/bin/lib/command-aliases.cjs +22 -0
- package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
- package/gsd-core/bin/lib/command-roster.cjs +44 -1
- package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
- package/gsd-core/bin/lib/commands.cjs +1172 -108
- package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
- package/gsd-core/bin/lib/complexity-trigger.cjs +1192 -0
- package/gsd-core/bin/lib/config-loader.cjs +187 -23
- package/gsd-core/bin/lib/config.cjs +102 -3
- package/gsd-core/bin/lib/configuration.cjs +129 -37
- package/gsd-core/bin/lib/core-utils.cjs +208 -33
- package/gsd-core/bin/lib/decisions.cjs +23 -0
- package/gsd-core/bin/lib/edge-probe.cjs +9 -1
- package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
- package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
- package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
- package/gsd-core/bin/lib/frontmatter.cjs +899 -229
- package/gsd-core/bin/lib/gap-checker.cjs +95 -10
- package/gsd-core/bin/lib/git-base-branch.cjs +276 -39
- package/gsd-core/bin/lib/gsd2-import.cjs +10 -1
- package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +149 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +268 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +187 -0
- package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +451 -0
- package/gsd-core/bin/lib/host-integration.cjs +39 -6
- package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
- package/gsd-core/bin/lib/init-command-router.cjs +118 -21
- package/gsd-core/bin/lib/init.cjs +439 -168
- package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
- package/gsd-core/bin/lib/install-engine.cjs +811 -259
- package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +235 -0
- package/gsd-core/bin/lib/install-profiles.cjs +212 -61
- package/gsd-core/bin/lib/install-scope.cjs +270 -0
- package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
- package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
- package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
- package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +148 -38
- package/gsd-core/bin/lib/intel.cjs +101 -26
- package/gsd-core/bin/lib/io.cjs +170 -15
- package/gsd-core/bin/lib/learnings.cjs +85 -14
- package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +2 -1
- package/gsd-core/bin/lib/markdown-table.cjs +183 -22
- package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
- package/gsd-core/bin/lib/milestone.cjs +842 -73
- package/gsd-core/bin/lib/model-catalog.cjs +232 -16
- package/gsd-core/bin/lib/model-resolver.cjs +193 -68
- package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
- package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
- package/gsd-core/bin/lib/pattern.cjs +122 -0
- package/gsd-core/bin/lib/phase-estimation.cjs +18 -9
- package/gsd-core/bin/lib/phase-id.cjs +514 -40
- package/gsd-core/bin/lib/phase-lifecycle.cjs +52 -19
- package/gsd-core/bin/lib/phase-locator.cjs +262 -34
- package/gsd-core/bin/lib/phase.cjs +1038 -214
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
- package/gsd-core/bin/lib/plan-document.cjs +263 -0
- package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
- package/gsd-core/bin/lib/plan-scan.cjs +98 -3
- package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
- package/gsd-core/bin/lib/planning-scope.cjs +31 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +894 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +112 -6
- package/gsd-core/bin/lib/probe-core.cjs +5 -2
- package/gsd-core/bin/lib/profile-output.cjs +1 -1
- package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
- package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
- package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +766 -0
- package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +22 -13
- package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +421 -66
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
- package/gsd-core/bin/lib/roadmap-command-router.cjs +59 -11
- package/gsd-core/bin/lib/roadmap-parser.cjs +1006 -184
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
- package/gsd-core/bin/lib/roadmap.cjs +442 -96
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +702 -52
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +459 -55
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
- package/gsd-core/bin/lib/runtime-homes.cjs +69 -3
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +402 -58
- package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
- package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
- package/gsd-core/bin/lib/runtime-slash.cjs +96 -8
- package/gsd-core/bin/lib/security.cjs +104 -5
- package/gsd-core/bin/lib/shell-command-projection.cjs +342 -7
- package/gsd-core/bin/lib/smart-entry.cjs +133 -23
- package/gsd-core/bin/lib/spec-section.cjs +12 -7
- package/gsd-core/bin/lib/state-command-router.cjs +52 -19
- package/gsd-core/bin/lib/state-contract.cjs +359 -0
- package/gsd-core/bin/lib/state-document.cjs +338 -8
- package/gsd-core/bin/lib/state-md-schema.cjs +221 -0
- package/gsd-core/bin/lib/state-transition.cjs +846 -176
- package/gsd-core/bin/lib/state.cjs +2589 -369
- package/gsd-core/bin/lib/surface.cjs +33 -11
- package/gsd-core/bin/lib/task-command-router.cjs +111 -1
- package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
- package/gsd-core/bin/lib/teams-status.cjs +4 -1
- package/gsd-core/bin/lib/text-lines.cjs +80 -0
- package/gsd-core/bin/lib/token-scanner.cjs +76 -0
- package/gsd-core/bin/lib/uat-predicate.cjs +67 -23
- package/gsd-core/bin/lib/uat.cjs +1761 -167
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
- package/gsd-core/bin/lib/ui-safety-gate.cjs +51 -12
- package/gsd-core/bin/lib/unusable-input.cjs +37 -0
- package/gsd-core/bin/lib/update-context.cjs +8 -2
- package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
- package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
- package/gsd-core/bin/lib/validate.cjs +20 -6
- package/gsd-core/bin/lib/vendor/README.md +75 -0
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
- package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
- package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +272 -9
- package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
- package/gsd-core/bin/lib/verify.cjs +453 -918
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +53 -32
- package/gsd-core/bin/lib/workstream-inventory.cjs +63 -10
- package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
- package/gsd-core/bin/lib/workstream.cjs +2 -2
- package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
- package/gsd-core/bin/lib/worktree-safety.cjs +341 -18
- package/gsd-core/bin/shared/config-defaults.manifest.json +8 -1
- package/gsd-core/bin/shared/config-schema.manifest.json +12 -1
- package/gsd-core/bin/shared/exit-codes.json +8 -0
- package/gsd-core/bin/shared/exit-codes.sh +20 -0
- package/gsd-core/bin/shared/model-catalog.json +8 -1
- package/gsd-core/references/agent-contracts.md +44 -26
- package/gsd-core/references/api-coverage.md +24 -2
- package/gsd-core/references/autonomous-smart-discuss.md +3 -3
- package/gsd-core/references/checkpoints.md +39 -21
- package/gsd-core/references/context-budget.md +1 -1
- package/gsd-core/references/decimal-phase-calculation.md +5 -5
- package/gsd-core/references/dispatch-isolation-gate.md +138 -0
- package/gsd-core/references/doc-conflict-engine.md +1 -1
- package/gsd-core/references/edge-probe.md +8 -0
- package/gsd-core/references/execute-mvp-tdd.md +4 -6
- package/gsd-core/references/execute-phase-between-wave-reset.md +15 -14
- package/gsd-core/references/execute-phase-context-guard.md +1 -1
- package/gsd-core/references/execute-phase-response-language.md +1 -1
- package/gsd-core/references/execute-phase-wave-guard.md +17 -11
- package/gsd-core/references/failing-direction.md +78 -0
- package/gsd-core/references/gate-prompts.md +1 -1
- package/gsd-core/references/git-integration.md +5 -5
- package/gsd-core/references/git-planning-commit.md +5 -4
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +61 -2
- package/gsd-core/references/model-profiles.md +12 -4
- package/gsd-core/references/mvp-concepts.md +9 -9
- package/gsd-core/references/nyquist-compliance.md +74 -0
- package/gsd-core/references/offer-next.md +3 -5
- package/gsd-core/references/phase-argument-parsing.md +3 -3
- package/gsd-core/references/planner-failing-direction.md +53 -0
- package/gsd-core/references/planner-guidance.md +3 -9
- package/gsd-core/references/planner-human-verify-mode.md +15 -1
- package/gsd-core/references/planner-preconditions.md +1 -1
- package/gsd-core/references/planner-reviews.md +1 -1
- package/gsd-core/references/planner-revision.md +1 -1
- package/gsd-core/references/planner-verify-command-grounding.md +17 -0
- package/gsd-core/references/planning-config.md +44 -13
- package/gsd-core/references/reviewer-instances.md +31 -0
- package/gsd-core/references/revision-loop.md +1 -1
- package/gsd-core/references/runtime-aware-dispatch.md +1 -1
- package/gsd-core/references/specless-probe-fallback.md +1 -1
- package/gsd-core/references/tdd.md +1 -3
- package/gsd-core/references/ui-brand.md +65 -21
- package/gsd-core/references/ui-consideration-probe.md +1 -1
- package/gsd-core/references/universal-anti-patterns.md +5 -5
- package/gsd-core/references/verifier-phase-gates.md +192 -0
- package/gsd-core/references/verify-command-path-resolvability.md +42 -0
- package/gsd-core/references/verify-mvp-mode.md +2 -2
- package/gsd-core/references/workstream-flag.md +33 -17
- package/gsd-core/templates/README.md +1 -1
- package/gsd-core/templates/SECURITY.md +3 -3
- package/gsd-core/templates/UI-SPEC.md +25 -3
- package/gsd-core/templates/VALIDATION.md +3 -3
- package/gsd-core/templates/discussion-log.md +1 -1
- package/gsd-core/templates/phase-prompt.md +5 -4
- package/gsd-core/templates/state.md +11 -4
- package/gsd-core/templates/verification-report.md +9 -1
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +1 -1
- package/gsd-core/workflows/add-phase.md +3 -3
- package/gsd-core/workflows/add-tests.md +3 -8
- package/gsd-core/workflows/add-todo.md +1 -1
- package/gsd-core/workflows/ai-integration-phase.md +13 -20
- package/gsd-core/workflows/audit-fix.md +12 -3
- package/gsd-core/workflows/audit-milestone.md +9 -9
- package/gsd-core/workflows/audit-uat.md +17 -2
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
- package/gsd-core/workflows/autonomous.md +11 -27
- package/gsd-core/workflows/check-todos.md +1 -1
- package/gsd-core/workflows/cleanup.md +64 -5
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +14 -4
- package/gsd-core/workflows/code-review-fix.md +38 -11
- package/gsd-core/workflows/code-review.md +159 -52
- package/gsd-core/workflows/complete-milestone.md +151 -23
- package/gsd-core/workflows/debug.md +12 -8
- package/gsd-core/workflows/diagnose-issues.md +47 -15
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -8
- package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/text.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
- package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +3 -6
- package/gsd-core/workflows/docs-update.md +5 -4
- package/gsd-core/workflows/edit-phase.md +27 -2
- package/gsd-core/workflows/eval-review.md +7 -14
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +142 -15
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +24 -4
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
- package/gsd-core/workflows/execute-phase.md +72 -100
- package/gsd-core/workflows/execute-plan.md +52 -15
- package/gsd-core/workflows/explore.md +131 -4
- package/gsd-core/workflows/extract-learnings.md +1 -1
- package/gsd-core/workflows/fast.md +10 -2
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +5 -5
- package/gsd-core/workflows/health.md +76 -10
- package/gsd-core/workflows/import.md +18 -15
- package/gsd-core/workflows/inbox.md +4 -5
- package/gsd-core/workflows/ingest-docs.md +49 -16
- package/gsd-core/workflows/insert-phase.md +5 -5
- package/gsd-core/workflows/list-seeds.md +5 -3
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +12 -23
- package/gsd-core/workflows/map-codebase.md +1 -1
- package/gsd-core/workflows/milestone-summary.md +1 -1
- package/gsd-core/workflows/mvp-phase.md +8 -5
- package/gsd-core/workflows/new-milestone.md +22 -29
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
- package/gsd-core/workflows/new-project.md +26 -40
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +14 -2
- package/gsd-core/workflows/pause-work.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -4
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +3 -3
- package/gsd-core/workflows/plan-phase.md +162 -59
- package/gsd-core/workflows/plan-review-convergence.md +96 -11
- package/gsd-core/workflows/plant-seed.md +2 -2
- package/gsd-core/workflows/pr-branch.md +187 -51
- package/gsd-core/workflows/profile-user.md +16 -14
- package/gsd-core/workflows/progress.md +61 -18
- package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +5 -7
- package/gsd-core/workflows/quick/steps/quick-verification.md +28 -9
- package/gsd-core/workflows/quick/steps/research-phase.md +4 -6
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
- package/gsd-core/workflows/quick.md +55 -44
- package/gsd-core/workflows/remove-phase.md +4 -4
- package/gsd-core/workflows/remove-workspace.md +2 -2
- package/gsd-core/workflows/resume-project.md +8 -12
- package/gsd-core/workflows/review.md +219 -20
- package/gsd-core/workflows/scan.md +1 -1
- package/gsd-core/workflows/secure-phase.md +3 -3
- package/gsd-core/workflows/session-report.md +2 -1
- package/gsd-core/workflows/settings-advanced.md +7 -9
- package/gsd-core/workflows/settings-integrations.md +64 -31
- package/gsd-core/workflows/settings.md +69 -7
- package/gsd-core/workflows/ship.md +116 -50
- package/gsd-core/workflows/sketch-wrap-up.md +11 -17
- package/gsd-core/workflows/sketch.md +12 -18
- package/gsd-core/workflows/smart-entry.md +3 -5
- package/gsd-core/workflows/spec-phase.md +53 -13
- package/gsd-core/workflows/spike-wrap-up.md +7 -11
- package/gsd-core/workflows/spike.md +20 -31
- package/gsd-core/workflows/stats.md +2 -2
- package/gsd-core/workflows/sync-skills.md +64 -9
- package/gsd-core/workflows/thread.md +11 -7
- package/gsd-core/workflows/transition.md +49 -14
- package/gsd-core/workflows/ui-phase.md +15 -21
- package/gsd-core/workflows/ui-review.md +8 -12
- package/gsd-core/workflows/ultraplan-phase.md +5 -13
- package/gsd-core/workflows/undo.md +8 -16
- package/gsd-core/workflows/update.md +7 -11
- package/gsd-core/workflows/validate-phase.md +3 -3
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
- package/gsd-core/workflows/verify-work.md +66 -25
- package/hooks/dist/gsd-agent-isolation-guard.js +158 -30
- package/hooks/dist/gsd-check-update-worker.js +56 -13
- package/hooks/dist/gsd-check-update.js +19 -1
- package/hooks/dist/gsd-config-reload.js +18 -12
- package/hooks/dist/gsd-context-monitor.js +19 -10
- package/hooks/dist/gsd-cursor-post-tool.js +3 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +2 -3
- package/hooks/dist/gsd-cursor-session-start.js +2 -1
- package/hooks/dist/gsd-cursor-stop.js +2 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +83 -3
- package/hooks/dist/gsd-cursor-subagent-stop.js +6 -3
- package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
- package/hooks/dist/gsd-graphify-update.sh +22 -18
- package/hooks/dist/gsd-node-runner.sh +76 -0
- package/hooks/dist/gsd-phase-boundary.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +37 -27
- package/hooks/dist/gsd-read-guard.js +16 -7
- package/hooks/dist/gsd-read-injection-scanner.js +55 -32
- package/hooks/dist/gsd-session-state.sh +1 -0
- package/hooks/dist/gsd-statusline.js +231 -24
- package/hooks/dist/gsd-update-banner.js +22 -1
- package/hooks/dist/gsd-validate-commit.sh +80 -6
- package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
- package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
- package/hooks/dist/gsd-workflow-guard.js +162 -46
- package/hooks/dist/gsd-worktree-path-guard.js +36 -21
- package/hooks/dist/gsd-write-guard.js +35 -25
- package/hooks/dist/lib/cli-exit.js +560 -0
- package/hooks/dist/lib/exit-code-registry.js +98 -0
- package/hooks/dist/lib/git-cmd.js +92 -59
- package/hooks/dist/lib/git-probe.js +84 -0
- package/hooks/dist/lib/hook-exit.js +81 -0
- package/hooks/dist/lib/injection-patterns.js +45 -0
- package/hooks/dist/lib/isolation-deny-reason.js +39 -0
- package/hooks/dist/lib/isolation-sentinel.js +9 -0
- package/hooks/dist/managed-hooks-registry.cjs +3 -0
- package/hooks/gsd-agent-isolation-guard.js +158 -30
- package/hooks/gsd-check-update-worker.js +56 -13
- package/hooks/gsd-check-update.js +19 -1
- package/hooks/gsd-config-reload.js +18 -12
- package/hooks/gsd-context-monitor.js +19 -10
- package/hooks/gsd-cursor-post-tool.js +3 -1
- package/hooks/gsd-cursor-pre-tool.js +2 -3
- package/hooks/gsd-cursor-session-start.js +2 -1
- package/hooks/gsd-cursor-stop.js +2 -1
- package/hooks/gsd-cursor-subagent-start.js +83 -3
- package/hooks/gsd-cursor-subagent-stop.js +6 -3
- package/hooks/gsd-ensure-canonical-path.js +2 -1
- package/hooks/gsd-graphify-update.sh +22 -18
- package/hooks/gsd-node-runner.sh +76 -0
- package/hooks/gsd-phase-boundary.sh +1 -0
- package/hooks/gsd-prompt-guard.js +37 -27
- package/hooks/gsd-read-guard.js +16 -7
- package/hooks/gsd-read-injection-scanner.js +55 -32
- package/hooks/gsd-session-state.sh +1 -0
- package/hooks/gsd-statusline.js +231 -24
- package/hooks/gsd-update-banner.js +22 -1
- package/hooks/gsd-validate-commit.sh +80 -6
- package/hooks/gsd-windsurf-pre-command.js +16 -11
- package/hooks/gsd-windsurf-pre-write.js +22 -13
- package/hooks/gsd-workflow-guard.js +162 -46
- package/hooks/gsd-worktree-path-guard.js +36 -21
- package/hooks/gsd-write-guard.js +35 -25
- package/hooks/lib/cli-exit.js +560 -0
- package/hooks/lib/exit-code-registry.js +98 -0
- package/hooks/lib/git-cmd.js +92 -59
- package/hooks/lib/git-probe.js +84 -0
- package/hooks/lib/hook-exit.js +81 -0
- package/hooks/lib/injection-patterns.js +45 -0
- package/hooks/lib/isolation-deny-reason.js +39 -0
- package/hooks/lib/isolation-sentinel.js +9 -0
- package/hooks/managed-hooks-registry.cjs +3 -0
- package/package.json +28 -11
- package/pi/gsd.cjs +19 -5
- package/scripts/base64-scan.sh +74 -12
- package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
- package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
- package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
- package/scripts/build-hooks.js +5 -0
- package/scripts/changeset/lint.cjs +60 -5
- package/scripts/check-alias-drift.cjs +7 -43
- package/scripts/check-contract-drift.cjs +297 -0
- package/scripts/check-glossary-refs.cjs +77 -15
- package/scripts/check-mutation-score-ratchet.cjs +156 -0
- package/scripts/ci-check-job-near-cap.cjs +49 -0
- package/scripts/ci-pr-mergeability.cjs +262 -0
- package/scripts/ci-test-scope.cjs +64 -14
- package/scripts/ci-timeout-report.cjs +230 -0
- package/scripts/command-contract-helpers.cjs +903 -1
- package/scripts/docs-guard-registry.cjs +396 -0
- package/scripts/gen-adr-index.cjs +728 -38
- package/scripts/gen-capability-registry.cjs +11 -21
- package/scripts/gen-context-index.cjs +2 -11
- package/scripts/gen-exit-code-docs.cjs +318 -0
- package/scripts/gen-exit-code-registry.cjs +891 -0
- package/scripts/gen-features.cjs +836 -0
- package/scripts/gen-health-docs.cjs +390 -0
- package/scripts/gen-hooks-cli-exit.cjs +239 -0
- package/scripts/gen-install-tree-fixtures.cjs +2 -2
- package/scripts/gen-inventory-manifest.cjs +50 -4
- package/scripts/gen-loop-host-contract.cjs +138 -25
- package/scripts/gen-registry.cjs +3 -14
- package/scripts/gen-scripts-cli-exit.cjs +185 -0
- package/scripts/gen-state-md-docs.cjs +727 -0
- package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
- package/scripts/lib/alias-drift-families.cjs +46 -0
- package/scripts/lib/ci-job-timing.cjs +72 -0
- package/scripts/lib/cli-exit.cjs +546 -44
- package/scripts/lib/drift-scan.cjs +308 -0
- package/scripts/lib/exit-code-registry.cjs +98 -0
- package/scripts/lib/ndjson-reporter.cjs +119 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -26
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
- package/scripts/lint-canary-version-leak.cjs +73 -0
- package/scripts/lint-command-contract.cjs +96 -13
- package/scripts/lint-completion-predicate-drift.cjs +933 -0
- package/scripts/lint-completion-ratio-drift.cjs +214 -0
- package/scripts/lint-default-flip-documentation.cjs +193 -0
- package/scripts/lint-docs-guard-registration.cjs +495 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +193 -0
- package/scripts/lint-eslint-glob-coverage.allowlist.json +38 -0
- package/scripts/lint-eslint-glob-coverage.cjs +340 -0
- package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
- package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
- package/scripts/lint-health-diagnostic-rule-table.cjs +461 -0
- package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
- package/scripts/lint-milestone-window-drift.cjs +468 -0
- package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
- package/scripts/lint-phase-enumeration-drift.cjs +492 -0
- package/scripts/lint-plan-count-drift.cjs +318 -0
- package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
- package/scripts/lint-planning-prompt-drift.cjs +471 -0
- package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
- package/scripts/lint-regression-test-names.cjs +15 -13
- package/scripts/lint-removed-but-needed.cjs +488 -0
- package/scripts/lint-seam-enforcement.cjs +182 -0
- package/scripts/lint-slug-derivation-drift.cjs +921 -0
- package/scripts/lint-source-test-name-collision.cjs +241 -0
- package/scripts/lint-state-field-drift.cjs +805 -0
- package/scripts/lint-state-write-path-drift.cjs +950 -0
- package/scripts/lint-test-file-count.allowlist.json +137 -8
- package/scripts/lint-test-file-count.cjs +25 -3
- package/scripts/lint-unreachable-guard-drift.cjs +830 -0
- package/scripts/lint-vendored-deps.cjs +297 -0
- package/scripts/mutation-matrix.cjs +599 -50
- package/scripts/pr-changed-files.cjs +63 -0
- package/scripts/pr-template-policy.cjs +14 -4
- package/scripts/prompt-injection-scan.sh +100 -14
- package/scripts/require-issue-link-policy.cjs +192 -0
- package/scripts/secret-scan.sh +75 -13
- package/scripts/select-docs-guards.cjs +56 -0
- package/scripts/sync-runtime-launcher.cjs +24 -7
- package/skills/gsd-autonomous/SKILL.md +0 -1
- package/skills/gsd-code-review/SKILL.md +1 -1
- package/skills/gsd-discuss-phase/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -2
- package/skills/gsd-import/SKILL.md +1 -1
- package/skills/gsd-map-codebase/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +1 -1
- package/skills/gsd-mempalace-recall/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-next/SKILL.md +0 -1
- package/skills/gsd-plan-phase/SKILL.md +0 -1
- package/skills/gsd-progress/SKILL.md +0 -1
- package/skills/gsd-quick/SKILL.md +9 -5
- package/skills/gsd-review-backlog/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +0 -1
- package/skills/gsd-verify-work/SKILL.md +1 -1
- package/vscode/package.json +1 -1
- package/bin/lib/ui-safety-gate.cjs +0 -107
- package/gsd-core/workflows/discovery-phase.md +0 -298
- package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
- package/gsd-core/workflows/verify-phase.md +0 -574
- package/scripts/affected-tests-lib.cjs +0 -554
- package/scripts/lint-allow-test-rule-refs.cjs +0 -162
- package/scripts/lint-emitted-drift-ack.cjs +0 -344
- package/scripts/run-affected-tests.cjs +0 -7
- package/scripts/run-tests.cjs +0 -1051
|
@@ -16,51 +16,61 @@ const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
|
16
16
|
const installer_migration_authoring_cjs_1 = require("./installer-migration-authoring.cjs");
|
|
17
17
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
18
18
|
const clock_cjs_1 = require("./clock.cjs");
|
|
19
|
+
const install_scope_cjs_1 = require("./install-scope.cjs");
|
|
20
|
+
// #2874 (ADR-58 cleanup phase): this file is the ~1200-line migration
|
|
21
|
+
// plan/apply/rollback/lock/journal engine — almost none of it is on the
|
|
22
|
+
// installRuntimeArtifacts call tree. Only `readInstallManifest` and
|
|
23
|
+
// `classifyArtifact` are reached (via install-engine.cts's
|
|
24
|
+
// _migrateLegacyOpencodeCommandDir and retired-artifact-cleanup.cts's
|
|
25
|
+
// pruneRetiredRuntimeArtifacts), so only those two entry points — plus their
|
|
26
|
+
// shared `readJsonIfPresent` helper and `classifyArtifact`'s `sha256File`
|
|
27
|
+
// hashing helper — are routed through the injectable seam. Everything else
|
|
28
|
+
// in this file (locking, journal, apply/rollback, migration discovery)
|
|
29
|
+
// keeps using real `fs` directly: it is not reachable from
|
|
30
|
+
// installRuntimeArtifacts, so routing it would grow this seam past what
|
|
31
|
+
// AC2 actually requires. See install-fs-adapter.cts's module doc.
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
33
|
+
const installFsAdapter = require("./install-fs-adapter.cjs");
|
|
34
|
+
const { installFs } = installFsAdapter;
|
|
19
35
|
const MANIFEST_NAME = 'gsd-file-manifest.json';
|
|
20
36
|
const INSTALL_STATE_NAME = 'gsd-install-state.json';
|
|
21
37
|
const INSTALL_MIGRATION_LOCK_NAME = 'gsd-install-migration.lock';
|
|
22
38
|
const DEFAULT_MIGRATIONS_DIR = node_path_1.default.join(__dirname, 'installer-migrations');
|
|
23
39
|
const DEFAULT_LOCK_TIMEOUT_MS = 30_000;
|
|
24
40
|
const STRICT_JSON = Symbol('strict-json');
|
|
41
|
+
// #2874: routed through installFs()'s openSync/readSync/closeSync trio
|
|
42
|
+
// instead of importing `node:fs` directly, so classifyArtifact — reachable
|
|
43
|
+
// from installRuntimeArtifacts — can be exercised against an injected
|
|
44
|
+
// adapter. This function was briefly converted to a single
|
|
45
|
+
// `installFs().readFileSync` call (buffering the whole file); that broke
|
|
46
|
+
// tests/installer-migrations.test.cjs's "classifies large files without
|
|
47
|
+
// loading the whole file through readFileSync", which monkeypatches real
|
|
48
|
+
// fs.readFileSync to throw for the file under test and asserts hashing still
|
|
49
|
+
// succeeds — an explicit, pre-existing contract that large files must be
|
|
50
|
+
// streamed, not buffered. Restored to the original raw-fd streaming shape,
|
|
51
|
+
// now going through the adapter instead of `node:fs` directly. This is the
|
|
52
|
+
// ONLY call site of sha256File in this file (confirmed by inspection) — no
|
|
53
|
+
// other caller is affected.
|
|
25
54
|
function sha256File(filePath) {
|
|
26
55
|
const hash = node_crypto_1.default.createHash('sha256');
|
|
27
56
|
const buffer = Buffer.allocUnsafe(1024 * 1024);
|
|
28
|
-
const fd =
|
|
57
|
+
const fd = installFs().openSync(filePath, 'r');
|
|
29
58
|
try {
|
|
30
59
|
while (true) {
|
|
31
|
-
const bytesRead =
|
|
60
|
+
const bytesRead = installFs().readSync(fd, buffer, 0, buffer.length, null);
|
|
32
61
|
if (bytesRead === 0)
|
|
33
62
|
break;
|
|
34
63
|
hash.update(buffer.subarray(0, bytesRead));
|
|
35
64
|
}
|
|
36
65
|
}
|
|
37
66
|
finally {
|
|
38
|
-
|
|
67
|
+
installFs().closeSync(fd);
|
|
39
68
|
}
|
|
40
69
|
return hash.digest('hex');
|
|
41
70
|
}
|
|
42
71
|
function sha256Text(value) {
|
|
43
72
|
return node_crypto_1.default.createHash('sha256').update(value).digest('hex');
|
|
44
73
|
}
|
|
45
|
-
/**
|
|
46
|
-
* Copy a managed path for the rollback snapshot or the user-facing backup,
|
|
47
|
-
* WITHOUT dereferencing a symlink.
|
|
48
|
-
*
|
|
49
|
-
* `fs.copyFileSync` follows symlinks, so a managed path that has been replaced
|
|
50
|
-
* by a link (tampering, or an unexpected user layout) would have had the
|
|
51
|
-
* LINK TARGET's bytes copied into `gsd-migration-journal/…-backups/` — e.g. a
|
|
52
|
-
* `gsd.cjs` symlinked at `~/.ssh/id_rsa` would land that key's contents in the
|
|
53
|
-
* backup tree. Nothing GSD installs is ever a symlink, so the faithful snapshot
|
|
54
|
-
* of a symlinked managed path is the link itself: recreating it preserves
|
|
55
|
-
* rollback fidelity (restore re-creates the same link) while never reading the
|
|
56
|
-
* referent. Deletion was already safe — `fs.rmSync` unlinks the link, never the
|
|
57
|
-
* target.
|
|
58
|
-
*
|
|
59
|
-
* Windows note: `fs.symlinkSync` can throw EPERM for unprivileged users. That
|
|
60
|
-
* surfaces as an apply failure and triggers the normal rollback path, which is
|
|
61
|
-
* the correct outcome — refusing to proceed beats silently copying referent
|
|
62
|
-
* bytes.
|
|
63
|
-
*/
|
|
64
74
|
/**
|
|
65
75
|
* Evaluate and, if safe, perform a `remove-empty-dir` action against `fullPath`.
|
|
66
76
|
*
|
|
@@ -133,23 +143,54 @@ function evaluateRemoveEmptyDir(configDir, fullPath) {
|
|
|
133
143
|
return 'left-in-place';
|
|
134
144
|
}
|
|
135
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Copy a managed path for the rollback snapshot or the user-facing backup,
|
|
148
|
+
* WITHOUT dereferencing a symlink.
|
|
149
|
+
*
|
|
150
|
+
* `fs.copyFileSync` follows symlinks, so a managed path that has been replaced
|
|
151
|
+
* by a link (tampering, or an unexpected user layout) would have had the
|
|
152
|
+
* LINK TARGET's bytes copied into `gsd-migration-journal/…-backups/` — e.g. a
|
|
153
|
+
* `gsd.cjs` symlinked at `~/.ssh/id_rsa` would land that key's contents in the
|
|
154
|
+
* backup tree. Nothing GSD installs is ever a symlink, so the faithful snapshot
|
|
155
|
+
* of a symlinked managed path is the link itself: recreating it preserves
|
|
156
|
+
* rollback fidelity (restore re-creates the same link) while never reading the
|
|
157
|
+
* referent. Deletion was already safe — `fs.rmSync` unlinks the link, never the
|
|
158
|
+
* target.
|
|
159
|
+
*
|
|
160
|
+
* Windows note: `fs.symlinkSync` can throw EPERM for unprivileged users. That
|
|
161
|
+
* surfaces as an apply failure and triggers the normal rollback path, which is
|
|
162
|
+
* the correct outcome — refusing to proceed beats silently copying referent
|
|
163
|
+
* bytes.
|
|
164
|
+
*
|
|
165
|
+
* #2875 (epic #2866 Phase 6): all five fs calls routed through `installFs()`
|
|
166
|
+
* so this primitive can be reused on the routed install path (by
|
|
167
|
+
* user-artifact-staging.cts) without punching a hole through the seam Phase 5
|
|
168
|
+
* built. Every EXISTING caller of this function is on the migration
|
|
169
|
+
* plan/apply/rollback tree, which never wraps a call in `withInstallFs` — the
|
|
170
|
+
* ambient adapter there resolves to real `node:fs` by default, so this
|
|
171
|
+
* routing is behavior-preserving for them (test-matrix D2).
|
|
172
|
+
*/
|
|
136
173
|
function copyPreservingSymlink(srcPath, destPath) {
|
|
137
|
-
if (
|
|
174
|
+
if (installFs().lstatSync(srcPath).isSymbolicLink()) {
|
|
138
175
|
// symlinkSync fails with EEXIST on an occupied path, so clear it first.
|
|
139
176
|
// Scoped to this branch on purpose: the regular-file path below keeps
|
|
140
177
|
// copyFileSync's overwrite-in-place, so a mid-restore failure cannot leave
|
|
141
178
|
// the destination destroyed.
|
|
142
|
-
|
|
143
|
-
|
|
179
|
+
installFs().rmSync(destPath, { force: true });
|
|
180
|
+
installFs().symlinkSync(installFs().readlinkSync(srcPath), destPath);
|
|
144
181
|
return;
|
|
145
182
|
}
|
|
146
|
-
|
|
183
|
+
installFs().copyFileSync(srcPath, destPath);
|
|
147
184
|
}
|
|
185
|
+
// Shared by readInstallManifest (on the installRuntimeArtifacts call tree —
|
|
186
|
+
// routed) and readInstallState/readJson (not on that call tree — the
|
|
187
|
+
// ambient default resolves to real fs for those, unchanged). Routing once
|
|
188
|
+
// here is safe for all three callers.
|
|
148
189
|
function readJsonIfPresent(filePath, fallback) {
|
|
149
|
-
if (!
|
|
190
|
+
if (!installFs().existsSync(filePath))
|
|
150
191
|
return fallback;
|
|
151
192
|
try {
|
|
152
|
-
return JSON.parse(
|
|
193
|
+
return JSON.parse(installFs().readFileSync(filePath, 'utf8'));
|
|
153
194
|
}
|
|
154
195
|
catch (error) {
|
|
155
196
|
if (fallback === STRICT_JSON) {
|
|
@@ -158,17 +199,81 @@ function readJsonIfPresent(filePath, fallback) {
|
|
|
158
199
|
return fallback;
|
|
159
200
|
}
|
|
160
201
|
}
|
|
202
|
+
/** Lowest manifest schema version that records `runtime`/`scope` (#2872). */
|
|
203
|
+
const MANIFEST_SCHEMA_VERSION = 2;
|
|
204
|
+
/**
|
|
205
|
+
* Longest `runtime` string this reader will report. Real runtime ids are
|
|
206
|
+
* registry keys (`claude`, `antigravity`, `kimi-code` — 11 chars at the
|
|
207
|
+
* longest), so this loses nothing legitimate; it exists because the manifest
|
|
208
|
+
* is attacker-influenceable (a project-local one lives inside a repository a
|
|
209
|
+
* user may merely have cloned) and the value reaches a consumer that renders
|
|
210
|
+
* it. Same 64-char convention as `truncatePostureValue`
|
|
211
|
+
* (`agent-install-check.cts`), deliberately, so the subsystem caps reported
|
|
212
|
+
* values one way.
|
|
213
|
+
*/
|
|
214
|
+
const MAX_REPORTED_RUNTIME_LENGTH = 64;
|
|
215
|
+
/**
|
|
216
|
+
* A manifest's `runtime` is reported as a FACT about the file — it is
|
|
217
|
+
* deliberately NOT validated against the capability registry, because an
|
|
218
|
+
* unregistered id is exactly the kind of mismatch the Installed Surface
|
|
219
|
+
* Resolver exists to surface (#2872 design row B8). It is, however, LENGTH
|
|
220
|
+
* bounded: "report the fact" never required "report unbounded bytes".
|
|
221
|
+
*/
|
|
222
|
+
function normalizeReportedRuntime(raw) {
|
|
223
|
+
if (typeof raw !== 'string')
|
|
224
|
+
return null;
|
|
225
|
+
if (raw.trim() === '')
|
|
226
|
+
return null;
|
|
227
|
+
return raw.length > MAX_REPORTED_RUNTIME_LENGTH
|
|
228
|
+
? `${raw.slice(0, MAX_REPORTED_RUNTIME_LENGTH)}…`
|
|
229
|
+
: raw;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Normalize a raw `manifestVersion`. Only a finite integer >= 1 is a version
|
|
233
|
+
* claim; everything else (absent, `"2"`, `0`, `-1`, `2.5`, `NaN`, `Infinity`)
|
|
234
|
+
* reads as `1` — a pre-#2872 manifest. Liberal in what it accepts, but the
|
|
235
|
+
* normalization is a stated value rather than a silent guess: a caller can
|
|
236
|
+
* always tell v1 (`1`) from "no manifest at all" (`null`).
|
|
237
|
+
*/
|
|
238
|
+
function normalizeManifestVersion(raw) {
|
|
239
|
+
if (typeof raw !== 'number')
|
|
240
|
+
return 1;
|
|
241
|
+
if (!Number.isInteger(raw))
|
|
242
|
+
return 1;
|
|
243
|
+
if (raw < 1)
|
|
244
|
+
return 1;
|
|
245
|
+
return raw;
|
|
246
|
+
}
|
|
161
247
|
function readInstallManifest(configDir) {
|
|
162
248
|
const manifest = readJsonIfPresent(node_path_1.default.join(configDir, MANIFEST_NAME), null);
|
|
163
|
-
|
|
164
|
-
|
|
249
|
+
// `typeof [] === 'object'` in JS, so a bare `typeof !== 'object'` guard lets
|
|
250
|
+
// a top-level JSON array (valid JSON, but not the manifest's documented
|
|
251
|
+
// object shape) fall through to the field reads below — `m.manifestVersion`
|
|
252
|
+
// reads `undefined` off an array, which `normalizeManifestVersion` then
|
|
253
|
+
// reports as `1` (a v1 manifest), misclassifying "not an object" as
|
|
254
|
+
// "installed". `Array.isArray` closes that gap explicitly rather than
|
|
255
|
+
// relying on the object-shape checks below to catch it incidentally.
|
|
256
|
+
if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) {
|
|
257
|
+
return {
|
|
258
|
+
version: null,
|
|
259
|
+
timestamp: null,
|
|
260
|
+
mode: null,
|
|
261
|
+
files: {},
|
|
262
|
+
manifestVersion: null,
|
|
263
|
+
runtime: null,
|
|
264
|
+
scope: null,
|
|
265
|
+
};
|
|
165
266
|
}
|
|
166
267
|
const m = manifest;
|
|
268
|
+
const rawRuntime = m.runtime;
|
|
167
269
|
return {
|
|
168
270
|
version: typeof m.version === 'string' ? m.version : null,
|
|
169
271
|
timestamp: typeof m.timestamp === 'string' ? m.timestamp : null,
|
|
170
272
|
mode: typeof m.mode === 'string' ? m.mode : null,
|
|
171
273
|
files: m.files && typeof m.files === 'object' ? m.files : {},
|
|
274
|
+
manifestVersion: normalizeManifestVersion(m.manifestVersion),
|
|
275
|
+
runtime: normalizeReportedRuntime(rawRuntime),
|
|
276
|
+
scope: (0, install_scope_cjs_1.isInstallScopeId)(m.scope) ? m.scope : null,
|
|
172
277
|
};
|
|
173
278
|
}
|
|
174
279
|
function readInstallState(configDir) {
|
|
@@ -235,7 +340,7 @@ function classifyArtifact(configDir, relPath, manifest) {
|
|
|
235
340
|
const normalized = normalizeRelPath(relPath);
|
|
236
341
|
const originalHash = manifest.files[normalized] || null;
|
|
237
342
|
const fullPath = node_path_1.default.join(configDir, normalized);
|
|
238
|
-
if (!
|
|
343
|
+
if (!installFs().existsSync(fullPath)) {
|
|
239
344
|
return { classification: originalHash ? 'managed-missing' : 'missing', originalHash, currentHash: null };
|
|
240
345
|
}
|
|
241
346
|
const currentHash = sha256File(fullPath);
|
|
@@ -400,17 +505,20 @@ function acquireInstallMigrationLock(configDir, { timeoutMs = DEFAULT_LOCK_TIMEO
|
|
|
400
505
|
let lockCreatedByUs = false;
|
|
401
506
|
try {
|
|
402
507
|
fd = node_fs_1.default.openSync(lockPath, 'wx');
|
|
403
|
-
// Close the open descriptor before writing so the file handle is
|
|
404
|
-
// released on Windows before the release closure unlinks it.
|
|
405
|
-
// Write payload via writeFileSync with the path (not the fd) so we
|
|
406
|
-
// don't hold an open fd across the lifetime of the lock.
|
|
407
|
-
node_fs_1.default.closeSync(fd);
|
|
408
|
-
fd = null;
|
|
409
508
|
lockCreatedByUs = true; // we own the file; clean it up on any subsequent error
|
|
410
|
-
|
|
509
|
+
// Write the payload through the exclusively-created descriptor: a
|
|
510
|
+
// second open-by-path here would be a TOCTOU window (CWE-367) where a
|
|
511
|
+
// co-writer of the directory could symlink-swap the just-created empty
|
|
512
|
+
// lock file before the payload lands.
|
|
513
|
+
node_fs_1.default.writeFileSync(fd, JSON.stringify({
|
|
411
514
|
pid: process.pid,
|
|
412
515
|
acquiredAt: new Date().toISOString(),
|
|
413
516
|
}) + '\n');
|
|
517
|
+
// Close before returning so no handle stays open across the lock's
|
|
518
|
+
// lifetime — Windows cannot unlink a file with an open handle when the
|
|
519
|
+
// release closure runs.
|
|
520
|
+
node_fs_1.default.closeSync(fd);
|
|
521
|
+
fd = null;
|
|
414
522
|
lockCreatedByUs = false; // release closure owns cleanup from here
|
|
415
523
|
return () => {
|
|
416
524
|
const failures = [];
|
|
@@ -937,8 +1045,10 @@ module.exports = {
|
|
|
937
1045
|
acquireInstallMigrationLock,
|
|
938
1046
|
applyInstallerMigrationPlan,
|
|
939
1047
|
classifyArtifact,
|
|
1048
|
+
copyPreservingSymlink,
|
|
940
1049
|
discoverInstallerMigrations,
|
|
941
1050
|
evaluateRemoveEmptyDir,
|
|
1051
|
+
MANIFEST_SCHEMA_VERSION,
|
|
942
1052
|
migrationChecksum,
|
|
943
1053
|
planInstallerMigrations,
|
|
944
1054
|
readInstallManifest,
|
|
@@ -23,6 +23,9 @@ const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs")
|
|
|
23
23
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
24
24
|
const capabilityStateMod = require("./capability-state.cjs");
|
|
25
25
|
const { isCapabilityActive } = capabilityStateMod;
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
27
|
+
const ioMod = require("./io.cjs");
|
|
28
|
+
const { formatDiagnosticToken } = ioMod;
|
|
26
29
|
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
27
30
|
const INTEL_DIR = '.planning/intel';
|
|
28
31
|
const INTEL_FILES = {
|
|
@@ -32,6 +35,16 @@ const INTEL_FILES = {
|
|
|
32
35
|
arch: 'arch-decisions.json',
|
|
33
36
|
stack: 'stack.json',
|
|
34
37
|
};
|
|
38
|
+
/**
|
|
39
|
+
* ADR-3473 §8.5 / #3885: recursion bound for the intel JSON search walk.
|
|
40
|
+
* Restored from the retired SDK lineage (`sdk/src/query/intel.ts` at `11918dcc3^`),
|
|
41
|
+
* lost in the ADR-0174 consolidation. Unlike the original, hitting the ceiling is
|
|
42
|
+
* NOT reported as a silent "no match" — the walk that stops early sets a
|
|
43
|
+
* `truncated` flag threaded back up to `intelQuery`'s result (Decision 4: a
|
|
44
|
+
* routine that discards an input says so). The bound is on DEPTH only; breadth
|
|
45
|
+
* (sibling count at any given depth) is unaffected.
|
|
46
|
+
*/
|
|
47
|
+
const MAX_JSON_SEARCH_DEPTH = 48;
|
|
35
48
|
// ─── Internal helpers ────────────────────────────────────────────────────────
|
|
36
49
|
/**
|
|
37
50
|
* Ensure the intel directory exists under the given planning dir.
|
|
@@ -71,16 +84,42 @@ function intelFilePath(planningDir, filename) {
|
|
|
71
84
|
}
|
|
72
85
|
/**
|
|
73
86
|
* Safely read and parse a JSON intel file.
|
|
74
|
-
*
|
|
87
|
+
*
|
|
88
|
+
* Returns null for THREE distinct on-disk states, only one of which is a
|
|
89
|
+
* "quiet" case (#3885, ADR-3473 §8.5):
|
|
90
|
+
* - ABSENT (ENOENT, via platformReadSync returning null): silent — not
|
|
91
|
+
* every project has every intel file, and callers already loop over
|
|
92
|
+
* the full INTEL_FILES set expecting misses. Never pushed to `errors`.
|
|
93
|
+
* - UNREADABLE (EACCES/EIO/... — platformReadSync rethrows anything that
|
|
94
|
+
* isn't ENOENT): surfaced, naming the file, when `errors` is supplied.
|
|
95
|
+
* - MALFORMED (JSON.parse throws on a file that WAS read successfully):
|
|
96
|
+
* also surfaced, naming the file — a corrupt intel file used to read
|
|
97
|
+
* identically to "no matches", which is the same defect one layer down.
|
|
98
|
+
*
|
|
99
|
+
* `errors` is an optional accumulator so callers that want to thread the
|
|
100
|
+
* outcome into their own result shape can pass an array and read it back
|
|
101
|
+
* after the call; callers that omit it keep the prior fold-to-null shape.
|
|
75
102
|
*/
|
|
76
|
-
function safeReadJson(filePath) {
|
|
103
|
+
function safeReadJson(filePath, errors) {
|
|
104
|
+
let raw;
|
|
105
|
+
try {
|
|
106
|
+
raw = (0, shell_command_projection_cjs_1.platformReadSync)(filePath);
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
if (errors) {
|
|
110
|
+
errors.push(`Could not read ${formatDiagnosticToken(filePath)}: ${formatDiagnosticToken(err?.message ?? String(err))}`);
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
if (raw === null)
|
|
115
|
+
return null; // ENOENT — genuinely absent, not an error.
|
|
77
116
|
try {
|
|
78
|
-
const raw = (0, shell_command_projection_cjs_1.platformReadSync)(filePath);
|
|
79
|
-
if (raw === null)
|
|
80
|
-
return null;
|
|
81
117
|
return JSON.parse(raw);
|
|
82
118
|
}
|
|
83
|
-
catch {
|
|
119
|
+
catch (err) {
|
|
120
|
+
if (errors) {
|
|
121
|
+
errors.push(`Could not parse ${formatDiagnosticToken(filePath)}: ${formatDiagnosticToken(err?.message ?? String(err))}`);
|
|
122
|
+
}
|
|
84
123
|
return null;
|
|
85
124
|
}
|
|
86
125
|
}
|
|
@@ -101,16 +140,17 @@ function hashFile(filePath) {
|
|
|
101
140
|
}
|
|
102
141
|
/**
|
|
103
142
|
* Search for a term (case-insensitive) in a JSON object's keys and string values.
|
|
104
|
-
* Returns
|
|
143
|
+
* Returns matching entries plus whether the walk hit MAX_JSON_SEARCH_DEPTH.
|
|
105
144
|
*/
|
|
106
145
|
function searchJsonEntries(data, term) {
|
|
107
146
|
if (!data || typeof data !== 'object')
|
|
108
|
-
return [];
|
|
147
|
+
return { matches: [], truncated: false };
|
|
109
148
|
const entries = data.entries || data;
|
|
110
149
|
if (!entries || typeof entries !== 'object')
|
|
111
|
-
return [];
|
|
150
|
+
return { matches: [], truncated: false };
|
|
112
151
|
const lowerTerm = term.toLowerCase();
|
|
113
152
|
const matches = [];
|
|
153
|
+
const state = { truncated: false };
|
|
114
154
|
for (const [key, value] of Object.entries(entries)) {
|
|
115
155
|
if (key === '_meta')
|
|
116
156
|
continue;
|
|
@@ -119,25 +159,40 @@ function searchJsonEntries(data, term) {
|
|
|
119
159
|
matches.push({ key, value });
|
|
120
160
|
continue;
|
|
121
161
|
}
|
|
122
|
-
// Check string value match (recursive for objects)
|
|
123
|
-
if (matchesInValue(value, lowerTerm)) {
|
|
162
|
+
// Check string value match (recursive for objects/arrays, bounded by depth)
|
|
163
|
+
if (matchesInValue(value, lowerTerm, 0, state)) {
|
|
124
164
|
matches.push({ key, value });
|
|
125
165
|
}
|
|
126
166
|
}
|
|
127
|
-
return matches;
|
|
167
|
+
return { matches, truncated: state.truncated };
|
|
128
168
|
}
|
|
129
169
|
/**
|
|
130
170
|
* Recursively check if a term appears in any string value.
|
|
171
|
+
*
|
|
172
|
+
* `depth` counts container unwraps already performed (starts at 0 for the
|
|
173
|
+
* entry's own value). Strings never fail the ceiling check themselves — only
|
|
174
|
+
* a container (object/array) refuses to recurse one level deeper once
|
|
175
|
+
* `depth > MAX_JSON_SEARCH_DEPTH`, at which point `state.truncated` is set so
|
|
176
|
+
* the caller can report "I stopped looking" rather than a bare "no match".
|
|
177
|
+
* The bound is on nesting depth only, never on sibling breadth.
|
|
131
178
|
*/
|
|
132
|
-
function matchesInValue(value, lowerTerm) {
|
|
179
|
+
function matchesInValue(value, lowerTerm, depth, state) {
|
|
133
180
|
if (typeof value === 'string') {
|
|
134
181
|
return value.toLowerCase().includes(lowerTerm);
|
|
135
182
|
}
|
|
136
183
|
if (Array.isArray(value)) {
|
|
137
|
-
|
|
184
|
+
if (depth > MAX_JSON_SEARCH_DEPTH) {
|
|
185
|
+
state.truncated = true;
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
return value.some(v => matchesInValue(v, lowerTerm, depth + 1, state));
|
|
138
189
|
}
|
|
139
190
|
if (value && typeof value === 'object') {
|
|
140
|
-
|
|
191
|
+
if (depth > MAX_JSON_SEARCH_DEPTH) {
|
|
192
|
+
state.truncated = true;
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
return Object.values(value).some(v => matchesInValue(v, lowerTerm, depth + 1, state));
|
|
141
196
|
}
|
|
142
197
|
return false;
|
|
143
198
|
}
|
|
@@ -150,19 +205,23 @@ function intelQuery(term, planningDir) {
|
|
|
150
205
|
return disabledResponse();
|
|
151
206
|
const matches = [];
|
|
152
207
|
let total = 0;
|
|
208
|
+
let truncated = false;
|
|
209
|
+
const readErrors = [];
|
|
153
210
|
// Search all JSON intel files
|
|
154
211
|
for (const [_key, filename] of Object.entries(INTEL_FILES)) {
|
|
155
212
|
const filePath = intelFilePath(planningDir, filename);
|
|
156
|
-
const data = safeReadJson(filePath);
|
|
213
|
+
const data = safeReadJson(filePath, readErrors);
|
|
157
214
|
if (!data)
|
|
158
215
|
continue;
|
|
159
|
-
const found = searchJsonEntries(data, term);
|
|
216
|
+
const { matches: found, truncated: fileTruncated } = searchJsonEntries(data, term);
|
|
217
|
+
if (fileTruncated)
|
|
218
|
+
truncated = true;
|
|
160
219
|
if (found.length > 0) {
|
|
161
220
|
matches.push({ source: filename, entries: found });
|
|
162
221
|
total += found.length;
|
|
163
222
|
}
|
|
164
223
|
}
|
|
165
|
-
return { matches, term, total };
|
|
224
|
+
return { matches, term, total, truncated, read_errors: readErrors };
|
|
166
225
|
}
|
|
167
226
|
/**
|
|
168
227
|
* Report status and staleness of each intel file.
|
|
@@ -179,13 +238,14 @@ function intelStatus(planningDir) {
|
|
|
179
238
|
const filePath = intelFilePath(planningDir, filename);
|
|
180
239
|
const exists = node_fs_1.default.existsSync(filePath);
|
|
181
240
|
if (!exists) {
|
|
182
|
-
files[filename] = { exists: false, updated_at: null, stale: true };
|
|
241
|
+
files[filename] = { exists: false, updated_at: null, stale: true, read_error: null };
|
|
183
242
|
overallStale = true;
|
|
184
243
|
continue;
|
|
185
244
|
}
|
|
186
245
|
let updatedAt = null;
|
|
187
246
|
// All intel files are JSON — read _meta.updated_at
|
|
188
|
-
const
|
|
247
|
+
const readErrors = [];
|
|
248
|
+
const data = safeReadJson(filePath, readErrors);
|
|
189
249
|
if (data && data._meta && data._meta.updated_at) {
|
|
190
250
|
updatedAt = data._meta.updated_at;
|
|
191
251
|
}
|
|
@@ -196,7 +256,7 @@ function intelStatus(planningDir) {
|
|
|
196
256
|
}
|
|
197
257
|
if (stale)
|
|
198
258
|
overallStale = true;
|
|
199
|
-
files[filename] = { exists: true, updated_at: updatedAt, stale };
|
|
259
|
+
files[filename] = { exists: true, updated_at: updatedAt, stale, read_error: readErrors[0] ?? null };
|
|
200
260
|
}
|
|
201
261
|
return { files, overall_stale: overallStale };
|
|
202
262
|
}
|
|
@@ -207,9 +267,15 @@ function intelDiff(planningDir) {
|
|
|
207
267
|
if (!isIntelCapabilityActive(planningDir))
|
|
208
268
|
return disabledResponse();
|
|
209
269
|
const snapshotPath = intelFilePath(planningDir, '.last-refresh.json');
|
|
210
|
-
const
|
|
270
|
+
const readErrors = [];
|
|
271
|
+
const snapshot = safeReadJson(snapshotPath, readErrors);
|
|
211
272
|
if (!snapshot) {
|
|
212
|
-
|
|
273
|
+
// #3885 (ADR-3473 §8.5): `no_baseline: true` stays true for BOTH a
|
|
274
|
+
// genuinely-never-snapshotted project (ENOENT, read_error stays null)
|
|
275
|
+
// AND a present-but-unreadable/malformed snapshot — collapsing those
|
|
276
|
+
// two into a bare `no_baseline: true` would manufacture "you've never
|
|
277
|
+
// run a refresh" out of a read failure. `read_error` names which one.
|
|
278
|
+
return { no_baseline: true, read_error: readErrors[0] ?? null };
|
|
213
279
|
}
|
|
214
280
|
const prevHashes = snapshot.hashes || {};
|
|
215
281
|
const changed = [];
|
|
@@ -374,7 +440,9 @@ function intelApiSurface(planningDir) {
|
|
|
374
440
|
const intelPath = ensureIntelDir(planningDir);
|
|
375
441
|
const apiMapPath = node_path_1.default.join(intelPath, INTEL_FILES.apis);
|
|
376
442
|
const outputPath = node_path_1.default.join(intelPath, 'API-SURFACE.md');
|
|
377
|
-
const
|
|
443
|
+
const readErrors = [];
|
|
444
|
+
const data = safeReadJson(apiMapPath, readErrors);
|
|
445
|
+
const readError = readErrors[0] ?? null;
|
|
378
446
|
const entries = (data && data.entries && typeof data.entries === 'object')
|
|
379
447
|
? Object.entries(data.entries)
|
|
380
448
|
: [];
|
|
@@ -392,7 +460,14 @@ function intelApiSurface(planningDir) {
|
|
|
392
460
|
lines.push('> Generated from `.planning/intel/api-map.json`. Do not edit by hand.');
|
|
393
461
|
lines.push('');
|
|
394
462
|
if (symbolCount === 0) {
|
|
395
|
-
|
|
463
|
+
if (readError) {
|
|
464
|
+
// #3885: a read/parse failure is NOT "not yet populated" — say so,
|
|
465
|
+
// rather than manufacturing the wrong reason for the empty surface.
|
|
466
|
+
lines.push(`> **Incomplete:** ${readError}`);
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
lines.push('> **Incomplete:** api-map.json has no entries (intel extraction is regex/JS-only or not yet populated).');
|
|
470
|
+
}
|
|
396
471
|
lines.push('> Treat absence here as "unknown", not "does not exist".');
|
|
397
472
|
lines.push('');
|
|
398
473
|
}
|
|
@@ -414,7 +489,7 @@ function intelApiSurface(planningDir) {
|
|
|
414
489
|
}
|
|
415
490
|
}
|
|
416
491
|
(0, shell_command_projection_cjs_1.platformWriteSync)(outputPath, lines.join('\n'));
|
|
417
|
-
return { written: outputPath, symbolCount, stale };
|
|
492
|
+
return { written: outputPath, symbolCount, stale, read_error: readError };
|
|
418
493
|
}
|
|
419
494
|
/**
|
|
420
495
|
* Patch _meta.updated_at in a JSON intel file to the current timestamp.
|