@opengsd/gsd-core 1.18.0 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +2 -1
- package/.opencode/plugins/gsd-core.js +2 -3
- package/agents/gsd-code-fixer.compact.md +7 -7
- package/agents/gsd-code-fixer.md +13 -7
- package/agents/gsd-code-reviewer.compact.md +49 -8
- package/agents/gsd-code-reviewer.md +53 -14
- package/agents/gsd-executor.md +8 -4
- package/agents/gsd-verifier.md +1 -1
- package/bin/install.js +422 -103
- package/commands/gsd/update.md +5 -2
- package/gsd-core/bin/gsd-tools.cjs +179 -30
- package/gsd-core/bin/lib/agent-install-check.cjs +29 -3
- package/gsd-core/bin/lib/args-terminator.cjs +114 -0
- package/gsd-core/bin/lib/broken-windows.cjs +59 -1
- package/gsd-core/bin/lib/capability-lifecycle.cjs +1 -12
- package/gsd-core/bin/lib/capability-lock.cjs +13 -2
- package/gsd-core/bin/lib/capability-registry.cjs +376 -103
- package/gsd-core/bin/lib/capability-state.cjs +14 -8
- package/gsd-core/bin/lib/capability-validator.cjs +32 -0
- package/gsd-core/bin/lib/commands.cjs +27 -17
- package/gsd-core/bin/lib/config-loader.cjs +13 -3
- package/gsd-core/bin/lib/config.cjs +256 -58
- package/gsd-core/bin/lib/core-utils.cjs +45 -0
- package/gsd-core/bin/lib/decision-coverage-support.cjs +74 -0
- package/gsd-core/bin/lib/frontmatter.cjs +21 -3
- package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +2 -2
- package/gsd-core/bin/lib/gate-context-drift.cjs +9 -4
- package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +6 -1
- package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +6 -1
- package/gsd-core/bin/lib/gate-evaluation-scope.cjs +134 -13
- package/gsd-core/bin/lib/gate-phase-context.cjs +7 -8
- package/gsd-core/bin/lib/gate-schema-drift.cjs +1 -1
- package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +63 -22
- package/gsd-core/bin/lib/gate-ui-safety.cjs +23 -5
- package/gsd-core/bin/lib/git-info-exclude.cjs +52 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +31 -12
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +9 -7
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +3 -3
- package/gsd-core/bin/lib/health-diagnostic-types.cjs +4 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +52 -1
- package/gsd-core/bin/lib/host-integration.cjs +13 -6
- package/gsd-core/bin/lib/init.cjs +245 -68
- package/gsd-core/bin/lib/install-engine.cjs +246 -32
- package/gsd-core/bin/lib/install-profiles.cjs +9 -4
- package/gsd-core/bin/lib/installer-migration-report.cjs +2 -0
- package/gsd-core/bin/lib/io.cjs +6 -0
- package/gsd-core/bin/lib/markdown-line-diff.cjs +147 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +147 -1
- package/gsd-core/bin/lib/markdown-table.cjs +23 -0
- package/gsd-core/bin/lib/mcp-server.cjs +8 -5
- package/gsd-core/bin/lib/milestone-lock.cjs +8 -1
- package/gsd-core/bin/lib/milestone-release-map.cjs +244 -0
- package/gsd-core/bin/lib/milestone-release-tags.cjs +440 -0
- package/gsd-core/bin/lib/milestone.cjs +61 -51
- package/gsd-core/bin/lib/model-catalog.cjs +8 -0
- package/gsd-core/bin/lib/normalize-test-command.cjs +67 -0
- package/gsd-core/bin/lib/phase-entry-grammar.cjs +119 -0
- package/gsd-core/bin/lib/phase-id-display.cjs +2 -0
- package/gsd-core/bin/lib/phase-id.cjs +629 -38
- package/gsd-core/bin/lib/phase-lifecycle.cjs +12 -44
- package/gsd-core/bin/lib/phase-locator.cjs +328 -19
- package/gsd-core/bin/lib/phase.cjs +3066 -653
- package/gsd-core/bin/lib/planning-document.cjs +26 -5
- package/gsd-core/bin/lib/planning-inspect.cjs +27 -35
- package/gsd-core/bin/lib/planning-snapshot.cjs +15 -8
- package/gsd-core/bin/lib/planning-workspace.cjs +21 -3
- package/gsd-core/bin/lib/pristine-baseline.cjs +11 -3
- package/gsd-core/bin/lib/read-only-git-env.cjs +79 -0
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +2 -2
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +57 -2
- package/gsd-core/bin/lib/review-lane-invocation.cjs +125 -18
- package/gsd-core/bin/lib/review-lane-runner.cjs +33 -4
- package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +11 -9
- package/gsd-core/bin/lib/roadmap-parser.cjs +735 -137
- package/gsd-core/bin/lib/roadmap-progress.cjs +136 -0
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +47 -7
- package/gsd-core/bin/lib/roadmap.cjs +383 -123
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +381 -14
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +8 -4
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +60 -16
- package/gsd-core/bin/lib/shell-command-projection.cjs +151 -19
- package/gsd-core/bin/lib/smart-entry.cjs +35 -11
- package/gsd-core/bin/lib/state-contract.cjs +39 -15
- package/gsd-core/bin/lib/state-document.cjs +344 -27
- package/gsd-core/bin/lib/state-transition.cjs +156 -32
- package/gsd-core/bin/lib/state.cjs +527 -186
- package/gsd-core/bin/lib/surface.cjs +9 -0
- package/gsd-core/bin/lib/task-command-router.cjs +5 -25
- package/gsd-core/bin/lib/task-content-resolution.cjs +46 -1
- package/gsd-core/bin/lib/template.cjs +2 -2
- package/gsd-core/bin/lib/validate.cjs +3 -2
- package/gsd-core/bin/lib/vendor/re2js.cjs +863 -577
- package/gsd-core/bin/lib/vendor/re2js.d.cts +284 -219
- package/gsd-core/bin/lib/verification.cjs +96 -9
- package/gsd-core/bin/lib/verify.cjs +4 -2
- package/gsd-core/bin/lib/workstream-inventory.cjs +26 -20
- package/gsd-core/bin/lib/worktree-safety.cjs +22 -1
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
- package/gsd-core/bin/verify-reapply-patches.cjs +411 -10
- package/gsd-core/references/cicd-review-patterns.md +45 -0
- package/gsd-core/references/git-integration.md +11 -0
- package/gsd-core/references/loop-hook-dispatch.md +13 -6
- package/gsd-core/references/necessity-review.md +48 -0
- package/gsd-core/references/planning-config.md +2 -0
- package/gsd-core/references/tdd.md +2 -2
- package/gsd-core/templates/DEBUG.md +16 -0
- package/gsd-core/templates/config.json +2 -1
- package/gsd-core/templates/verification-report.md +1 -1
- package/gsd-core/workflows/audit-fix.md +2 -1
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +10 -1
- package/gsd-core/workflows/code-review-fix.md +46 -5
- package/gsd-core/workflows/code-review.md +64 -7
- package/gsd-core/workflows/complete-milestone/steps/project-md-evolution-review.md +145 -0
- package/gsd-core/workflows/complete-milestone/steps/release-map.md +80 -0
- package/gsd-core/workflows/complete-milestone.md +37 -150
- package/gsd-core/workflows/diagnose-issues.md +29 -5
- package/gsd-core/workflows/discuss-phase/modes/power.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +33 -2
- package/gsd-core/workflows/discuss-phase-power.md +19 -2
- package/gsd-core/workflows/discuss-phase.md +6 -10
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +13 -7
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +7 -2
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -1
- package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +11 -2
- package/gsd-core/workflows/execute-phase.md +10 -6
- package/gsd-core/workflows/execute-plan.md +1 -1
- package/gsd-core/workflows/health.md +2 -1
- package/gsd-core/workflows/import.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +2 -2
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +13 -9
- package/gsd-core/workflows/plan-phase.md +14 -14
- package/gsd-core/workflows/pr-branch.md +2 -0
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +1 -1
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +8 -1
- package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +2 -1
- package/gsd-core/workflows/quick.md +1 -1
- package/gsd-core/workflows/reapply-patches.md +104 -0
- package/gsd-core/workflows/review.md +70 -154
- package/gsd-core/workflows/section-manifest.json +5 -0
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/settings-advanced.md +57 -9
- package/gsd-core/workflows/settings.md +1 -0
- package/gsd-core/workflows/ship.md +15 -4
- package/gsd-core/workflows/spec-phase.md +3 -6
- package/gsd-core/workflows/verify-work.md +4 -3
- package/hooks/dist/gsd-agent-isolation-guard.js +11 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +6 -2
- package/hooks/dist/gsd-graphify-update.sh +6 -0
- package/hooks/dist/gsd-path-export.js +95 -0
- package/hooks/dist/gsd-secret-read-guard.js +172 -39
- package/hooks/dist/gsd-statusline.js +37 -3
- package/hooks/dist/gsd-windsurf-pre-write.js +29 -2
- package/hooks/dist/gsd-workflow-guard.js +3 -2
- package/hooks/dist/gsd-worktree-path-guard.js +29 -2
- package/hooks/dist/lib/git-probe.js +16 -0
- package/hooks/dist/lib/isolation-sentinel.js +44 -0
- package/hooks/dist/managed-hooks-registry.cjs +1 -0
- package/hooks/gsd-agent-isolation-guard.js +11 -1
- package/hooks/gsd-cursor-subagent-start.js +6 -2
- package/hooks/gsd-graphify-update.sh +6 -0
- package/hooks/gsd-path-export.js +95 -0
- package/hooks/gsd-secret-read-guard.js +172 -39
- package/hooks/gsd-statusline.js +37 -3
- package/hooks/gsd-windsurf-pre-write.js +29 -2
- package/hooks/gsd-workflow-guard.js +3 -2
- package/hooks/gsd-worktree-path-guard.js +29 -2
- package/hooks/hooks.json +1 -0
- package/hooks/lib/git-probe.js +16 -0
- package/hooks/lib/isolation-sentinel.js +44 -0
- package/hooks/managed-hooks-registry.cjs +1 -0
- package/package.json +15 -8
- package/pi/gsd.cjs +1047 -10
- package/pi/package-agents/gsd-advisor-researcher.md +113 -0
- package/pi/package-agents/gsd-ai-researcher.md +111 -0
- package/pi/package-agents/gsd-assumptions-analyzer.md +110 -0
- package/pi/package-agents/gsd-code-fixer.md +774 -0
- package/pi/package-agents/gsd-code-reviewer.md +441 -0
- package/pi/package-agents/gsd-codebase-mapper.md +850 -0
- package/pi/package-agents/gsd-debug-session-manager.md +416 -0
- package/pi/package-agents/gsd-debugger.md +1274 -0
- package/pi/package-agents/gsd-doc-classifier.md +269 -0
- package/pi/package-agents/gsd-doc-synthesizer.md +259 -0
- package/pi/package-agents/gsd-doc-verifier.md +212 -0
- package/pi/package-agents/gsd-doc-writer.md +626 -0
- package/pi/package-agents/gsd-dom-verifier.md +164 -0
- package/pi/package-agents/gsd-domain-researcher.md +144 -0
- package/pi/package-agents/gsd-eval-auditor.md +185 -0
- package/pi/package-agents/gsd-eval-planner.md +149 -0
- package/pi/package-agents/gsd-executor.md +898 -0
- package/pi/package-agents/gsd-framework-selector.md +159 -0
- package/pi/package-agents/gsd-integration-checker.md +476 -0
- package/pi/package-agents/gsd-intel-updater.md +338 -0
- package/pi/package-agents/gsd-mempalace-curator.md +49 -0
- package/pi/package-agents/gsd-nyquist-auditor.md +200 -0
- package/pi/package-agents/gsd-pattern-mapper.md +340 -0
- package/pi/package-agents/gsd-phase-researcher.md +897 -0
- package/pi/package-agents/gsd-plan-checker.md +1077 -0
- package/pi/package-agents/gsd-planner.md +1003 -0
- package/pi/package-agents/gsd-project-researcher.md +611 -0
- package/pi/package-agents/gsd-research-synthesizer.md +259 -0
- package/pi/package-agents/gsd-roadmapper.md +779 -0
- package/pi/package-agents/gsd-security-auditor.md +171 -0
- package/pi/package-agents/gsd-ui-auditor.md +581 -0
- package/pi/package-agents/gsd-ui-checker.md +417 -0
- package/pi/package-agents/gsd-ui-researcher.md +469 -0
- package/pi/package-agents/gsd-user-profiler.md +174 -0
- package/pi/package-agents/gsd-verifier.md +963 -0
- package/pi/package-agents/manifest.json +39 -0
- package/pi/package-skills/gsd-add-tests/SKILL.md +39 -0
- package/pi/package-skills/gsd-ai-integration-phase/SKILL.md +36 -0
- package/pi/package-skills/gsd-audit-fix/SKILL.md +34 -0
- package/pi/package-skills/gsd-audit-milestone/SKILL.md +40 -0
- package/pi/package-skills/gsd-audit-uat/SKILL.md +26 -0
- package/pi/package-skills/gsd-autonomous/SKILL.md +52 -0
- package/pi/package-skills/gsd-capture/SKILL.md +68 -0
- package/pi/package-skills/gsd-cleanup/SKILL.md +25 -0
- package/pi/package-skills/gsd-code-review/SKILL.md +71 -0
- package/pi/package-skills/gsd-complete-milestone/SKILL.md +147 -0
- package/pi/package-skills/gsd-config/SKILL.md +60 -0
- package/pi/package-skills/gsd-debug/SKILL.md +64 -0
- package/pi/package-skills/gsd-discuss-phase/SKILL.md +74 -0
- package/pi/package-skills/gsd-docs-update/SKILL.md +50 -0
- package/pi/package-skills/gsd-eval-review/SKILL.md +35 -0
- package/pi/package-skills/gsd-execute-phase/SKILL.md +62 -0
- package/pi/package-skills/gsd-explore/SKILL.md +26 -0
- package/pi/package-skills/gsd-extract-learnings/SKILL.md +25 -0
- package/pi/package-skills/gsd-fast/SKILL.md +33 -0
- package/pi/package-skills/gsd-forensics/SKILL.md +59 -0
- package/pi/package-skills/gsd-graphify/SKILL.md +210 -0
- package/pi/package-skills/gsd-health/SKILL.md +36 -0
- package/pi/package-skills/gsd-help/SKILL.md +37 -0
- package/pi/package-skills/gsd-import/SKILL.md +46 -0
- package/pi/package-skills/gsd-inbox/SKILL.md +42 -0
- package/pi/package-skills/gsd-ingest-docs/SKILL.md +43 -0
- package/pi/package-skills/gsd-manager/SKILL.md +46 -0
- package/pi/package-skills/gsd-map-codebase/SKILL.md +85 -0
- package/pi/package-skills/gsd-mempalace-capture/SKILL.md +111 -0
- package/pi/package-skills/gsd-mempalace-recall/SKILL.md +111 -0
- package/pi/package-skills/gsd-milestone-summary/SKILL.md +54 -0
- package/pi/package-skills/gsd-mvp-phase/SKILL.md +45 -0
- package/pi/package-skills/gsd-new-milestone/SKILL.md +49 -0
- package/pi/package-skills/gsd-new-project/SKILL.md +49 -0
- package/pi/package-skills/gsd-next/SKILL.md +34 -0
- package/pi/package-skills/gsd-ns-context/SKILL.md +31 -0
- package/pi/package-skills/gsd-ns-ideate/SKILL.md +30 -0
- package/pi/package-skills/gsd-ns-manage/SKILL.md +42 -0
- package/pi/package-skills/gsd-ns-project/SKILL.md +34 -0
- package/pi/package-skills/gsd-ns-review/SKILL.md +35 -0
- package/pi/package-skills/gsd-ns-workflow/SKILL.md +44 -0
- package/pi/package-skills/gsd-onboard/SKILL.md +46 -0
- package/pi/package-skills/gsd-pause-work/SKILL.md +48 -0
- package/pi/package-skills/gsd-phase/SKILL.md +61 -0
- package/pi/package-skills/gsd-plan-phase/SKILL.md +62 -0
- package/pi/package-skills/gsd-plan-review-convergence/SKILL.md +63 -0
- package/pi/package-skills/gsd-pr-branch/SKILL.md +31 -0
- package/pi/package-skills/gsd-profile-user/SKILL.md +48 -0
- package/pi/package-skills/gsd-progress/SKILL.md +51 -0
- package/pi/package-skills/gsd-quick/SKILL.md +179 -0
- package/pi/package-skills/gsd-quick-batch/SKILL.md +114 -0
- package/pi/package-skills/gsd-resume-work/SKILL.md +31 -0
- package/pi/package-skills/gsd-review/SKILL.md +50 -0
- package/pi/package-skills/gsd-review-backlog/SKILL.md +65 -0
- package/pi/package-skills/gsd-secure-phase/SKILL.md +37 -0
- package/pi/package-skills/gsd-settings/SKILL.md +30 -0
- package/pi/package-skills/gsd-ship/SKILL.md +27 -0
- package/pi/package-skills/gsd-sketch/SKILL.md +56 -0
- package/pi/package-skills/gsd-spec-phase/SKILL.md +63 -0
- package/pi/package-skills/gsd-spike/SKILL.md +53 -0
- package/pi/package-skills/gsd-stats/SKILL.md +22 -0
- package/pi/package-skills/gsd-surface/SKILL.md +177 -0
- package/pi/package-skills/gsd-thread/SKILL.md +29 -0
- package/pi/package-skills/gsd-ui-phase/SKILL.md +35 -0
- package/pi/package-skills/gsd-ui-review/SKILL.md +35 -0
- package/pi/package-skills/gsd-ultraplan-phase/SKILL.md +38 -0
- package/pi/package-skills/gsd-undo/SKILL.md +38 -0
- package/pi/package-skills/gsd-update/SKILL.md +54 -0
- package/pi/package-skills/gsd-validate-phase/SKILL.md +37 -0
- package/pi/package-skills/gsd-verify-work/SKILL.md +41 -0
- package/pi/package-skills/gsd-workspace/SKILL.md +57 -0
- package/pi/package-skills/gsd-workstreams/SKILL.md +73 -0
- package/scripts/build-hooks.js +4 -0
- package/scripts/ci-next-health.cjs +246 -23
- package/scripts/command-contract-helpers.cjs +1 -1
- package/scripts/docs-guard-registry.cjs +22 -7
- package/scripts/gen-features.cjs +14 -2
- package/scripts/gen-health-docs.cjs +13 -1
- package/scripts/gen-pi-package-skills.cjs +217 -0
- package/scripts/lib/drift-scan.cjs +27 -0
- package/scripts/lib/macos-conformance-tier.generated.cjs +17 -1
- package/scripts/lib/platform-conformance-tier.generated.cjs +37 -1
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +3 -3
- package/scripts/lint-phase-enumeration-drift.cjs +8 -4
- package/scripts/lint-retired-runtime-name.cjs +5 -0
- package/scripts/lint-roadmap-progress-drift.cjs +421 -0
- package/scripts/mutation-matrix.cjs +23 -1
- package/scripts/npm-audit-baseline.cjs +41 -7
- package/scripts/reviewed-or-maintainer-policy.cjs +181 -0
- package/scripts/select-docs-guards.cjs +1 -1
- package/scripts/self-test-report-policy.cjs +350 -0
- package/scripts/sync-next-version.cjs +38 -6
- package/skills/gsd-update/SKILL.md +5 -2
- package/vscode/package.json +1 -1
- package/scripts/diagnose-validate-commit-in-tier.cjs +0 -270
- package/scripts/diagnose-validate-commit-phases.cjs +0 -348
- package/scripts/xtrace-phase-timing.cjs +0 -205
package/commands/gsd/update.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: gsd:update
|
|
3
3
|
description: Update GSD to latest version with changelog display
|
|
4
|
-
argument-hint: "[--sync | --reapply | --next | --rc]"
|
|
4
|
+
argument-hint: "[--sync | --reapply | --next | --rc] [--interactive=exceptions|all]"
|
|
5
5
|
allowed-tools:
|
|
6
6
|
- Read
|
|
7
7
|
- Write
|
|
@@ -35,14 +35,17 @@ Routes to the update workflow which handles:
|
|
|
35
35
|
<flags>
|
|
36
36
|
- **--sync**: Sync managed GSD skills across runtime roots so multi-runtime users stay aligned after an update. Runs the sync-skills workflow (--from, --to, --dry-run, --apply flags supported).
|
|
37
37
|
- **--reapply**: Reapply local modifications after a GSD update. Uses three-way comparison (pristine baseline, user-modified backup, newly installed version) to merge user customizations back. Runs the reapply-patches workflow.
|
|
38
|
+
- **--interactive[=exceptions|all]** (with `--reapply` only): show each user edit next to the stock text with a recommended disposition and wait for a keep / retire / rewrite decision per edit. `all` shows every edit; `exceptions` (bare `--interactive`) shows only edits upstream adopted, partly adopted, reworded around, or orphaned, and keeps the rest. Without it, `--reapply` behaves exactly as before.
|
|
38
39
|
- **--next** (alias **--rc**): Target the `@next` RC dist-tag instead of `@latest` so you can install or refresh a release candidate (e.g. `1.4.0-rc.1`) through the normal update flow — scope/runtime detection, changelog preview, custom-file backup, and cache clearing all still apply. Omitting it keeps targeting `@latest` (no change). See ADR #660 for the RC channel.
|
|
39
40
|
- **(no flag)**: Standard update — check for new version, show changelog, install.
|
|
40
41
|
</flags>
|
|
41
42
|
|
|
42
43
|
<process>
|
|
44
|
+
If the arguments contain `--interactive` (bare or with a value) but not `--reapply`, STOP with: `--interactive applies only to --reapply (for example: /gsd:update --reapply --interactive=all)`. Do nothing else.
|
|
45
|
+
|
|
43
46
|
Parse the first token of the `<arguments>` block:
|
|
44
47
|
- If it is `--sync`: strip the flag, execute the sync-skills workflow (passing remaining args for --from/--to/--dry-run/--apply).
|
|
45
|
-
- If it is `--reapply`: strip the flag, execute the reapply-patches workflow.
|
|
48
|
+
- If it is `--reapply`: strip the flag, execute the reapply-patches workflow, passing the remaining arguments (for example `--interactive=all`) to it.
|
|
46
49
|
- Otherwise (including `--next` / `--rc`): execute the update workflow end-to-end, passing the `<arguments>` block through so the workflow's parse_update_channel step can select the release channel.
|
|
47
50
|
|
|
48
51
|
</process>
|
|
@@ -85,6 +85,14 @@
|
|
|
85
85
|
* [--no-archive-phases] Skip moving phase dirs to milestones/vX.Y-phases/ (archived by default)
|
|
86
86
|
* [--archive-quick] Move .planning/quick/* dirs to milestones/vX.Y-quick/ + reset the
|
|
87
87
|
* Quick Tasks Completed table (#2142; opt-in, default OFF)
|
|
88
|
+
* [--release-map <file>] Archive a per-repository release map (JSON array of
|
|
89
|
+
* {repo, release, reference, environment, status, tag}) as
|
|
90
|
+
* milestones/vX.Y-RELEASE-MAP.json and record it in MILESTONES.md
|
|
91
|
+
* (#5706; validated before any write, absent = unchanged)
|
|
92
|
+
*
|
|
93
|
+
* milestone release-tags --map <file.json> [--dry-run]
|
|
94
|
+
* Preflight, then create annotated tags per sub-repo from a JSON array of
|
|
95
|
+
* {repo, tag, ref?, message?, skip?}; never pushes, never overwrites (#5705)
|
|
88
96
|
*
|
|
89
97
|
* milestone archive-quick <version> Move .planning/quick/* dirs to milestones/vX.Y-quick/ + reset the
|
|
90
98
|
* Quick Tasks Completed table, WITHOUT the milestone complete close-out
|
|
@@ -326,6 +334,7 @@ const config = require('./lib/config.cjs');
|
|
|
326
334
|
const estimateCli = require('./lib/estimate-cli.cjs');
|
|
327
335
|
const template = require('./lib/template.cjs');
|
|
328
336
|
const milestone = require('./lib/milestone.cjs');
|
|
337
|
+
const milestoneReleaseTags = require('./lib/milestone-release-tags.cjs');
|
|
329
338
|
const commands = require('./lib/commands.cjs');
|
|
330
339
|
const runtimeIdentity = require('./lib/runtime-identity.cjs');
|
|
331
340
|
const init = require('./lib/init.cjs');
|
|
@@ -848,7 +857,31 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
848
857
|
error,
|
|
849
858
|
output: output,
|
|
850
859
|
});
|
|
851
|
-
if (
|
|
860
|
+
if (handled) return;
|
|
861
|
+
// #5268: this verb's flag surface is closed, as `todo complete`'s is
|
|
862
|
+
// (#4096). `--dry-run` is parsed and plumbed, and any OTHER flag fails loudly
|
|
863
|
+
// instead of being silently ignored: an accepted-but-ignored `--dry-run`
|
|
864
|
+
// wrote the config it was meant to preview. (`--raw` is spliced by the
|
|
865
|
+
// dispatcher before routing; it stays in the set as a guard against that
|
|
866
|
+
// splice ever moving.) The choices JSON is the one positional argument,
|
|
867
|
+
// wherever the flag sits, and a second one is refused for the same reason.
|
|
868
|
+
// A flag is a dash followed by a letter: `-1` is a (negative-number) JSON
|
|
869
|
+
// argument, refused below as choices that are not an object, not a flag.
|
|
870
|
+
const CONFIG_NEW_PROJECT_KNOWN_FLAGS = new Set(['--dry-run', '--raw']);
|
|
871
|
+
const positionals = [];
|
|
872
|
+
for (const a of args.slice(1)) {
|
|
873
|
+
if (typeof a === 'string' && /^--?[A-Za-z]/.test(a)) {
|
|
874
|
+
if (!CONFIG_NEW_PROJECT_KNOWN_FLAGS.has(a)) {
|
|
875
|
+
error(`Unknown flag for config-new-project: ${a}`, ERROR_REASON.USAGE);
|
|
876
|
+
}
|
|
877
|
+
continue;
|
|
878
|
+
}
|
|
879
|
+
positionals.push(a);
|
|
880
|
+
}
|
|
881
|
+
if (positionals.length > 1) {
|
|
882
|
+
error(`Unexpected argument for config-new-project: ${positionals[1]}. Usage: config-new-project [choices-json] [--dry-run]`, ERROR_REASON.USAGE);
|
|
883
|
+
}
|
|
884
|
+
config.cmdConfigNewProject(cwd, positionals[0], raw, { dryRun: args.includes('--dry-run') });
|
|
852
885
|
}
|
|
853
886
|
|
|
854
887
|
function routeConfigPath({ cwd, raw, workstreamContext }) {
|
|
@@ -1598,7 +1631,8 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1598
1631
|
const fsx = require('node:fs');
|
|
1599
1632
|
const os = require('node:os');
|
|
1600
1633
|
const { REVIEWER_LANES, mergeReviewerLanes } = require('./lib/review-lane-descriptor.cjs');
|
|
1601
|
-
const { resolveLanePlan: resolveLanePlanBase, resolveLaneEffort,
|
|
1634
|
+
const { resolveLanePlan: resolveLanePlanBase, resolveLaneEffort, resolveLaneCap, refuseIfOverCap, renderPromptRefusalStub, artifactPaths, applyPromptOverride } = require('./lib/review-lane-invocation.cjs');
|
|
1635
|
+
const { estimateTokens } = require('./lib/prompt-budget.cjs');
|
|
1602
1636
|
// OpenCode 2.x argv differs from 1.x (no --variant; effort rides on the model as `#variant`;
|
|
1603
1637
|
// ADR-5331 D5/D8). The generation is probed lazily, only when the opencode lane is resolved.
|
|
1604
1638
|
const resolveLanePlan = (laneInput) => {
|
|
@@ -1632,8 +1666,9 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1632
1666
|
// `plan`/`invoke` are the only subs that need the expensive plan-building path
|
|
1633
1667
|
// below; `sections`/`flags` return earlier still. Anything else errors here, before
|
|
1634
1668
|
// any of that work starts.
|
|
1635
|
-
|
|
1636
|
-
|
|
1669
|
+
const REVIEW_LANE_USAGE = 'Usage: review-lane <plan|invoke|sections|flags|availability|dispatch-step|explicit-from-argv> [--selected a,b] [--run-dir D] [--repo-root R]';
|
|
1670
|
+
if (!['plan', 'invoke', 'sections', 'flags', 'availability', 'dispatch-step', 'explicit-from-argv'].includes(sub)) {
|
|
1671
|
+
error(REVIEW_LANE_USAGE);
|
|
1637
1672
|
return;
|
|
1638
1673
|
}
|
|
1639
1674
|
const runDir = flag('--run-dir') || '.';
|
|
@@ -1709,8 +1744,34 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1709
1744
|
method: opts.method,
|
|
1710
1745
|
headers: opts.body ? { 'Content-Type': 'application/json' } : undefined,
|
|
1711
1746
|
body: opts.body,
|
|
1747
|
+
// Never follow a redirect: the egress check compares the CONFIGURED host, so a
|
|
1748
|
+
// followed redirect would send the review prompt to a host the user never named (#5433).
|
|
1749
|
+
redirect: 'manual',
|
|
1712
1750
|
signal: AbortSignal.timeout(opts.timeoutMs),
|
|
1713
1751
|
});
|
|
1752
|
+
// probe:true marks a reachability probe (returns status only, body ''): the status line is the answer, so do not
|
|
1753
|
+
// let a stalled body turn a replying server into `missing`.
|
|
1754
|
+
if (opts.probe) {
|
|
1755
|
+
res.body?.cancel().catch(() => {});
|
|
1756
|
+
return { ok: res.ok, status: res.status, body: '' };
|
|
1757
|
+
}
|
|
1758
|
+
if (res.status >= 300 && res.status < 400) {
|
|
1759
|
+
res.body?.cancel().catch(() => {});
|
|
1760
|
+
const location = res.headers.get('location');
|
|
1761
|
+
let target = 'without a Location header';
|
|
1762
|
+
if (location) {
|
|
1763
|
+
target = 'to an unparseable Location';
|
|
1764
|
+
try { target = `to ${new URL(location, url).host}`; } catch { /* keep the unparseable wording */ }
|
|
1765
|
+
}
|
|
1766
|
+
// Host and path only: a configured URL may carry credentials that must not reach a review file.
|
|
1767
|
+
const asked = (() => { try { const u = new URL(url); return `${u.host}${u.pathname}`; } catch { return 'the configured URL'; } })();
|
|
1768
|
+
return {
|
|
1769
|
+
ok: false,
|
|
1770
|
+
status: res.status,
|
|
1771
|
+
body: '',
|
|
1772
|
+
error: `${asked} answered HTTP ${res.status}, redirecting ${target}. The lane does not follow redirects; configure the final URL as the lane's host.`,
|
|
1773
|
+
};
|
|
1774
|
+
}
|
|
1714
1775
|
return { ok: res.ok, status: res.status, body: await res.text() };
|
|
1715
1776
|
} catch (e) {
|
|
1716
1777
|
return { ok: false, status: 0, body: '', error: e && e.message ? e.message : String(e) };
|
|
@@ -1737,6 +1798,23 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1737
1798
|
|
|
1738
1799
|
const selected = (flag('--selected') || '')
|
|
1739
1800
|
.split(',').map((s) => s.trim()).filter(Boolean);
|
|
1801
|
+
// #5266: `<slug>:available|missing` per openai-http lane. Host + default come from the lane
|
|
1802
|
+
// descriptor (resolveLanePlan), probing is runner.probeLane — review.md keeps no host table.
|
|
1803
|
+
// Prerequisite binaries (jq) are reported separately by the workflow, not folded in here.
|
|
1804
|
+
// First-party lanes only: ADR-2782 D5 requires egress/consent before a third-party lane's host is
|
|
1805
|
+
// contacted; detection has no such check, so installed third-party lanes are never probed here.
|
|
1806
|
+
if (sub === 'availability') {
|
|
1807
|
+
const { rows, warnings } = await runner.probeHttpLanes(
|
|
1808
|
+
REVIEWER_LANES,
|
|
1809
|
+
selected,
|
|
1810
|
+
(lane) => resolveLanePlan({ lane, configGet, runDir, repoRoot }),
|
|
1811
|
+
buildLaneRunnerDeps(),
|
|
1812
|
+
);
|
|
1813
|
+
for (const w of warnings) process.stderr.write(`review-lane availability: ${w}\n`);
|
|
1814
|
+
process.stdout.write(rows.join('\n') + (rows.length ? '\n' : ''));
|
|
1815
|
+
return;
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1740
1818
|
// ADR-2782 D8 (#2927): the lane map is first-party ∪ INSTALLED overlay
|
|
1741
1819
|
// `reviewer` bodies, first-party winning on slug collision. Before this merge
|
|
1742
1820
|
// the map was built from the frozen REVIEWER_LANES array alone, so an installed,
|
|
@@ -1838,11 +1916,11 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1838
1916
|
};
|
|
1839
1917
|
const effortFor = (lane) => resolveLaneEffort(lane, configGet, renderLaneEffort);
|
|
1840
1918
|
|
|
1841
|
-
// Per-lane prompt
|
|
1919
|
+
// Per-lane prompt cap: `resolveLaneCap` (review-lane-invocation.cjs) owns the #2797 + #4559
|
|
1842
1920
|
// resolution semantics (shared with src/reviewer-step-dispatch.cts, #4209 R3 — was two
|
|
1843
|
-
// verbatim copies).
|
|
1844
|
-
//
|
|
1845
|
-
const
|
|
1921
|
+
// verbatim copies). `plan` reports it; `invoke` REFUSES a lane whose prompt is over it
|
|
1922
|
+
// (#4559) rather than trimming — the prompt is never edited on the way to a lane.
|
|
1923
|
+
const capFor = (lane) => resolveLaneCap(lane, configGet);
|
|
1846
1924
|
|
|
1847
1925
|
// #4209 (ADR-2782 seam) — the ONE interpreter route for a step that declared
|
|
1848
1926
|
// `supportsReviewerLanes: true`. Wires `dispatchReviewerLanes` (src/reviewer-step-dispatch.cts)
|
|
@@ -1982,17 +2060,19 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1982
2060
|
} catch (e) {
|
|
1983
2061
|
return { slug, ok: false, reason: 'malformed_lane', detail: `resolver threw: ${e && e.message ? e.message : String(e)}` };
|
|
1984
2062
|
}
|
|
1985
|
-
return r.
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
:
|
|
2063
|
+
if (!r.ok) return { slug, ok: false, reason: r.reason, detail: r.detail };
|
|
2064
|
+
const laneCap = capFor(lane);
|
|
2065
|
+
return {
|
|
2066
|
+
slug,
|
|
2067
|
+
ok: true,
|
|
2068
|
+
section: lane.reviewsSection,
|
|
2069
|
+
transport: r.plan.transport,
|
|
2070
|
+
promptBudget: laneCap.cap,
|
|
2071
|
+
promptCapSource: laneCap.source,
|
|
2072
|
+
contextWindowTokens: laneCap.contextWindowTokens,
|
|
2073
|
+
promptPath: r.plan.transport === 'spawn' ? r.plan.stdin : r.plan.promptPath,
|
|
2074
|
+
plan: r.plan,
|
|
2075
|
+
};
|
|
1996
2076
|
});
|
|
1997
2077
|
|
|
1998
2078
|
if (sub === 'plan') {
|
|
@@ -2001,7 +2081,7 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
2001
2081
|
}
|
|
2002
2082
|
|
|
2003
2083
|
if (sub !== 'invoke') {
|
|
2004
|
-
error(
|
|
2084
|
+
error(REVIEW_LANE_USAGE);
|
|
2005
2085
|
return;
|
|
2006
2086
|
}
|
|
2007
2087
|
|
|
@@ -2101,14 +2181,61 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
2101
2181
|
];
|
|
2102
2182
|
}
|
|
2103
2183
|
|
|
2104
|
-
// `--prompt-file` lets the caller substitute a
|
|
2105
|
-
// channel this lane actually reads from (stdin, argv, an argv-embedded file
|
|
2106
|
-
// http promptPath), so one flag serves every lane (#5049).
|
|
2184
|
+
// `--prompt-file` lets the caller substitute a different prompt (a section subset, say). Applied
|
|
2185
|
+
// to whichever channel this lane actually reads from (stdin, argv, an argv-embedded file
|
|
2186
|
+
// reference, or the http promptPath), so one flag serves every lane (#5049). It is no longer a
|
|
2187
|
+
// trim hand-off: nothing in review.md trims a prompt to fit a lane since #4559.
|
|
2107
2188
|
const promptOverride = flag('--prompt-file');
|
|
2108
2189
|
if (promptOverride) {
|
|
2109
2190
|
entry.plan = applyPromptOverride(entry.plan, promptOverride, repoRoot);
|
|
2110
2191
|
}
|
|
2111
2192
|
|
|
2193
|
+
// #4559: refuse, never trim. Measure the prompt this lane would actually receive — the file
|
|
2194
|
+
// the RESOLVED plan reads (stdin file, or the plan's prompt path for argv / argv-file-ref /
|
|
2195
|
+
// http lanes; `--prompt-file` already retargeted the plan above) — against its cap BEFORE any
|
|
2196
|
+
// consent lookup, probe or spawn. An unreadable file is not a refusal: the runner reports its
|
|
2197
|
+
// own error for it. The decision itself lives in `refuseIfOverCap`; this only does the IO.
|
|
2198
|
+
{
|
|
2199
|
+
const plannedPrompt = entry.plan.transport === 'spawn' && entry.plan.stdin
|
|
2200
|
+
? entry.plan.stdin
|
|
2201
|
+
: entry.plan.promptPath;
|
|
2202
|
+
let promptText = null;
|
|
2203
|
+
try {
|
|
2204
|
+
promptText = fs.readFileSync(plannedPrompt || artifactPaths(runDir, entry.slug).promptPath, 'utf8');
|
|
2205
|
+
} catch { promptText = null; }
|
|
2206
|
+
if (promptText !== null) {
|
|
2207
|
+
const refused = refuseIfOverCap({
|
|
2208
|
+
lane: laneBySlug.get(entry.slug),
|
|
2209
|
+
estimatedTokens: estimateTokens(promptText),
|
|
2210
|
+
configGet,
|
|
2211
|
+
});
|
|
2212
|
+
if (refused) {
|
|
2213
|
+
const reviewPath = entry.plan.reviewPath;
|
|
2214
|
+
let stubWritten = true;
|
|
2215
|
+
try {
|
|
2216
|
+
fs.mkdirSync(path.dirname(reviewPath), { recursive: true });
|
|
2217
|
+
fs.writeFileSync(reviewPath, renderPromptRefusalStub(entry.slug, refused.detail), 'utf8');
|
|
2218
|
+
} catch (e) {
|
|
2219
|
+
stubWritten = false;
|
|
2220
|
+
process.stderr.write(
|
|
2221
|
+
`review-lane invoke: could not write the refusal stub to ${reviewPath}: ` +
|
|
2222
|
+
`${e && e.message ? e.message : String(e)}\n`,
|
|
2223
|
+
);
|
|
2224
|
+
}
|
|
2225
|
+
output({
|
|
2226
|
+
slug: entry.slug,
|
|
2227
|
+
ok: false,
|
|
2228
|
+
reason: refused.reason,
|
|
2229
|
+
detail: refused.detail,
|
|
2230
|
+
reviewPath,
|
|
2231
|
+
stubWritten,
|
|
2232
|
+
refusal: refused.refusal,
|
|
2233
|
+
}, raw);
|
|
2234
|
+
return;
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2112
2239
|
// ADR-2782 D5 rule 4: re-resolve the consented destination at INVOCATION. `undefined` (no
|
|
2113
2240
|
// consent record, or a record predating rule 1) means "nothing to compare" and ALLOWS — a
|
|
2114
2241
|
// first-party lane ships inside the SHA-pinned distribution and is never consent-gated, so
|
|
@@ -2141,6 +2268,13 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
2141
2268
|
testCommandNormalizer.cmdNormalizeTestCommand(cwd, args[1]);
|
|
2142
2269
|
}
|
|
2143
2270
|
|
|
2271
|
+
function routeResolvePackageManager({ args, cwd }) {
|
|
2272
|
+
// #5346: the gates' no-configured-command fallback asks here for the
|
|
2273
|
+
// project's package manager (packageManager field, then lockfile, else npm).
|
|
2274
|
+
// args[1] is the script name; --cwd (already parsed into `cwd`) locates package.json.
|
|
2275
|
+
require('./lib/normalize-test-command.cjs').cmdResolvePackageManager(cwd, args[1]);
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2144
2278
|
function routeDispatchShouldFlatten({ args, cwd, raw, error }) {
|
|
2145
2279
|
// #1708 / #853: typed query replacing the `RUNTIME === 'codex'` prose rule.
|
|
2146
2280
|
//
|
|
@@ -3142,7 +3276,18 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
3142
3276
|
// --dry-run) the command refuses before touching anything. Distinct
|
|
3143
3277
|
// from --force, which bypasses the narrow scope guards only.
|
|
3144
3278
|
const confirm = args.includes('--confirm');
|
|
3145
|
-
|
|
3279
|
+
// #5706 (epic #3628): optional caller-supplied release map file.
|
|
3280
|
+
// A flag with no usable value fails loud rather than being dropped.
|
|
3281
|
+
let releaseMap = null;
|
|
3282
|
+
const releaseMapIdx = args.indexOf('--release-map');
|
|
3283
|
+
if (releaseMapIdx !== -1) {
|
|
3284
|
+
const releaseMapValue = args[releaseMapIdx + 1];
|
|
3285
|
+
if (typeof releaseMapValue !== 'string' || releaseMapValue.startsWith('--') || releaseMapValue.trim() === '') {
|
|
3286
|
+
error('--release-map requires a file path (a JSON array of release-map entries)');
|
|
3287
|
+
}
|
|
3288
|
+
releaseMap = releaseMapValue;
|
|
3289
|
+
}
|
|
3290
|
+
milestone.cmdMilestoneComplete(cwd, args[2], { name: milestoneName, archivePhases, force, dryRun, archiveQuick, confirm, releaseMap }, raw);
|
|
3146
3291
|
} else if (subcommand === 'archive-quick') {
|
|
3147
3292
|
// #2142 escalation: narrow archival-only entry point (does NOT
|
|
3148
3293
|
// touch ROADMAP/REQUIREMENTS/MILESTONES.md, runs no completion
|
|
@@ -3150,8 +3295,11 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
3150
3295
|
// unlike `milestone complete --archive-quick`.
|
|
3151
3296
|
const dryRun = args.includes('--dry-run');
|
|
3152
3297
|
milestone.cmdQuickArchive(cwd, args[2], { dryRun }, raw);
|
|
3298
|
+
} else if (subcommand === 'release-tags') {
|
|
3299
|
+
// #5705 (epic #3628 Phase 1): repository-scoped annotated release tags.
|
|
3300
|
+
milestoneReleaseTags.cmdMilestoneReleaseTags(cwd, args, raw);
|
|
3153
3301
|
} else {
|
|
3154
|
-
error('Unknown milestone subcommand. Available: complete, archive-quick', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
3302
|
+
error('Unknown milestone subcommand. Available: complete, archive-quick, release-tags', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
3155
3303
|
}
|
|
3156
3304
|
}
|
|
3157
3305
|
|
|
@@ -4506,7 +4654,7 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
4506
4654
|
// #1956: deterministic STATE.md-vs-ROADMAP.md phase-status drift.
|
|
4507
4655
|
const { planningDir } = require('./lib/planning-workspace.cjs');
|
|
4508
4656
|
const { stateExtractField, stateCurrentPositionSlice } = require('./lib/state-document.cjs');
|
|
4509
|
-
const {
|
|
4657
|
+
const { locateRoadmapProgress } = require('./lib/roadmap-progress.cjs');
|
|
4510
4658
|
const { phaseKeyFromProse } = require('./lib/phase-id.cjs');
|
|
4511
4659
|
// STATE.md's YAML frontmatter carries its own lowercase `status:`
|
|
4512
4660
|
// scalar ahead of the body's `## Current Position` prose "Status:"
|
|
@@ -4604,9 +4752,9 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
4604
4752
|
|
|
4605
4753
|
const stateStatus = stateExtractField(currentPositionBody, 'Status');
|
|
4606
4754
|
|
|
4607
|
-
// #1956/#2012: scoped to `## Progress` first (decoy-avoidance) —
|
|
4608
|
-
//
|
|
4609
|
-
const table =
|
|
4755
|
+
// #1956/#2012: scoped to `## Progress` first (decoy-avoidance) — the
|
|
4756
|
+
// Progress owner's rule (roadmap-progress.cts), shared with every reader and writer (#5762).
|
|
4757
|
+
const { table } = locateRoadmapProgress(roadmapContent);
|
|
4610
4758
|
const matchedRow = table
|
|
4611
4759
|
? table.rows.find((row) => phaseKeyFromProse(row.Phase) === phase && phase !== null)
|
|
4612
4760
|
: undefined;
|
|
@@ -4809,6 +4957,7 @@ const HOST_COMMAND_ROUTERS = {
|
|
|
4809
4957
|
// #3676 (Phase 4, epic #3344): quick-batch coordination verbs.
|
|
4810
4958
|
'quick-batch': routeQuickBatchCommand,
|
|
4811
4959
|
'normalize-test-command': routeNormalizeTestCommand,
|
|
4960
|
+
'resolve-package-manager': routeResolvePackageManager,
|
|
4812
4961
|
'dispatch-should-flatten': routeDispatchShouldFlatten,
|
|
4813
4962
|
'dispatch-isolation': routeDispatchIsolation,
|
|
4814
4963
|
'dispatch-capacity': routeDispatchCapacity,
|
|
@@ -5114,7 +5263,7 @@ function runWithTimeout(argv) {
|
|
|
5114
5263
|
// drifting apart when a query command was added to only one or two.
|
|
5115
5264
|
const TOP_LEVEL_USAGE = 'Usage: gsd-tools <command> [args] [--raw] [--pick <field>] [--cwd <path>] [--project-dir <path>] [--ws <name>] [--json-errors] [--exit-contract=<v>]\n' +
|
|
5116
5265
|
'Commands: agent, agent-skills, assumption-delta, audit-open, audit-uat, check, check-commit, commit, commit-docs-guard, commit-to-subrepo, pr-subrepo, ' +
|
|
5117
|
-
'config-ensure-section, config-get, config-new-project, config-path, config-set, migrate-config, normalize-test-command, ' +
|
|
5266
|
+
'config-ensure-section, config-get, config-new-project, config-path, config-set, migrate-config, normalize-test-command, resolve-package-manager, ' +
|
|
5118
5267
|
'context-predicates, current-timestamp, detect-custom-files, docs-init, drift-guard, effort, extract-messages, find-phase, ' +
|
|
5119
5268
|
'from-gsd2, frontmatter, gap-analysis, generate-claude-md, generate-claude-profile, ' +
|
|
5120
5269
|
'generate-dev-preferences, generate-slug, graphify, history-digest, init, intel, ' +
|
|
@@ -328,8 +328,33 @@ function checkCodexModelPosture(runtime, projectRoot) {
|
|
|
328
328
|
// derives its sandbox_mode from actually lives, in every install shape
|
|
329
329
|
// (repo-dev tree or npm-global package, where the package still ships its
|
|
330
330
|
// own agents/ alongside bin/ and gsd-core/).
|
|
331
|
-
|
|
332
|
-
|
|
331
|
+
//
|
|
332
|
+
// #5496: "install-relative" has three shapes. This module compiles to
|
|
333
|
+
// `<root>/gsd-core/bin/lib/`, so `<root>` is three parents up. In the repository
|
|
334
|
+
// and the npm package the canonical sources are `<root>/agents`. In an INSTALLED
|
|
335
|
+
// config dir `<root>/agents` holds the CONVERTED runtime copies (no `tools:`
|
|
336
|
+
// declaration — every role then derived `read-only`, 26 false violations on a
|
|
337
|
+
// clean Codex install) and the canonical sources are `<root>/gsd-core/agents`.
|
|
338
|
+
// Prefer the nested directory when it actually holds agent sources; fall back to
|
|
339
|
+
// `<root>/agents`. `root` is a parameter so each layout is testable.
|
|
340
|
+
//
|
|
341
|
+
// #5636: "holds agent sources" is a recognized agent file, `gsd-<name>.md` (every file in the shipped
|
|
342
|
+
// roster) that is not a `.compact.md` variant. Any markdown would let a stray README in the nested
|
|
343
|
+
// directory hide the real sources, so the posture check found no role's canonical file and skipped all.
|
|
344
|
+
const CANONICAL_AGENT_SOURCE_FILE = /^gsd-.+\.md$/;
|
|
345
|
+
function isCanonicalAgentSourceFile(entry) {
|
|
346
|
+
return CANONICAL_AGENT_SOURCE_FILE.test(entry) && !entry.endsWith('.compact.md');
|
|
347
|
+
}
|
|
348
|
+
function canonicalAgentSourceDir(root = node_path_1.default.join(__dirname, '..', '..', '..')) {
|
|
349
|
+
const nested = node_path_1.default.join(root, 'gsd-core', 'agents');
|
|
350
|
+
try {
|
|
351
|
+
if (node_fs_1.default.readdirSync(nested).some(isCanonicalAgentSourceFile))
|
|
352
|
+
return nested;
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
// Absent or unreadable: not this layout.
|
|
356
|
+
}
|
|
357
|
+
return node_path_1.default.join(root, 'agents');
|
|
333
358
|
}
|
|
334
359
|
/**
|
|
335
360
|
* Validate installed Codex `.toml` agents' `sandbox_mode` against what each
|
|
@@ -384,7 +409,7 @@ function checkCodexSandboxPosture(runtime, projectRoot) {
|
|
|
384
409
|
reason: POSTURE_REASON.AGENTS_DIR_MISSING,
|
|
385
410
|
};
|
|
386
411
|
}
|
|
387
|
-
const canonicalAgentsDir =
|
|
412
|
+
const canonicalAgentsDir = canonicalAgentSourceDir();
|
|
388
413
|
// Same symlink guard as checkCodexModelPosture, same reason (never follow
|
|
389
414
|
// an agents-dir symlink into an arbitrary file's content).
|
|
390
415
|
const tomlFiles = node_fs_1.default
|
|
@@ -533,6 +558,7 @@ module.exports = {
|
|
|
533
558
|
checkAgentsInstalled,
|
|
534
559
|
checkCodexModelPosture,
|
|
535
560
|
checkCodexSandboxPosture,
|
|
561
|
+
canonicalAgentSourceDir,
|
|
536
562
|
POSTURE_REASON,
|
|
537
563
|
resolveAgentHint,
|
|
538
564
|
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Per-install terminators for the typed-text capture heredocs (#5637).
|
|
4
|
+
*
|
|
5
|
+
* Every workflow that reads the typed text opens with
|
|
6
|
+
*
|
|
7
|
+
* IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
|
|
8
|
+
* $ARGUMENTS
|
|
9
|
+
* GSD_ARGS_END
|
|
10
|
+
*
|
|
11
|
+
* and `/gsd-quick-batch` feeds `parse-args` through the same shape with `GSD_QUICK_BATCH_ARGS_END`.
|
|
12
|
+
* The harness replaces `$ARGUMENTS` with the typed text before the shell parses the fence, so typed
|
|
13
|
+
* text with a terminator line of its own closes the heredoc early and the lines after it run as
|
|
14
|
+
* shell. A literal in a shipped file cannot be made unforgeable, so each install replaces it with
|
|
15
|
+
* `<TERMINATOR>_<nonce>`, a nonce the typed text has no way to know. The shipped source keeps the
|
|
16
|
+
* canonical tokens; only the files an install writes carry the nonce.
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.ARGS_NONCE_RE = exports.ARGS_TERMINATOR = exports.ARGS_TERMINATORS = void 0;
|
|
20
|
+
exports.generateArgsNonce = generateArgsNonce;
|
|
21
|
+
exports.stampArgsTerminator = stampArgsTerminator;
|
|
22
|
+
exports.readInstalledNonce = readInstalledNonce;
|
|
23
|
+
exports.armArgsNonce = armArgsNonce;
|
|
24
|
+
exports.disarmArgsNonce = disarmArgsNonce;
|
|
25
|
+
exports.stampInstalledContent = stampInstalledContent;
|
|
26
|
+
const node_crypto_1 = require("node:crypto");
|
|
27
|
+
const node_fs_1 = require("node:fs");
|
|
28
|
+
const node_path_1 = require("node:path");
|
|
29
|
+
/** The canonical terminators the shipped workflows and commands carry. */
|
|
30
|
+
exports.ARGS_TERMINATORS = Object.freeze(['GSD_ARGS_END', 'GSD_QUICK_BATCH_ARGS_END']);
|
|
31
|
+
/** The terminator every workflow carries. */
|
|
32
|
+
exports.ARGS_TERMINATOR = 'GSD_ARGS_END';
|
|
33
|
+
/** A nonce is 128 random bits as 32 lowercase hex characters. */
|
|
34
|
+
exports.ARGS_NONCE_RE = /^[0-9a-f]{32}$/;
|
|
35
|
+
const OPENER_RE = new RegExp(`<<'(${exports.ARGS_TERMINATORS.join('|')})'`, 'g');
|
|
36
|
+
const CLOSER_RE = new RegExp(`^(${exports.ARGS_TERMINATORS.join('|')})(?=\\r?$)`, 'gm');
|
|
37
|
+
/** A fresh nonce. `randomBytes` is a seam for tests. */
|
|
38
|
+
function generateArgsNonce(randomBytes = node_crypto_1.randomBytes) {
|
|
39
|
+
return randomBytes(16).toString('hex');
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* `content` with each canonical terminator replaced by the stamped one: the heredoc opener tag and
|
|
43
|
+
* the closing line. A terminator that is already stamped does not match and is left alone, and so
|
|
44
|
+
* is prose that names a token inside a line.
|
|
45
|
+
*
|
|
46
|
+
* @throws {TypeError} when `nonce` is not 32 lowercase hex characters
|
|
47
|
+
*/
|
|
48
|
+
function stampArgsTerminator(content, nonce) {
|
|
49
|
+
if (typeof nonce !== 'string' || !exports.ARGS_NONCE_RE.test(nonce)) {
|
|
50
|
+
throw new TypeError('stampArgsTerminator: nonce must be 32 lowercase hex characters');
|
|
51
|
+
}
|
|
52
|
+
return content
|
|
53
|
+
.replace(OPENER_RE, (_m, token) => `<<'${token}_${nonce}'`)
|
|
54
|
+
.replace(CLOSER_RE, (_m, token) => `${token}_${nonce}`);
|
|
55
|
+
}
|
|
56
|
+
let armedNonce = null;
|
|
57
|
+
const INSTALLED_NONCE_RE = /^GSD_ARGS_END_([0-9a-f]{32})\r?$/m;
|
|
58
|
+
/** A workflow is tens of KiB; anything near this is not one, and is not read. */
|
|
59
|
+
const MAX_WORKFLOW_BYTES = 2 * 1024 * 1024;
|
|
60
|
+
/**
|
|
61
|
+
* The nonce a previous install left in `configDir`, read from the first installed workflow that
|
|
62
|
+
* carries one; null when there is none. Entries that are not regular files (a directory, a FIFO,
|
|
63
|
+
* a symlink) and files over MAX_WORKFLOW_BYTES are skipped, and one unreadable entry does not end
|
|
64
|
+
* the scan.
|
|
65
|
+
*/
|
|
66
|
+
function readInstalledNonce(configDir) {
|
|
67
|
+
let dir;
|
|
68
|
+
let names;
|
|
69
|
+
try {
|
|
70
|
+
dir = (0, node_path_1.join)(configDir, 'gsd-core', 'workflows');
|
|
71
|
+
names = (0, node_fs_1.readdirSync)(dir).sort();
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
for (const name of names) {
|
|
77
|
+
if (!name.endsWith('.md'))
|
|
78
|
+
continue;
|
|
79
|
+
try {
|
|
80
|
+
const full = (0, node_path_1.join)(dir, name);
|
|
81
|
+
const stat = (0, node_fs_1.lstatSync)(full);
|
|
82
|
+
if (!stat.isFile() || stat.size > MAX_WORKFLOW_BYTES)
|
|
83
|
+
continue;
|
|
84
|
+
const match = INSTALLED_NONCE_RE.exec((0, node_fs_1.readFileSync)(full, 'utf8'));
|
|
85
|
+
if (match)
|
|
86
|
+
return match[1];
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// This entry is unreadable; the next one may still carry the nonce.
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Arm stamping for the install into `configDir`, before it writes anything. When `reuse` is true
|
|
96
|
+
* the directory's installed nonce is kept, so a reinstall changes nothing; otherwise, or when
|
|
97
|
+
* there is none, a fresh one is minted. Only a global install (a directory under the user's home,
|
|
98
|
+
* which a cloned repository cannot seed) may reuse: a project-local directory is often committed,
|
|
99
|
+
* and a nonce planted there would be adopted. Generators never arm it, so the committed generated
|
|
100
|
+
* skills stay canonical and deterministic.
|
|
101
|
+
*/
|
|
102
|
+
function armArgsNonce(configDir, reuse = false) {
|
|
103
|
+
const prior = reuse && configDir !== undefined ? readInstalledNonce(configDir) : null;
|
|
104
|
+
armedNonce = prior ?? generateArgsNonce();
|
|
105
|
+
return armedNonce;
|
|
106
|
+
}
|
|
107
|
+
/** Stop stamping: the process is no longer writing for an install that has a nonce. */
|
|
108
|
+
function disarmArgsNonce() {
|
|
109
|
+
armedNonce = null;
|
|
110
|
+
}
|
|
111
|
+
/** `content` stamped with the armed nonce, or untouched when this process is not an installer. */
|
|
112
|
+
function stampInstalledContent(content) {
|
|
113
|
+
return armedNonce === null ? content : stampArgsTerminator(content, armedNonce);
|
|
114
|
+
}
|
|
@@ -504,6 +504,63 @@ function validateEntryShape(e, i) {
|
|
|
504
504
|
...('milestone' in o ? { milestone: typeof o.milestone === 'string' ? o.milestone : null } : {}),
|
|
505
505
|
};
|
|
506
506
|
}
|
|
507
|
+
/**
|
|
508
|
+
* #5418: when the frontmatter counts disagree with the parsed entries, the usual
|
|
509
|
+
* cause is a hand-edited table: a row added to the rendered table (and the counts
|
|
510
|
+
* bumped to match) while the fenced JSON block, the sole source of entries, was left
|
|
511
|
+
* behind. Names the row ids the table and the JSON block disagree about so the
|
|
512
|
+
* counts message does not point at the (correct) frontmatter. Returns '' when there
|
|
513
|
+
* is nothing to add. Best-effort by design: it runs while a WindowsError is already
|
|
514
|
+
* on its way out, so it must never throw and replace that error.
|
|
515
|
+
*/
|
|
516
|
+
/** Row ids spelled out in the divergence note; the rest are counted. */
|
|
517
|
+
const ROW_ID_LIST_LIMIT = 20;
|
|
518
|
+
function describeTableJsonDivergence(raw, entries) {
|
|
519
|
+
try {
|
|
520
|
+
// The hint is the PARSED entry count, not frontmatter total_count: the latter
|
|
521
|
+
// is the number that is wrong here.
|
|
522
|
+
const table = extractTableRegion(raw, entries.length);
|
|
523
|
+
if (table === null)
|
|
524
|
+
return '';
|
|
525
|
+
const rowId = (line) => (line.split('|')[1] ?? '').trim();
|
|
526
|
+
// Ids are positive integers (the JSON side enforces it), so a first cell that is
|
|
527
|
+
// not one — the `_(none)_` empty-ledger placeholder, a continuation line of a
|
|
528
|
+
// multi-line description, control characters — is not a row id and is never
|
|
529
|
+
// echoed into the message.
|
|
530
|
+
const tableIds = new Set();
|
|
531
|
+
const tableLines = table.split('\n');
|
|
532
|
+
for (let i = 2; i < tableLines.length; i++) {
|
|
533
|
+
if (!tableLines[i].startsWith('|'))
|
|
534
|
+
continue;
|
|
535
|
+
const id = rowId(tableLines[i]);
|
|
536
|
+
if (/^\d{1,15}$/.test(id))
|
|
537
|
+
tableIds.add(id);
|
|
538
|
+
}
|
|
539
|
+
const jsonIds = new Set(entries.map((e) => String(e.id)));
|
|
540
|
+
const byNumber = (a, b) => Number(a) - Number(b);
|
|
541
|
+
const list = (ids) => {
|
|
542
|
+
const shown = ids.slice(0, ROW_ID_LIST_LIMIT).join(', ');
|
|
543
|
+
return ids.length > ROW_ID_LIST_LIMIT ? `${shown} and ${ids.length - ROW_ID_LIST_LIMIT} more` : shown;
|
|
544
|
+
};
|
|
545
|
+
const tableOnly = [...tableIds].filter((id) => !jsonIds.has(id)).sort(byNumber);
|
|
546
|
+
const jsonOnly = [...jsonIds].filter((id) => !tableIds.has(id)).sort(byNumber);
|
|
547
|
+
const parts = [];
|
|
548
|
+
if (tableOnly.length > 0) {
|
|
549
|
+
parts.push(`row id(s) in the table but not in the fenced JSON block: ${list(tableOnly)}`);
|
|
550
|
+
}
|
|
551
|
+
if (jsonOnly.length > 0) {
|
|
552
|
+
parts.push(`row id(s) in the fenced JSON block but not in the table: ${list(jsonOnly)}`);
|
|
553
|
+
}
|
|
554
|
+
if (parts.length === 0)
|
|
555
|
+
return '';
|
|
556
|
+
return ` The table and the fenced JSON block disagree (${parts.join('; ')}). The JSON block is the ` +
|
|
557
|
+
'authoritative entries source: add the missing entries there (or remove the stray table rows) ' +
|
|
558
|
+
'and keep the frontmatter counts in step with it.';
|
|
559
|
+
}
|
|
560
|
+
catch {
|
|
561
|
+
return '';
|
|
562
|
+
}
|
|
563
|
+
}
|
|
507
564
|
function parseLedger(raw) {
|
|
508
565
|
const fm = parseFrontmatterStrict(raw);
|
|
509
566
|
if (fm.schema_version !== exports.SCHEMA_VERSION) {
|
|
@@ -537,7 +594,8 @@ function parseLedger(raw) {
|
|
|
537
594
|
recomputed.total_count !== ledger.total_count) {
|
|
538
595
|
throw new WindowsError(exports.REASON.WINDOWS_LEDGER_MALFORMED, `Ledger counts disagree with entries: frontmatter open/waived/fixed/total=` +
|
|
539
596
|
`${ledger.open_count}/${ledger.waived_count}/${ledger.fixed_count}/${ledger.total_count}` +
|
|
540
|
-
` but entries yield ${recomputed.open_count}/${recomputed.waived_count}/${recomputed.fixed_count}/${recomputed.total_count}.`
|
|
597
|
+
` but entries yield ${recomputed.open_count}/${recomputed.waived_count}/${recomputed.fixed_count}/${recomputed.total_count}.` +
|
|
598
|
+
describeTableJsonDivergence(raw, entries));
|
|
541
599
|
}
|
|
542
600
|
return ledger;
|
|
543
601
|
}
|
|
@@ -34,7 +34,7 @@ const consentMod = require('./capability-consent.cjs');
|
|
|
34
34
|
const projectRootMod = require('./project-root.cjs');
|
|
35
35
|
// #1459 finding 4: the SHARED hardened lock primitive (single source of truth for lifecycle + consent).
|
|
36
36
|
const lockMod = require('./capability-lock.cjs');
|
|
37
|
-
const { platformWriteSync, retryRenameSync } = require('./shell-command-projection.cjs');
|
|
37
|
+
const { platformWriteSync, retryRenameSync, shellSingleQuote } = require('./shell-command-projection.cjs');
|
|
38
38
|
// #1463: numeric major.minor.patch comparison for the outdated check (the SAME compare the resolver
|
|
39
39
|
// and capability list use). -1 (a<b), 0 (equal), 1 (a>b).
|
|
40
40
|
const semverMod = require('./semver-compare.cjs');
|
|
@@ -308,17 +308,6 @@ function isSafeHookScriptPath(script) {
|
|
|
308
308
|
return false;
|
|
309
309
|
return true;
|
|
310
310
|
}
|
|
311
|
-
/**
|
|
312
|
-
* #1460 (R) HIGH: POSIX single-quote an arbitrary string for safe inclusion in a shell command.
|
|
313
|
-
* The emitted hook `command` is the ABSOLUTE confined script path, which begins with the
|
|
314
|
-
* (non-manifest) install-prefix — commonly a home dir containing spaces/special chars (e.g.
|
|
315
|
-
* "/Users/Bob Smith/.claude/..."). Written unquoted it would word-split (and, with a hostile
|
|
316
|
-
* prefix, could inject). Wrapping in single quotes — with each embedded `'` escaped as `'\''` —
|
|
317
|
-
* makes the whole path a single shell token that no metacharacter inside it can break.
|
|
318
|
-
*/
|
|
319
|
-
function shellSingleQuote(value) {
|
|
320
|
-
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
321
|
-
}
|
|
322
311
|
/**
|
|
323
312
|
* #1634: build the emitted hook `command` for an ABSOLUTE confined script path. For `.js`-family
|
|
324
313
|
* hooks (`.js`/`.cjs`/`.mjs`) prefix with `node` so the hook runs regardless of the source's
|