@opengsd/gsd-core 1.10.0 → 1.11.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-debug-session-manager.md +11 -0
- package/agents/gsd-doc-synthesizer.md +2 -4
- package/agents/gsd-executor.md +5 -5
- package/agents/gsd-mempalace-curator.md +5 -2
- package/agents/gsd-phase-researcher.md +20 -1
- package/agents/gsd-plan-checker.md +37 -0
- package/agents/gsd-planner.md +44 -46
- package/agents/gsd-user-profiler.md +3 -0
- package/agents/gsd-verifier.md +12 -3
- package/bin/install.js +841 -971
- package/bin/lib/ui-safety-gate.cjs +2 -0
- package/commands/gsd/code-review.md +1 -1
- package/commands/gsd/execute-phase.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 +1 -1
- package/commands/gsd/review-backlog.md +2 -1
- package/commands/gsd/verify-work.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +469 -88
- package/gsd-core/bin/lib/active-workstream-store.cjs +138 -22
- package/gsd-core/bin/lib/agent-install-check.cjs +230 -32
- package/gsd-core/bin/lib/api-coverage.cjs +3 -5
- package/gsd-core/bin/lib/artifacts.cjs +3 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +2 -4
- package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
- package/gsd-core/bin/lib/audit.cjs +876 -240
- package/gsd-core/bin/lib/broken-windows.cjs +1 -1
- 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-registry.cjs +575 -101
- package/gsd-core/bin/lib/capability-source.cjs +92 -0
- package/gsd-core/bin/lib/capability-trust.cjs +444 -25
- package/gsd-core/bin/lib/capability-validator.cjs +495 -22
- package/gsd-core/bin/lib/capability-writer.cjs +3 -2
- package/gsd-core/bin/lib/check-command-router.cjs +71 -37
- package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
- package/gsd-core/bin/lib/codex-agent-toml.cjs +329 -0
- package/gsd-core/bin/lib/command-aliases.cjs +22 -0
- package/gsd-core/bin/lib/command-roster.cjs +44 -1
- package/gsd-core/bin/lib/commands.cjs +651 -86
- package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
- package/gsd-core/bin/lib/complexity-trigger.cjs +1172 -0
- package/gsd-core/bin/lib/config-loader.cjs +75 -0
- package/gsd-core/bin/lib/config.cjs +10 -1
- package/gsd-core/bin/lib/core-utils.cjs +127 -29
- package/gsd-core/bin/lib/decisions.cjs +23 -0
- package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
- package/gsd-core/bin/lib/frontmatter.cjs +155 -20
- package/gsd-core/bin/lib/gap-checker.cjs +68 -7
- package/gsd-core/bin/lib/git-base-branch.cjs +102 -0
- 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 +145 -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 +265 -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 +173 -0
- package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +431 -0
- package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
- package/gsd-core/bin/lib/init.cjs +321 -129
- package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
- package/gsd-core/bin/lib/install-engine.cjs +745 -258
- package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +203 -0
- package/gsd-core/bin/lib/install-profiles.cjs +134 -57
- 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-migrations.cjs +138 -31
- package/gsd-core/bin/lib/io.cjs +10 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +2 -1
- package/gsd-core/bin/lib/markdown-table.cjs +133 -20
- package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
- package/gsd-core/bin/lib/milestone.cjs +754 -70
- package/gsd-core/bin/lib/model-catalog.cjs +59 -1
- package/gsd-core/bin/lib/model-resolver.cjs +183 -40
- package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
- package/gsd-core/bin/lib/pattern.cjs +122 -0
- package/gsd-core/bin/lib/phase-estimation.cjs +1 -1
- package/gsd-core/bin/lib/phase-id.cjs +444 -36
- package/gsd-core/bin/lib/phase-lifecycle.cjs +28 -3
- package/gsd-core/bin/lib/phase-locator.cjs +125 -18
- package/gsd-core/bin/lib/phase.cjs +646 -143
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
- package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
- package/gsd-core/bin/lib/plan-scan.cjs +86 -2
- package/gsd-core/bin/lib/planning-scope.cjs +31 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +890 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +56 -6
- package/gsd-core/bin/lib/probe-core.cjs +1 -1
- package/gsd-core/bin/lib/profile-output.cjs +1 -1
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +740 -0
- package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +13 -4
- 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 +34 -0
- package/gsd-core/bin/lib/roadmap-parser.cjs +943 -184
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
- package/gsd-core/bin/lib/roadmap.cjs +385 -94
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +608 -46
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +426 -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 +115 -3
- package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
- package/gsd-core/bin/lib/runtime-slash.cjs +27 -9
- package/gsd-core/bin/lib/security.cjs +104 -5
- package/gsd-core/bin/lib/shell-command-projection.cjs +275 -3
- package/gsd-core/bin/lib/smart-entry.cjs +142 -22
- package/gsd-core/bin/lib/state-command-router.cjs +5 -1
- package/gsd-core/bin/lib/state-document.cjs +152 -8
- package/gsd-core/bin/lib/state-transition.cjs +371 -117
- package/gsd-core/bin/lib/state.cjs +1794 -357
- package/gsd-core/bin/lib/surface.cjs +23 -9
- 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 +9 -3
- package/gsd-core/bin/lib/uat.cjs +399 -56
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
- package/gsd-core/bin/lib/ui-safety-gate.cjs +14 -5
- package/gsd-core/bin/lib/unusable-input.cjs +24 -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.cjs +20 -6
- package/gsd-core/bin/lib/vendor/README.md +37 -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 +258 -8
- package/gsd-core/bin/lib/verify.cjs +368 -888
- 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.cjs +2 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +176 -9
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +7 -1
- package/gsd-core/references/agent-contracts.md +43 -26
- package/gsd-core/references/checkpoints.md +2 -2
- package/gsd-core/references/context-budget.md +1 -1
- package/gsd-core/references/dispatch-isolation-gate.md +138 -0
- package/gsd-core/references/doc-conflict-engine.md +1 -1
- package/gsd-core/references/execute-mvp-tdd.md +3 -3
- package/gsd-core/references/execute-phase-between-wave-reset.md +6 -2
- 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 +6 -2
- package/gsd-core/references/gate-prompts.md +1 -1
- package/gsd-core/references/git-planning-commit.md +2 -1
- package/gsd-core/references/loop-hook-dispatch.md +39 -2
- package/gsd-core/references/model-profiles.md +12 -4
- package/gsd-core/references/mvp-concepts.md +9 -9
- package/gsd-core/references/planner-guidance.md +3 -9
- package/gsd-core/references/planner-preconditions.md +1 -1
- package/gsd-core/references/planner-reviews.md +1 -1
- package/gsd-core/references/planning-config.md +8 -6
- package/gsd-core/references/revision-loop.md +1 -1
- package/gsd-core/references/specless-probe-fallback.md +1 -1
- package/gsd-core/references/universal-anti-patterns.md +3 -3
- package/gsd-core/references/verifier-phase-gates.md +192 -0
- package/gsd-core/references/verify-mvp-mode.md +1 -1
- package/gsd-core/references/workstream-flag.md +22 -6
- package/gsd-core/templates/discussion-log.md +1 -1
- package/gsd-core/templates/phase-prompt.md +2 -4
- package/gsd-core/templates/state.md +4 -4
- package/gsd-core/templates/verification-report.md +9 -1
- package/gsd-core/workflows/ai-integration-phase.md +9 -11
- package/gsd-core/workflows/autonomous.md +1 -1
- package/gsd-core/workflows/cleanup.md +62 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +13 -3
- package/gsd-core/workflows/code-review-fix.md +37 -10
- package/gsd-core/workflows/code-review.md +38 -12
- package/gsd-core/workflows/complete-milestone.md +141 -18
- package/gsd-core/workflows/debug.md +7 -5
- package/gsd-core/workflows/diagnose-issues.md +35 -9
- package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -1
- package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +2 -1
- package/gsd-core/workflows/edit-phase.md +26 -1
- package/gsd-core/workflows/eval-review.md +3 -5
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +31 -6
- 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 +2 -0
- package/gsd-core/workflows/execute-phase.md +38 -50
- package/gsd-core/workflows/execute-plan.md +36 -4
- package/gsd-core/workflows/explore.md +131 -4
- package/gsd-core/workflows/fast.md +10 -2
- package/gsd-core/workflows/health.md +73 -4
- package/gsd-core/workflows/import.md +4 -4
- package/gsd-core/workflows/ingest-docs.md +5 -5
- package/gsd-core/workflows/mvp-phase.md +6 -3
- package/gsd-core/workflows/new-milestone.md +14 -9
- package/gsd-core/workflows/new-project.md +14 -14
- package/gsd-core/workflows/next.md +12 -0
- package/gsd-core/workflows/plan-phase.md +41 -17
- package/gsd-core/workflows/plan-review-convergence.md +50 -2
- package/gsd-core/workflows/progress.md +34 -6
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +4 -4
- package/gsd-core/workflows/quick/steps/quick-verification.md +27 -6
- package/gsd-core/workflows/quick/steps/research-phase.md +2 -2
- package/gsd-core/workflows/quick.md +35 -15
- package/gsd-core/workflows/review.md +26 -5
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/session-report.md +2 -1
- package/gsd-core/workflows/settings.md +66 -2
- package/gsd-core/workflows/ship.md +104 -44
- package/gsd-core/workflows/spec-phase.md +30 -12
- package/gsd-core/workflows/sync-skills.md +63 -8
- package/gsd-core/workflows/transition.md +46 -11
- package/gsd-core/workflows/ui-phase.md +5 -5
- package/gsd-core/workflows/ui-review.md +2 -2
- package/gsd-core/workflows/update.md +1 -1
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-work.md +9 -7
- package/hooks/dist/gsd-agent-isolation-guard.js +103 -14
- package/hooks/dist/gsd-check-update-worker.js +56 -13
- package/hooks/dist/gsd-check-update.js +19 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +0 -3
- package/hooks/dist/gsd-cursor-subagent-start.js +77 -2
- package/hooks/dist/gsd-cursor-subagent-stop.js +3 -2
- package/hooks/dist/gsd-prompt-guard.js +21 -20
- package/hooks/dist/gsd-read-injection-scanner.js +38 -24
- package/hooks/dist/gsd-statusline.js +18 -0
- package/hooks/dist/gsd-update-banner.js +22 -1
- package/hooks/dist/gsd-workflow-guard.js +134 -36
- package/hooks/dist/lib/git-cmd.js +92 -59
- 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/gsd-agent-isolation-guard.js +103 -14
- package/hooks/gsd-check-update-worker.js +56 -13
- package/hooks/gsd-check-update.js +19 -1
- package/hooks/gsd-cursor-pre-tool.js +0 -3
- package/hooks/gsd-cursor-subagent-start.js +77 -2
- package/hooks/gsd-cursor-subagent-stop.js +3 -2
- package/hooks/gsd-prompt-guard.js +21 -20
- package/hooks/gsd-read-injection-scanner.js +38 -24
- package/hooks/gsd-statusline.js +18 -0
- package/hooks/gsd-update-banner.js +22 -1
- package/hooks/gsd-workflow-guard.js +134 -36
- package/hooks/lib/git-cmd.js +92 -59
- 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/package.json +21 -9
- package/pi/gsd.cjs +19 -5
- 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/changeset/lint.cjs +60 -5
- package/scripts/check-alias-drift.cjs +7 -43
- package/scripts/check-contract-drift.cjs +297 -0
- package/scripts/ci-test-scope.cjs +19 -2
- package/scripts/command-contract-helpers.cjs +903 -1
- package/scripts/gen-adr-index.cjs +728 -38
- package/scripts/gen-capability-registry.cjs +3 -15
- package/scripts/gen-context-index.cjs +2 -11
- package/scripts/gen-health-docs.cjs +390 -0
- package/scripts/gen-inventory-manifest.cjs +50 -4
- package/scripts/gen-loop-host-contract.cjs +4 -24
- package/scripts/gen-registry.cjs +3 -14
- package/scripts/lib/alias-drift-families.cjs +46 -0
- package/scripts/lib/drift-scan.cjs +278 -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-eslint-glob-coverage.allowlist.json +34 -0
- package/scripts/lint-eslint-glob-coverage.cjs +340 -0
- package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
- package/scripts/lint-health-diagnostic-rule-table.cjs +404 -0
- package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
- package/scripts/lint-milestone-window-drift.cjs +468 -0
- package/scripts/lint-phase-enumeration-drift.cjs +479 -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 +434 -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 +320 -0
- package/scripts/lint-state-field-drift.cjs +805 -0
- package/scripts/lint-state-write-path-drift.cjs +1045 -0
- package/scripts/lint-test-file-count.allowlist.json +21 -10
- package/scripts/lint-unreachable-guard-drift.cjs +843 -0
- package/scripts/lint-vendored-deps.cjs +124 -0
- package/scripts/pr-changed-files.cjs +63 -0
- package/scripts/pr-template-policy.cjs +14 -4
- package/scripts/prompt-injection-scan.sh +25 -0
- package/scripts/require-issue-link-policy.cjs +192 -0
- package/scripts/state-write-path-drift-baseline.json +19 -0
- package/scripts/sync-runtime-launcher.cjs +2 -4
- package/skills/gsd-autonomous/SKILL.md +0 -1
- package/skills/gsd-code-review/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -2
- 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 +1 -1
- 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/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/run-affected-tests.cjs +0 -7
- package/scripts/run-tests.cjs +0 -1051
|
@@ -34,7 +34,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
34
34
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
35
35
|
};
|
|
36
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
-
exports.LEGACY_NON_REGISTRY_RUNTIME_IDS = void 0;
|
|
37
|
+
exports.GSD_LOCATION_ENV_KEYS = exports.NON_REGISTRY_CONFIG_HOME_DESCRIPTORS = exports.KIMI_CODE_HOOKS_TOML_DESCRIPTOR = exports.KIMI_HOOKS_TOML_DESCRIPTOR = exports.LEGACY_NON_REGISTRY_RUNTIME_IDS = void 0;
|
|
38
38
|
exports.isRegisteredRuntimeId = isRegisteredRuntimeId;
|
|
39
39
|
exports.resolveConfigHomeFromDescriptor = resolveConfigHomeFromDescriptor;
|
|
40
40
|
exports.resolveAntigravityGlobalDir = resolveAntigravityGlobalDir;
|
|
@@ -338,6 +338,72 @@ function resolveKimiGlobalDir(opts = {}) {
|
|
|
338
338
|
probeExists: 'skills',
|
|
339
339
|
}, { env, home, existsSync: existsSyncFn });
|
|
340
340
|
}
|
|
341
|
+
/**
|
|
342
|
+
* Kimi CLI's own native config.toml home. Hoisted out of resolveKimiHooksTomlDir
|
|
343
|
+
* so it is ENUMERABLE, not merely resolvable.
|
|
344
|
+
*
|
|
345
|
+
* #2665 round 3: a config-location var that lives only inside a function body is
|
|
346
|
+
* invisible to every consumer that needs the SET rather than the path — the test
|
|
347
|
+
* scrub list and the hermeticity guard both derive from descriptors, and this one
|
|
348
|
+
* reached neither. `kimi` is the sharp case precisely because it owns TWO config
|
|
349
|
+
* homes: KIMI_CONFIG_DIR (registry-visible, already covered) and KIMI_SHARE_DIR
|
|
350
|
+
* (this one), so a derivation keyed only on the registry looks complete and is not.
|
|
351
|
+
*/
|
|
352
|
+
exports.KIMI_HOOKS_TOML_DESCRIPTOR = {
|
|
353
|
+
kind: 'dot-home',
|
|
354
|
+
name: '.kimi',
|
|
355
|
+
env: ['KIMI_SHARE_DIR'],
|
|
356
|
+
};
|
|
357
|
+
/**
|
|
358
|
+
* Kimi Code's native config.toml home — the `kimi-code` counterpart of the
|
|
359
|
+
* descriptor above, hoisted for exactly the same reason.
|
|
360
|
+
*
|
|
361
|
+
* #2755 landed kimi-code hooks support on `next` while this PR was open, and
|
|
362
|
+
* declared this descriptor as an inline object literal inside
|
|
363
|
+
* resolveKimiHooksTomlDir's body — the same resolvable-but-not-enumerable shape
|
|
364
|
+
* round 3 hoisted KIMI_SHARE_DIR out of. Hoisting it puts `KIMI_CODE_HOME` into
|
|
365
|
+
* the derived scrub set and the hermeticity guard's watch roots in the SAME
|
|
366
|
+
* commit, which is the property NON_REGISTRY_CONFIG_HOME_DESCRIPTORS exists to
|
|
367
|
+
* guarantee. Each product's env var stays scoped to that product (#2755).
|
|
368
|
+
*/
|
|
369
|
+
exports.KIMI_CODE_HOOKS_TOML_DESCRIPTOR = {
|
|
370
|
+
kind: 'dot-home',
|
|
371
|
+
name: '.kimi-code',
|
|
372
|
+
env: ['KIMI_CODE_HOME'],
|
|
373
|
+
};
|
|
374
|
+
/**
|
|
375
|
+
* Config-home descriptors resolved OUTSIDE the capability registry.
|
|
376
|
+
*
|
|
377
|
+
* Anything added here is picked up by every derived consumer in the same commit —
|
|
378
|
+
* which is the property that makes the derivation structurally incapable of being
|
|
379
|
+
* narrower than the surface it guards. Adding a hardcoded resolver WITHOUT adding
|
|
380
|
+
* its descriptor here is the defect this array exists to make hard.
|
|
381
|
+
*/
|
|
382
|
+
exports.NON_REGISTRY_CONFIG_HOME_DESCRIPTORS = [
|
|
383
|
+
exports.KIMI_HOOKS_TOML_DESCRIPTOR,
|
|
384
|
+
exports.KIMI_CODE_HOOKS_TOML_DESCRIPTOR,
|
|
385
|
+
];
|
|
386
|
+
/**
|
|
387
|
+
* GSD's OWN location vars — a second family, not runtime configHomes.
|
|
388
|
+
*
|
|
389
|
+
* #2665 round 3: the registry describes where each *third-party runtime* keeps its
|
|
390
|
+
* config. It says nothing about where GSD keeps its own user-owned state, and that
|
|
391
|
+
* is a separate env-first surface:
|
|
392
|
+
*
|
|
393
|
+
* GSD_HOME — `process.env['GSD_HOME'] || os.homedir()`, the root of
|
|
394
|
+
* `$GSD_HOME/.gsd/` (consent.json, defaults.json, capability
|
|
395
|
+
* overlays). Read env-first by capability-loader, capability-consent,
|
|
396
|
+
* capability-state, capability-writer, config-loader, install-profiles
|
|
397
|
+
* and bin/install.js. A WRITE surface.
|
|
398
|
+
* GSD_AGENTS_DIR — `if (process.env['GSD_AGENTS_DIR']) return it`, priority 1 in
|
|
399
|
+
* getAgentsDir. Misdirects a READ rather than a write, hence lower
|
|
400
|
+
* severity — but it is env-first and unconditional, so it belongs
|
|
401
|
+
* to the same class.
|
|
402
|
+
*
|
|
403
|
+
* Deliberately NOT folded into the descriptor array above: these do not resolve
|
|
404
|
+
* through resolveConfigHomeFromDescriptor and have no `kind`/`name` shape.
|
|
405
|
+
*/
|
|
406
|
+
exports.GSD_LOCATION_ENV_KEYS = ['GSD_HOME', 'GSD_AGENTS_DIR'];
|
|
341
407
|
/**
|
|
342
408
|
* Resolve the directory holding the Kimi product's OWN native config.toml —
|
|
343
409
|
* the file that product itself reads for providers/models/hooks/etc, and the
|
|
@@ -375,8 +441,8 @@ function resolveKimiHooksTomlDir(opts = {}) {
|
|
|
375
441
|
// value originates from argv, and an index would resolve inherited keys
|
|
376
442
|
// (`constructor`, `__proto__`) to something that is not a descriptor.
|
|
377
443
|
const descriptor = opts.runtime === 'kimi-code'
|
|
378
|
-
?
|
|
379
|
-
:
|
|
444
|
+
? exports.KIMI_CODE_HOOKS_TOML_DESCRIPTOR
|
|
445
|
+
: exports.KIMI_HOOKS_TOML_DESCRIPTOR;
|
|
380
446
|
return resolveConfigHomeFromDescriptor(descriptor, { env, home });
|
|
381
447
|
}
|
|
382
448
|
/**
|
|
@@ -21,9 +21,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
21
21
|
* BEHAVIOR-PRESERVING RELOCATION: all logic is copied verbatim from
|
|
22
22
|
* bin/install.js. No behavior change, no descriptor reads, no new IO.
|
|
23
23
|
*
|
|
24
|
-
* bin/install.js re-
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
* #2876 (epic #2866 Phase 7): bin/install.js previously re-exported every
|
|
25
|
+
* symbol from this module, but a repo-wide audit found zero production
|
|
26
|
+
* consumers of those re-exports — no `require('../bin/install.js').
|
|
27
|
+
* writeCursorHooksJson` (or any sibling) exists outside a doc comment
|
|
28
|
+
* anywhere in the tree. The re-exports were test-suite-only pass-throughs;
|
|
29
|
+
* tests now require this module directly instead.
|
|
27
30
|
*/
|
|
28
31
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
29
32
|
const node_path_1 = __importDefault(require("node:path"));
|
|
@@ -488,6 +491,98 @@ function rewriteLegacyManagedNodeHookCommands(settings, absoluteRunner, opts) {
|
|
|
488
491
|
}
|
|
489
492
|
return changed;
|
|
490
493
|
}
|
|
494
|
+
// ---------------------------------------------------------------------------
|
|
495
|
+
// Shared: reconcileManagedShellHookCommands (#3329)
|
|
496
|
+
// ---------------------------------------------------------------------------
|
|
497
|
+
/**
|
|
498
|
+
* Rewrite already-registered managed `.sh` hook `command` strings to the shape
|
|
499
|
+
* the current installer would generate (#3329).
|
|
500
|
+
*
|
|
501
|
+
* applySettingsJsonHooks registers the four `.sh` managed hooks only-if-absent,
|
|
502
|
+
* so an entry registered by an older installer keeps its old command forever —
|
|
503
|
+
* `/gsd-update` (which re-invokes the installer) never re-derived it. On
|
|
504
|
+
* Claude/win32 that left the pre-#580/#3393 bash-runner-prefixed commands
|
|
505
|
+
* (`bash "<script>.sh"`, `"<git>/bash.exe" "<script>.sh"`) in place, spawning a
|
|
506
|
+
* nested bash on every hook fire. The #2979 rewriter above cannot help: it is
|
|
507
|
+
* Node-only by design (its basename gate contains only `.js` filenames).
|
|
508
|
+
*
|
|
509
|
+
* Scoping / safety:
|
|
510
|
+
* - Inert unless `shellHookOmitsBashRunner({ platform, runtime, isShellHook:
|
|
511
|
+
* true })` — the exact combination whose correct command shape changed. Where
|
|
512
|
+
* the bash runner is still correct (non-Windows, non-claude), nothing is
|
|
513
|
+
* rewritten, so the reconcile cannot churn unrelated installs.
|
|
514
|
+
* - Only entries whose parsed script token's basename exactly equals one of the
|
|
515
|
+
* expected managed `.sh` filenames are touched. A user hook would have to
|
|
516
|
+
* live at a path ending in exactly `gsd-session-state.sh` etc. — i.e. the
|
|
517
|
+
* GSD-installed file — to match. Extra-token commands (env prefixes, extra
|
|
518
|
+
* args) and args-form launcher entries (#976) never match the strict
|
|
519
|
+
* `[runner ]<script>` two-token shape and are left alone.
|
|
520
|
+
* - A null/empty expected command disables rewriting for that hook (a
|
|
521
|
+
* bash-runner-unavailable install must not have its entry nulled).
|
|
522
|
+
*
|
|
523
|
+
* @param settings settings.json-shaped object; mutated in place
|
|
524
|
+
* @param expected map of managed `.sh` filename → the command this install
|
|
525
|
+
* would register today (from buildHookCommand / buildLocalShellHookCommand)
|
|
526
|
+
* @param opts platform/runtime override (default: current process)
|
|
527
|
+
* @returns true when any command was rewritten
|
|
528
|
+
*/
|
|
529
|
+
function reconcileManagedShellHookCommands(settings, expected, opts) {
|
|
530
|
+
if (!settings || !settings.hooks || !expected)
|
|
531
|
+
return false;
|
|
532
|
+
if (!opts)
|
|
533
|
+
opts = {};
|
|
534
|
+
const platform = opts.platform || process.platform;
|
|
535
|
+
const runtime = opts.runtime || 'generic';
|
|
536
|
+
if (!shellHookOmitsBashRunner({ platform, runtime, isShellHook: true }))
|
|
537
|
+
return false;
|
|
538
|
+
const expectedByBasename = new Map();
|
|
539
|
+
for (const [hookFile, command] of Object.entries(expected)) {
|
|
540
|
+
if (typeof command === 'string' && command.length > 0) {
|
|
541
|
+
expectedByBasename.set(hookFile, command);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
if (expectedByBasename.size === 0)
|
|
545
|
+
return false;
|
|
546
|
+
let changed = false;
|
|
547
|
+
for (const entries of Object.values(settings.hooks)) {
|
|
548
|
+
if (!Array.isArray(entries))
|
|
549
|
+
continue;
|
|
550
|
+
for (const entry of entries) {
|
|
551
|
+
if (!entry || !Array.isArray(entry.hooks))
|
|
552
|
+
continue;
|
|
553
|
+
for (const h of entry.hooks) {
|
|
554
|
+
if (!h || typeof h.command !== 'string')
|
|
555
|
+
continue;
|
|
556
|
+
if (Array.isArray(h.args) && h.args.length > 0)
|
|
557
|
+
continue;
|
|
558
|
+
let trimmed = h.command.trim();
|
|
559
|
+
const hadPowerShellCallOperator = platform === 'win32' && /^&\s+/.test(trimmed);
|
|
560
|
+
if (hadPowerShellCallOperator) {
|
|
561
|
+
trimmed = trimmed.replace(/^&\s+/, '').trim();
|
|
562
|
+
}
|
|
563
|
+
// Strict `[runner ]<script>` shape: an optional single runner token
|
|
564
|
+
// (bare, 'single-quoted', or "double-quoted") followed by the script
|
|
565
|
+
// token. Anything else (env prefixes, extra flags, pipelines) does not
|
|
566
|
+
// match and is left untouched.
|
|
567
|
+
const m = trimmed.match(/^(?:(?:"([^"]+)"|'([^']+)'|(\S+))\s+)?(?:"([^"]+)"|'([^']+)'|(\S+))\s*$/);
|
|
568
|
+
if (!m)
|
|
569
|
+
continue;
|
|
570
|
+
const scriptToken = m[4] || m[5] || m[6] || '';
|
|
571
|
+
if (!scriptToken)
|
|
572
|
+
continue;
|
|
573
|
+
const basename = shellCmdProjection.posixNormalize(scriptToken).split('/').pop() || '';
|
|
574
|
+
const expectedCommand = expectedByBasename.get(basename);
|
|
575
|
+
if (!expectedCommand)
|
|
576
|
+
continue;
|
|
577
|
+
if (h.command === expectedCommand)
|
|
578
|
+
continue;
|
|
579
|
+
h.command = expectedCommand;
|
|
580
|
+
changed = true;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return changed;
|
|
585
|
+
}
|
|
491
586
|
function buildCodexHookBlock(targetDir, opts) {
|
|
492
587
|
const absoluteRunner = opts && opts.absoluteRunner;
|
|
493
588
|
if (!absoluteRunner)
|
|
@@ -1764,6 +1859,22 @@ function applySettingsJsonHooks(settings, opts) {
|
|
|
1764
1859
|
else if (!hasPhaseBoundaryHook && !phaseBoundaryCommand) {
|
|
1765
1860
|
console.warn(` ${yellow}⚠${reset} Skipped phase boundary hook — Bash executable path unavailable (#3393)`);
|
|
1766
1861
|
}
|
|
1862
|
+
// #3329: the four `.sh` sites above register only-if-absent, so an entry
|
|
1863
|
+
// registered by an older installer keeps its old command forever —
|
|
1864
|
+
// /gsd-update (which re-invokes the installer) never re-derived it. On
|
|
1865
|
+
// Claude/win32 that left the pre-#580/#3393 bash-runner-prefixed commands
|
|
1866
|
+
// in settings.json indefinitely. Reconcile existing managed `.sh` entries
|
|
1867
|
+
// to the command this install would generate today. Inert wherever the
|
|
1868
|
+
// bash runner is still the correct shape; scoped to exact managed
|
|
1869
|
+
// basenames so user-authored hooks are never touched.
|
|
1870
|
+
if (reconcileManagedShellHookCommands(settings, {
|
|
1871
|
+
'gsd-validate-commit.sh': validateCommitCommand,
|
|
1872
|
+
'gsd-graphify-update.sh': graphifyUpdateCommand,
|
|
1873
|
+
'gsd-session-state.sh': sessionStateCommand,
|
|
1874
|
+
'gsd-phase-boundary.sh': phaseBoundaryCommand,
|
|
1875
|
+
}, { platform: hookOpts.platform, runtime })) {
|
|
1876
|
+
console.log(` ${green}✓${reset} Reconciled managed .sh hook commands to current format (#3329)`);
|
|
1877
|
+
}
|
|
1767
1878
|
// ── Extended hook events: SubagentStop / Stop / PreCompact / SubagentStart
|
|
1768
1879
|
// (#788 + #770 + #2092) ────────────────────────────────────────────────
|
|
1769
1880
|
// Claude Code (since #770) and Qwen Code (since #788) both support the
|
|
@@ -2195,6 +2306,7 @@ module.exports = {
|
|
|
2195
2306
|
applySettingsJsonHooks,
|
|
2196
2307
|
referencesHook,
|
|
2197
2308
|
rewriteLegacyManagedNodeHookCommands,
|
|
2309
|
+
reconcileManagedShellHookCommands,
|
|
2198
2310
|
normalizeNodePath,
|
|
2199
2311
|
resolveNodeRunner,
|
|
2200
2312
|
resolveBashRunner,
|
|
@@ -193,7 +193,9 @@ exports.NO_LOCAL_CONFIG_DIR_SENTINEL = '(no-local-config-dir)';
|
|
|
193
193
|
* Pure runtime-identity projection. Relocated from `bin/install.js` per
|
|
194
194
|
* ADR-1508 (epic #1507, #1510 Phase 1) so the Runtime Artifact Conversion
|
|
195
195
|
* Module's rewrite engine can consume it without importing the installer.
|
|
196
|
-
* `bin/install.js` re-
|
|
196
|
+
* Consumers import it from here. `bin/install.js` used to re-export it for
|
|
197
|
+
* back-compat; that re-export was retired in #2876, once an audit found the
|
|
198
|
+
* compatibility spine had no production consumer at all.
|
|
197
199
|
*/
|
|
198
200
|
function getDirName(runtime) {
|
|
199
201
|
if (!runtime)
|
|
@@ -29,6 +29,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
29
29
|
};
|
|
30
30
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
31
|
exports.formatGsdSlash = formatGsdSlash;
|
|
32
|
+
exports.resolveExplicitRuntime = resolveExplicitRuntime;
|
|
32
33
|
exports.resolveRuntime = resolveRuntime;
|
|
33
34
|
exports.formatGsdSlashFor = formatGsdSlashFor;
|
|
34
35
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
@@ -75,19 +76,21 @@ function formatGsdSlash(commandName, runtime) {
|
|
|
75
76
|
return `/gsd-${token}${tail}`;
|
|
76
77
|
}
|
|
77
78
|
/**
|
|
78
|
-
* Resolve the
|
|
79
|
+
* Resolve the explicit runtime for a project directory, from the two
|
|
80
|
+
* explicit sources only — no default is applied.
|
|
79
81
|
*
|
|
80
|
-
*
|
|
82
|
+
* env.GSD_RUNTIME > config.runtime > null
|
|
81
83
|
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
84
|
+
* Returns null when neither explicit source is set, so a caller can
|
|
85
|
+
* distinguish "config said claude" from "nothing was set" (needed by
|
|
86
|
+
* #3245's host-detection rung, which must only run when this returns null).
|
|
85
87
|
*
|
|
86
88
|
* @param projectDir - path to the project directory, or null/undefined
|
|
87
|
-
* @
|
|
89
|
+
* @param env - environment variables to read GSD_RUNTIME from; defaults to process.env
|
|
90
|
+
* @returns the resolved runtime name, or null if neither source is set
|
|
88
91
|
*/
|
|
89
|
-
function
|
|
90
|
-
const envRuntime = (0, runtime_name_policy_cjs_1.resolveRuntimeNameFromCandidates)(
|
|
92
|
+
function resolveExplicitRuntime(projectDir, env = process.env) {
|
|
93
|
+
const envRuntime = (0, runtime_name_policy_cjs_1.resolveRuntimeNameFromCandidates)(env['GSD_RUNTIME']);
|
|
91
94
|
if (envRuntime)
|
|
92
95
|
return envRuntime;
|
|
93
96
|
if (projectDir) {
|
|
@@ -113,7 +116,22 @@ function resolveRuntime(projectDir) {
|
|
|
113
116
|
// runtime output formatting.
|
|
114
117
|
}
|
|
115
118
|
}
|
|
116
|
-
return
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Resolve the effective runtime for a project directory.
|
|
123
|
+
*
|
|
124
|
+
* process.env.GSD_RUNTIME > config.runtime > 'claude'
|
|
125
|
+
*
|
|
126
|
+
* Mirrors the precedence already used by profile-output.cjs and the rest of
|
|
127
|
+
* the runtime resolution chain. Returns a lowercased string so downstream
|
|
128
|
+
* comparisons can be case-blind.
|
|
129
|
+
*
|
|
130
|
+
* @param projectDir - path to the project directory, or null/undefined
|
|
131
|
+
* @returns the resolved runtime name
|
|
132
|
+
*/
|
|
133
|
+
function resolveRuntime(projectDir) {
|
|
134
|
+
return resolveExplicitRuntime(projectDir) ?? 'claude';
|
|
117
135
|
}
|
|
118
136
|
/**
|
|
119
137
|
* Convenience: format using the runtime resolved from a project directory.
|
|
@@ -29,6 +29,7 @@ exports.requireSafePath = requireSafePath;
|
|
|
29
29
|
exports.scanForInjection = scanForInjection;
|
|
30
30
|
exports.sanitizeForPrompt = sanitizeForPrompt;
|
|
31
31
|
exports.sanitizeForDisplay = sanitizeForDisplay;
|
|
32
|
+
exports.sanitizeLabel = sanitizeLabel;
|
|
32
33
|
exports.validateShellArg = validateShellArg;
|
|
33
34
|
exports.safeJsonParse = safeJsonParse;
|
|
34
35
|
exports.validatePhaseNumber = validatePhaseNumber;
|
|
@@ -73,13 +74,52 @@ function validatePath(filePath, baseDir, opts = {}) {
|
|
|
73
74
|
resolvedPath = node_fs_1.default.realpathSync(resolvedPath);
|
|
74
75
|
}
|
|
75
76
|
catch {
|
|
76
|
-
|
|
77
|
+
// realpathSync failed — either resolvedPath doesn't exist at all, or it's
|
|
78
|
+
// a dangling symlink (the link itself exists but its target doesn't).
|
|
79
|
+
// lstat (unlike stat/realpath) stats the link itself and does NOT follow
|
|
80
|
+
// it, so it succeeds for a dangling symlink and throws ENOENT for a
|
|
81
|
+
// genuinely absent path. That's the discriminator: without it, a dangling
|
|
82
|
+
// symlink to a non-existent OUTSIDE path would fall through to the
|
|
83
|
+
// parent-resolution fallback below and be re-accepted as an in-project
|
|
84
|
+
// path, while a symlink to an EXISTING outside path is correctly
|
|
85
|
+
// rejected via the realpathSync success branch above — a state
|
|
86
|
+
// difference an attacker can use as an existence oracle for arbitrary
|
|
87
|
+
// absolute paths.
|
|
77
88
|
try {
|
|
78
|
-
|
|
79
|
-
|
|
89
|
+
if (node_fs_1.default.lstatSync(resolvedPath).isSymbolicLink()) {
|
|
90
|
+
return { safe: false, resolved: '', error: 'Path is an unresolvable symbolic link' };
|
|
91
|
+
}
|
|
80
92
|
}
|
|
81
93
|
catch {
|
|
82
|
-
//
|
|
94
|
+
// lstat also threw — resolvedPath (and its would-be link) genuinely
|
|
95
|
+
// doesn't exist. Fall through to ancestor resolution below.
|
|
96
|
+
}
|
|
97
|
+
// Walk up to the nearest ancestor that exists and realpath THAT, then
|
|
98
|
+
// re-append the remaining (not-yet-created) segments. This canonicalizes
|
|
99
|
+
// resolvedPath the same way resolvedBase was canonicalized above,
|
|
100
|
+
// regardless of how many leading directories are missing — a single
|
|
101
|
+
// parent-only check would leave resolvedPath un-canonicalized whenever
|
|
102
|
+
// the parent is also missing, which breaks the startsWith comparison
|
|
103
|
+
// below on any non-canonical cwd (e.g. macOS /var/... vs
|
|
104
|
+
// /private/var/...).
|
|
105
|
+
let ancestor = node_path_1.default.dirname(resolvedPath);
|
|
106
|
+
const remainder = [node_path_1.default.basename(resolvedPath)];
|
|
107
|
+
for (;;) {
|
|
108
|
+
try {
|
|
109
|
+
const realAncestor = node_fs_1.default.realpathSync(ancestor);
|
|
110
|
+
resolvedPath = node_path_1.default.join(realAncestor, ...remainder);
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
const parent = node_path_1.default.dirname(ancestor);
|
|
115
|
+
if (parent === ancestor) {
|
|
116
|
+
// Reached filesystem root without finding an existing ancestor —
|
|
117
|
+
// keep resolvedPath as-is.
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
remainder.unshift(node_path_1.default.basename(ancestor));
|
|
121
|
+
ancestor = parent;
|
|
122
|
+
}
|
|
83
123
|
}
|
|
84
124
|
}
|
|
85
125
|
const normalizedBase = resolvedBase + node_path_1.default.sep;
|
|
@@ -193,7 +233,7 @@ exports.INJECTION_PATTERNS = [
|
|
|
193
233
|
/override\s+(system|previous)\s+(prompt|instructions)/i,
|
|
194
234
|
// Role/identity manipulation
|
|
195
235
|
/you\s+are\s+now\s+(?:a|an|the)\s+/i,
|
|
196
|
-
|
|
236
|
+
/\bact\s+as\s+(?:a|an|the)\s+(?!plan|phase|wave)/i,
|
|
197
237
|
/pretend\s+(?:you(?:'re| are)\s+|to\s+be\s+)/i,
|
|
198
238
|
/from\s+now\s+on,?\s+you\s+(?:are|will|should|must)/i,
|
|
199
239
|
// System prompt extraction
|
|
@@ -349,6 +389,65 @@ function sanitizeForDisplay(text) {
|
|
|
349
389
|
.join('\n');
|
|
350
390
|
return sanitized;
|
|
351
391
|
}
|
|
392
|
+
/**
|
|
393
|
+
* Sanitize a value that must render as a SINGLE LINE and is derived from a
|
|
394
|
+
* filesystem name (a phase directory's number/name token, an archived
|
|
395
|
+
* milestone label, a bare filename) — not from file/frontmatter CONTENT.
|
|
396
|
+
*
|
|
397
|
+
* Why this is NOT `sanitizeForDisplay`: that helper's job is multi-line
|
|
398
|
+
* prose — it strips whole protocol-leak LINES while deliberately preserving
|
|
399
|
+
* `\n` between legitimate ones (see its docstring and
|
|
400
|
+
* `tests/security.test.cjs`'s neighbouring describe). A filesystem name is
|
|
401
|
+
* the opposite shape: it is supposed to be one line, so a `\n`/`\r` inside
|
|
402
|
+
* one is never legitimate content to preserve — it is an attacker (or a
|
|
403
|
+
* doctored checkout) using the directory NAME itself as the injection
|
|
404
|
+
* vector. #3458's reproduction: a phase directory literally named
|
|
405
|
+
* `zz\n0 open items require decisions.\n\x1b[2K\x1b[1G FORGED`
|
|
406
|
+
* flows verbatim into `audit-open`'s human report (the phase-number
|
|
407
|
+
* fallback taken when the name doesn't match `PHASE_NUMBER_TOKEN_SOURCE`).
|
|
408
|
+
* `sanitizeForDisplay` would pass every one of those bytes straight through
|
|
409
|
+
* — by design, since it never touches control characters — so the embedded
|
|
410
|
+
* `\n` becomes a real newline in the report, printing a forged
|
|
411
|
+
* "0 open items require decisions." as its own line, and the raw ESC bytes
|
|
412
|
+
* reach the terminal.
|
|
413
|
+
*
|
|
414
|
+
* This helper closes that hole by ESCAPING (never silently stripping) the
|
|
415
|
+
* C0 control range (0x00–0x1F, including ESC 0x1B, CR, LF), DEL (0x7F), and
|
|
416
|
+
* the C1 range (0x80–0x9F) into a visible representation (`\n`, `\x1b`,
|
|
417
|
+
* ...). Escaping rather than stripping is deliberate: a reviewer reading the
|
|
418
|
+
* report should be able to SEE that a name was doctored, not have it quietly
|
|
419
|
+
* normalized away as if nothing happened. Every other character — including
|
|
420
|
+
* all ordinary printable and non-ASCII text — passes through byte-identical.
|
|
421
|
+
*/
|
|
422
|
+
function sanitizeLabel(text) {
|
|
423
|
+
if (!text || typeof text !== 'string')
|
|
424
|
+
return text;
|
|
425
|
+
const NAMED_ESCAPES = {
|
|
426
|
+
0x00: '\\0',
|
|
427
|
+
0x07: '\\a',
|
|
428
|
+
0x08: '\\b',
|
|
429
|
+
0x09: '\\t',
|
|
430
|
+
0x0a: '\\n',
|
|
431
|
+
0x0b: '\\v',
|
|
432
|
+
0x0c: '\\f',
|
|
433
|
+
0x0d: '\\r',
|
|
434
|
+
0x1b: '\\x1b',
|
|
435
|
+
};
|
|
436
|
+
let out = '';
|
|
437
|
+
for (const ch of text) {
|
|
438
|
+
const code = ch.codePointAt(0);
|
|
439
|
+
const isC0 = code <= 0x1f;
|
|
440
|
+
const isDel = code === 0x7f;
|
|
441
|
+
const isC1 = code >= 0x80 && code <= 0x9f;
|
|
442
|
+
if (isC0 || isDel || isC1) {
|
|
443
|
+
out += NAMED_ESCAPES[code] ?? `\\x${code.toString(16).padStart(2, '0')}`;
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
out += ch;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return out;
|
|
450
|
+
}
|
|
352
451
|
// ─── Shell Safety ───────────────────────────────────────────────────────────────────────
|
|
353
452
|
/**
|
|
354
453
|
* Validate that a string is safe to use as a shell argument when quoted.
|