@opengsd/gsd-core 1.12.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/.opencode/plugins/gsd-core.js +12 -0
- 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-executor.md +63 -35
- 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-plan-checker.md +76 -57
- package/agents/gsd-planner.md +14 -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-checker.md +19 -3
- package/agents/gsd-ui-researcher.compact.md +282 -0
- package/agents/gsd-ui-researcher.md +29 -0
- package/agents/gsd-user-profiler.compact.md +108 -0
- package/agents/gsd-verifier.md +23 -1
- package/bin/install.js +444 -134
- 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/execute-phase.md +1 -1
- 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/ns-workflow.md +2 -1
- package/commands/gsd/pause-work.md +1 -0
- package/commands/gsd/phase.md +2 -1
- package/commands/gsd/pr-branch.md +1 -0
- package/commands/gsd/quick-batch.md +105 -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/surface.md +18 -8
- 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 +532 -174
- 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-activation.cjs +27 -0
- 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 +528 -116
- package/gsd-core/bin/lib/capability-source.cjs +19 -2
- package/gsd-core/bin/lib/capability-state.cjs +7 -1
- package/gsd-core/bin/lib/capability-validator.cjs +134 -5
- package/gsd-core/bin/lib/capability-writer.cjs +14 -4
- package/gsd-core/bin/lib/check-command-router.cjs +198 -38
- package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
- package/gsd-core/bin/lib/clusters.cjs +1 -0
- package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
- package/gsd-core/bin/lib/command-aliases.cjs +16 -0
- package/gsd-core/bin/lib/commands.cjs +981 -79
- package/gsd-core/bin/lib/config-loader.cjs +4 -0
- package/gsd-core/bin/lib/config.cjs +153 -38
- package/gsd-core/bin/lib/core-utils.cjs +34 -7
- package/gsd-core/bin/lib/coverage.cjs +1 -1
- package/gsd-core/bin/lib/decisions.cjs +343 -28
- package/gsd-core/bin/lib/edge-probe.cjs +14 -1
- package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
- package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
- package/gsd-core/bin/lib/frontmatter.cjs +137 -23
- package/gsd-core/bin/lib/gap-checker.cjs +22 -13
- package/gsd-core/bin/lib/git-base-branch.cjs +10 -2
- package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +54 -11
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +87 -23
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
- package/gsd-core/bin/lib/host-integration.cjs +57 -5
- package/gsd-core/bin/lib/init-command-router.cjs +14 -0
- package/gsd-core/bin/lib/init.cjs +539 -60
- package/gsd-core/bin/lib/install-engine.cjs +199 -14
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +45 -0
- package/gsd-core/bin/lib/install-profiles.cjs +36 -14
- package/gsd-core/bin/lib/installer-migration-report.cjs +1 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +33 -4
- package/gsd-core/bin/lib/io.cjs +35 -0
- package/gsd-core/bin/lib/loop-resolver.cjs +64 -39
- package/gsd-core/bin/lib/markdown-table.cjs +123 -0
- package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
- package/gsd-core/bin/lib/milestone.cjs +41 -10
- package/gsd-core/bin/lib/model-resolver.cjs +101 -10
- package/gsd-core/bin/lib/phase-command-router.cjs +20 -7
- package/gsd-core/bin/lib/phase-id.cjs +412 -31
- package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
- package/gsd-core/bin/lib/phase.cjs +941 -98
- package/gsd-core/bin/lib/plan-document.cjs +10 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +34 -18
- package/gsd-core/bin/lib/planning-snapshot.cjs +206 -30
- package/gsd-core/bin/lib/planning-workspace.cjs +153 -29
- 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-command-router.cjs +285 -0
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
- package/gsd-core/bin/lib/quick-batch.cjs +840 -0
- 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-descriptor.cjs +53 -5
- package/gsd-core/bin/lib/review-lane-invocation.cjs +96 -1
- package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
- package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
- package/gsd-core/bin/lib/roadmap-parser.cjs +555 -41
- package/gsd-core/bin/lib/roadmap.cjs +292 -69
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +260 -43
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +28 -20
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +294 -108
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +408 -47
- package/gsd-core/bin/lib/security.cjs +126 -7
- package/gsd-core/bin/lib/shell-command-projection.cjs +4 -0
- package/gsd-core/bin/lib/smart-entry.cjs +7 -9
- package/gsd-core/bin/lib/state-document.cjs +159 -32
- package/gsd-core/bin/lib/state-md-schema.cjs +44 -27
- package/gsd-core/bin/lib/state-transition.cjs +465 -62
- package/gsd-core/bin/lib/state.cjs +906 -151
- package/gsd-core/bin/lib/surface.cjs +83 -10
- package/gsd-core/bin/lib/task-command-router.cjs +12 -6
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
- package/gsd-core/bin/lib/uat.cjs +1420 -516
- package/gsd-core/bin/lib/update-context.cjs +36 -26
- package/gsd-core/bin/lib/validate.cjs +230 -12
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +316 -23
- package/gsd-core/bin/lib/verify-command-grounding.cjs +1 -1
- package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
- package/gsd-core/bin/lib/verify.cjs +531 -36
- package/gsd-core/bin/lib/workstream-inventory.cjs +21 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +21 -7
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +13 -0
- package/gsd-core/bin/verify-reapply-patches.cjs +507 -81
- package/gsd-core/references/agent-contracts.md +3 -3
- package/gsd-core/references/compact-content-gate.md +66 -0
- package/gsd-core/references/edge-probe.md +17 -13
- package/gsd-core/references/execute-mvp-tdd.md +18 -16
- package/gsd-core/references/execute-phase-response-language.md +6 -0
- package/gsd-core/references/executor-examples.md +42 -0
- package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
- package/gsd-core/references/loop-hook-dispatch.md +18 -0
- package/gsd-core/references/model-profiles.md +12 -3
- package/gsd-core/references/mvp-concepts.md +2 -2
- package/gsd-core/references/plan-checker-examples.md +41 -0
- package/gsd-core/references/planner-antipatterns.md +25 -0
- package/gsd-core/references/planner-chunked.md +5 -1
- package/gsd-core/references/planner-coupling.md +42 -0
- package/gsd-core/references/planner-quick-batch.md +71 -0
- package/gsd-core/references/planner-reviews.md +47 -0
- package/gsd-core/references/planner-revision.md +75 -2
- package/gsd-core/references/planning-config.md +5 -1
- package/gsd-core/references/response-language-directive.md +9 -0
- package/gsd-core/references/revision-loop.md +118 -11
- package/gsd-core/references/tdd.md +17 -9
- package/gsd-core/references/thinking-models-planning.md +18 -2
- package/gsd-core/references/verification-patterns.md +17 -4
- package/gsd-core/references/verifier-evidence-gate.md +160 -0
- package/gsd-core/references/worktree-path-safety.md +112 -2
- package/gsd-core/templates/README.md +7 -1
- package/gsd-core/templates/phase-prompt.md +4 -0
- 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/templates/verification-report.md +5 -0
- package/gsd-core/workflows/add-backlog.md +2 -0
- package/gsd-core/workflows/add-phase.md +2 -0
- package/gsd-core/workflows/add-tests.md +1 -1
- package/gsd-core/workflows/add-todo.md +4 -3
- package/gsd-core/workflows/ai-integration-phase.md +1 -1
- package/gsd-core/workflows/analyze-dependencies.md +2 -0
- package/gsd-core/workflows/audit-fix.md +2 -0
- package/gsd-core/workflows/audit-milestone.md +2 -0
- package/gsd-core/workflows/audit-uat.md +2 -0
- package/gsd-core/workflows/autonomous.md +15 -10
- package/gsd-core/workflows/check-todos.md +5 -3
- package/gsd-core/workflows/cleanup.md +4 -2
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +22 -13
- package/gsd-core/workflows/code-review-fix.md +5 -3
- package/gsd-core/workflows/code-review.md +211 -43
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
- package/gsd-core/workflows/complete-milestone.md +40 -254
- package/gsd-core/workflows/debug.md +1 -1
- package/gsd-core/workflows/diagnose-issues.md +5 -1
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/text.md +2 -0
- package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
- package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
- package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
- package/gsd-core/workflows/discuss-phase-power.md +2 -0
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +43 -13
- package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
- package/gsd-core/workflows/docs-update.md +15 -156
- package/gsd-core/workflows/edit-phase.md +2 -0
- package/gsd-core/workflows/eval-review.md +1 -1
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +20 -3
- package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +24 -3
- package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +8 -2
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -0
- package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
- package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
- package/gsd-core/workflows/execute-phase.md +78 -159
- package/gsd-core/workflows/execute-plan.md +28 -15
- package/gsd-core/workflows/explore.md +2 -0
- package/gsd-core/workflows/extract-learnings.md +2 -0
- package/gsd-core/workflows/fast.md +6 -0
- package/gsd-core/workflows/forensics.md +2 -0
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +1 -1
- package/gsd-core/workflows/help/modes/brief.md +2 -0
- package/gsd-core/workflows/help/modes/default.md +2 -0
- package/gsd-core/workflows/help/modes/full.compact.md +398 -0
- package/gsd-core/workflows/help/modes/full.md +12 -0
- package/gsd-core/workflows/help/modes/topic.md +2 -0
- package/gsd-core/workflows/help.md +3 -1
- package/gsd-core/workflows/import.md +3 -3
- package/gsd-core/workflows/inbox.md +1 -1
- package/gsd-core/workflows/ingest-docs.md +1 -1
- package/gsd-core/workflows/insert-phase.md +2 -0
- package/gsd-core/workflows/list-phase-assumptions.md +2 -0
- package/gsd-core/workflows/list-seeds.md +2 -0
- package/gsd-core/workflows/list-workspaces.md +2 -0
- package/gsd-core/workflows/manager.md +3 -3
- package/gsd-core/workflows/map-codebase.md +52 -3
- package/gsd-core/workflows/milestone-summary.md +2 -0
- package/gsd-core/workflows/mvp-phase.md +1 -1
- package/gsd-core/workflows/new-milestone.md +55 -13
- package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
- package/gsd-core/workflows/new-project.md +37 -205
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +2 -0
- package/gsd-core/workflows/node-repair.md +2 -0
- package/gsd-core/workflows/note.md +2 -0
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +19 -4
- package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -0
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +9 -0
- package/gsd-core/workflows/plan-phase.md +144 -185
- package/gsd-core/workflows/plan-review-convergence.md +102 -10
- package/gsd-core/workflows/plant-seed.md +1 -1
- package/gsd-core/workflows/pr-branch.md +30 -10
- package/gsd-core/workflows/profile-user.md +1 -1
- package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
- package/gsd-core/workflows/progress.md +25 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +37 -2
- package/gsd-core/workflows/quick/steps/research-phase.md +3 -3
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
- package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
- package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
- package/gsd-core/workflows/quick-batch.md +203 -0
- package/gsd-core/workflows/quick.md +21 -4
- package/gsd-core/workflows/reapply-patches.md +79 -3
- package/gsd-core/workflows/remove-phase.md +2 -0
- package/gsd-core/workflows/remove-workspace.md +1 -1
- package/gsd-core/workflows/resume-project.md +6 -2
- package/gsd-core/workflows/review.md +215 -10
- package/gsd-core/workflows/scan.md +2 -0
- package/gsd-core/workflows/section-manifest.json +12 -0
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/session-report.md +2 -0
- package/gsd-core/workflows/settings-advanced.md +2 -0
- package/gsd-core/workflows/settings-integrations.md +9 -8
- package/gsd-core/workflows/settings.md +19 -6
- package/gsd-core/workflows/ship.md +10 -10
- package/gsd-core/workflows/sketch-wrap-up.md +2 -0
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +1 -1
- package/gsd-core/workflows/spec-phase.md +24 -19
- package/gsd-core/workflows/spike-wrap-up.md +2 -0
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +2 -0
- package/gsd-core/workflows/sync-skills.md +12 -4
- package/gsd-core/workflows/thread.md +2 -0
- package/gsd-core/workflows/transition.md +2 -0
- package/gsd-core/workflows/ui-phase.md +26 -5
- package/gsd-core/workflows/ui-review.md +1 -1
- package/gsd-core/workflows/ultraplan-phase.md +2 -0
- package/gsd-core/workflows/undo.md +1 -1
- package/gsd-core/workflows/update.md +48 -43
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
- package/gsd-core/workflows/verify-work.md +68 -182
- package/hooks/dist/gsd-agent-isolation-guard.js +42 -16
- package/hooks/dist/gsd-check-update-worker.js +19 -2
- package/hooks/dist/gsd-context-monitor.js +371 -27
- package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
- package/hooks/dist/gsd-node-runner.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +30 -5
- package/hooks/dist/gsd-read-guard.js +2 -0
- package/hooks/dist/gsd-read-injection-scanner.js +5 -5
- package/hooks/dist/gsd-secret-read-guard.js +1105 -0
- package/hooks/dist/gsd-statusline.js +18 -10
- package/hooks/dist/gsd-validate-commit.sh +474 -7
- package/hooks/dist/gsd-workflow-guard.js +2 -1
- 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/git-cmd.js +210 -1
- package/hooks/dist/lib/injection-patterns.js +36 -6
- package/hooks/dist/lib/isolation-deny-reason.js +53 -1
- package/hooks/dist/lib/isolation-sentinel.js +58 -19
- package/hooks/dist/managed-hooks-registry.cjs +1 -0
- package/hooks/gsd-agent-isolation-guard.js +42 -16
- package/hooks/gsd-check-update-worker.js +19 -2
- package/hooks/gsd-context-monitor.js +371 -27
- package/hooks/gsd-cursor-subagent-start.js +34 -14
- package/hooks/gsd-node-runner.sh +1 -0
- package/hooks/gsd-prompt-guard.js +30 -5
- package/hooks/gsd-read-guard.js +2 -0
- package/hooks/gsd-read-injection-scanner.js +5 -5
- package/hooks/gsd-secret-read-guard.js +1105 -0
- package/hooks/gsd-statusline.js +18 -10
- package/hooks/gsd-validate-commit.sh +474 -7
- package/hooks/gsd-workflow-guard.js +2 -1
- package/hooks/gsd-worktree-path-guard.js +25 -14
- package/hooks/gsd-write-guard.js +46 -1
- package/hooks/hooks.json +6 -0
- package/hooks/lib/dispatch-identity.js +187 -0
- package/hooks/lib/filename-classification.js +64 -0
- package/hooks/lib/git-cmd.js +210 -1
- package/hooks/lib/injection-patterns.js +36 -6
- package/hooks/lib/isolation-deny-reason.js +53 -1
- package/hooks/lib/isolation-sentinel.js +58 -19
- package/hooks/managed-hooks-registry.cjs +1 -0
- package/package.json +13 -9
- package/scripts/benchmark-compact-content-variants.cjs +298 -0
- package/scripts/benchmark-compact-content.cjs +368 -0
- package/scripts/build-hooks.js +11 -4
- 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 +133 -20
- package/scripts/ci-timeout-report.cjs +1 -1
- package/scripts/diff-touches-shipped-paths.cjs +1 -1
- package/scripts/docs-guard-registry.cjs +17 -2
- package/scripts/gen-adr-index.cjs +8 -2
- package/scripts/gen-inventory-manifest.cjs +12 -0
- package/scripts/gen-loop-host-contract.cjs +67 -15
- 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/shellcheck-fetch.cjs +247 -0
- package/scripts/lib/suite-detection.cjs +32 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
- package/scripts/lint-allowed-tools-parity.cjs +221 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +24 -2
- package/scripts/lint-phase-enumeration-drift.cjs +24 -6
- package/scripts/lint-phase-id-drift.cjs +465 -15
- package/scripts/lint-portable-grep.cjs +176 -0
- package/scripts/lint-response-language-coverage.cjs +530 -0
- package/scripts/lint-source-test-name-collision.cjs +1 -1
- package/scripts/lint-test-file-count.allowlist.json +4 -1
- package/scripts/lint-vendored-deps.cjs +128 -17
- package/scripts/lint-workflow-shellcheck-baseline.json +1112 -0
- package/scripts/lint-workflow-shellcheck.cjs +614 -0
- package/scripts/npm-audit-baseline.cjs +376 -0
- package/scripts/prompt-injection-scan.sh +22 -0
- package/scripts/require-issue-link-policy.cjs +16 -1
- 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-execute-phase/SKILL.md +1 -1
- 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-ns-workflow/SKILL.md +1 -0
- package/skills/gsd-pause-work/SKILL.md +1 -0
- package/skills/gsd-phase/SKILL.md +2 -1
- package/skills/gsd-pr-branch/SKILL.md +1 -0
- package/skills/gsd-quick-batch/SKILL.md +105 -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-surface/SKILL.md +18 -8
- 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
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
* truth). Behaviour is preserved byte-for-behaviour from the prior hand-written
|
|
6
6
|
* .cjs; only types are added.
|
|
7
7
|
*
|
|
8
|
-
* Accepts
|
|
8
|
+
* Accepts numeric (D-42), alphanumeric (D-INFRA-01), and phase-prefixed
|
|
9
|
+
* (D4-01 — an optional digit-run between the leading letter and the hyphen,
|
|
10
|
+
* #4130) IDs.
|
|
9
11
|
* Returns {id, text, category, tags, trackable} per decision.
|
|
10
12
|
* CJS callers that only use {id, text} safely ignore the extra fields.
|
|
11
13
|
*
|
|
@@ -16,6 +18,11 @@
|
|
|
16
18
|
* - Outer bullet loop → seam's `iterateBullets` (for the header-fallback path)
|
|
17
19
|
*
|
|
18
20
|
* Resolves #1364 (markdown-header + em-dash recall) and #1365 (fail-loud gate).
|
|
21
|
+
*
|
|
22
|
+
* #4130 follow-up (hardening): the three bullet grammars below consume the
|
|
23
|
+
* decision ID atomically and narrow the em-dash first separator, eliminating
|
|
24
|
+
* the quadratic-backtracking cliff on pathological single bullets. Output is
|
|
25
|
+
* byte-identical on all legal inputs — see the notes at DECISION_ID_SOURCE.
|
|
19
26
|
*/
|
|
20
27
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
28
|
exports.extractDecisions = extractDecisions;
|
|
@@ -30,21 +37,84 @@ const DISCRETION_HEADINGS = new Set([
|
|
|
30
37
|
const NON_TRACKABLE_TAGS = new Set(['informational', 'folded', 'deferred']);
|
|
31
38
|
// ─── Bullet parsers (decisions-specific grammar) ─────────────────────────────
|
|
32
39
|
/**
|
|
33
|
-
*
|
|
40
|
+
* #4130: the ID grammar every extractor regex below shares, as ONE source.
|
|
41
|
+
* `D`, an OPTIONAL digit-run phase prefix, a hyphen, then the pre-existing
|
|
42
|
+
* alphanumeric tail — so `D-01` (bare), `D4-01`/`D12-01` (phase-prefixed,
|
|
43
|
+
* the reporter's multi-phase convention where bare D-01 collides across
|
|
44
|
+
* phases), and `D-INFRA-01` (alnum tail) are all the same grammar now.
|
|
45
|
+
* #2347 had already taught the shape DETECTOR to call `D4-01` decision-shaped
|
|
46
|
+
* while the EXTRACTOR still anchored on the literal `**D-` — the disagreement
|
|
47
|
+
* that made a whole phase-prefixed CONTEXT.md report could-not-parse. Deriving
|
|
48
|
+
* the three grammars (and the token evidence below) from this one constant is
|
|
49
|
+
* the parity pin: the extractor's ID universe cannot drift from the declared
|
|
50
|
+
* grammar again without editing this line, which the #4130 property tests
|
|
51
|
+
* watch from the other side.
|
|
52
|
+
*/
|
|
53
|
+
const DECISION_ID_SOURCE = 'D[0-9]*-[A-Za-z0-9][A-Za-z0-9_-]*';
|
|
54
|
+
/**
|
|
55
|
+
* #4130 follow-up (hardening): how the three grammars below CONSUME the ID —
|
|
56
|
+
* atomically, via the `(?=(X))\1` lookahead emulation (lookarounds are atomic
|
|
57
|
+
* in ECMAScript; the backreference must replay exactly what the lookahead
|
|
58
|
+
* captured, so the engine can never give the ID tail back one character at a
|
|
59
|
+
* time). That give-back was quadratic driver #1: the tail class
|
|
60
|
+
* `[A-Za-z0-9_-]*` overlaps the pre-separator class `[^:*]*` (every id char
|
|
61
|
+
* is also `[^:*]`), so on a FAILING bullet the base regex re-split the tail
|
|
62
|
+
* O(n) times with an O(n) scan after each — measured ~1.1s @ 40k chars on
|
|
63
|
+
* `- **D-` + `a-`×20k (the #4357 review's deferred cliff).
|
|
64
|
+
*
|
|
65
|
+
* Byte-identical on all legal inputs: a successful match always consumes the
|
|
66
|
+
* MAXIMAL id run (the lookahead's own match is exactly that maximal run), and
|
|
67
|
+
* the continuation's success depends only on the position of the first
|
|
68
|
+
* `:`/`*` (or `*` for the em-dash form) after the id boundary — id chars
|
|
69
|
+
* contain neither, so moving the boundary inside the run cannot change
|
|
70
|
+
* success or any capture. Group 1 stays the full id (the lookahead's capture
|
|
71
|
+
* IS group 1), so handlers keep reading match[1]/[2]/[3] untouched. Pinned by
|
|
72
|
+
* the differential property test against a frozen copy of the pre-hardening
|
|
73
|
+
* grammars and by the regex-lattice test in tests/decisions.test.cjs.
|
|
74
|
+
*/
|
|
75
|
+
/**
|
|
76
|
+
* #4130: the bold lead-in that ATTEMPTS the ID grammar above — used by the
|
|
77
|
+
* parse-miss guard and the #3939 join regexes, where recognising MORE shapes
|
|
78
|
+
* is the conservative direction (an over-broad match can only make a
|
|
79
|
+
* malformed bullet fail loud). The prefix run is either empty (bare `D-`) or
|
|
80
|
+
* DIGIT-INITIAL (`4`, `4x` — a phase prefix with a typo still counts as an
|
|
81
|
+
* attempted ID, so `D4x-01` reaches the guard and fails loud instead of
|
|
82
|
+
* vanishing), but never letter-initial: `D` + letters + `-` (`Deferred-until`)
|
|
83
|
+
* is a prose word, and prose must stay `none-present` (#2347's law).
|
|
84
|
+
*/
|
|
85
|
+
const ID_ATTEMPT_SOURCE = 'D(?:[0-9][A-Za-z0-9]*)?-';
|
|
86
|
+
/**
|
|
87
|
+
* Colon form: `- **D[phase]-NN[ [tags]]:** text`
|
|
34
88
|
* (#1343: `[^:*]*` subsumes any pre-colon prose, stops at `:**`)
|
|
89
|
+
* Group 1 captures the FULL id including any phase prefix (#4130).
|
|
90
|
+
* The ID is consumed atomically `(?=(…))\1` — see the hardening note above
|
|
91
|
+
* the constants (#4130 follow-up); with the tail unable to give back, the
|
|
92
|
+
* remaining `[^:*]*:` scan has a single viable split and the whole match is
|
|
93
|
+
* linear in line length.
|
|
35
94
|
*/
|
|
36
|
-
const bulletColonRe =
|
|
95
|
+
const bulletColonRe = new RegExp(`^\\s*-\\s+\\*\\*(?=(${DECISION_ID_SOURCE}))\\1(?:\\s*\\[([^\\]]+)\\])?[^:*]*:\\*\\*\\s*(.*)$`);
|
|
37
96
|
/**
|
|
38
|
-
* Em-dash form: `- **D-NN[ [tags]] — title** body`
|
|
97
|
+
* Em-dash form: `- **D[phase]-NN[ [tags]] — title** body`
|
|
39
98
|
* The em-dash (U+2014) or its lookalike separates the ID+tags group from a title
|
|
40
99
|
* that lives inside the bold markers; the body (which may be empty) follows
|
|
41
100
|
* outside the closing `**`. This form was not handled pre-T1 (bug #1364).
|
|
42
101
|
*
|
|
43
102
|
* Accepts both U+2014 em-dash (—) and U+2013 en-dash (–) for robustness.
|
|
103
|
+
*
|
|
104
|
+
* #4130 follow-up (hardening), quadratic driver #2: the first separator was
|
|
105
|
+
* `[^*]*[—–]`, whose leading class ALSO accepts the dash — on a failing
|
|
106
|
+
* dash-laden title the engine retried the separator at every dash position
|
|
107
|
+
* with an O(n) scan after each (~1.7s @ 40k). Narrowed to `[^*—–]*[—–]`:
|
|
108
|
+
* the leading class now excludes the dash, so the separator is the FIRST
|
|
109
|
+
* dash — one viable split, single pass. Behavior-preserving because every
|
|
110
|
+
* candidate dash lies before the first `*` (the leading class cannot cross
|
|
111
|
+
* a star), so the trailing `[^*]*` reaches that same first star from any
|
|
112
|
+
* candidate and `**` succeeds or fails identically; no capture involves the
|
|
113
|
+
* dash position. The ID is atomic like the other forms (driver #1).
|
|
44
114
|
*/
|
|
45
|
-
const bulletEmDashRe =
|
|
115
|
+
const bulletEmDashRe = new RegExp(`^\\s*-\\s+\\*\\*(?=(${DECISION_ID_SOURCE}))\\1(?:\\s*\\[([^\\]]+)\\])?[^*—–]*[—–][^*]*\\*\\*\\s*(.*)$`);
|
|
46
116
|
/**
|
|
47
|
-
* Titled-colon form: `- **D-NN[ [tags]]: Title.** body`
|
|
117
|
+
* Titled-colon form: `- **D[phase]-NN[ [tags]]: Title.** body`
|
|
48
118
|
* A title sits between the colon and the closing `**` (so the `:**` anchor of
|
|
49
119
|
* bulletColonRe fails, and there is no em-dash for bulletEmDashRe). This is a strict
|
|
50
120
|
* superset of the colon-immediate form, so it MUST be checked AFTER bulletColonRe and
|
|
@@ -53,16 +123,34 @@ const bulletEmDashRe = /^\s*-\s+\*\*D-([A-Za-z0-9][A-Za-z0-9_-]*)(?:\s*\[([^\]]+
|
|
|
53
123
|
* (e.g. `D-07 ratio 3:1:**`) still fails the anchor and falls through to the parse-miss
|
|
54
124
|
* guard — matching bulletColonRe's `[^:*]*` discipline that the separator colon is the
|
|
55
125
|
* only colon permitted before `**`. (#1639)
|
|
126
|
+
*
|
|
127
|
+
* The ID is consumed atomically `(?=(…))\1` like the other forms — the
|
|
128
|
+
* hardening note above the constants explains why (#4130 follow-up).
|
|
129
|
+
*/
|
|
130
|
+
const bulletTitledColonRe = new RegExp(`^\\s*-\\s+\\*\\*(?=(${DECISION_ID_SOURCE}))\\1(?:\\s*\\[([^\\]]+)\\])?[^:*]*:[^:*]*\\*\\*\\s*(.*)$`);
|
|
131
|
+
/**
|
|
132
|
+
* #4130: the parse-miss guard's probe — a line whose bold lead-in ATTEMPTS the
|
|
133
|
+
* ID grammar (see `ID_ATTEMPT_SOURCE`) but failed all three bullet patterns
|
|
134
|
+
* above. Bare `D-` attempts behave exactly as before #4130; a digit-initial
|
|
135
|
+
* prefix run (`D4-`… including a typo'd `D4x-`) is new evidence of an attempt,
|
|
136
|
+
* so the malformed-prefixed bullet fails loud instead of silently vanishing.
|
|
137
|
+
*/
|
|
138
|
+
const parseMissGuardRe = new RegExp(`^\\s*-\\s+\\*\\*${ID_ATTEMPT_SOURCE}`);
|
|
139
|
+
/**
|
|
140
|
+
* #4130: bare-token evidence of decision-shaped content — a `D…-<alnum>` token
|
|
141
|
+
* in running text. `D-01` matched before; the digit-run phase prefix (`D4-01`)
|
|
142
|
+
* is added so token evidence agrees with the extractor's ID grammar
|
|
143
|
+
* (DECISION_ID_SOURCE) instead of silently ignoring prefixed mentions.
|
|
56
144
|
*/
|
|
57
|
-
const
|
|
145
|
+
const decisionTokenRe = new RegExp(`\\bD[0-9]*-[A-Za-z0-9]`, 'm');
|
|
58
146
|
/**
|
|
59
147
|
* #2347: format-agnostic evidence that a block/section holds real decision
|
|
60
148
|
* ENTRIES the parser could not read — a bullet whose bold lead-in is an
|
|
61
149
|
* ID-SHAPED token (uppercase prefix, optional digits, hyphen, alnum), whatever
|
|
62
|
-
* the exact ID grammar.
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
150
|
+
* the exact ID grammar. #1365's fail-loud guard originally reused the parser's
|
|
151
|
+
* own `\bD-` test as its "is this decision-shaped?" evidence, so any prefix the
|
|
152
|
+
* parser could not read (e.g. `D5-01` then, `DEC-01` now) was invisible to
|
|
153
|
+
* BOTH parser and guard, collapsing `could-not-parse` into a clean
|
|
66
154
|
* `none-present` pass.
|
|
67
155
|
*
|
|
68
156
|
* The ID-shape requirement (not "any bold bullet") is deliberate: a decisions
|
|
@@ -70,11 +158,231 @@ const bulletTitledColonRe = /^\s*-\s+\*\*D-([A-Za-z0-9][A-Za-z0-9_-]*)(?:\s*\[([
|
|
|
70
158
|
* bullets with bold labels (`- **Scope:** …`, `- **Why:** …`, `- **Note:** …`).
|
|
71
159
|
* Those are NOT decision entries and must stay `none-present` — a false
|
|
72
160
|
* `could-not-parse` hard-blocks the plan gate. `[A-Z]+[0-9]*-[A-Za-z0-9]` matches
|
|
73
|
-
* `D-01` / `
|
|
161
|
+
* `D-01` / `D4-01` / `DEC-01` but not `Scope:` / `Why:` / `Follow-up:` (mixed
|
|
74
162
|
* case) / `TODO:` (no `-<alnum>` id) — mirroring the parser's own `D-<alnum>`
|
|
75
163
|
* shape without hardcoding the `D`.
|
|
164
|
+
*
|
|
165
|
+
* #4130 parity note: for the D-prefixed universe this detector's grammar
|
|
166
|
+
* (`D` + digit-run + `-` + alnum) is exactly `DECISION_ID_SOURCE` above, so a
|
|
167
|
+
* well-formed bullet the detector calls decision-shaped is now always one the
|
|
168
|
+
* extractor can read. The detector stays WIDER on purpose (`DEC-01` is still
|
|
169
|
+
* evidence): an ID grammar outside the parser's universe must keep failing
|
|
170
|
+
* loud, never silently passing. The #4130 property tests pin both directions.
|
|
76
171
|
*/
|
|
77
172
|
const boldLeadInBulletRe = /^\s*-\s+\*\*[A-Z]+[0-9]*-[A-Za-z0-9]/m;
|
|
173
|
+
/**
|
|
174
|
+
* #3939: a decision bullet's DECLARATION line — the `- **D[phase]-NN … **` bold
|
|
175
|
+
* lead-in the three grammars above anchor on — may wrap across a line break.
|
|
176
|
+
* Physical line breaks inside a bullet are markdown-insignificant, and GSD's own
|
|
177
|
+
* discuss-phase writer emits the wrapped shape whenever a decision title runs
|
|
178
|
+
* past the wrap column. All three grammars require the closing `**` in the same
|
|
179
|
+
* string as the `- **D…-` anchor, so a wrapped declaration matched none of them
|
|
180
|
+
* and fell to the #1365 parse-miss guard, forcing `could-not-parse` (which
|
|
181
|
+
* hard-blocks `check.decision-coverage-plan`) on a well-formed CONTEXT.md.
|
|
182
|
+
*
|
|
183
|
+
* The repair is confined to how the LOGICAL bullet is assembled — the grammars
|
|
184
|
+
* themselves are untouched, so every single-line form parses exactly as before.
|
|
185
|
+
* #4130: the anchor uses `ID_ATTEMPT_SOURCE` (digit-run phase prefixes join
|
|
186
|
+
* like bare ones; recognising more start shapes only reassembles the logical
|
|
187
|
+
* bullet, which then parses or fails loud as itself).
|
|
188
|
+
*/
|
|
189
|
+
const decisionBulletStartRe = new RegExp(`^\\s*-\\s+\\*\\*${ID_ATTEMPT_SOURCE}`);
|
|
190
|
+
/**
|
|
191
|
+
* A line that opens a new BLOCK-LEVEL construct, and therefore terminates the
|
|
192
|
+
* bullet above it: a list marker of any family (`-`, `*`, `+`, `1.`, `1)`), an
|
|
193
|
+
* ATX heading, a blockquote, or a table row. Joining never reaches across one of
|
|
194
|
+
* these (nor across a blank/whitespace-only line, checked separately), so a
|
|
195
|
+
* declaration whose bold run genuinely never closes cannot absorb the block
|
|
196
|
+
* below it and get "closed" by an unrelated inline `**` — it stays a parse-miss
|
|
197
|
+
* and still fails loud, which #1365's contract requires.
|
|
198
|
+
*
|
|
199
|
+
* The four MARKER families demand trailing whitespace so that a continuation
|
|
200
|
+
* line opening with emphasis (`*in* the header.** …`) is text, not a bullet.
|
|
201
|
+
* The table-row alternative deliberately does not: CommonMark tables may open
|
|
202
|
+
* flush (`|Col1|Col2|`), and a leading `|` is never ordinary decision prose.
|
|
203
|
+
* A `- ` line at ANY indent stops the join: a deeper one is #3169 nested
|
|
204
|
+
* elaboration, which the main loop folds into the open decision itself.
|
|
205
|
+
*
|
|
206
|
+
* DELIBERATE DIVERGENCE from the sectionizer seam (ADR-1372): `iterateBullets`
|
|
207
|
+
* recognises only the `N. ` ordered-list form (`numberedRe`,
|
|
208
|
+
* src/markdown-sectionizer.cts), while this set also stops at the `N) ` form.
|
|
209
|
+
* That is intentional and one-directional — this regex answers "may the join
|
|
210
|
+
* cross this line?", where recognising MORE block openers is the conservative
|
|
211
|
+
* answer (a missed terminator can manufacture a decision; a spare one can only
|
|
212
|
+
* make a malformed bullet fail loud, which #1365 already wants). `N)` is a
|
|
213
|
+
* CommonMark ordered-list marker, so a join must not reach across it whether or
|
|
214
|
+
* not the seam's own bullet iterator yields it. Both forms are pinned by tests,
|
|
215
|
+
* and a drift test asserts the seam still does NOT treat `N)` as a bullet, so
|
|
216
|
+
* this divergence stays visible if either side moves.
|
|
217
|
+
*
|
|
218
|
+
* Accepted over-termination: continuation prose that happens to open with
|
|
219
|
+
* digits-then-`.`/`)` ("10. really keeps going") or a literal `|` stops the join
|
|
220
|
+
* early, so such a bullet fails loud rather than parsing. That is the same
|
|
221
|
+
* markdown ambiguity every line-oriented reader carries, and this direction of
|
|
222
|
+
* the trade is the one #1365 asks for — fail loud, never guess.
|
|
223
|
+
*/
|
|
224
|
+
const blockConstructRe = /^(?:[-*+]\s|\d+[.)]\s|#{1,6}\s|>\s|\|)/;
|
|
225
|
+
/**
|
|
226
|
+
* The id-adjacent `[tags]` REGION of a logical bullet, as far as it has been
|
|
227
|
+
* assembled. Matching means the region is still unsettled, in one of two ways:
|
|
228
|
+
* capture group 1 is present when the bracket is open (group 1 is the content
|
|
229
|
+
* seen so far), and absent when the id has been read but no `[` has followed
|
|
230
|
+
* yet — so a bracket may still open on the next absorbed line.
|
|
231
|
+
*
|
|
232
|
+
* A NON-match means the region is settled for good: the bracket closed, or
|
|
233
|
+
* something other than `[` followed the id. Either way the join no longer has
|
|
234
|
+
* to watch for a splice.
|
|
235
|
+
*
|
|
236
|
+
* The id character class is deliberately looser than the grammars' (it admits
|
|
237
|
+
* an empty id, so a bare `- **D-` still counts as unsettled, and — #4130 — a
|
|
238
|
+
* digit-run phase prefix between the `D` and the first hyphen). This regex only
|
|
239
|
+
* answers "may an id-adjacent bracket still open here?", where recognising MORE
|
|
240
|
+
* shapes is the conservative direction: an over-broad match can only make a
|
|
241
|
+
* malformed bullet fail loud, while a missed one silently re-classifies.
|
|
242
|
+
*
|
|
243
|
+
* Only the ID-ADJACENT bracket matters: that is the one the three grammars turn
|
|
244
|
+
* into `tags` (and therefore into `trackable`). A `[` further along the title is
|
|
245
|
+
* ordinary text and does not restrict the join.
|
|
246
|
+
*/
|
|
247
|
+
const tagRegionRe = new RegExp(`^\\s*-\\s+\\*\\*D[0-9]*-[A-Za-z0-9_-]*\\s*(?:\\[([^\\]]*))?$`);
|
|
248
|
+
/**
|
|
249
|
+
* #3939 (review): would folding `next` onto a lead-in whose `[tags]` bracket is
|
|
250
|
+
* still open splice the inserted space INTO a tag token?
|
|
251
|
+
*
|
|
252
|
+
* Tags are comma-split and trimmed, so a space landing next to a delimiter
|
|
253
|
+
* (`[`, `,`, `]`) changes nothing — `[informational,` + `deferred]` is still
|
|
254
|
+
* exactly `[informational, deferred]`. A space landing anywhere else splits one
|
|
255
|
+
* token into two (`[defer` + `red]` → `defer red`), which would not fail; it
|
|
256
|
+
* would parse to a DIFFERENT tag, silently flipping `trackable` on a gate that
|
|
257
|
+
* decides whether a decision must be covered. Refusing to join there leaves the
|
|
258
|
+
* bullet unchanged, so it reaches the #1365 parse-miss guard and fails loud —
|
|
259
|
+
* a wrong answer about coverage is worse than a blocked gate.
|
|
260
|
+
*
|
|
261
|
+
* `tail` is the bracket content accumulated so far, `next` the trimmed
|
|
262
|
+
* continuation line.
|
|
263
|
+
*/
|
|
264
|
+
function wouldSpliceTagToken(tail, next) {
|
|
265
|
+
const before = tail.trimEnd();
|
|
266
|
+
if (before === '' || before.endsWith(',') || before.endsWith('['))
|
|
267
|
+
return false;
|
|
268
|
+
return !(next.startsWith(',') || next.startsWith(']'));
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* True when the bullet's own bold lead-in — the FIRST bold run on the line —
|
|
272
|
+
* is still open at end-of-line. Deliberately asks only about that first run
|
|
273
|
+
* (not `**`-parity over the whole string), because that is the run the three
|
|
274
|
+
* grammars anchor on: a balanced inline `**bold**` later in the body must not
|
|
275
|
+
* make a terminated lead-in look open.
|
|
276
|
+
*/
|
|
277
|
+
function boldLeadInIsUnterminated(text) {
|
|
278
|
+
const open = text.indexOf('**');
|
|
279
|
+
if (open === -1)
|
|
280
|
+
return false;
|
|
281
|
+
return text.indexOf('**', open + 2) === -1;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Fold a decision bullet whose bold lead-in wraps into ONE logical line, so the
|
|
285
|
+
* declaration grammars see the whole lead-in (#3939).
|
|
286
|
+
*
|
|
287
|
+
* Bounded and fail-loud-preserving: a wrapped declaration absorbs following
|
|
288
|
+
* lines only until its lead-in closes, and a blank/whitespace-only line, a new
|
|
289
|
+
* block-level construct (`blockConstructRe`), or the end of the block stops it.
|
|
290
|
+
* If the lead-in never closes, the original line is emitted UNCHANGED — a
|
|
291
|
+
* genuinely malformed bullet (e.g. an unterminated bold run) still reaches the
|
|
292
|
+
* parse-miss guard and still fails loud, exactly as #1365 requires. Non-decision
|
|
293
|
+
* lines pass through untouched, so continuation lines (#1372 FIX) and nested
|
|
294
|
+
* cross-reference bullets (#3169) are handled by the main loop as before.
|
|
295
|
+
*
|
|
296
|
+
* The joined line keeps the FIRST physical line's leading whitespace, so the
|
|
297
|
+
* `indentWidth` signal #3169 depends on is unchanged. Absorbed lines are
|
|
298
|
+
* trimmed and re-joined with a single space, which is what a soft line break
|
|
299
|
+
* means in markdown — so the join reproduces the rendered one-line text rather
|
|
300
|
+
* than concatenating the raw bytes.
|
|
301
|
+
*
|
|
302
|
+
* One place that equivalence does not hold is inside the id-adjacent `[tags]`
|
|
303
|
+
* bracket, where an inserted space can split a tag token and silently flip
|
|
304
|
+
* `trackable`. The join stops there instead (`wouldSpliceTagToken`), leaving the
|
|
305
|
+
* bullet to fail loud.
|
|
306
|
+
*
|
|
307
|
+
* Absorption stops at the first `**` on a continuation line, so an inline
|
|
308
|
+
* `**bold**` INSIDE a wrapped title closes the run early. That is deliberate:
|
|
309
|
+
* the result is byte-identical to what the same bullet written on one physical
|
|
310
|
+
* line parses to (the text past the early close re-attaches through the main
|
|
311
|
+
* loop's continuation folding), which is the whole contract here — wrapping is
|
|
312
|
+
* markdown-insignificant, never a second grammar.
|
|
313
|
+
*/
|
|
314
|
+
function joinWrappedBoldLeadIns(lines) {
|
|
315
|
+
const joined = [];
|
|
316
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
317
|
+
const line = lines[i];
|
|
318
|
+
if (!decisionBulletStartRe.test(line) || !boldLeadInIsUnterminated(line)) {
|
|
319
|
+
joined.push(line);
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
// Absorbed lines accumulate as SEGMENTS joined by a single space, and each
|
|
323
|
+
// new segment is searched on its own: the lead-in is known to be open at the
|
|
324
|
+
// end of the declaration line, and the inserted space means a closing `**`
|
|
325
|
+
// can never straddle a segment boundary, so the first `**` in any later
|
|
326
|
+
// segment is the close. Scanning per segment (rather than re-searching the
|
|
327
|
+
// accumulated string, which forces a rope flatten every iteration) keeps a
|
|
328
|
+
// long unterminated run linear on the plan gate's hot path.
|
|
329
|
+
const segments = [line];
|
|
330
|
+
// Bracket content accumulated while the id-adjacent `[tags]` bracket is
|
|
331
|
+
// still open; null when it is not open. O(1) per segment.
|
|
332
|
+
let tagTail = null;
|
|
333
|
+
// The logical text assembled so far, kept ONLY while the id-adjacent
|
|
334
|
+
// bracket has yet to open, so a bracket that opens on ANY absorbed line
|
|
335
|
+
// arms the splice guard — not just one that opens on the declaration line.
|
|
336
|
+
// Null once the region settles (the bracket opened and `tagTail` took over,
|
|
337
|
+
// or the id was followed by something else), so this never re-walks a long
|
|
338
|
+
// absorption: a non-empty segment that is not a bracket-open settles the
|
|
339
|
+
// region immediately, which bounds the string to a single extra join.
|
|
340
|
+
let tagRegion = null;
|
|
341
|
+
const declRegion = tagRegionRe.exec(line);
|
|
342
|
+
if (declRegion !== null) {
|
|
343
|
+
if (declRegion[1] === undefined)
|
|
344
|
+
tagRegion = line;
|
|
345
|
+
else
|
|
346
|
+
tagTail = declRegion[1];
|
|
347
|
+
}
|
|
348
|
+
let scan = i + 1;
|
|
349
|
+
let closed = false;
|
|
350
|
+
while (scan < lines.length) {
|
|
351
|
+
const trimmed = lines[scan].trim();
|
|
352
|
+
if (trimmed === '' || blockConstructRe.test(trimmed))
|
|
353
|
+
break;
|
|
354
|
+
if (tagTail !== null && wouldSpliceTagToken(tagTail, trimmed))
|
|
355
|
+
break;
|
|
356
|
+
segments.push(trimmed);
|
|
357
|
+
scan += 1;
|
|
358
|
+
if (tagTail !== null) {
|
|
359
|
+
tagTail = trimmed.indexOf(']') === -1 ? trimmed : null;
|
|
360
|
+
}
|
|
361
|
+
else if (tagRegion !== null) {
|
|
362
|
+
tagRegion = `${tagRegion} ${trimmed}`;
|
|
363
|
+
const opened = tagRegionRe.exec(tagRegion);
|
|
364
|
+
if (opened === null)
|
|
365
|
+
tagRegion = null;
|
|
366
|
+
else if (opened[1] !== undefined) {
|
|
367
|
+
tagTail = opened[1];
|
|
368
|
+
tagRegion = null;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if (trimmed.indexOf('**') !== -1) {
|
|
372
|
+
closed = true;
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
if (closed) {
|
|
377
|
+
joined.push(segments.join(' '));
|
|
378
|
+
i = scan - 1;
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
joined.push(line);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return joined;
|
|
385
|
+
}
|
|
78
386
|
/**
|
|
79
387
|
* Parse decision lines from a block of text (the inner text of a <decisions>
|
|
80
388
|
* or markdown-header section body). Returns the extracted decisions and a count
|
|
@@ -83,9 +391,12 @@ const boldLeadInBulletRe = /^\s*-\s+\*\*[A-Z]+[0-9]*-[A-Za-z0-9]/m;
|
|
|
83
391
|
* FIX B (#1365): parseMisses > 0 means the caller must treat the result as
|
|
84
392
|
* could-not-parse even when some decisions were extracted — a silent drop is
|
|
85
393
|
* worse than a fail-loud signal.
|
|
394
|
+
*
|
|
395
|
+
* #3939: physical lines are folded into logical bullets first, so a declaration
|
|
396
|
+
* whose bold lead-in wraps is matched as the one bullet it is.
|
|
86
397
|
*/
|
|
87
398
|
function parseDecisionLines(block) {
|
|
88
|
-
const lines = block.split(/\r?\n/);
|
|
399
|
+
const lines = joinWrappedBoldLeadIns(block.split(/\r?\n/));
|
|
89
400
|
const out = [];
|
|
90
401
|
let category = '';
|
|
91
402
|
let inDiscretion = false;
|
|
@@ -135,11 +446,11 @@ function parseDecisionLines(block) {
|
|
|
135
446
|
current.text += ' ' + trimmed;
|
|
136
447
|
continue;
|
|
137
448
|
}
|
|
138
|
-
// Colon form: `- **D-NN[ [tags]]:** text`
|
|
449
|
+
// Colon form: `- **D[phase]-NN[ [tags]]:** text`
|
|
139
450
|
const colonMatch = line.match(bulletColonRe);
|
|
140
451
|
if (colonMatch) {
|
|
141
452
|
flush();
|
|
142
|
-
const id =
|
|
453
|
+
const id = colonMatch[1];
|
|
143
454
|
const tags = colonMatch[2]
|
|
144
455
|
? colonMatch[2].split(',').map((t) => t.trim().toLowerCase()).filter(Boolean)
|
|
145
456
|
: [];
|
|
@@ -148,11 +459,11 @@ function parseDecisionLines(block) {
|
|
|
148
459
|
openIndent = (0, token_scanner_cjs_1.indentWidth)(line);
|
|
149
460
|
continue;
|
|
150
461
|
}
|
|
151
|
-
// Em-dash form: `- **D-NN[ [tags]] — title** body`
|
|
462
|
+
// Em-dash form: `- **D[phase]-NN[ [tags]] — title** body`
|
|
152
463
|
const emDashMatch = line.match(bulletEmDashRe);
|
|
153
464
|
if (emDashMatch) {
|
|
154
465
|
flush();
|
|
155
|
-
const id =
|
|
466
|
+
const id = emDashMatch[1];
|
|
156
467
|
const tags = emDashMatch[2]
|
|
157
468
|
? emDashMatch[2].split(',').map((t) => t.trim().toLowerCase()).filter(Boolean)
|
|
158
469
|
: [];
|
|
@@ -164,14 +475,14 @@ function parseDecisionLines(block) {
|
|
|
164
475
|
openIndent = (0, token_scanner_cjs_1.indentWidth)(line);
|
|
165
476
|
continue;
|
|
166
477
|
}
|
|
167
|
-
// Titled-colon form: `- **D-NN[ [tags]]: Title.** body` (#1639). Checked LAST — it is
|
|
478
|
+
// Titled-colon form: `- **D[phase]-NN[ [tags]]: Title.** body` (#1639). Checked LAST — it is
|
|
168
479
|
// a strict superset of bulletColonRe, so it only catches bullets the colon-immediate
|
|
169
480
|
// and em-dash forms missed (minimal blast radius). id + [tags] trackability honored;
|
|
170
481
|
// the body after the closing bold run is reported as text.
|
|
171
482
|
const titledColonMatch = line.match(bulletTitledColonRe);
|
|
172
483
|
if (titledColonMatch) {
|
|
173
484
|
flush();
|
|
174
|
-
const id =
|
|
485
|
+
const id = titledColonMatch[1];
|
|
175
486
|
const tags = titledColonMatch[2]
|
|
176
487
|
? titledColonMatch[2].split(',').map((t) => t.trim().toLowerCase()).filter(Boolean)
|
|
177
488
|
: [];
|
|
@@ -180,10 +491,14 @@ function parseDecisionLines(block) {
|
|
|
180
491
|
openIndent = (0, token_scanner_cjs_1.indentWidth)(line);
|
|
181
492
|
continue;
|
|
182
493
|
}
|
|
183
|
-
// Parse-miss guard (FIX B + #1343): a line
|
|
184
|
-
//
|
|
494
|
+
// Parse-miss guard (FIX B + #1343, grammar widened #4130): a line whose bold
|
|
495
|
+
// lead-in ATTEMPTS the ID grammar but failed all three patterns — flush,
|
|
496
|
+
// warn, and record the miss. `ID_ATTEMPT_SOURCE` accepts the bare `D-` form
|
|
497
|
+
// (as before) plus a digit-initial prefix run, so a typo'd phase prefix
|
|
498
|
+
// (`D4x-01`) fails loud instead of silently vanishing, while a letter-initial
|
|
499
|
+
// run (`Deferred-until`) stays prose and stays invisible.
|
|
185
500
|
// parseMisses > 0 forces could-not-parse even when other decisions parsed.
|
|
186
|
-
if (
|
|
501
|
+
if (parseMissGuardRe.test(line)) {
|
|
187
502
|
flush();
|
|
188
503
|
parseMisses += 1;
|
|
189
504
|
console.warn(`parseDecisions: ignored unparseable decision bullet: ${trimmed}`);
|
|
@@ -237,10 +552,10 @@ function extractDecisions(content) {
|
|
|
237
552
|
// FIX A: Block present but 0 extracted and no parse-misses.
|
|
238
553
|
// Only report could-not-parse when there is genuine evidence of real decisions
|
|
239
554
|
// that failed to parse: a bold-lead-in bullet (`- **…**`, any ID grammar — #2347),
|
|
240
|
-
// a
|
|
555
|
+
// a bare `D[phase]-<alnum>` token (#4130) in the block text, or an unterminated fence. An empty scaffold
|
|
241
556
|
// (<decisions></decisions>) or an all-prose block has no such evidence — treat
|
|
242
557
|
// as none-present so the gate passes cleanly.
|
|
243
|
-
const hasDecisionTokenInBlock =
|
|
558
|
+
const hasDecisionTokenInBlock = decisionTokenRe.test(combined);
|
|
244
559
|
const hasBoldLeadInBullet = boldLeadInBulletRe.test(combined);
|
|
245
560
|
if (hasDecisionTokenInBlock || hasBoldLeadInBullet || unterminatedFence) {
|
|
246
561
|
return { decisions: [], outcome: 'could-not-parse' };
|
|
@@ -263,10 +578,10 @@ function extractDecisions(content) {
|
|
|
263
578
|
}
|
|
264
579
|
// FIX A: Heading found but 0 extracted and no parse-misses.
|
|
265
580
|
// Report could-not-parse when the section body holds a decision-entry-shaped
|
|
266
|
-
// bold-lead-in bullet (`- **…**`, any ID grammar — #2347) or a D
|
|
581
|
+
// bold-lead-in bullet (`- **…**`, any ID grammar — #2347) or a `D[phase]-<alnum>` token (#4130). A
|
|
267
582
|
// heading with only prose, sub-headings, or all-discretion content (no such
|
|
268
583
|
// evidence) is a legitimate empty/discretion section → none-present.
|
|
269
|
-
const hasDecisionTokenInSection =
|
|
584
|
+
const hasDecisionTokenInSection = decisionTokenRe.test(section.body);
|
|
270
585
|
const hasBoldLeadInBulletInSection = boldLeadInBulletRe.test(section.body);
|
|
271
586
|
if (hasDecisionTokenInSection || hasBoldLeadInBulletInSection) {
|
|
272
587
|
return { decisions: [], outcome: 'could-not-parse' };
|
|
@@ -275,8 +590,8 @@ function extractDecisions(content) {
|
|
|
275
590
|
}
|
|
276
591
|
// ── Path 3: no blocks, no heading ────────────────────────────────────────────
|
|
277
592
|
// Apply shape heuristics to distinguish none-present from could-not-parse.
|
|
278
|
-
// We re-use the already-computed unterminatedFence and check for
|
|
279
|
-
const hasDecisionToken =
|
|
593
|
+
// We re-use the already-computed unterminatedFence and check for decision tokens.
|
|
594
|
+
const hasDecisionToken = decisionTokenRe.test(stripped);
|
|
280
595
|
if (unterminatedFence || hasDecisionToken) {
|
|
281
596
|
return { decisions: [], outcome: 'could-not-parse' };
|
|
282
597
|
}
|
|
@@ -110,6 +110,15 @@ function validateRequirement(requirement) {
|
|
|
110
110
|
if (r.shapes == null && !(typeof r.text === 'string' && r.text.trim())) {
|
|
111
111
|
throw new Error(`requirement ${requirement.id} text must be a non-empty string when no shapes override is provided`);
|
|
112
112
|
}
|
|
113
|
+
// text_en (#3717) is optional, but when present it must be a non-empty string. An empty
|
|
114
|
+
// string is NOT caught by `??` (only null/undefined are), so an unvalidated `text_en: ''`
|
|
115
|
+
// would silently win `text_en ?? text` and classify against '' — the same fail-open shape
|
|
116
|
+
// #1110/#2773 already exist to eliminate, just moved one field over. Validated
|
|
117
|
+
// unconditionally (not gated on whether `shapes` will make it unused) so bad data fails
|
|
118
|
+
// closed even when it happens to be dead for this particular call.
|
|
119
|
+
if (r.text_en != null && !(typeof r.text_en === 'string' && r.text_en.trim())) {
|
|
120
|
+
throw new Error(`requirement ${requirement.id} text_en must be a non-empty string when present`);
|
|
121
|
+
}
|
|
113
122
|
}
|
|
114
123
|
/** Validate an edge resolution against the edge verification vocabulary. */
|
|
115
124
|
function validateResolution(resolution) {
|
|
@@ -135,7 +144,11 @@ function proposeEdges(requirement) {
|
|
|
135
144
|
shapes = requirement.shapes;
|
|
136
145
|
}
|
|
137
146
|
else {
|
|
138
|
-
|
|
147
|
+
// #3717: prefer the English translation when present — SHAPE_CUES are English-only
|
|
148
|
+
// word-boundary patterns, so a non-English `text` (e.g. response_language projects)
|
|
149
|
+
// would otherwise classify to zero shapes. validateRequirement (called above) has
|
|
150
|
+
// already guaranteed text_en, if present, is a non-empty string.
|
|
151
|
+
shapes = classifyShape(requirement.text_en ?? requirement.text);
|
|
139
152
|
if (shapes.length === 0) {
|
|
140
153
|
// Prose present but no shape cue matched. Do NOT silently drop it (#1110): an
|
|
141
154
|
// edge-relevant requirement whose phrasing missed every cue would otherwise vanish from
|
|
@@ -18,13 +18,10 @@
|
|
|
18
18
|
* concern sharing the word "trust".
|
|
19
19
|
*/
|
|
20
20
|
'use strict';
|
|
21
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
22
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
23
|
-
};
|
|
24
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
22
|
exports.isPathConfined = isPathConfined;
|
|
26
23
|
exports.assertDescriptorConfined = assertDescriptorConfined;
|
|
27
|
-
const
|
|
24
|
+
const security_cjs_1 = require("./security.cjs");
|
|
28
25
|
/**
|
|
29
26
|
* Pure LEXICAL path-containment check (cross-platform). `target` is confined
|
|
30
27
|
* to `root` iff resolving it relative to `root` (via `path.resolve` — string
|
|
@@ -36,20 +33,38 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
36
33
|
* `root`) that would redirect the LEXICALLY-confined path to a physically
|
|
37
34
|
* different, unconfined location on disk. A caller relying on this for a
|
|
38
35
|
* write-confinement guarantee against a symlink-planting attacker must pair
|
|
39
|
-
* it with a symlink check (or refuse to follow symlinks at write time)
|
|
40
|
-
* capability-
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
36
|
+
* it with a symlink check (or refuse to follow symlinks at write time). Only
|
|
37
|
+
* the capability-loader.cts route into assertDescriptorConfined gets this for
|
|
38
|
+
* free today: capability-source.cts's staging path rejects symlinks upstream,
|
|
39
|
+
* before a target ever reaches a lexical-only check like this one — see
|
|
40
|
+
* copyDirRecursive's `entry.isSymbolicLink()` throw (capability-source.cts:585-586)
|
|
41
|
+
* and the post-copy budget-walk re-check (capability-source.cts:671-674). This
|
|
42
|
+
* does NOT extend to isPathConfined's other callers: install-engine.cts:1608
|
|
43
|
+
* and install-profiles.cts:755,880 do not go through capability-source.cts's
|
|
44
|
+
* adapters at all and have no symlink guard of their own here. Of the
|
|
45
|
+
* remaining callers, only retired-artifact-cleanup.cts:69 carries its own
|
|
46
|
+
* defense, via a local `lstatSync(destDir).isSymbolicLink()` check at line 77.
|
|
47
|
+
*
|
|
48
|
+
* `opts.pathImpl` (default: the ambient `path` module) lets a caller inject
|
|
49
|
+
* `path.win32` or `path.posix`. This is security-relevant: the win32 branch
|
|
50
|
+
* (drive letters, UNC paths, `\` separator) is otherwise only reachable by
|
|
51
|
+
* actually running this process on a Windows host, so without injection a
|
|
52
|
+
* win32-specific confinement escape would be unverified on every other
|
|
53
|
+
* platform. This mirrors the platform-injection seam already used elsewhere
|
|
54
|
+
* in this repo, e.g. src/shell-command-projection.cts's `opts.platform`
|
|
55
|
+
* (#4641). All existing 2-arg callers are unaffected: the default resolves to
|
|
56
|
+
* the ambient `path`, preserving byte-identical behaviour.
|
|
57
|
+
*
|
|
58
|
+
* The containment DECISION here now comes from the canonical predicate in
|
|
59
|
+
* src/security.cts (`tryWithinRootLexical`, ADR-4650 decision 6) — this
|
|
60
|
+
* function keeps only the lexical RESOLUTION policy (no realpath, no
|
|
61
|
+
* filesystem access) as its own choice; the comparison itself is shared.
|
|
44
62
|
*/
|
|
45
|
-
function isPathConfined(target, root) {
|
|
63
|
+
function isPathConfined(target, root, opts = {}) {
|
|
46
64
|
if (typeof target !== 'string' || typeof root !== 'string' || target.length === 0 || root.length === 0) {
|
|
47
65
|
return false;
|
|
48
66
|
}
|
|
49
|
-
|
|
50
|
-
const targetResolved = node_path_1.default.resolve(root, target);
|
|
51
|
-
const prefix = rootResolved + node_path_1.default.sep;
|
|
52
|
-
return targetResolved === rootResolved || targetResolved.startsWith(prefix);
|
|
67
|
+
return (0, security_cjs_1.tryWithinRootLexical)(target, root, { pathImpl: opts.pathImpl }) !== null;
|
|
53
68
|
}
|
|
54
69
|
/**
|
|
55
70
|
* Assert every destSubpath the descriptor declares (global + local artifact
|