@opengsd/gsd-core 1.12.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.opencode/plugins/gsd-core.js +12 -0
- package/agents/gsd-executor.md +63 -35
- package/agents/gsd-plan-checker.md +76 -57
- package/agents/gsd-planner.md +14 -0
- package/agents/gsd-ui-checker.md +19 -3
- package/agents/gsd-ui-researcher.md +29 -0
- package/agents/gsd-verifier.md +23 -1
- package/bin/install.js +239 -67
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/ns-workflow.md +2 -1
- package/commands/gsd/phase.md +1 -1
- package/commands/gsd/quick-batch.md +105 -0
- package/commands/gsd/surface.md +18 -8
- package/gsd-core/bin/gsd-tools.cjs +195 -50
- package/gsd-core/bin/lib/capability-activation.cjs +27 -0
- package/gsd-core/bin/lib/capability-registry.cjs +514 -114
- package/gsd-core/bin/lib/capability-state.cjs +7 -1
- package/gsd-core/bin/lib/capability-validator.cjs +120 -4
- package/gsd-core/bin/lib/capability-writer.cjs +14 -4
- package/gsd-core/bin/lib/check-command-router.cjs +85 -2
- package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
- package/gsd-core/bin/lib/clusters.cjs +1 -0
- package/gsd-core/bin/lib/command-aliases.cjs +16 -0
- package/gsd-core/bin/lib/commands.cjs +337 -13
- package/gsd-core/bin/lib/config-loader.cjs +3 -0
- package/gsd-core/bin/lib/core-utils.cjs +34 -7
- package/gsd-core/bin/lib/decisions.cjs +213 -1
- package/gsd-core/bin/lib/edge-probe.cjs +14 -1
- package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
- package/gsd-core/bin/lib/frontmatter.cjs +137 -23
- package/gsd-core/bin/lib/gap-checker.cjs +22 -13
- package/gsd-core/bin/lib/git-base-branch.cjs +10 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +54 -11
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +75 -22
- package/gsd-core/bin/lib/host-integration.cjs +57 -5
- package/gsd-core/bin/lib/init-command-router.cjs +14 -0
- package/gsd-core/bin/lib/init.cjs +132 -15
- package/gsd-core/bin/lib/install-engine.cjs +184 -12
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +45 -0
- package/gsd-core/bin/lib/install-profiles.cjs +22 -14
- package/gsd-core/bin/lib/installer-migration-report.cjs +1 -0
- package/gsd-core/bin/lib/io.cjs +35 -0
- package/gsd-core/bin/lib/loop-resolver.cjs +14 -8
- package/gsd-core/bin/lib/markdown-table.cjs +123 -0
- package/gsd-core/bin/lib/milestone.cjs +22 -2
- package/gsd-core/bin/lib/phase-command-router.cjs +13 -6
- package/gsd-core/bin/lib/phase-id.cjs +251 -9
- package/gsd-core/bin/lib/phase.cjs +774 -35
- package/gsd-core/bin/lib/plan-document.cjs +10 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +147 -20
- package/gsd-core/bin/lib/planning-workspace.cjs +103 -28
- package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
- package/gsd-core/bin/lib/quick-batch.cjs +840 -0
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +53 -5
- package/gsd-core/bin/lib/review-lane-invocation.cjs +73 -1
- package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
- package/gsd-core/bin/lib/roadmap-parser.cjs +499 -26
- package/gsd-core/bin/lib/roadmap.cjs +187 -58
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +233 -33
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +16 -17
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +286 -108
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +215 -43
- package/gsd-core/bin/lib/shell-command-projection.cjs +4 -0
- package/gsd-core/bin/lib/smart-entry.cjs +7 -9
- package/gsd-core/bin/lib/state-document.cjs +30 -5
- package/gsd-core/bin/lib/state-md-schema.cjs +23 -13
- package/gsd-core/bin/lib/state-transition.cjs +333 -44
- package/gsd-core/bin/lib/state.cjs +684 -125
- package/gsd-core/bin/lib/surface.cjs +23 -8
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
- package/gsd-core/bin/lib/uat.cjs +1419 -515
- package/gsd-core/bin/lib/update-context.cjs +6 -2
- package/gsd-core/bin/lib/validate.cjs +230 -12
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +273 -12
- package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
- package/gsd-core/bin/lib/verify.cjs +346 -16
- package/gsd-core/bin/lib/workstream-inventory.cjs +20 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +8 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +8 -0
- package/gsd-core/bin/verify-reapply-patches.cjs +70 -3
- package/gsd-core/references/agent-contracts.md +3 -3
- package/gsd-core/references/edge-probe.md +17 -13
- package/gsd-core/references/execute-mvp-tdd.md +18 -16
- package/gsd-core/references/execute-phase-response-language.md +6 -0
- package/gsd-core/references/executor-examples.md +42 -0
- package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
- package/gsd-core/references/mvp-concepts.md +2 -2
- package/gsd-core/references/plan-checker-examples.md +41 -0
- package/gsd-core/references/planner-antipatterns.md +25 -0
- package/gsd-core/references/planner-chunked.md +5 -1
- package/gsd-core/references/planner-coupling.md +42 -0
- package/gsd-core/references/planner-quick-batch.md +71 -0
- package/gsd-core/references/planner-reviews.md +47 -0
- package/gsd-core/references/planner-revision.md +75 -2
- package/gsd-core/references/planning-config.md +2 -1
- package/gsd-core/references/response-language-directive.md +9 -0
- package/gsd-core/references/revision-loop.md +118 -11
- package/gsd-core/references/tdd.md +14 -9
- package/gsd-core/references/verifier-evidence-gate.md +160 -0
- package/gsd-core/templates/phase-prompt.md +4 -0
- package/gsd-core/templates/verification-report.md +5 -0
- package/gsd-core/workflows/add-backlog.md +2 -0
- package/gsd-core/workflows/add-phase.md +2 -0
- package/gsd-core/workflows/add-tests.md +1 -1
- package/gsd-core/workflows/add-todo.md +1 -1
- package/gsd-core/workflows/ai-integration-phase.md +1 -1
- package/gsd-core/workflows/analyze-dependencies.md +2 -0
- package/gsd-core/workflows/audit-fix.md +2 -0
- package/gsd-core/workflows/audit-milestone.md +2 -0
- package/gsd-core/workflows/audit-uat.md +2 -0
- package/gsd-core/workflows/autonomous.md +2 -0
- package/gsd-core/workflows/check-todos.md +1 -1
- package/gsd-core/workflows/cleanup.md +1 -1
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +15 -13
- package/gsd-core/workflows/code-review-fix.md +2 -0
- package/gsd-core/workflows/code-review.md +73 -31
- package/gsd-core/workflows/complete-milestone.md +13 -4
- package/gsd-core/workflows/debug.md +1 -1
- package/gsd-core/workflows/diagnose-issues.md +5 -1
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/text.md +2 -0
- package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
- package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
- package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
- package/gsd-core/workflows/discuss-phase-power.md +2 -0
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +43 -13
- package/gsd-core/workflows/docs-update.md +1 -1
- package/gsd-core/workflows/edit-phase.md +2 -0
- package/gsd-core/workflows/eval-review.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +2 -0
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +17 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +8 -2
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -0
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
- package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
- package/gsd-core/workflows/execute-phase.md +32 -14
- package/gsd-core/workflows/execute-plan.md +8 -8
- package/gsd-core/workflows/explore.md +2 -0
- package/gsd-core/workflows/extract-learnings.md +2 -0
- package/gsd-core/workflows/fast.md +6 -0
- package/gsd-core/workflows/forensics.md +2 -0
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +1 -1
- package/gsd-core/workflows/help/modes/brief.md +2 -0
- package/gsd-core/workflows/help/modes/default.md +2 -0
- package/gsd-core/workflows/help/modes/full.md +12 -0
- package/gsd-core/workflows/help/modes/topic.md +2 -0
- package/gsd-core/workflows/help.md +2 -0
- package/gsd-core/workflows/import.md +3 -3
- package/gsd-core/workflows/inbox.md +1 -1
- package/gsd-core/workflows/ingest-docs.md +1 -1
- package/gsd-core/workflows/insert-phase.md +2 -0
- package/gsd-core/workflows/list-phase-assumptions.md +2 -0
- package/gsd-core/workflows/list-seeds.md +2 -0
- package/gsd-core/workflows/list-workspaces.md +2 -0
- package/gsd-core/workflows/manager.md +3 -3
- package/gsd-core/workflows/map-codebase.md +2 -0
- package/gsd-core/workflows/milestone-summary.md +2 -0
- package/gsd-core/workflows/mvp-phase.md +1 -1
- package/gsd-core/workflows/new-milestone.md +1 -1
- package/gsd-core/workflows/new-project.md +5 -3
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +2 -0
- package/gsd-core/workflows/node-repair.md +2 -0
- package/gsd-core/workflows/note.md +2 -0
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +19 -4
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -0
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +9 -0
- package/gsd-core/workflows/plan-phase.md +130 -12
- package/gsd-core/workflows/plan-review-convergence.md +102 -10
- package/gsd-core/workflows/plant-seed.md +1 -1
- package/gsd-core/workflows/pr-branch.md +11 -3
- package/gsd-core/workflows/profile-user.md +1 -1
- package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
- package/gsd-core/workflows/progress.md +25 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +37 -2
- package/gsd-core/workflows/quick/steps/research-phase.md +3 -3
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
- package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
- package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
- package/gsd-core/workflows/quick-batch.md +203 -0
- package/gsd-core/workflows/quick.md +13 -3
- package/gsd-core/workflows/reapply-patches.md +2 -0
- package/gsd-core/workflows/remove-phase.md +2 -0
- package/gsd-core/workflows/remove-workspace.md +1 -1
- package/gsd-core/workflows/resume-project.md +6 -2
- package/gsd-core/workflows/review.md +215 -10
- package/gsd-core/workflows/scan.md +2 -0
- package/gsd-core/workflows/section-manifest.json +12 -0
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/session-report.md +2 -0
- package/gsd-core/workflows/settings-advanced.md +2 -0
- package/gsd-core/workflows/settings-integrations.md +9 -8
- package/gsd-core/workflows/settings.md +1 -1
- package/gsd-core/workflows/ship.md +10 -10
- package/gsd-core/workflows/sketch-wrap-up.md +2 -0
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +1 -1
- package/gsd-core/workflows/spec-phase.md +24 -19
- package/gsd-core/workflows/spike-wrap-up.md +2 -0
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +2 -0
- package/gsd-core/workflows/sync-skills.md +12 -4
- package/gsd-core/workflows/thread.md +2 -0
- package/gsd-core/workflows/transition.md +2 -0
- package/gsd-core/workflows/ui-phase.md +26 -5
- package/gsd-core/workflows/ui-review.md +1 -1
- package/gsd-core/workflows/ultraplan-phase.md +2 -0
- package/gsd-core/workflows/undo.md +1 -1
- package/gsd-core/workflows/update.md +41 -38
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-work.md +49 -3
- package/hooks/dist/gsd-check-update-worker.js +19 -2
- package/hooks/dist/gsd-context-monitor.js +283 -12
- package/hooks/dist/gsd-node-runner.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +30 -5
- package/hooks/dist/gsd-read-guard.js +2 -0
- package/hooks/dist/gsd-read-injection-scanner.js +5 -5
- package/hooks/dist/gsd-secret-read-guard.js +1079 -0
- package/hooks/dist/gsd-statusline.js +7 -3
- package/hooks/dist/gsd-validate-commit.sh +444 -7
- package/hooks/dist/gsd-workflow-guard.js +2 -1
- package/hooks/dist/lib/git-cmd.js +210 -1
- package/hooks/dist/lib/injection-patterns.js +36 -6
- package/hooks/dist/managed-hooks-registry.cjs +1 -0
- package/hooks/gsd-check-update-worker.js +19 -2
- package/hooks/gsd-context-monitor.js +283 -12
- package/hooks/gsd-node-runner.sh +1 -0
- package/hooks/gsd-prompt-guard.js +30 -5
- package/hooks/gsd-read-guard.js +2 -0
- package/hooks/gsd-read-injection-scanner.js +5 -5
- package/hooks/gsd-secret-read-guard.js +1079 -0
- package/hooks/gsd-statusline.js +7 -3
- package/hooks/gsd-validate-commit.sh +444 -7
- package/hooks/gsd-workflow-guard.js +2 -1
- package/hooks/hooks.json +6 -0
- package/hooks/lib/git-cmd.js +210 -1
- package/hooks/lib/injection-patterns.js +36 -6
- package/hooks/managed-hooks-registry.cjs +1 -0
- package/package.json +5 -5
- package/scripts/build-hooks.js +11 -4
- package/scripts/ci-test-scope.cjs +7 -0
- package/scripts/docs-guard-registry.cjs +10 -0
- package/scripts/gen-loop-host-contract.cjs +67 -15
- package/scripts/lib/shellcheck-fetch.cjs +247 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +5 -0
- package/scripts/lint-phase-enumeration-drift.cjs +24 -6
- package/scripts/lint-phase-id-drift.cjs +133 -8
- package/scripts/lint-portable-grep.cjs +176 -0
- package/scripts/lint-response-language-coverage.cjs +524 -0
- package/scripts/lint-test-file-count.allowlist.json +3 -1
- package/scripts/lint-workflow-shellcheck-baseline.json +1027 -0
- package/scripts/lint-workflow-shellcheck.cjs +614 -0
- package/scripts/npm-audit-baseline.cjs +376 -0
- package/scripts/prompt-injection-scan.sh +8 -0
- package/scripts/require-issue-link-policy.cjs +16 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -1
- package/skills/gsd-ns-workflow/SKILL.md +1 -0
- package/skills/gsd-phase/SKILL.md +1 -1
- package/skills/gsd-quick-batch/SKILL.md +105 -0
- package/skills/gsd-surface/SKILL.md +18 -8
- package/vscode/package.json +1 -1
|
@@ -34,6 +34,9 @@ const phaseId = require("./phase-id.cjs");
|
|
|
34
34
|
const worktreeSafety = require("./worktree-safety.cjs");
|
|
35
35
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-workspace.cjs is an export= CommonJS module
|
|
36
36
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
38
|
+
const planningScopeMod = require("./planning-scope.cjs");
|
|
39
|
+
const { SCOPE } = planningScopeMod;
|
|
37
40
|
const secrets_cjs_1 = require("./secrets.cjs");
|
|
38
41
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- plan-scan.cjs is an export= CommonJS module
|
|
39
42
|
const scanPhasePlans = require("./plan-scan.cjs");
|
|
@@ -83,7 +86,7 @@ const { resolveModelInternal, resolveGranularityInternal, assertValidGranularity
|
|
|
83
86
|
const { findPhaseInternal, listMilestonePhaseDirs, listAllPhaseDirs } = phaseLocator;
|
|
84
87
|
const { getRoadmapPhaseInternal, getMilestoneInfo, stripShippedMilestones, extractCurrentMilestone, } = roadmapParser;
|
|
85
88
|
const { pathExistsInternal, generateSlugInternal, toPosixPath } = coreUtils;
|
|
86
|
-
const { normalizePhaseName, matchPhaseDirs, stripProjectCodePrefix, PHASE_NUMBER_TOKEN_SOURCE, isForeignPrefixedPhaseQuery, isSentinelPhaseId, extractPhaseToken, scopeToPhase } = phaseId;
|
|
89
|
+
const { comparePhaseNum, normalizePhaseName, matchPhaseDirs, stripProjectCodePrefix, PHASE_NUMBER_TOKEN_SOURCE, isForeignPrefixedPhaseQuery, isSentinelPhaseId, extractPhaseToken, scopeToPhase } = phaseId;
|
|
87
90
|
const { pruneOrphanedWorktrees } = worktreeSafety;
|
|
88
91
|
const { planningPaths, planningDir, planningRoot, listAvailableWorkstreams, peekActiveWorkstream, diagnoseUnresolvedActiveWorkstream, describeUnresolvedWorkstreamReason, findContextMdIn, } = planningWorkspace;
|
|
89
92
|
const { determinePhaseStatus } = commandsMod;
|
|
@@ -771,7 +774,6 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
|
|
|
771
774
|
? reqMatch[1].replace(/[\[\]]/g, '').split(',').map((s) => s.trim()).filter(Boolean).join(', ')
|
|
772
775
|
: null;
|
|
773
776
|
const phase_req_ids = reqExtracted && reqExtracted !== 'TBD' ? reqExtracted : null;
|
|
774
|
-
const wf = (config.workflow ?? {});
|
|
775
777
|
// #3188: these paths are null when the file is absent, matching the contract
|
|
776
778
|
// the conditional sibling fields (context_path, patterns_path, ...) already
|
|
777
779
|
// honour and that ultraplan-phase.md / execute-phase.md gate on. Hoisted so
|
|
@@ -782,7 +784,7 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
|
|
|
782
784
|
const result = {
|
|
783
785
|
executor_model: resolveModelInternal(cwd, 'gsd-executor'),
|
|
784
786
|
verifier_model: resolveModelInternal(cwd, 'gsd-verifier'),
|
|
785
|
-
tdd_mode: options['tdd'] || Boolean(
|
|
787
|
+
tdd_mode: options['tdd'] || Boolean(config.tdd_mode) || false,
|
|
786
788
|
commit_docs: config.commit_docs,
|
|
787
789
|
sub_repos: config.sub_repos,
|
|
788
790
|
parallelization: config.parallelization,
|
|
@@ -924,7 +926,6 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
|
|
|
924
926
|
const granularityOverride = options['granularity'];
|
|
925
927
|
assertValidGranularityOverride(granularityOverride, error);
|
|
926
928
|
const granularity = resolveGranularityInternal(cwd, 'planning', granularityOverride || undefined);
|
|
927
|
-
const wf = (config.workflow ?? {});
|
|
928
929
|
// #3188: see cmdInitExecutePhase — null when absent, parity with the
|
|
929
930
|
// conditional sibling fields in this same result object.
|
|
930
931
|
const statePath = node_path_1.default.join(planningDir(cwd), 'STATE.md');
|
|
@@ -934,11 +935,11 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
|
|
|
934
935
|
researcher_model: resolveModelInternal(cwd, 'gsd-phase-researcher'),
|
|
935
936
|
planner_model: resolveModelInternal(cwd, 'gsd-planner'),
|
|
936
937
|
checker_model: resolveModelInternal(cwd, 'gsd-plan-checker'),
|
|
937
|
-
tdd_mode: options['tdd'] || Boolean(
|
|
938
|
+
tdd_mode: options['tdd'] || Boolean(config.tdd_mode) || false,
|
|
938
939
|
granularity,
|
|
939
|
-
research_enabled:
|
|
940
|
+
research_enabled: config.research,
|
|
940
941
|
plan_checker_enabled: config.plan_checker,
|
|
941
|
-
nyquist_validation_enabled:
|
|
942
|
+
nyquist_validation_enabled: config.nyquist_validation,
|
|
942
943
|
commit_docs: config.commit_docs,
|
|
943
944
|
text_mode: config.text_mode,
|
|
944
945
|
auto_advance: !!(config.auto_advance),
|
|
@@ -960,6 +961,11 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
|
|
|
960
961
|
: 'Pending',
|
|
961
962
|
has_research: phaseInfo?.['has_research'] || false,
|
|
962
963
|
has_context: phaseInfo?.['has_context'] || false,
|
|
964
|
+
// #4014 (epic #3473 B4-unreadable): additive scope signal adjacent to
|
|
965
|
+
// has_context — SCOPE.COMPLETE by default (no phase directory to read is
|
|
966
|
+
// a genuine, not-unreadable answer), overwritten below to whatever
|
|
967
|
+
// findContextMdIn(phaseDirFull) reports once a directory is known.
|
|
968
|
+
context_scope: SCOPE.COMPLETE,
|
|
963
969
|
has_reviews: phaseInfo?.['has_reviews'] || false,
|
|
964
970
|
has_plans: (phaseInfo?.['plans']?.length || 0) > 0,
|
|
965
971
|
plan_count: phaseInfo?.['plans']?.length || 0,
|
|
@@ -974,6 +980,13 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
|
|
|
974
980
|
};
|
|
975
981
|
if (phaseInfo?.['directory']) {
|
|
976
982
|
const phaseDirFull = node_path_1.default.join(cwd, phaseInfo['directory']);
|
|
983
|
+
// #4014 (epic #3473 B4-unreadable): findContextMdIn's directory-string
|
|
984
|
+
// form never throws, so this can record the real scope BEFORE the
|
|
985
|
+
// pre-existing `fs.readdirSync(phaseDirFull)` immediately below
|
|
986
|
+
// (unchanged) throws on the same unreadable directory and is caught
|
|
987
|
+
// exactly as before — additive only, the failure control-flow for
|
|
988
|
+
// context_path/research_path/etc. is untouched.
|
|
989
|
+
result['context_scope'] = findContextMdIn(phaseDirFull).scope;
|
|
977
990
|
try {
|
|
978
991
|
const files = node_fs_1.default.readdirSync(phaseDirFull);
|
|
979
992
|
const phaseDirName = node_path_1.default.basename(phaseDirFull);
|
|
@@ -1100,6 +1113,34 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
|
|
|
1100
1113
|
result['prior_verify_commands'] = priorVerifyCommands;
|
|
1101
1114
|
output(withProjectRoot(cwd, result), raw);
|
|
1102
1115
|
}
|
|
1116
|
+
// #4040: shared partial-init discriminator for the init.progress / init.resume /
|
|
1117
|
+
// init.new-project payloads. A bootstrap interrupted before the core quartet
|
|
1118
|
+
// (PROJECT.md / REQUIREMENTS.md / ROADMAP.md / STATE.md) all landed is a
|
|
1119
|
+
// DISTINCT routing state from "new project" and from "between milestones";
|
|
1120
|
+
// pre-#4040 the payloads could not express it, so progress.md mis-routed it to
|
|
1121
|
+
// Route F and resume-project.md offered STATE.md reconstruction.
|
|
1122
|
+
//
|
|
1123
|
+
// Negative-space guard: `milestone.complete` archives ROADMAP.md (and
|
|
1124
|
+
// REQUIREMENTS.md) but always leaves MILESTONES.md behind — so MILESTONES.md
|
|
1125
|
+
// present proves missing core files are archival (between-milestones), never an
|
|
1126
|
+
// unfinished bootstrap. REQUIREMENTS.md is written by new-project BEFORE
|
|
1127
|
+
// ROADMAP/STATE, so its absence also proves init never finished.
|
|
1128
|
+
function buildInitCompletenessFields(cwd) {
|
|
1129
|
+
const dir = planningDir(cwd);
|
|
1130
|
+
const planningExists = node_fs_1.default.existsSync(dir);
|
|
1131
|
+
const requirementsExists = node_fs_1.default.existsSync(node_path_1.default.join(dir, 'REQUIREMENTS.md'));
|
|
1132
|
+
const milestonesExists = node_fs_1.default.existsSync(node_path_1.default.join(dir, 'MILESTONES.md'));
|
|
1133
|
+
const coreComplete = node_fs_1.default.existsSync(node_path_1.default.join(dir, 'PROJECT.md')) &&
|
|
1134
|
+
requirementsExists &&
|
|
1135
|
+
node_fs_1.default.existsSync(node_path_1.default.join(dir, 'ROADMAP.md')) &&
|
|
1136
|
+
node_fs_1.default.existsSync(node_path_1.default.join(dir, 'STATE.md'));
|
|
1137
|
+
return {
|
|
1138
|
+
planning_exists: planningExists,
|
|
1139
|
+
requirements_exists: requirementsExists,
|
|
1140
|
+
milestones_exists: milestonesExists,
|
|
1141
|
+
init_incomplete: planningExists && !coreComplete && !milestonesExists,
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1103
1144
|
function cmdInitNewProject(cwd, raw, options = {}) {
|
|
1104
1145
|
const config = loadConfig(cwd);
|
|
1105
1146
|
const homedir = node_os_1.default.homedir();
|
|
@@ -1119,6 +1160,11 @@ function cmdInitNewProject(cwd, raw, options = {}) {
|
|
|
1119
1160
|
synthesizer_model: resolveModelInternal(cwd, 'gsd-research-synthesizer'),
|
|
1120
1161
|
roadmapper_model: resolveModelInternal(cwd, 'gsd-roadmapper'),
|
|
1121
1162
|
commit_docs: config.commit_docs,
|
|
1163
|
+
// #4040: partial-init discriminator (see buildInitCompletenessFields).
|
|
1164
|
+
// Spread BEFORE this literal's own planning_exists so the existing
|
|
1165
|
+
// root-scoped (`pathExistsInternal(cwd, '.planning')`) semantics for that
|
|
1166
|
+
// one key stay byte-identical for existing consumers.
|
|
1167
|
+
...buildInitCompletenessFields(cwd),
|
|
1122
1168
|
project_exists: pathExistsInternal(cwd, toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'PROJECT.md')))),
|
|
1123
1169
|
has_codebase_map: hasCodebaseMap,
|
|
1124
1170
|
planning_exists: pathExistsInternal(cwd, '.planning'),
|
|
@@ -1158,13 +1204,12 @@ function cmdInitNewMilestone(cwd, raw, options = {}) {
|
|
|
1158
1204
|
// owns — routed through it instead of a local readdirSync + hand-rolled
|
|
1159
1205
|
// window filter (which also never excluded sentinels, unlike the owner).
|
|
1160
1206
|
const phaseDirCount = listMilestonePhaseDirs(phasesDir, { cwd }).value.length;
|
|
1161
|
-
const wf = (config.workflow ?? {});
|
|
1162
1207
|
const result = {
|
|
1163
1208
|
researcher_model: resolveModelInternal(cwd, 'gsd-project-researcher'),
|
|
1164
1209
|
synthesizer_model: resolveModelInternal(cwd, 'gsd-research-synthesizer'),
|
|
1165
1210
|
roadmapper_model: resolveModelInternal(cwd, 'gsd-roadmapper'),
|
|
1166
1211
|
commit_docs: config.commit_docs,
|
|
1167
|
-
research_enabled:
|
|
1212
|
+
research_enabled: config.research,
|
|
1168
1213
|
// #3216 review Finding 2: `?? null` so an unresolved milestone still emits
|
|
1169
1214
|
// the key with an explicit `null` rather than letting JSON.stringify drop
|
|
1170
1215
|
// it — an omitted key reaches the prompt layer's `{current_milestone}`
|
|
@@ -1246,6 +1291,10 @@ function cmdInitQuick(cwd, description, raw, options = {}) {
|
|
|
1246
1291
|
executor_model: resolveModelInternal(cwd, 'gsd-executor'),
|
|
1247
1292
|
checker_model: resolveModelInternal(cwd, 'gsd-plan-checker'),
|
|
1248
1293
|
verifier_model: resolveModelInternal(cwd, 'gsd-verifier'),
|
|
1294
|
+
// #3936: Step 4.75 dispatches gsd-phase-researcher; resolve its own tier
|
|
1295
|
+
// (parity with cmdInitPlanPhase) so the research spawn stops pinning
|
|
1296
|
+
// planner_model.
|
|
1297
|
+
researcher_model: resolveModelInternal(cwd, 'gsd-phase-researcher'),
|
|
1249
1298
|
// #2072: the quick review step spawns gsd-code-reviewer; resolve its own model
|
|
1250
1299
|
// so model_overrides / models.verification apply (was reusing executor_model).
|
|
1251
1300
|
reviewer_model: resolveModelInternal(cwd, 'gsd-code-reviewer'),
|
|
@@ -1281,6 +1330,45 @@ function cmdInitQuick(cwd, description, raw, options = {}) {
|
|
|
1281
1330
|
result['section_manifest'] = buildSectionManifestField(cwd, null, sectionManifestOptions, 'quick');
|
|
1282
1331
|
output(withProjectRoot(cwd, result), raw);
|
|
1283
1332
|
}
|
|
1333
|
+
/**
|
|
1334
|
+
* `init.quick-batch` (#3676, Phase 4 of epic #3344, ADR-1239 "Quick-batch
|
|
1335
|
+
* binding"). Unlike `cmdInitQuick`, this init bundle does NOT allocate a
|
|
1336
|
+
* quick id / slug / task directory itself — batch-level id allocation and
|
|
1337
|
+
* `BATCH.json` creation is the job of the `quick-batch create` CLI verb
|
|
1338
|
+
* (`src/quick-batch-command-router.cts`, wrapping `createBatch` in
|
|
1339
|
+
* `src/quick-batch.cts`). This bundle supplies the per-role model profiles,
|
|
1340
|
+
* `commit_docs`, the roadmap/planning existence checks `quick-batch.md`'s
|
|
1341
|
+
* ROADMAP.md gate needs (same check `cmdInitQuick` runs), the `.planning/quick`
|
|
1342
|
+
* directory path, and the `section_manifest` field gating the optional
|
|
1343
|
+
* `--research`/`--validate` step fragments — the same `flag:--research`/
|
|
1344
|
+
* `flag:--validate` atoms `quick`'s own section manifest already uses
|
|
1345
|
+
* (`WHEN_VOCABULARY` is workflow-agnostic; no new atom is needed). `--discuss`/
|
|
1346
|
+
* `--full` are rejected by `quick-batch-dispatch.cts`'s `parseQuickBatchArgs`
|
|
1347
|
+
* before this init bundle is ever reached, so no `discuss`/`full` flag key is
|
|
1348
|
+
* accepted here (unlike `cmdInitQuick`, which still supports both).
|
|
1349
|
+
*/
|
|
1350
|
+
function cmdInitQuickBatch(cwd, raw, options = {}) {
|
|
1351
|
+
const config = loadConfig(cwd);
|
|
1352
|
+
const result = {
|
|
1353
|
+
planner_model: resolveModelInternal(cwd, 'gsd-planner'),
|
|
1354
|
+
executor_model: resolveModelInternal(cwd, 'gsd-executor'),
|
|
1355
|
+
checker_model: resolveModelInternal(cwd, 'gsd-plan-checker'),
|
|
1356
|
+
verifier_model: resolveModelInternal(cwd, 'gsd-verifier'),
|
|
1357
|
+
researcher_model: resolveModelInternal(cwd, 'gsd-phase-researcher'),
|
|
1358
|
+
reviewer_model: resolveModelInternal(cwd, 'gsd-code-reviewer'),
|
|
1359
|
+
commit_docs: config.commit_docs,
|
|
1360
|
+
// #2376: absolute — see comment on phase_dir in cmdInitExecutePhase; a
|
|
1361
|
+
// per-item task_dir is re-derived by the workflow itself (quick_id +
|
|
1362
|
+
// generate-slug over that item's description), never allocated here.
|
|
1363
|
+
quick_dir: toPosixPath(node_path_1.default.join(planningDir(cwd), 'quick')),
|
|
1364
|
+
quick_batches_dir: toPosixPath(node_path_1.default.join(planningDir(cwd), 'quick-batches')),
|
|
1365
|
+
roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
1366
|
+
planning_exists: node_fs_1.default.existsSync(planningRoot(cwd)),
|
|
1367
|
+
};
|
|
1368
|
+
// #2992 (Phase 6.1): additive, optional field — degrades to null, never throws.
|
|
1369
|
+
result['section_manifest'] = buildSectionManifestField(cwd, null, options, 'quick-batch');
|
|
1370
|
+
output(withProjectRoot(cwd, result), raw);
|
|
1371
|
+
}
|
|
1284
1372
|
function cmdInitIngestDocs(cwd, raw) {
|
|
1285
1373
|
const config = loadConfig(cwd);
|
|
1286
1374
|
const result = {
|
|
@@ -1305,12 +1393,11 @@ function cmdInitIngestDocs(cwd, raw) {
|
|
|
1305
1393
|
}
|
|
1306
1394
|
function cmdInitOnboard(cwd, raw, options = {}) {
|
|
1307
1395
|
const config = loadConfig(cwd);
|
|
1308
|
-
const workflowConfig = (config.workflow ?? {});
|
|
1309
1396
|
const result = {
|
|
1310
1397
|
...buildOnboardProjection(cwd, {
|
|
1311
1398
|
commitDocs: !!config.commit_docs,
|
|
1312
1399
|
fast: options['fast'] === true,
|
|
1313
|
-
textMode: options['text'] === true || !!config.text_mode
|
|
1400
|
+
textMode: options['text'] === true || !!config.text_mode,
|
|
1314
1401
|
}),
|
|
1315
1402
|
...getInitGitState(cwd),
|
|
1316
1403
|
};
|
|
@@ -1323,6 +1410,11 @@ function cmdInitResume(cwd, raw) {
|
|
|
1323
1410
|
if (agentIdRaw !== null)
|
|
1324
1411
|
interruptedAgentId = agentIdRaw.trim();
|
|
1325
1412
|
const result = {
|
|
1413
|
+
// #4040: partial-init discriminator (see buildInitCompletenessFields).
|
|
1414
|
+
// Spread FIRST so this literal's own root-scoped planning_exists
|
|
1415
|
+
// (planningRoot) keeps its existing semantics; init_incomplete itself
|
|
1416
|
+
// keys off the artifact dir (planningDir) where the core docs live.
|
|
1417
|
+
...buildInitCompletenessFields(cwd),
|
|
1326
1418
|
state_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
|
|
1327
1419
|
roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
1328
1420
|
project_exists: pathExistsInternal(cwd, toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'PROJECT.md')))),
|
|
@@ -1720,6 +1812,9 @@ function cmdInitPhaseOp(cwd, phase, raw) {
|
|
|
1720
1812
|
padded_phase: phaseNumber ? normalizePhaseName(phaseNumber) : null,
|
|
1721
1813
|
has_research: phaseInfo?.['has_research'] || false,
|
|
1722
1814
|
has_context: phaseInfo?.['has_context'] || false,
|
|
1815
|
+
// #4014 (epic #3473 B4-unreadable): see the parallel field in
|
|
1816
|
+
// cmdInitPlanPhase — additive scope signal adjacent to has_context.
|
|
1817
|
+
context_scope: SCOPE.COMPLETE,
|
|
1723
1818
|
has_plans: (phaseInfo?.['plans']?.length || 0) > 0,
|
|
1724
1819
|
has_verification: phaseInfo?.['has_verification'] || false,
|
|
1725
1820
|
has_reviews: phaseInfo?.['has_reviews'] || false,
|
|
@@ -1734,6 +1829,9 @@ function cmdInitPhaseOp(cwd, phase, raw) {
|
|
|
1734
1829
|
};
|
|
1735
1830
|
if (phaseInfo?.['directory']) {
|
|
1736
1831
|
const phaseDirFull = node_path_1.default.join(cwd, phaseInfo['directory']);
|
|
1832
|
+
// #4014 (epic #3473 B4-unreadable): see the parallel site in
|
|
1833
|
+
// cmdInitPlanPhase — additive only, failure control-flow below unchanged.
|
|
1834
|
+
result['context_scope'] = findContextMdIn(phaseDirFull).scope;
|
|
1737
1835
|
try {
|
|
1738
1836
|
const files = node_fs_1.default.readdirSync(phaseDirFull);
|
|
1739
1837
|
const phaseDirName = node_path_1.default.basename(phaseDirFull);
|
|
@@ -2062,6 +2160,9 @@ function cmdInitManager(cwd, raw) {
|
|
|
2062
2160
|
let hasResearch = false;
|
|
2063
2161
|
let lastActivity = null;
|
|
2064
2162
|
let isActive = false;
|
|
2163
|
+
// #4014 (epic #3473 B4-unreadable): default COMPLETE — no directory at
|
|
2164
|
+
// all (dirMatch not found) is a genuine, not-unreadable answer.
|
|
2165
|
+
let contextScope = SCOPE.COMPLETE;
|
|
2065
2166
|
let completion = buildPhaseCompletionProjection(cwd, phaseNum, null, planCount, summaryCount, _slashRuntime);
|
|
2066
2167
|
try {
|
|
2067
2168
|
// #3185 (ADR-3180 Decision 2) moved this lookup off the
|
|
@@ -2072,6 +2173,17 @@ function cmdInitManager(cwd, raw) {
|
|
|
2072
2173
|
if (dirMatch) {
|
|
2073
2174
|
const fullDir = node_path_1.default.join(phasesDir, dirMatch);
|
|
2074
2175
|
const phaseDirRel = toPosixPath(node_path_1.default.relative(cwd, fullDir));
|
|
2176
|
+
// #4014 (epic #3473 B4-unreadable): this whole block used to swallow
|
|
2177
|
+
// ANY readdirSync failure below into the bare `catch { /* empty */ }`
|
|
2178
|
+
// at the bottom — an unreadable phase directory reported the exact
|
|
2179
|
+
// same `has_context: false` / `disk_status: 'no_directory'` as a
|
|
2180
|
+
// directory that never existed. findContextMdIn's directory-string
|
|
2181
|
+
// form never throws, so it can record the real scope (COMPLETE vs
|
|
2182
|
+
// UNREADABLE) here, BEFORE the pre-existing `fs.readdirSync(fullDir)`
|
|
2183
|
+
// immediately below (unchanged) throws on the exact same unreadable
|
|
2184
|
+
// directory and is caught exactly as before — this line is additive
|
|
2185
|
+
// only, the failure control-flow is untouched.
|
|
2186
|
+
contextScope = findContextMdIn(fullDir).scope;
|
|
2075
2187
|
const phaseFiles = node_fs_1.default.readdirSync(fullDir);
|
|
2076
2188
|
planCount = listPhasePlanFiles(fullDir).length;
|
|
2077
2189
|
summaryCount = listPhaseSummaryFiles(fullDir).length;
|
|
@@ -2146,6 +2258,7 @@ function cmdInitManager(cwd, raw) {
|
|
|
2146
2258
|
...completion,
|
|
2147
2259
|
last_activity: lastActivity,
|
|
2148
2260
|
is_active: isActive,
|
|
2261
|
+
context_scope: contextScope,
|
|
2149
2262
|
});
|
|
2150
2263
|
}
|
|
2151
2264
|
const MAX_NAME_WIDTH = 20;
|
|
@@ -2522,7 +2635,7 @@ function cmdInitTransition(cwd, raw, options = {}) {
|
|
|
2522
2635
|
* location has one source instead of two kept in sync by hand.
|
|
2523
2636
|
* - `debugger_model` — `resolveModelInternal`, which IS what `query
|
|
2524
2637
|
* resolve-model --pick model` returns (`cmdResolveModel`, src/commands.cts).
|
|
2525
|
-
* - `tdd_mode` — the `Boolean(
|
|
2638
|
+
* - `tdd_mode` — the `Boolean(config.tdd_mode)` idiom `cmdInitExecutePhase` and
|
|
2526
2639
|
* `cmdInitPlanPhase` already use. `/gsd:debug` has no `--tdd` flag, so the
|
|
2527
2640
|
* sibling handlers' `options['tdd'] ||` disjunct is deliberately omitted
|
|
2528
2641
|
* rather than carried as a phantom.
|
|
@@ -2540,7 +2653,6 @@ function cmdInitTransition(cwd, raw, options = {}) {
|
|
|
2540
2653
|
*/
|
|
2541
2654
|
function cmdInitDebug(cwd, raw, options = {}) {
|
|
2542
2655
|
const config = loadConfig(cwd);
|
|
2543
|
-
const wf = (config.workflow ?? {});
|
|
2544
2656
|
const result = {
|
|
2545
2657
|
commit_docs: config.commit_docs,
|
|
2546
2658
|
// #2376: absolute — debug.md builds `debug_file_path` as
|
|
@@ -2548,7 +2660,7 @@ function cmdInitDebug(cwd, raw, options = {}) {
|
|
|
2548
2660
|
// own cwd may differ from the orchestrator's.
|
|
2549
2661
|
debug_dir: toPosixPath(planningPaths(cwd).debug),
|
|
2550
2662
|
debugger_model: resolveModelInternal(cwd, 'gsd-debugger'),
|
|
2551
|
-
tdd_mode: Boolean(
|
|
2663
|
+
tdd_mode: Boolean(config.tdd_mode),
|
|
2552
2664
|
diagnose: options['diagnose'] === true,
|
|
2553
2665
|
};
|
|
2554
2666
|
// Additive, optional field — degrades to null while `debug` has no key in
|
|
@@ -2709,7 +2821,7 @@ function cmdInitProgress(cwd, raw, options = {}) {
|
|
|
2709
2821
|
}
|
|
2710
2822
|
}
|
|
2711
2823
|
}
|
|
2712
|
-
phases.sort((a, b) =>
|
|
2824
|
+
phases.sort((a, b) => comparePhaseNum(a['number'], b['number']));
|
|
2713
2825
|
// #3581: the frontier is ROADMAP ORDER, not artifact presence. The disk loop
|
|
2714
2826
|
// above could claim nextPhase from a stray out-of-order artifact directory
|
|
2715
2827
|
// (a phase-9 UAT evidence dir while roadmap phase 8 was pending and
|
|
@@ -2771,6 +2883,10 @@ function cmdInitProgress(cwd, raw, options = {}) {
|
|
|
2771
2883
|
project_exists: pathExistsInternal(cwd, toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'PROJECT.md')))),
|
|
2772
2884
|
roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
2773
2885
|
state_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
|
|
2886
|
+
// #4040: partial-init discriminator (see buildInitCompletenessFields) —
|
|
2887
|
+
// also adds planning_exists / requirements_exists / milestones_exists so
|
|
2888
|
+
// progress.md's init_context routing never has to fall back to Glob.
|
|
2889
|
+
...buildInitCompletenessFields(cwd),
|
|
2774
2890
|
// #2376: absolute — see comment on phase_dir in cmdInitExecutePhase.
|
|
2775
2891
|
state_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
|
|
2776
2892
|
roadmap_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
@@ -3419,6 +3535,7 @@ module.exports = {
|
|
|
3419
3535
|
cmdInitNewProject,
|
|
3420
3536
|
cmdInitNewMilestone,
|
|
3421
3537
|
cmdInitQuick,
|
|
3538
|
+
cmdInitQuickBatch,
|
|
3422
3539
|
cmdInitIngestDocs,
|
|
3423
3540
|
cmdInitOnboard,
|
|
3424
3541
|
cmdInitResume,
|
|
@@ -55,6 +55,148 @@ const { processAttribution } = runtimeArtifactConversion;
|
|
|
55
55
|
// resolveRuntimeArtifactLayout: accessed via module ref (not destructured) so
|
|
56
56
|
// test stubs that monkeypatch the module's exports are seen at call time.
|
|
57
57
|
const { getDirName } = runtimeNamePolicy;
|
|
58
|
+
function withInstallerPackageSource(configDir, fn) {
|
|
59
|
+
// Reuse the existing compatibility-marker contract through the existing fs
|
|
60
|
+
// seam. The marker exists only in this synchronous call tree: no disk state,
|
|
61
|
+
// layout export, stage argument, or caller-settable authority flag is added.
|
|
62
|
+
const markerPath = node_path_1.default.resolve(configDir, '.gsd-source');
|
|
63
|
+
const packageCommandsRoot = runtimeArtifactLayout.findInstallSourceRoot();
|
|
64
|
+
const markerBytes = Buffer.from(packageCommandsRoot + '\n');
|
|
65
|
+
const base = installFs();
|
|
66
|
+
const overlay = {
|
|
67
|
+
...base,
|
|
68
|
+
existsSync: (candidate) => node_path_1.default.resolve(candidate) === markerPath || base.existsSync(candidate),
|
|
69
|
+
lstatSync: (candidate) => node_path_1.default.resolve(candidate) === markerPath
|
|
70
|
+
? { isFile: () => true, isDirectory: () => false, isSymbolicLink: () => false }
|
|
71
|
+
: base.lstatSync(candidate),
|
|
72
|
+
readFileSync: ((candidate, encoding) => {
|
|
73
|
+
if (node_path_1.default.resolve(candidate) !== markerPath) {
|
|
74
|
+
return encoding ? base.readFileSync(candidate, encoding) : base.readFileSync(candidate);
|
|
75
|
+
}
|
|
76
|
+
return encoding ? markerBytes.toString(encoding) : Buffer.from(markerBytes);
|
|
77
|
+
}),
|
|
78
|
+
};
|
|
79
|
+
return withInstallFs(overlay, fn);
|
|
80
|
+
}
|
|
81
|
+
function isRuntimeSurfaceSourceUnavailable(message) {
|
|
82
|
+
return message.startsWith('Runtime Surface source is unavailable or incomplete for ') &&
|
|
83
|
+
message.endsWith('install or upgrade gsd-core before materializing this surface.');
|
|
84
|
+
}
|
|
85
|
+
function assertCorpusTreeHasNoSymlinks(root) {
|
|
86
|
+
if (!installFs().existsSync(root))
|
|
87
|
+
return;
|
|
88
|
+
const stat = installFs().lstatSync(root);
|
|
89
|
+
if (stat.isSymbolicLink()) {
|
|
90
|
+
throw new Error(`Runtime Surface corpus path is a symlink: ${root}`);
|
|
91
|
+
}
|
|
92
|
+
if (!stat.isDirectory())
|
|
93
|
+
return;
|
|
94
|
+
for (const name of installFs().readdirSync(root)) {
|
|
95
|
+
assertCorpusTreeHasNoSymlinks(node_path_1.default.join(root, name));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function previousOwnedCorpusFiles(configDir, prefix) {
|
|
99
|
+
try {
|
|
100
|
+
const files = installerMigrations.readInstallManifest(configDir).files;
|
|
101
|
+
return Object.keys(files)
|
|
102
|
+
.filter((entry) => entry.startsWith(prefix))
|
|
103
|
+
.map((entry) => entry.slice(prefix.length))
|
|
104
|
+
.filter((entry) => entry !== '' && !node_path_1.default.posix.isAbsolute(entry) && !entry.split('/').some((part) => part === '' || part === '.' || part === '..'));
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// An absent or unreadable prior manifest provides no ownership evidence.
|
|
108
|
+
// Preserve existing entries rather than guessing that they are stale.
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function pruneEmptyCorpusParents(start, stop) {
|
|
113
|
+
let current = node_path_1.default.dirname(start);
|
|
114
|
+
while (current !== stop && current.startsWith(stop + node_path_1.default.sep)) {
|
|
115
|
+
if (installFs().readdirSync(current).length > 0)
|
|
116
|
+
return;
|
|
117
|
+
installFs().rmdirSync(current);
|
|
118
|
+
current = node_path_1.default.dirname(current);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function syncRuntimeSurfaceCorpus(source, destination, configDir, manifestPrefix) {
|
|
122
|
+
if (hasExistingSymlinkBetween(node_path_1.default.resolve(configDir), destination, { allowOptInFollow: isSymlinkedDestOptIn() })) {
|
|
123
|
+
throw new Error(`syncRuntimeSurfaceCorpus: destination "${destination}" contains a symlink the install root "${configDir}" does not trust — refusing to write.`);
|
|
124
|
+
}
|
|
125
|
+
assertCorpusTreeHasNoSymlinks(destination);
|
|
126
|
+
// Remove only paths the previous manifest proves GSD owned and which the
|
|
127
|
+
// executing package no longer ships. Unknown neighbouring files survive.
|
|
128
|
+
for (const relative of previousOwnedCorpusFiles(configDir, manifestPrefix)) {
|
|
129
|
+
const sourceEntry = node_path_1.default.join(source, ...relative.split('/'));
|
|
130
|
+
let sourceIsFile = false;
|
|
131
|
+
try {
|
|
132
|
+
sourceIsFile = installFs().lstatSync(sourceEntry).isFile();
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
sourceIsFile = false;
|
|
136
|
+
}
|
|
137
|
+
if (sourceIsFile)
|
|
138
|
+
continue;
|
|
139
|
+
const target = node_path_1.default.join(destination, ...relative.split('/'));
|
|
140
|
+
if (!installFs().existsSync(target))
|
|
141
|
+
continue;
|
|
142
|
+
const targetStat = installFs().lstatSync(target);
|
|
143
|
+
if (!targetStat.isFile()) {
|
|
144
|
+
throw new Error(`Runtime Surface corpus ownership conflict at ${target}`);
|
|
145
|
+
}
|
|
146
|
+
installFs().rmSync(target, { force: true });
|
|
147
|
+
pruneEmptyCorpusParents(target, destination);
|
|
148
|
+
}
|
|
149
|
+
installFs().mkdirSync(node_path_1.default.dirname(destination), { recursive: true });
|
|
150
|
+
installFs().cpSync(source, destination, { recursive: true });
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Provision the raw, installation-owned input needed to re-materialize a
|
|
154
|
+
* global Runtime Surface after the executing package tree disappears.
|
|
155
|
+
*
|
|
156
|
+
* The corpus deliberately lives below the already-installed `gsd-core/`
|
|
157
|
+
* tree and is accepted only after its manifest ownership and hashes verify.
|
|
158
|
+
* The compatibility marker does not replace that installed-corpus authority.
|
|
159
|
+
*/
|
|
160
|
+
function provisionRuntimeSurfaceCorpus(layout, configDir, scope) {
|
|
161
|
+
const required = new Set();
|
|
162
|
+
if ((0, install_scope_cjs_1.isGlobalScope)(scope)) {
|
|
163
|
+
for (const kind of layout.kinds) {
|
|
164
|
+
if (kind.kind === 'commands' || kind.kind === 'skills')
|
|
165
|
+
required.add('commands');
|
|
166
|
+
if (kind.kind === 'agents' || kind.kind === 'kimi-agents')
|
|
167
|
+
required.add('agents');
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (required.size === 0)
|
|
171
|
+
return;
|
|
172
|
+
const corpusRoot = node_path_1.default.join(configDir, 'gsd-core');
|
|
173
|
+
if (required.has('commands')) {
|
|
174
|
+
const source = runtimeArtifactLayout.findInstallSourceRoot();
|
|
175
|
+
const destination = node_path_1.default.join(corpusRoot, 'commands', 'gsd');
|
|
176
|
+
syncRuntimeSurfaceCorpus(source, destination, configDir, 'gsd-core/commands/gsd/');
|
|
177
|
+
}
|
|
178
|
+
if (required.has('agents')) {
|
|
179
|
+
const source = node_path_1.default.join(executingPackageRoot(), 'agents');
|
|
180
|
+
const destination = node_path_1.default.join(corpusRoot, 'agents');
|
|
181
|
+
syncRuntimeSurfaceCorpus(source, destination, configDir, 'gsd-core/agents/');
|
|
182
|
+
}
|
|
183
|
+
const markerFile = _hostBehaviors(layout.runtime).sourceMarkerFile;
|
|
184
|
+
if (typeof markerFile === 'string' && markerFile !== '' && required.has('commands')) {
|
|
185
|
+
try {
|
|
186
|
+
const markerPath = runtimeArtifactInstallPlan.assertDestWithinConfigHome(configDir, markerFile);
|
|
187
|
+
if (hasExistingSymlinkBetween(node_path_1.default.resolve(configDir), markerPath, { allowOptInFollow: isSymlinkedDestOptIn() })) {
|
|
188
|
+
throw new Error(`compatibility marker "${markerPath}" contains an untrusted symlink`);
|
|
189
|
+
}
|
|
190
|
+
installFs().writeFileSync(markerPath, node_path_1.default.join(corpusRoot, 'commands', 'gsd') + '\n', 'utf8');
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
// The existing installer marker writer owns the user-facing warning and
|
|
194
|
+
// keeps marker failure non-fatal. The installed corpus remains usable
|
|
195
|
+
// without the compatibility marker.
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function executingPackageRoot() { return node_path_1.default.dirname(node_path_1.default.dirname(runtimeArtifactLayout.findInstallSourceRoot())); }
|
|
58
200
|
// ---------------------------------------------------------------------------
|
|
59
201
|
// USER_OWNED_ARTIFACTS
|
|
60
202
|
// ---------------------------------------------------------------------------
|
|
@@ -977,6 +1119,7 @@ function _runLegacyUninstallCleanup(runtime, configDir, scope = 'global') {
|
|
|
977
1119
|
*/
|
|
978
1120
|
function installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile, resolveAttribution = () => undefined, capabilityRegistry, deps = {}) {
|
|
979
1121
|
return withInstallFs(deps.fs, () => {
|
|
1122
|
+
const layout = runtimeArtifactLayout.resolveRuntimeArtifactLayout(runtime, configDir, scope, capabilityRegistry);
|
|
980
1123
|
// A removed descriptor kind is no longer visited by the layout loop, so it
|
|
981
1124
|
// cannot prune its own previous output. Clean manifest-proven retired files
|
|
982
1125
|
// before materializing the current layout (#2644).
|
|
@@ -986,6 +1129,7 @@ function installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile, res
|
|
|
986
1129
|
// generic layout-driven loop below, mirroring the bespoke install path that
|
|
987
1130
|
// previously lived inline in bin/install.js.
|
|
988
1131
|
const behaviors = _hostBehaviors(runtime);
|
|
1132
|
+
const projectDir = scope === 'global' ? process.cwd() : configDir;
|
|
989
1133
|
if (behaviors.combinedFamilyInstall) {
|
|
990
1134
|
// #2329: combined-family runtimes (OpenCode/Kilo) bypass
|
|
991
1135
|
// _runLegacyInstallMigrations below entirely (early return), so their
|
|
@@ -994,17 +1138,24 @@ function installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile, res
|
|
|
994
1138
|
// #2874 design row 2: this early return must ALSO return an executed
|
|
995
1139
|
// plan — installOpencodeFamilyArtifacts reports what it wrote, so a
|
|
996
1140
|
// whole runtime family returning undefined is no longer a hole.
|
|
997
|
-
|
|
1141
|
+
// An injected filesystem supplies its own hermetic corpus fixture. The
|
|
1142
|
+
// real installer is the authority that refreshes package bytes into the
|
|
1143
|
+
// durable installed corpus; attempting that cross-filesystem copy through
|
|
1144
|
+
// an in-memory destination adapter would read from the wrong filesystem.
|
|
1145
|
+
if (!deps.fs)
|
|
1146
|
+
provisionRuntimeSurfaceCorpus(layout, configDir, scope);
|
|
1147
|
+
return installOpencodeFamilyArtifacts(runtime, configDir, scope, resolvedProfile, resolveAttribution, behaviors, capabilityRegistry, deps.packageRoot, projectDir);
|
|
998
1148
|
}
|
|
999
1149
|
// Legacy cleanup before layout-driven writes
|
|
1000
1150
|
_runLegacyInstallMigrations(runtime, configDir, scope);
|
|
1001
|
-
|
|
1151
|
+
if (!deps.fs)
|
|
1152
|
+
provisionRuntimeSurfaceCorpus(layout, configDir, scope);
|
|
1002
1153
|
// #3712: a global `home` override escapes the sandboxed configDir. Refuse to
|
|
1003
1154
|
// execute when a test run would land that escape in the developer's real home.
|
|
1004
1155
|
testHomeGuard.assertTestHomeSandboxed('installRuntimeArtifacts', runtime, layout?.kinds, {
|
|
1005
1156
|
os: deps.os, env: deps.env,
|
|
1006
1157
|
});
|
|
1007
|
-
const
|
|
1158
|
+
const createPlan = () => runtimeArtifactInstallPlan.createRuntimeArtifactInstallPlan({
|
|
1008
1159
|
// `Layout` is structurally identical across the layout/install-plan .cjs
|
|
1009
1160
|
// modules but nominally distinct to tsc (untyped .cjs boundary) — bridge it.
|
|
1010
1161
|
layout: layout,
|
|
@@ -1012,7 +1163,16 @@ function installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile, res
|
|
|
1012
1163
|
homedir: () => node_os_1.default.homedir(),
|
|
1013
1164
|
platform: process.platform,
|
|
1014
1165
|
resolveAttribution,
|
|
1166
|
+
projectDir,
|
|
1015
1167
|
});
|
|
1168
|
+
let planResult = createPlan();
|
|
1169
|
+
if (scope === 'global' &&
|
|
1170
|
+
!planResult.ok &&
|
|
1171
|
+
planResult.kind === 'stage_failed' &&
|
|
1172
|
+
planResult.cleanupDirs.length === 0 &&
|
|
1173
|
+
isRuntimeSurfaceSourceUnavailable(planResult.message)) {
|
|
1174
|
+
planResult = withInstallerPackageSource(configDir, createPlan);
|
|
1175
|
+
}
|
|
1016
1176
|
const cleanupDirs = planResult.ok ? planResult.plan.cleanupDirs : planResult.cleanupDirs;
|
|
1017
1177
|
// #2874 row 1/4/5: per-kind executed-plan entries, appended only as the
|
|
1018
1178
|
// loop below actually finishes writing each kind — a kind that throws
|
|
@@ -1176,8 +1336,10 @@ function installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile, res
|
|
|
1176
1336
|
// is safe even when configDir has no .gsd-source marker (artifactLayout: []).
|
|
1177
1337
|
let nativePluginInstalled = false;
|
|
1178
1338
|
if (behaviors.nativePlugin) {
|
|
1179
|
-
|
|
1180
|
-
|
|
1339
|
+
// Native plugin sources live only in the executing package, never in the
|
|
1340
|
+
// durable Runtime Surface corpus. Do not derive this package root from a
|
|
1341
|
+
// config-scoped provider that may now correctly resolve installed input.
|
|
1342
|
+
const src = deps.packageRoot ?? executingPackageRoot();
|
|
1181
1343
|
_installNativePluginIfDeclared(runtime, configDir, behaviors, src);
|
|
1182
1344
|
nativePluginInstalled = true;
|
|
1183
1345
|
}
|
|
@@ -1388,12 +1550,13 @@ function installOpencodeFamilySkills(runtime, targetDir, rawCommandsDir, pathPre
|
|
|
1388
1550
|
* @param capabilityRegistry - #2362: optional composed capability registry, threaded
|
|
1389
1551
|
* straight through to resolveRuntimeArtifactLayout (unused by the agents kind today,
|
|
1390
1552
|
* but kept for signature parity with the skills/commands siblings on this call tree)
|
|
1553
|
+
* @param projectDir - project/config discovery root, distinct from the artifact destination
|
|
1391
1554
|
* @returns `{ sourceDir, destDir }` describing what was written, or `null` when the
|
|
1392
1555
|
* runtime's layout declares no `agents` kind.
|
|
1393
1556
|
*/
|
|
1394
|
-
function installAgentsKindStandalone(runtime, targetDir, scope, resolvedProfile, pathPrefix, resolveAttribution = () => undefined, capabilityRegistry) {
|
|
1557
|
+
function installAgentsKindStandalone(runtime, targetDir, scope, resolvedProfile, pathPrefix, resolveAttribution = () => undefined, capabilityRegistry, projectDir) {
|
|
1395
1558
|
const layout = runtimeArtifactLayout.resolveRuntimeArtifactLayout(runtime, targetDir, scope, capabilityRegistry);
|
|
1396
|
-
const agentsKindEntry = layout.kinds.find((
|
|
1559
|
+
const agentsKindEntry = layout.kinds.find((kind) => kind.kind === 'agents');
|
|
1397
1560
|
if (!agentsKindEntry)
|
|
1398
1561
|
return null;
|
|
1399
1562
|
// #3712: this writer selects `agentsKindEntry.home` over targetDir below and then
|
|
@@ -1408,8 +1571,16 @@ function installAgentsKindStandalone(runtime, targetDir, scope, resolvedProfile,
|
|
|
1408
1571
|
// for the generic layout-driven loop (runtime-artifact-install-plan.cts) —
|
|
1409
1572
|
// targetDir IS the install root the inline agent loop called `targetDir`.
|
|
1410
1573
|
const attribution = resolveAttribution ? resolveAttribution(runtime) : undefined;
|
|
1411
|
-
const agentCtx = { runtime, pathPrefix, attribution, targetDir };
|
|
1412
|
-
|
|
1574
|
+
const agentCtx = { runtime, pathPrefix, attribution, targetDir, projectDir: projectDir ?? targetDir };
|
|
1575
|
+
let stagedDir;
|
|
1576
|
+
try {
|
|
1577
|
+
stagedDir = agentsKindEntry.stage(resolvedProfile, agentCtx);
|
|
1578
|
+
}
|
|
1579
|
+
catch (err) {
|
|
1580
|
+
if (scope !== 'global' || !isRuntimeSurfaceSourceUnavailable(err.message))
|
|
1581
|
+
throw err;
|
|
1582
|
+
stagedDir = withInstallerPackageSource(targetDir, () => agentsKindEntry.stage(resolvedProfile, agentCtx));
|
|
1583
|
+
}
|
|
1413
1584
|
const stagedAgentFiles = installFs().existsSync(stagedDir)
|
|
1414
1585
|
? installFs().readdirSync(stagedDir).filter((f) => f.endsWith('.md'))
|
|
1415
1586
|
: [];
|
|
@@ -1670,12 +1841,13 @@ function _migrateLegacyOpencodeCommandDir(runtime, configDir, behaviors) {
|
|
|
1670
1841
|
* installOpencodeFamilySkills so an installed third-party capability skill
|
|
1671
1842
|
* materializes for this combined-family (OpenCode/Kilo) install path too.
|
|
1672
1843
|
* Absent -> no third-party skills staged (fail closed).
|
|
1844
|
+
* @param projectDir - project/config discovery root, distinct from configDir for global installs
|
|
1673
1845
|
* @returns #2874 design row 2: an executed-plan value, same top-level shape
|
|
1674
1846
|
* (`runtime`/`scope`/`kinds`/`cleanup`/`postSteps`) as the generic
|
|
1675
1847
|
* `installRuntimeArtifacts` branch — this was the one early return a
|
|
1676
1848
|
* `void`-shaped hole survived unnoticed in.
|
|
1677
1849
|
*/
|
|
1678
|
-
function installOpencodeFamilyArtifacts(runtime, configDir, scope, resolvedProfile, resolveAttribution = () => undefined, behaviors = {}, capabilityRegistry) {
|
|
1850
|
+
function installOpencodeFamilyArtifacts(runtime, configDir, scope, resolvedProfile, resolveAttribution = () => undefined, behaviors = {}, capabilityRegistry, packageRoot, projectDir) {
|
|
1679
1851
|
// #2870: `scope` keeps its exported required `string` signature (no
|
|
1680
1852
|
// signature change). It is always the `installRuntimeArtifacts`-forwarded
|
|
1681
1853
|
// 'global' | 'local' literal produced by bin/install.js's scope-resolution
|
|
@@ -1688,7 +1860,7 @@ function installOpencodeFamilyArtifacts(runtime, configDir, scope, resolvedProfi
|
|
|
1688
1860
|
// into stageSkillsForProfile/stageSkillsForRuntimeAsSkills. The repo/package
|
|
1689
1861
|
// root (needed below for the native plugin source) is two levels up.
|
|
1690
1862
|
const commandsGsdDir = runtimeArtifactLayout.findInstallSourceRoot(configDir);
|
|
1691
|
-
const src =
|
|
1863
|
+
const src = packageRoot ?? executingPackageRoot();
|
|
1692
1864
|
const rawCommandsDir = installProfiles.stageSkillsForProfile(commandsGsdDir, resolvedProfile);
|
|
1693
1865
|
const pathPrefix = runtimeArtifactConversion._computePathPrefix({
|
|
1694
1866
|
isGlobal,
|
|
@@ -1711,7 +1883,7 @@ function installOpencodeFamilyArtifacts(runtime, configDir, scope, resolvedProfi
|
|
|
1711
1883
|
// generic layout-driven loop uses (see installAgentsKindStandalone's own
|
|
1712
1884
|
// doc). A `null` result means this runtime's layout declares no `agents`
|
|
1713
1885
|
// kind — nothing written, nothing reported (no #1879-F15 inert claim).
|
|
1714
|
-
const agentsResult = installAgentsKindStandalone(runtime, configDir, scope, resolvedProfile, pathPrefix, resolveAttribution, capabilityRegistry);
|
|
1886
|
+
const agentsResult = installAgentsKindStandalone(runtime, configDir, scope, resolvedProfile, pathPrefix, resolveAttribution, capabilityRegistry, projectDir);
|
|
1715
1887
|
_installNativePluginIfDeclared(runtime, configDir, behaviors, src);
|
|
1716
1888
|
// #2874 design row 2: report what this combined-family install wrote,
|
|
1717
1889
|
// mirroring the generic branch's top-level shape. `cleanup` is `[]` — this
|