@opengsd/gsd-core 1.13.0 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/agents/gsd-advisor-researcher.compact.md +85 -0
- package/agents/gsd-ai-researcher.compact.md +96 -0
- package/agents/gsd-assumptions-analyzer.compact.md +81 -0
- package/agents/gsd-code-fixer.compact.md +458 -0
- package/agents/gsd-code-fixer.md +5 -5
- package/agents/gsd-code-reviewer.compact.md +269 -0
- package/agents/gsd-code-reviewer.md +15 -3
- package/agents/gsd-codebase-mapper.compact.md +760 -0
- package/agents/gsd-debug-session-manager.compact.md +345 -0
- package/agents/gsd-doc-classifier.compact.md +192 -0
- package/agents/gsd-doc-synthesizer.compact.md +200 -0
- package/agents/gsd-doc-verifier.compact.md +143 -0
- package/agents/gsd-doc-writer.compact.md +440 -0
- package/agents/gsd-dom-verifier.compact.md +138 -0
- package/agents/gsd-domain-researcher.compact.md +141 -0
- package/agents/gsd-eval-auditor.compact.md +160 -0
- package/agents/gsd-eval-planner.compact.md +137 -0
- package/agents/gsd-framework-selector.compact.md +82 -0
- package/agents/gsd-integration-checker.compact.md +245 -0
- package/agents/gsd-intel-updater.compact.md +226 -0
- package/agents/gsd-mempalace-curator.compact.md +45 -0
- package/agents/gsd-nyquist-auditor.compact.md +179 -0
- package/agents/gsd-pattern-mapper.compact.md +275 -0
- package/agents/gsd-project-researcher.compact.md +587 -0
- package/agents/gsd-research-synthesizer.compact.md +212 -0
- package/agents/gsd-roadmapper.compact.md +454 -0
- package/agents/gsd-roadmapper.md +13 -0
- package/agents/gsd-security-auditor.compact.md +162 -0
- package/agents/gsd-ui-auditor.compact.md +404 -0
- package/agents/gsd-ui-checker.compact.md +277 -0
- package/agents/gsd-ui-researcher.compact.md +282 -0
- package/agents/gsd-user-profiler.compact.md +108 -0
- package/bin/install.js +206 -68
- package/commands/gsd/cleanup.md +1 -0
- package/commands/gsd/code-review.md +2 -1
- package/commands/gsd/complete-milestone.md +1 -0
- package/commands/gsd/config.md +1 -0
- package/commands/gsd/debug.md +1 -0
- package/commands/gsd/graphify.md +1 -0
- package/commands/gsd/health.md +1 -0
- package/commands/gsd/mempalace-capture.md +1 -0
- package/commands/gsd/mempalace-recall.md +1 -0
- package/commands/gsd/new-milestone.md +1 -0
- package/commands/gsd/new-project.md +1 -0
- package/commands/gsd/next.md +1 -0
- package/commands/gsd/pause-work.md +1 -0
- package/commands/gsd/phase.md +1 -0
- package/commands/gsd/pr-branch.md +1 -0
- package/commands/gsd/resume-work.md +1 -0
- package/commands/gsd/review-backlog.md +1 -0
- package/commands/gsd/settings.md +2 -1
- package/commands/gsd/stats.md +1 -0
- package/commands/gsd/thread.md +1 -0
- package/commands/gsd/workspace.md +1 -0
- package/commands/gsd/workstreams.md +1 -0
- package/gsd-core/bin/check-latest-version.cjs +8 -3
- package/gsd-core/bin/gsd-tools.cjs +338 -125
- package/gsd-core/bin/lib/adr-parser.cjs +1 -1
- package/gsd-core/bin/lib/artifacts.cjs +2 -1
- package/gsd-core/bin/lib/audit.cjs +39 -22
- package/gsd-core/bin/lib/broken-windows.cjs +168 -49
- package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
- package/gsd-core/bin/lib/capability-loader.cjs +135 -1
- package/gsd-core/bin/lib/capability-registry.cjs +79 -67
- package/gsd-core/bin/lib/capability-source.cjs +19 -2
- package/gsd-core/bin/lib/capability-validator.cjs +14 -1
- package/gsd-core/bin/lib/check-command-router.cjs +113 -36
- package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
- package/gsd-core/bin/lib/commands.cjs +650 -72
- package/gsd-core/bin/lib/config-loader.cjs +1 -0
- package/gsd-core/bin/lib/config.cjs +153 -38
- package/gsd-core/bin/lib/coverage.cjs +1 -1
- package/gsd-core/bin/lib/decisions.cjs +137 -34
- package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
- package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +12 -1
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
- package/gsd-core/bin/lib/init.cjs +409 -47
- package/gsd-core/bin/lib/install-engine.cjs +16 -3
- package/gsd-core/bin/lib/install-profiles.cjs +14 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +33 -4
- package/gsd-core/bin/lib/loop-resolver.cjs +50 -31
- package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
- package/gsd-core/bin/lib/milestone.cjs +19 -8
- package/gsd-core/bin/lib/model-resolver.cjs +101 -10
- package/gsd-core/bin/lib/phase-command-router.cjs +7 -1
- package/gsd-core/bin/lib/phase-id.cjs +161 -22
- package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
- package/gsd-core/bin/lib/phase.cjs +167 -63
- package/gsd-core/bin/lib/planning-inspect.cjs +34 -18
- package/gsd-core/bin/lib/planning-snapshot.cjs +61 -12
- package/gsd-core/bin/lib/planning-workspace.cjs +50 -1
- package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
- package/gsd-core/bin/lib/quick-batch.cjs +1 -1
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
- package/gsd-core/bin/lib/research-store.cjs +11 -12
- package/gsd-core/bin/lib/review-lane-invocation.cjs +23 -0
- package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
- package/gsd-core/bin/lib/roadmap-parser.cjs +56 -15
- package/gsd-core/bin/lib/roadmap.cjs +108 -14
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +27 -10
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +12 -3
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +13 -5
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +193 -4
- package/gsd-core/bin/lib/security.cjs +126 -7
- package/gsd-core/bin/lib/state-document.cjs +130 -28
- package/gsd-core/bin/lib/state-md-schema.cjs +21 -14
- package/gsd-core/bin/lib/state-transition.cjs +142 -28
- package/gsd-core/bin/lib/state.cjs +223 -27
- package/gsd-core/bin/lib/surface.cjs +60 -2
- package/gsd-core/bin/lib/task-command-router.cjs +12 -6
- package/gsd-core/bin/lib/uat.cjs +1 -1
- package/gsd-core/bin/lib/update-context.cjs +30 -24
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
- package/gsd-core/bin/lib/verification.cjs +47 -15
- package/gsd-core/bin/lib/verify-command-grounding.cjs +1 -1
- package/gsd-core/bin/lib/verify.cjs +188 -23
- package/gsd-core/bin/lib/workstream-inventory.cjs +1 -0
- package/gsd-core/bin/lib/worktree-safety.cjs +13 -7
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +5 -0
- package/gsd-core/bin/verify-reapply-patches.cjs +439 -80
- package/gsd-core/references/compact-content-gate.md +66 -0
- package/gsd-core/references/loop-hook-dispatch.md +18 -0
- package/gsd-core/references/model-profiles.md +12 -3
- package/gsd-core/references/planning-config.md +3 -0
- package/gsd-core/references/tdd.md +5 -2
- package/gsd-core/references/thinking-models-planning.md +18 -2
- package/gsd-core/references/verification-patterns.md +17 -4
- package/gsd-core/references/worktree-path-safety.md +112 -2
- package/gsd-core/templates/README.md +7 -1
- package/gsd-core/templates/state.md +6 -3
- package/gsd-core/templates/summary.compact.md +212 -0
- package/gsd-core/templates/user-setup.compact.md +199 -0
- package/gsd-core/templates/user-setup.md +0 -9
- package/gsd-core/workflows/add-todo.md +3 -2
- package/gsd-core/workflows/autonomous.md +13 -10
- package/gsd-core/workflows/check-todos.md +4 -2
- package/gsd-core/workflows/cleanup.md +3 -1
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +7 -0
- package/gsd-core/workflows/code-review-fix.md +3 -3
- package/gsd-core/workflows/code-review.md +156 -30
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
- package/gsd-core/workflows/complete-milestone.md +39 -262
- package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
- package/gsd-core/workflows/docs-update.md +14 -155
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +18 -3
- package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +7 -2
- package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
- package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
- package/gsd-core/workflows/execute-phase.md +53 -152
- package/gsd-core/workflows/execute-plan.md +20 -7
- package/gsd-core/workflows/help/modes/full.compact.md +398 -0
- package/gsd-core/workflows/help.md +1 -1
- package/gsd-core/workflows/map-codebase.md +50 -3
- package/gsd-core/workflows/new-milestone.md +54 -12
- package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
- package/gsd-core/workflows/new-project.md +32 -202
- package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
- package/gsd-core/workflows/plan-phase.md +22 -181
- package/gsd-core/workflows/pr-branch.md +19 -7
- package/gsd-core/workflows/quick.md +8 -1
- package/gsd-core/workflows/reapply-patches.md +77 -3
- package/gsd-core/workflows/settings.md +18 -5
- package/gsd-core/workflows/update.md +7 -5
- package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
- package/gsd-core/workflows/verify-work.md +20 -180
- package/hooks/dist/gsd-agent-isolation-guard.js +42 -16
- package/hooks/dist/gsd-context-monitor.js +88 -15
- package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
- package/hooks/dist/gsd-secret-read-guard.js +44 -18
- package/hooks/dist/gsd-statusline.js +11 -7
- package/hooks/dist/gsd-validate-commit.sh +34 -4
- package/hooks/dist/gsd-worktree-path-guard.js +25 -14
- package/hooks/dist/gsd-write-guard.js +46 -1
- package/hooks/dist/lib/dispatch-identity.js +187 -0
- package/hooks/dist/lib/filename-classification.js +64 -0
- package/hooks/dist/lib/isolation-deny-reason.js +53 -1
- package/hooks/dist/lib/isolation-sentinel.js +58 -19
- package/hooks/gsd-agent-isolation-guard.js +42 -16
- package/hooks/gsd-context-monitor.js +88 -15
- package/hooks/gsd-cursor-subagent-start.js +34 -14
- package/hooks/gsd-secret-read-guard.js +44 -18
- package/hooks/gsd-statusline.js +11 -7
- package/hooks/gsd-validate-commit.sh +34 -4
- package/hooks/gsd-worktree-path-guard.js +25 -14
- package/hooks/gsd-write-guard.js +46 -1
- package/hooks/lib/dispatch-identity.js +187 -0
- package/hooks/lib/filename-classification.js +64 -0
- package/hooks/lib/isolation-deny-reason.js +53 -1
- package/hooks/lib/isolation-sentinel.js +58 -19
- package/package.json +10 -6
- package/scripts/benchmark-compact-content-variants.cjs +298 -0
- package/scripts/benchmark-compact-content.cjs +368 -0
- package/scripts/check-contract-drift.cjs +4 -1
- package/scripts/check-env.cjs +36 -8
- package/scripts/check-glossary-refs.cjs +25 -21
- package/scripts/ci-next-health.cjs +271 -0
- package/scripts/ci-prepare-test-scope.cjs +7 -7
- package/scripts/ci-test-scope.cjs +126 -20
- package/scripts/ci-timeout-report.cjs +1 -1
- package/scripts/diff-touches-shipped-paths.cjs +1 -1
- package/scripts/docs-guard-registry.cjs +7 -2
- package/scripts/gen-adr-index.cjs +8 -2
- package/scripts/gen-inventory-manifest.cjs +12 -0
- package/scripts/gen-platform-conformance-tier.cjs +557 -0
- package/scripts/lib/drift-scan.cjs +1 -1
- package/scripts/lib/macos-conformance-tier.generated.cjs +210 -0
- package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
- package/scripts/lib/platform-conformance-tier.generated.cjs +276 -0
- package/scripts/lib/suite-detection.cjs +32 -0
- package/scripts/lint-allowed-tools-parity.cjs +221 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +19 -2
- package/scripts/lint-phase-id-drift.cjs +338 -13
- package/scripts/lint-response-language-coverage.cjs +9 -3
- package/scripts/lint-source-test-name-collision.cjs +1 -1
- package/scripts/lint-test-file-count.allowlist.json +1 -0
- package/scripts/lint-vendored-deps.cjs +128 -17
- package/scripts/lint-workflow-shellcheck-baseline.json +85 -0
- package/scripts/prompt-injection-scan.sh +14 -0
- package/scripts/workflow-size.cjs +139 -0
- package/skills/gsd-cleanup/SKILL.md +1 -0
- package/skills/gsd-code-review/SKILL.md +2 -1
- package/skills/gsd-complete-milestone/SKILL.md +1 -0
- package/skills/gsd-config/SKILL.md +1 -0
- package/skills/gsd-debug/SKILL.md +1 -0
- package/skills/gsd-graphify/SKILL.md +1 -0
- package/skills/gsd-health/SKILL.md +1 -0
- package/skills/gsd-mempalace-capture/SKILL.md +1 -0
- package/skills/gsd-mempalace-recall/SKILL.md +1 -0
- package/skills/gsd-new-milestone/SKILL.md +1 -0
- package/skills/gsd-new-project/SKILL.md +1 -0
- package/skills/gsd-next/SKILL.md +1 -0
- package/skills/gsd-pause-work/SKILL.md +1 -0
- package/skills/gsd-phase/SKILL.md +1 -0
- package/skills/gsd-pr-branch/SKILL.md +1 -0
- package/skills/gsd-resume-work/SKILL.md +1 -0
- package/skills/gsd-review-backlog/SKILL.md +1 -0
- package/skills/gsd-settings/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +1 -0
- package/skills/gsd-thread/SKILL.md +1 -0
- package/skills/gsd-workspace/SKILL.md +1 -0
- package/skills/gsd-workstreams/SKILL.md +1 -0
- package/vscode/package.json +1 -1
- package/gsd-core/templates/claude-md.md +0 -145
- package/gsd-core/templates/codebase/concerns.md +0 -310
- package/gsd-core/templates/codebase/conventions.md +0 -307
- package/gsd-core/templates/codebase/integrations.md +0 -280
- package/gsd-core/templates/codebase/structure.md +0 -285
- package/gsd-core/templates/codebase/testing.md +0 -480
- package/gsd-core/templates/debug-subagent-prompt.md +0 -91
- package/gsd-core/templates/discovery.md +0 -146
|
@@ -15,6 +15,7 @@ const node_os_1 = __importDefault(require("node:os"));
|
|
|
15
15
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
16
16
|
const clock_cjs_1 = require("./clock.cjs");
|
|
17
17
|
const pattern_cjs_1 = require("./pattern.cjs");
|
|
18
|
+
const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
|
|
18
19
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- io.cjs is an export= CommonJS module
|
|
19
20
|
const io = require("./io.cjs");
|
|
20
21
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- config-loader.cjs is an export= CommonJS module
|
|
@@ -86,9 +87,9 @@ const { resolveModelInternal, resolveGranularityInternal, assertValidGranularity
|
|
|
86
87
|
const { findPhaseInternal, listMilestonePhaseDirs, listAllPhaseDirs } = phaseLocator;
|
|
87
88
|
const { getRoadmapPhaseInternal, getMilestoneInfo, stripShippedMilestones, extractCurrentMilestone, } = roadmapParser;
|
|
88
89
|
const { pathExistsInternal, generateSlugInternal, toPosixPath } = coreUtils;
|
|
89
|
-
const { comparePhaseNum, normalizePhaseName, matchPhaseDirs, stripProjectCodePrefix, PHASE_NUMBER_TOKEN_SOURCE, isForeignPrefixedPhaseQuery, isSentinelPhaseId, extractPhaseToken, scopeToPhase } = phaseId;
|
|
90
|
+
const { comparePhaseNum, normalizePhaseName, matchPhaseDirs, stripProjectCodePrefix, PHASE_NUMBER_TOKEN_SOURCE, isForeignPrefixedPhaseQuery, isSentinelPhaseId, extractPhaseToken, scopeToPhase, renderPhaseBranchName } = phaseId;
|
|
90
91
|
const { pruneOrphanedWorktrees } = worktreeSafety;
|
|
91
|
-
const { planningPaths, planningDir, planningRoot, listAvailableWorkstreams, peekActiveWorkstream, diagnoseUnresolvedActiveWorkstream, describeUnresolvedWorkstreamReason, findContextMdIn, } = planningWorkspace;
|
|
92
|
+
const { planningPaths, planningDir, planningRoot, todosDir, listAvailableWorkstreams, peekActiveWorkstream, diagnoseUnresolvedActiveWorkstream, describeUnresolvedWorkstreamReason, findContextMdIn, } = planningWorkspace;
|
|
92
93
|
const { determinePhaseStatus } = commandsMod;
|
|
93
94
|
const { extractFrontmatter } = frontmatterMod;
|
|
94
95
|
const { isPhaseComplete, resolveVerificationFile, resolveUatFile } = verificationMod;
|
|
@@ -257,7 +258,15 @@ function withProjectRoot(cwd, result) {
|
|
|
257
258
|
if (config.project_code) {
|
|
258
259
|
result['project_code'] = config.project_code;
|
|
259
260
|
}
|
|
260
|
-
|
|
261
|
+
// #4455 follow-up (self-discovered): PROJECT.md is shared across a
|
|
262
|
+
// project's own workstreams (never cloned per workstream) but DOES
|
|
263
|
+
// respect the separate GSD_PROJECT multi-project namespace (#3749) — see
|
|
264
|
+
// cmdInitCompleteMilestone's projectPath comment for the full evidence.
|
|
265
|
+
// `ws` explicitly nulled, `project` left to default from GSD_PROJECT.
|
|
266
|
+
// Reading via the workstream-aware planningDir(cwd) meant every init.*
|
|
267
|
+
// call's project_title silently vanished whenever a workstream was
|
|
268
|
+
// active, since no PROJECT.md ever exists at the workstream path.
|
|
269
|
+
const projectMdPath = node_path_1.default.join(planningDir(cwd, null), 'PROJECT.md');
|
|
261
270
|
const content = (0, shell_command_projection_cjs_1.platformReadSync)(projectMdPath);
|
|
262
271
|
if (content) {
|
|
263
272
|
const h1Match = content.match(/^#\s+(.+)$/m);
|
|
@@ -477,6 +486,16 @@ function readConfigJsonBoolean(cwd, keyPath) {
|
|
|
477
486
|
return false;
|
|
478
487
|
}
|
|
479
488
|
}
|
|
489
|
+
/** Reads `filePath`; returns its content, or `null` when missing/unreadable/empty. */
|
|
490
|
+
function readNonEmptyFileOrNull(filePath) {
|
|
491
|
+
try {
|
|
492
|
+
const content = (0, shell_command_projection_cjs_1.platformReadSync)(filePath);
|
|
493
|
+
return content && content.length > 0 ? content : null;
|
|
494
|
+
}
|
|
495
|
+
catch {
|
|
496
|
+
return null;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
480
499
|
/**
|
|
481
500
|
* Bounded, non-throwing read of a dotted key path from `.planning/config.json`,
|
|
482
501
|
* returning the raw resolved value (any JSON type) or `undefined` on any
|
|
@@ -828,10 +847,7 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
|
|
|
828
847
|
runnable_plans: phaseInfo?.['runnable_plans'] || [],
|
|
829
848
|
runnable_count: phaseInfo?.['runnable_plans']?.length || 0,
|
|
830
849
|
branch_name: config.branching_strategy === 'phase' && phaseInfo
|
|
831
|
-
? config.phase_branch_template
|
|
832
|
-
.replace('{project}', config.project_code || '')
|
|
833
|
-
.replace('{phase}', normalizePhaseName(phaseInfo['phase_number']))
|
|
834
|
-
.replace('{slug}', phaseInfo['phase_slug'] || 'phase')
|
|
850
|
+
? renderPhaseBranchName(config.phase_branch_template.replace('{project}', config.project_code || ''), phaseInfo['phase_number'], phaseInfo['phase_slug'])
|
|
835
851
|
: config.branching_strategy === 'milestone'
|
|
836
852
|
? config.milestone_branch_template
|
|
837
853
|
.replace('{milestone}', milestone['version'] ?? '')
|
|
@@ -847,6 +863,17 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
|
|
|
847
863
|
// #3188: null when the file is absent (parity with patterns_path/context_path).
|
|
848
864
|
state_path: node_fs_1.default.existsSync(statePath) ? toPosixPath(statePath) : null,
|
|
849
865
|
roadmap_path: node_fs_1.default.existsSync(roadmapPath) ? toPosixPath(roadmapPath) : null,
|
|
866
|
+
// #4456 correction: an isolated review pass initially "fixed" this to
|
|
867
|
+
// planningDir(cwd, null) on the assumption that config.json is shared
|
|
868
|
+
// like PROJECT.md (workstream-flag.md's directory diagram marks it
|
|
869
|
+
// `# Shared`) — but ADR-0006's own tests (tests/init.test.cjs, "init
|
|
870
|
+
// handlers honor GSD_WORKSTREAM") assert config_path IS workstream-scoped
|
|
871
|
+
// for execute-phase/new-project/new-milestone/progress, and gsd-test
|
|
872
|
+
// caught the regression immediately. The diagram is stale for
|
|
873
|
+
// config.json specifically (same class of staleness already found for
|
|
874
|
+
// `milestones/` during the #4455 follow-up) — reverted to the
|
|
875
|
+
// workstream-aware planningDir(cwd), matching the established,
|
|
876
|
+
// ADR-governed, tested contract.
|
|
850
877
|
config_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'config.json')),
|
|
851
878
|
// #2376: execute-phase.md's verify_phase_goal step reads this instead of
|
|
852
879
|
// hardcoding '.planning/REQUIREMENTS.md' into the gsd-verifier spawn prompt.
|
|
@@ -1130,7 +1157,13 @@ function buildInitCompletenessFields(cwd) {
|
|
|
1130
1157
|
const planningExists = node_fs_1.default.existsSync(dir);
|
|
1131
1158
|
const requirementsExists = node_fs_1.default.existsSync(node_path_1.default.join(dir, 'REQUIREMENTS.md'));
|
|
1132
1159
|
const milestonesExists = node_fs_1.default.existsSync(node_path_1.default.join(dir, 'MILESTONES.md'));
|
|
1133
|
-
|
|
1160
|
+
// #4455 follow-up (code-review finding): PROJECT.md is shared across
|
|
1161
|
+
// workstreams (see cmdInitCompleteMilestone's projectPath comment for the
|
|
1162
|
+
// full evidence) — checked at planningRoot(cwd), never the workstream-scoped
|
|
1163
|
+
// `dir`, so a workstream whose own REQUIREMENTS/ROADMAP/STATE are all
|
|
1164
|
+
// present isn't wrongly reported incomplete just because the shared
|
|
1165
|
+
// PROJECT.md isn't ALSO duplicated under its own directory.
|
|
1166
|
+
const coreComplete = node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd, null), 'PROJECT.md')) &&
|
|
1134
1167
|
requirementsExists &&
|
|
1135
1168
|
node_fs_1.default.existsSync(node_path_1.default.join(dir, 'ROADMAP.md')) &&
|
|
1136
1169
|
node_fs_1.default.existsSync(node_path_1.default.join(dir, 'STATE.md'));
|
|
@@ -1165,23 +1198,46 @@ function cmdInitNewProject(cwd, raw, options = {}) {
|
|
|
1165
1198
|
// root-scoped (`pathExistsInternal(cwd, '.planning')`) semantics for that
|
|
1166
1199
|
// one key stay byte-identical for existing consumers.
|
|
1167
1200
|
...buildInitCompletenessFields(cwd),
|
|
1168
|
-
|
|
1201
|
+
// #4455 follow-up (code-review finding): PROJECT.md is shared across
|
|
1202
|
+
// workstreams — see cmdInitCompleteMilestone's projectPath comment for
|
|
1203
|
+
// the full evidence.
|
|
1204
|
+
project_exists: pathExistsInternal(cwd, toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd, null), 'PROJECT.md')))),
|
|
1169
1205
|
has_codebase_map: hasCodebaseMap,
|
|
1170
1206
|
planning_exists: pathExistsInternal(cwd, '.planning'),
|
|
1171
1207
|
has_existing_code: hasCode,
|
|
1172
1208
|
has_package_file: hasPackageFile,
|
|
1173
1209
|
is_brownfield: isBrownfield,
|
|
1210
|
+
// #4458: new-project.md's Step 5.1 (Sub-Repo Detection) used to run its own
|
|
1211
|
+
// narrower `find ... -exec test -d "{}/.git"` predicate, which requires
|
|
1212
|
+
// .git to be a DIRECTORY and so silently excluded linked git worktree
|
|
1213
|
+
// children (.git is a FILE there). detectSubRepos already handled this
|
|
1214
|
+
// correctly (fs.existsSync, not isDirectory) but had zero callers anywhere
|
|
1215
|
+
// in the codebase — reused here instead of leaving the workflow to
|
|
1216
|
+
// maintain its own duplicate, narrower detection logic.
|
|
1217
|
+
sub_repos_detected: coreUtils.detectSubRepos(cwd),
|
|
1174
1218
|
needs_codebase_map: isBrownfield && !hasCodebaseMap,
|
|
1175
1219
|
...getInitGitState(cwd),
|
|
1176
1220
|
brave_search_available: hasBraveSearch,
|
|
1177
1221
|
firecrawl_available: hasFirecrawl,
|
|
1178
1222
|
exa_search_available: hasExaSearch,
|
|
1179
1223
|
// #2376: absolute — see comment on phase_dir in cmdInitExecutePhase.
|
|
1180
|
-
|
|
1224
|
+
// #4455 follow-up: PROJECT.md is shared across workstreams.
|
|
1225
|
+
project_path: toPosixPath(node_path_1.default.join(planningDir(cwd, null), 'PROJECT.md')),
|
|
1181
1226
|
// #2376: new-project.md's research-synthesizer/roadmapper spawn prompts
|
|
1182
1227
|
// read these instead of hardcoding '.planning/...' literals.
|
|
1183
1228
|
requirements_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'REQUIREMENTS.md')),
|
|
1184
1229
|
roadmap_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
1230
|
+
// #4456 correction: an isolated review pass initially "fixed" this to
|
|
1231
|
+
// planningDir(cwd, null) on the assumption that config.json is shared
|
|
1232
|
+
// like PROJECT.md (workstream-flag.md's directory diagram marks it
|
|
1233
|
+
// `# Shared`) — but ADR-0006's own tests (tests/init.test.cjs, "init
|
|
1234
|
+
// handlers honor GSD_WORKSTREAM") assert config_path IS workstream-scoped
|
|
1235
|
+
// for execute-phase/new-project/new-milestone/progress, and gsd-test
|
|
1236
|
+
// caught the regression immediately. The diagram is stale for
|
|
1237
|
+
// config.json specifically (same class of staleness already found for
|
|
1238
|
+
// `milestones/` during the #4455 follow-up) — reverted to the
|
|
1239
|
+
// workstream-aware planningDir(cwd), matching the established,
|
|
1240
|
+
// ADR-governed, tested contract.
|
|
1185
1241
|
config_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'config.json')),
|
|
1186
1242
|
research_dir: toPosixPath(node_path_1.default.join(planningRoot(cwd), 'research')),
|
|
1187
1243
|
};
|
|
@@ -1223,18 +1279,41 @@ function cmdInitNewMilestone(cwd, raw, options = {}) {
|
|
|
1223
1279
|
phase_archive_path: latestCompleted
|
|
1224
1280
|
? toPosixPath(node_path_1.default.join(planningRoot(cwd), 'milestones', `${latestCompleted.version}-phases`))
|
|
1225
1281
|
: null,
|
|
1226
|
-
|
|
1282
|
+
// #4455 follow-up (code-review finding): PROJECT.md is shared across
|
|
1283
|
+
// workstreams — see cmdInitCompleteMilestone's projectPath comment for
|
|
1284
|
+
// the full evidence.
|
|
1285
|
+
project_exists: pathExistsInternal(cwd, toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd, null), 'PROJECT.md')))),
|
|
1227
1286
|
roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
1228
1287
|
state_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
|
|
1229
|
-
|
|
1288
|
+
// #4455 follow-up: PROJECT.md is shared across workstreams.
|
|
1289
|
+
project_path: toPosixPath(node_path_1.default.join(planningDir(cwd, null), 'PROJECT.md')),
|
|
1230
1290
|
roadmap_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
1231
1291
|
state_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
|
|
1232
1292
|
// #2376: new-milestone.md's research-synthesizer/roadmapper spawn prompts
|
|
1233
1293
|
// read these instead of hardcoding '.planning/...' literals.
|
|
1234
1294
|
requirements_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'REQUIREMENTS.md')),
|
|
1295
|
+
// #4456 correction: an isolated review pass initially "fixed" this to
|
|
1296
|
+
// planningDir(cwd, null) on the assumption that config.json is shared
|
|
1297
|
+
// like PROJECT.md (workstream-flag.md's directory diagram marks it
|
|
1298
|
+
// `# Shared`) — but ADR-0006's own tests (tests/init.test.cjs, "init
|
|
1299
|
+
// handlers honor GSD_WORKSTREAM") assert config_path IS workstream-scoped
|
|
1300
|
+
// for execute-phase/new-project/new-milestone/progress, and gsd-test
|
|
1301
|
+
// caught the regression immediately. The diagram is stale for
|
|
1302
|
+
// config.json specifically (same class of staleness already found for
|
|
1303
|
+
// `milestones/` during the #4455 follow-up) — reverted to the
|
|
1304
|
+
// workstream-aware planningDir(cwd), matching the established,
|
|
1305
|
+
// ADR-governed, tested contract.
|
|
1235
1306
|
config_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'config.json')),
|
|
1236
1307
|
research_dir: toPosixPath(node_path_1.default.join(planningRoot(cwd), 'research')),
|
|
1237
1308
|
milestones_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'MILESTONES.md')),
|
|
1309
|
+
// #4456: new-milestone.md's Step 6 stages the phase-archive move
|
|
1310
|
+
// (`git add .planning/milestones/ .planning/phases/`) — both
|
|
1311
|
+
// workstream-scoped (phases_dir mirrors the phasesDir local above;
|
|
1312
|
+
// archive_dir mirrors cmdInitCompleteMilestone's own field of the same
|
|
1313
|
+
// name), so a literal root `git add` misses the actual files
|
|
1314
|
+
// phases.clear just moved under an active workstream.
|
|
1315
|
+
phases_dir: toPosixPath(phasesDir),
|
|
1316
|
+
archive_dir: toPosixPath(node_path_1.default.join(planningDir(cwd), 'milestones')),
|
|
1238
1317
|
};
|
|
1239
1318
|
// `state:flat-mode` (#2994): whether NO workstream is active — the inverse
|
|
1240
1319
|
// of `state:workstream-active` (introduced for `cmdInitTransition` below).
|
|
@@ -1372,7 +1451,10 @@ function cmdInitQuickBatch(cwd, raw, options = {}) {
|
|
|
1372
1451
|
function cmdInitIngestDocs(cwd, raw) {
|
|
1373
1452
|
const config = loadConfig(cwd);
|
|
1374
1453
|
const result = {
|
|
1375
|
-
|
|
1454
|
+
// #4455 follow-up (code-review finding): PROJECT.md is shared across
|
|
1455
|
+
// workstreams — see cmdInitCompleteMilestone's projectPath comment for
|
|
1456
|
+
// the full evidence.
|
|
1457
|
+
project_exists: pathExistsInternal(cwd, toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd, null), 'PROJECT.md')))),
|
|
1376
1458
|
planning_exists: node_fs_1.default.existsSync(planningRoot(cwd)),
|
|
1377
1459
|
...getInitGitState(cwd),
|
|
1378
1460
|
// #2376: absolute — see comment on phase_dir in cmdInitExecutePhase. The
|
|
@@ -1381,7 +1463,8 @@ function cmdInitIngestDocs(cwd, raw) {
|
|
|
1381
1463
|
// hardcoded bare '.planning/intel/...', '.planning/PROJECT.md', etc.
|
|
1382
1464
|
// literals into their Agent(prompt=...) blocks; those now interpolate
|
|
1383
1465
|
// these fields instead.
|
|
1384
|
-
|
|
1466
|
+
// #4455 follow-up: PROJECT.md is shared across workstreams.
|
|
1467
|
+
project_path: toPosixPath(node_path_1.default.join(planningDir(cwd, null), 'PROJECT.md')),
|
|
1385
1468
|
requirements_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'REQUIREMENTS.md')),
|
|
1386
1469
|
roadmap_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
1387
1470
|
state_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
|
|
@@ -1417,12 +1500,16 @@ function cmdInitResume(cwd, raw) {
|
|
|
1417
1500
|
...buildInitCompletenessFields(cwd),
|
|
1418
1501
|
state_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
|
|
1419
1502
|
roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
1420
|
-
|
|
1503
|
+
// #4455 follow-up (code-review finding): PROJECT.md is shared across
|
|
1504
|
+
// workstreams — see cmdInitCompleteMilestone's projectPath comment for
|
|
1505
|
+
// the full evidence.
|
|
1506
|
+
project_exists: pathExistsInternal(cwd, toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd, null), 'PROJECT.md')))),
|
|
1421
1507
|
planning_exists: node_fs_1.default.existsSync(planningRoot(cwd)),
|
|
1422
1508
|
// #2376: absolute — see comment on phase_dir in cmdInitExecutePhase.
|
|
1423
1509
|
state_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
|
|
1424
1510
|
roadmap_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
1425
|
-
|
|
1511
|
+
// #4455 follow-up: PROJECT.md is shared across workstreams.
|
|
1512
|
+
project_path: toPosixPath(node_path_1.default.join(planningDir(cwd, null), 'PROJECT.md')),
|
|
1426
1513
|
has_interrupted_agent: !!interruptedAgentId,
|
|
1427
1514
|
interrupted_agent_id: interruptedAgentId,
|
|
1428
1515
|
commit_docs: config.commit_docs,
|
|
@@ -1898,13 +1985,156 @@ function cmdInitPhaseOp(cwd, phase, raw) {
|
|
|
1898
1985
|
}
|
|
1899
1986
|
output(withProjectRoot(cwd, result), raw);
|
|
1900
1987
|
}
|
|
1988
|
+
// #2618: bullet-cap and title-floor for renderPendingTodosMarkdown below.
|
|
1989
|
+
// 240 matches the bound already vetted by maintainer review on the prior
|
|
1990
|
+
// attempt at this issue (PR #2662) — re-deriving a different number would be
|
|
1991
|
+
// pure bikeshedding, not a correctness improvement. See
|
|
1992
|
+
// .gsd/phase/feat-2618-compact-todo-pointers/40-design.md.
|
|
1993
|
+
const PENDING_TODO_BULLET_MAX_CHARS = 240;
|
|
1994
|
+
const PENDING_TODO_TITLE_FLOOR = 15;
|
|
1995
|
+
const PENDING_TODO_AREA_FLOOR = 3;
|
|
1996
|
+
function sanitizePendingTodoInline(value) {
|
|
1997
|
+
// Defensive: the regex captures that populate title/area/needs can only
|
|
1998
|
+
// ever match a single line, so this is belt-and-suspenders against any
|
|
1999
|
+
// future non-regex-sourced input, not a reachable case today.
|
|
2000
|
+
return value.replace(/[\r\n]+/g, ' ').trim();
|
|
2001
|
+
}
|
|
2002
|
+
function truncatePendingTodoText(value, maxLen) {
|
|
2003
|
+
if (value.length <= maxLen)
|
|
2004
|
+
return value;
|
|
2005
|
+
if (maxLen <= 1)
|
|
2006
|
+
return value.slice(0, Math.max(0, maxLen));
|
|
2007
|
+
return `${value.slice(0, maxLen - 1)}…`;
|
|
2008
|
+
}
|
|
2009
|
+
/**
|
|
2010
|
+
* #2618: pure renderer for STATE.md's "### Pending Todos" section BODY (not
|
|
2011
|
+
* the heading). One bullet per todo, each capped at
|
|
2012
|
+
* PENDING_TODO_BULLET_MAX_CHARS. `gsd-core/workflows/add-todo.md` and
|
|
2013
|
+
* `check-todos.md` splice this string in verbatim instead of free-hand
|
|
2014
|
+
* editing STATE.md — see the design doc for why this is real, unit-tested
|
|
2015
|
+
* code rather than a prose algorithm (DEFECT.GENERATIVE-FIX: a prose
|
|
2016
|
+
* algorithm duplicated as a test oracle is exactly the divergence class
|
|
2017
|
+
* this avoids).
|
|
2018
|
+
*
|
|
2019
|
+
* #4384 regression fix: the optional `projectRoot` makes the bullet's
|
|
2020
|
+
* `[todo file](…)` link repo-relative (see pendingTodoLinkTarget) so the cap
|
|
2021
|
+
* is deterministic w.r.t. where the repo is checked out. Omitting it keeps
|
|
2022
|
+
* the legacy absolute-link behavior for existing direct callers.
|
|
2023
|
+
*/
|
|
2024
|
+
function renderPendingTodosMarkdown(todos, projectRoot) {
|
|
2025
|
+
if (!Array.isArray(todos) || todos.length === 0) {
|
|
2026
|
+
return 'None yet.';
|
|
2027
|
+
}
|
|
2028
|
+
return todos.map((todo) => renderPendingTodoBullet(todo, projectRoot)).join('\n');
|
|
2029
|
+
}
|
|
2030
|
+
function pendingTodoFieldAsString(value, fallback) {
|
|
2031
|
+
return typeof value === 'string' && value.length > 0 ? value : fallback;
|
|
2032
|
+
}
|
|
2033
|
+
/**
|
|
2034
|
+
* #4384 regression fix: the bullet's markdown link target, rendered
|
|
2035
|
+
* repo-relative when `projectRoot` is given and the todo's `path` is
|
|
2036
|
+
* absolute. The JSON `todos[].path` field stays absolute (#2376 contract);
|
|
2037
|
+
* only the rendered display link changes — embedding the machine-variable
|
|
2038
|
+
* absolute base let macOS's /private/var/folders/… temp paths consume the
|
|
2039
|
+
* 240-char budget and drop the "Needs <solution>" clause on long-path
|
|
2040
|
+
* machines only (next's own macos CI shard went red on exactly this, run
|
|
2041
|
+
* 34038716700). Repo-relative links also resolve correctly from STATE.md at
|
|
2042
|
+
* the repo root and survive repo moves.
|
|
2043
|
+
*/
|
|
2044
|
+
function pendingTodoLinkTarget(todo, projectRoot) {
|
|
2045
|
+
const raw = pendingTodoFieldAsString(todo['path'], '');
|
|
2046
|
+
if (typeof projectRoot !== 'string' || projectRoot.length === 0 || !node_path_1.default.isAbsolute(raw)) {
|
|
2047
|
+
return raw;
|
|
2048
|
+
}
|
|
2049
|
+
const rel = toPosixPath(node_path_1.default.relative(projectRoot, raw));
|
|
2050
|
+
if (rel.length === 0 || node_path_1.default.isAbsolute(rel)) {
|
|
2051
|
+
// Degenerate (path === projectRoot) or Windows cross-drive fallback:
|
|
2052
|
+
// keep the raw target rather than emitting an empty or incorrect link.
|
|
2053
|
+
return raw;
|
|
2054
|
+
}
|
|
2055
|
+
return rel;
|
|
2056
|
+
}
|
|
2057
|
+
/**
|
|
2058
|
+
* #4439: the stored `created:` frontmatter (and the JSON `todos[].created`
|
|
2059
|
+
* field it round-trips through) is always a full ISO-8601 timestamp, by
|
|
2060
|
+
* design — this is the display-only seam that reformats it to the
|
|
2061
|
+
* date-only `[date]` bullet documented in docs/reference/state-md.md and
|
|
2062
|
+
* docs/COMMANDS.md. A value that doesn't start with a well-formed
|
|
2063
|
+
* `YYYY-MM-DD` (the 'unknown' fallback, or any other non-conforming
|
|
2064
|
+
* string) passes through unchanged rather than being mangled.
|
|
2065
|
+
*/
|
|
2066
|
+
function pendingTodoDateOnly(value) {
|
|
2067
|
+
const match = value.match(/^\d{4}-\d{2}-\d{2}/);
|
|
2068
|
+
return match ? match[0] : value;
|
|
2069
|
+
}
|
|
2070
|
+
function renderPendingTodoBullet(todo, projectRoot) {
|
|
2071
|
+
const date = pendingTodoDateOnly(sanitizePendingTodoInline(pendingTodoFieldAsString(todo['created'], 'unknown')));
|
|
2072
|
+
let area = sanitizePendingTodoInline(pendingTodoFieldAsString(todo['area'], 'general'));
|
|
2073
|
+
let title = sanitizePendingTodoInline(pendingTodoFieldAsString(todo['title'], 'Untitled'));
|
|
2074
|
+
// Strip trailing "." so the fixed "Needs ....` template below never
|
|
2075
|
+
// produces a doubled period when the source text already ended in one.
|
|
2076
|
+
let needs = typeof todo['needs'] === 'string'
|
|
2077
|
+
? sanitizePendingTodoInline(todo['needs']).replace(/\.+$/, '')
|
|
2078
|
+
: '';
|
|
2079
|
+
const link = `[todo file](${pendingTodoLinkTarget(todo, projectRoot)})`;
|
|
2080
|
+
const assemble = () => {
|
|
2081
|
+
const needsClause = needs ? ` — Needs ${needs}.` : '';
|
|
2082
|
+
return `- [${date}] [${area}] ${title} — ${link}${needsClause}`;
|
|
2083
|
+
};
|
|
2084
|
+
let line = assemble();
|
|
2085
|
+
if (line.length <= PENDING_TODO_BULLET_MAX_CHARS)
|
|
2086
|
+
return line;
|
|
2087
|
+
// 1) Drop the needs clause entirely first — date/area/title/link untouched.
|
|
2088
|
+
needs = '';
|
|
2089
|
+
line = assemble();
|
|
2090
|
+
if (line.length <= PENDING_TODO_BULLET_MAX_CHARS)
|
|
2091
|
+
return line;
|
|
2092
|
+
// 2) Shorten the title next, down to a floor — date/area/link untouched.
|
|
2093
|
+
const titleOverage = line.length - PENDING_TODO_BULLET_MAX_CHARS;
|
|
2094
|
+
const targetTitleLen = Math.max(PENDING_TODO_TITLE_FLOOR, title.length - titleOverage);
|
|
2095
|
+
if (targetTitleLen < title.length) {
|
|
2096
|
+
title = truncatePendingTodoText(title, targetTitleLen);
|
|
2097
|
+
line = assemble();
|
|
2098
|
+
}
|
|
2099
|
+
if (line.length <= PENDING_TODO_BULLET_MAX_CHARS)
|
|
2100
|
+
return line;
|
|
2101
|
+
// 3) Shorten area as a last resort — date and the markdown link are never
|
|
2102
|
+
// altered (link correctness > strict cap; see design doc "Known limits").
|
|
2103
|
+
const areaOverage = line.length - PENDING_TODO_BULLET_MAX_CHARS;
|
|
2104
|
+
const targetAreaLen = Math.max(PENDING_TODO_AREA_FLOOR, area.length - areaOverage);
|
|
2105
|
+
if (targetAreaLen < area.length) {
|
|
2106
|
+
area = truncatePendingTodoText(area, targetAreaLen);
|
|
2107
|
+
line = assemble();
|
|
2108
|
+
}
|
|
2109
|
+
return line;
|
|
2110
|
+
}
|
|
1901
2111
|
function cmdInitTodos(cwd, area, raw) {
|
|
1902
2112
|
const config = loadConfig(cwd);
|
|
1903
|
-
|
|
2113
|
+
// #4256: todos are root-scoped shared state (migrateToWorkstreams keeps
|
|
2114
|
+
// them at .planning/todos/ and every workflow writer writes that literal
|
|
2115
|
+
// path), so this read resolves via todosDir(cwd) — NOT planningDir(cwd),
|
|
2116
|
+
// which would look in .planning/workstreams/<ws>/todos/ under a workstream
|
|
2117
|
+
// (a directory nothing creates) and report existing todos as absent.
|
|
2118
|
+
const todosRoot = todosDir(cwd);
|
|
2119
|
+
const pendingDir = node_path_1.default.join(todosRoot, 'pending');
|
|
1904
2120
|
let count = 0;
|
|
1905
2121
|
const todos = [];
|
|
2122
|
+
// #2618: distinct from "genuinely zero pending todos" — false only when
|
|
2123
|
+
// readdirSync itself failed for a reason OTHER than the directory simply
|
|
2124
|
+
// not existing yet (ENOENT), mirroring the ENOENT-vs-other-errno split
|
|
2125
|
+
// already used above in this file (#3885, ADR-3473 §8.5). Without this,
|
|
2126
|
+
// a real I/O/permission error on the pending dir would look identical to
|
|
2127
|
+
// "no pending todos" and could wipe an existing, non-empty Pending Todos
|
|
2128
|
+
// section in STATE.md on refresh — the fail-safe requirement for #2618.
|
|
2129
|
+
let pendingReadOk = true;
|
|
1906
2130
|
try {
|
|
1907
|
-
|
|
2131
|
+
// #2618: sorted so pending_todos_markdown's bullet order is stable across
|
|
2132
|
+
// runs — readdirSync's order is filesystem-dependent, not contractually
|
|
2133
|
+
// stable, and an unstable order would reorder every bullet on an
|
|
2134
|
+
// unrelated re-render, turning a one-line git diff into a full-section
|
|
2135
|
+
// rewrite (must-have #3). Filenames are `YYYY-MM-DD-slug.md`, so this
|
|
2136
|
+
// also yields a sensible chronological order as a side effect.
|
|
2137
|
+
const files = node_fs_1.default.readdirSync(pendingDir).filter((f) => f.endsWith('.md')).sort();
|
|
1908
2138
|
for (const file of files) {
|
|
1909
2139
|
const content = (0, shell_command_projection_cjs_1.platformReadSync)(node_path_1.default.join(pendingDir, file));
|
|
1910
2140
|
if (content === null)
|
|
@@ -1916,6 +2146,21 @@ function cmdInitTodos(cwd, area, raw) {
|
|
|
1916
2146
|
// #2337: kept in parity with cmdListTodos — surface severity when
|
|
1917
2147
|
// present, omit the key entirely for todos with no severity line.
|
|
1918
2148
|
const severityMatch = content.match(/^severity:\s*(.+)$/m);
|
|
2149
|
+
// #2618: first non-empty line of the `## Solution` body, used as the
|
|
2150
|
+
// bullet's "Needs ..." clause. "TBD" (the create_file template's own
|
|
2151
|
+
// placeholder for an unresolved solution) renders no clause at all
|
|
2152
|
+
// rather than the useless literal "Needs TBD.".
|
|
2153
|
+
const solutionSection = (0, markdown_sectionizer_cjs_1.collectSection)(content, (h) => h.level === 2 && h.text.trim() === 'Solution');
|
|
2154
|
+
let needs;
|
|
2155
|
+
if (solutionSection) {
|
|
2156
|
+
const firstLine = solutionSection.body
|
|
2157
|
+
.split('\n')
|
|
2158
|
+
.map((l) => l.trim())
|
|
2159
|
+
.find((l) => l.length > 0);
|
|
2160
|
+
if (firstLine && firstLine.toUpperCase() !== 'TBD') {
|
|
2161
|
+
needs = firstLine;
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
1919
2164
|
const todoArea = areaMatch ? areaMatch[1].trim() : 'general';
|
|
1920
2165
|
if (area && todoArea !== area)
|
|
1921
2166
|
continue;
|
|
@@ -1926,8 +2171,9 @@ function cmdInitTodos(cwd, area, raw) {
|
|
|
1926
2171
|
title: titleMatch ? titleMatch[1].trim() : 'Untitled',
|
|
1927
2172
|
area: todoArea,
|
|
1928
2173
|
// #2376: absolute — see comment on phase_dir in cmdInitExecutePhase.
|
|
1929
|
-
path: toPosixPath(node_path_1.default.join(
|
|
2174
|
+
path: toPosixPath(node_path_1.default.join(pendingDir, file)),
|
|
1930
2175
|
...(severityMatch ? { severity: severityMatch[1].trim() } : {}),
|
|
2176
|
+
...(needs ? { needs } : {}),
|
|
1931
2177
|
});
|
|
1932
2178
|
}
|
|
1933
2179
|
catch {
|
|
@@ -1935,8 +2181,11 @@ function cmdInitTodos(cwd, area, raw) {
|
|
|
1935
2181
|
}
|
|
1936
2182
|
}
|
|
1937
2183
|
}
|
|
1938
|
-
catch {
|
|
1939
|
-
|
|
2184
|
+
catch (err) {
|
|
2185
|
+
const code = err?.code;
|
|
2186
|
+
if (code !== 'ENOENT') {
|
|
2187
|
+
pendingReadOk = false;
|
|
2188
|
+
}
|
|
1940
2189
|
}
|
|
1941
2190
|
const result = {
|
|
1942
2191
|
commit_docs: config.commit_docs,
|
|
@@ -1946,11 +2195,22 @@ function cmdInitTodos(cwd, area, raw) {
|
|
|
1946
2195
|
todos,
|
|
1947
2196
|
area_filter: area || null,
|
|
1948
2197
|
// #2376: absolute — see comment on phase_dir in cmdInitExecutePhase.
|
|
1949
|
-
|
|
1950
|
-
|
|
2198
|
+
// #4256: both dir fields probe the ROOT todos tree via todosDir(cwd).
|
|
2199
|
+
pending_dir: toPosixPath(pendingDir),
|
|
2200
|
+
completed_dir: toPosixPath(node_path_1.default.join(todosRoot, 'completed')),
|
|
2201
|
+
// planning_exists intentionally stays workstream/project-scoped — it
|
|
2202
|
+
// answers "does the ACTIVE planning dir exist", not a todos question.
|
|
1951
2203
|
planning_exists: node_fs_1.default.existsSync(planningDir(cwd)),
|
|
1952
|
-
todos_dir_exists: node_fs_1.default.existsSync(
|
|
1953
|
-
pending_dir_exists: node_fs_1.default.existsSync(
|
|
2204
|
+
todos_dir_exists: node_fs_1.default.existsSync(todosRoot),
|
|
2205
|
+
pending_dir_exists: node_fs_1.default.existsSync(pendingDir),
|
|
2206
|
+
// #2618: see PENDING_TODO_BULLET_MAX_CHARS comment / design doc. Consumed
|
|
2207
|
+
// by add-todo.md / check-todos.md's update_state step; omitted entirely
|
|
2208
|
+
// (rather than emitted with possibly-wrong data) when pendingReadOk is
|
|
2209
|
+
// false, so the workflow's fail-safe check can key off field presence.
|
|
2210
|
+
pending_read_ok: pendingReadOk,
|
|
2211
|
+
// #4384 fix: pass cwd as projectRoot so the bullet link renders
|
|
2212
|
+
// repo-relative — see pendingTodoLinkTarget.
|
|
2213
|
+
...(pendingReadOk ? { pending_todos_markdown: renderPendingTodosMarkdown(todos, cwd) } : {}),
|
|
1954
2214
|
};
|
|
1955
2215
|
output(withProjectRoot(cwd, result), raw);
|
|
1956
2216
|
}
|
|
@@ -2065,7 +2325,10 @@ function cmdInitMilestoneOp(cwd, raw) {
|
|
|
2065
2325
|
all_phases_complete: phaseCount > 0 && phaseCount === completedPhases,
|
|
2066
2326
|
archived_milestones: archivedMilestones,
|
|
2067
2327
|
archive_count: archivedMilestones.length,
|
|
2068
|
-
|
|
2328
|
+
// #4455 follow-up (code-review finding): PROJECT.md is shared across
|
|
2329
|
+
// workstreams — see cmdInitCompleteMilestone's projectPath comment for
|
|
2330
|
+
// the full evidence.
|
|
2331
|
+
project_exists: pathExistsInternal(cwd, toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd, null), 'PROJECT.md')))),
|
|
2069
2332
|
roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
2070
2333
|
state_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
|
|
2071
2334
|
archive_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningRoot(cwd), 'archive')),
|
|
@@ -2447,10 +2710,23 @@ function cmdInitManager(cwd, raw) {
|
|
|
2447
2710
|
recommended_actions: filteredActions,
|
|
2448
2711
|
waiting_signal: waitingSignal,
|
|
2449
2712
|
all_complete: completedCount === nonBacklogPhases.length && nonBacklogPhases.length > 0,
|
|
2450
|
-
|
|
2713
|
+
// #4455 follow-up (code-review finding): PROJECT.md is shared across
|
|
2714
|
+
// workstreams — see cmdInitCompleteMilestone's projectPath comment for
|
|
2715
|
+
// the full evidence.
|
|
2716
|
+
project_exists: pathExistsInternal(cwd, toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd, null), 'PROJECT.md')))),
|
|
2451
2717
|
roadmap_exists: true,
|
|
2452
2718
|
state_exists: true,
|
|
2453
2719
|
manager_flags: managerFlags,
|
|
2720
|
+
// #4455: workstream-scoped STATE/ROADMAP/milestone-archive paths — same
|
|
2721
|
+
// pattern cmdInitPlanPhase already uses (existence-checked, toPosixPath'd,
|
|
2722
|
+
// null when absent) plus the archive dir composition milestone.cts's
|
|
2723
|
+
// `cmdMilestoneComplete` uses (#1911: planningPaths(cwd).planning +
|
|
2724
|
+
// 'milestones', workstream-aware). autonomous.md's discover_phases/
|
|
2725
|
+
// iterate/lifecycle steps consume these instead of hardcoding
|
|
2726
|
+
// `.planning/STATE.md` / `.planning/milestones/...`.
|
|
2727
|
+
state_path: node_fs_1.default.existsSync(paths.state) ? toPosixPath(paths.state) : null,
|
|
2728
|
+
roadmap_path: node_fs_1.default.existsSync(paths.roadmap) ? toPosixPath(paths.roadmap) : null,
|
|
2729
|
+
archive_dir: toPosixPath(node_path_1.default.join(paths.planning, 'milestones')),
|
|
2454
2730
|
};
|
|
2455
2731
|
output(withProjectRoot(cwd, result), raw);
|
|
2456
2732
|
}
|
|
@@ -2469,10 +2745,57 @@ function cmdInitManager(cwd, raw) {
|
|
|
2469
2745
|
*/
|
|
2470
2746
|
function cmdInitCompleteMilestone(cwd, raw, options = {}) {
|
|
2471
2747
|
const gitCreateTag = detectGitCreateTag(cwd);
|
|
2748
|
+
// #4455: workstream-scoped STATE/ROADMAP/milestone-archive paths for
|
|
2749
|
+
// complete-milestone.md's reorganize_roadmap_and_delete_originals step —
|
|
2750
|
+
// same pattern cmdInitPlanPhase already uses, mirrored here since this is
|
|
2751
|
+
// that workflow's own dedicated init entry point.
|
|
2752
|
+
const planningBase = planningDir(cwd);
|
|
2753
|
+
const statePath = node_path_1.default.join(planningBase, 'STATE.md');
|
|
2754
|
+
const roadmapPath = node_path_1.default.join(planningBase, 'ROADMAP.md');
|
|
2755
|
+
const archiveDir = node_path_1.default.join(planningBase, 'milestones');
|
|
2756
|
+
// #4455 follow-up (code-review finding): MILESTONES.md is workstream-scoped
|
|
2757
|
+
// too — cmdMilestoneComplete (src/milestone.cts) writes it via
|
|
2758
|
+
// planningPaths(cwd).planning (the workstream base, not root; #1911). It is
|
|
2759
|
+
// not the deliberately-root-scoped exception `todos` is (#4256) — an
|
|
2760
|
+
// earlier version of this fix wrongly treated it as a shared root file,
|
|
2761
|
+
// which would have made the safety commit below silently miss the actual
|
|
2762
|
+
// file milestone.complete just wrote under an active workstream.
|
|
2763
|
+
const milestonesPath = node_path_1.default.join(planningBase, 'MILESTONES.md');
|
|
2764
|
+
// #4455 follow-up round 2 (self-discovered regression): PROJECT.md, unlike
|
|
2765
|
+
// MILESTONES.md, is genuinely SHARED across a project's own workstreams —
|
|
2766
|
+
// never cloned per workstream. gsd-core/references/workstream-flag.md's
|
|
2767
|
+
// directory diagram marks it `# Shared`; new-milestone.md states it
|
|
2768
|
+
// outright ("PROJECT.md is shared across workstreams") and explicitly
|
|
2769
|
+
// SKIPS writing its `## Current Milestone` heading under an active
|
|
2770
|
+
// workstream specifically to avoid clobbering the one shared file (#2308);
|
|
2771
|
+
// cmdWorkstreamCreate (src/workstream.cts) never creates a PROJECT.md
|
|
2772
|
+
// under a workstream directory. The first version of this #4455 follow-up
|
|
2773
|
+
// wrongly generalized from planningPaths()'s structural shape (which
|
|
2774
|
+
// composes `project` under the workstream base) without checking an
|
|
2775
|
+
// actual PROJECT.md write path — resolved against planningRoot(cwd)
|
|
2776
|
+
// (round 2), but that ALSO ignores the separate GSD_PROJECT dimension
|
|
2777
|
+
// (multi-project namespacing, #3749: PROJECT.md legitimately lives at
|
|
2778
|
+
// `.planning/<project>/PROJECT.md` when GSD_PROJECT is set — a real,
|
|
2779
|
+
// tested, pre-existing feature planningRoot's blanket root-only read
|
|
2780
|
+
// broke), caught by gsd-test on this fix's own first push. `planningDir`
|
|
2781
|
+
// with `ws` explicitly nulled (never read from GSD_WORKSTREAM) but
|
|
2782
|
+
// `project` left to default from GSD_PROJECT is the correct middle
|
|
2783
|
+
// ground: respects project-namespacing, ignores workstream-namespacing.
|
|
2784
|
+
const projectPath = node_path_1.default.join(planningDir(cwd, null), 'PROJECT.md');
|
|
2785
|
+
// REQUIREMENTS.md is workstream-scoped the same way (planningPaths(cwd).requirements,
|
|
2786
|
+
// src/planning-workspace.cts) — the git-rm-after-archive step needs the
|
|
2787
|
+
// resolved path too, not the literal root file.
|
|
2788
|
+
const requirementsPath = node_path_1.default.join(planningBase, 'REQUIREMENTS.md');
|
|
2472
2789
|
const result = {
|
|
2473
2790
|
// #2994: hoisted from complete-milestone.md's git_tag step
|
|
2474
2791
|
// <config-check> resolver (git.create_tag, fail-open default true).
|
|
2475
2792
|
git_create_tag: gitCreateTag,
|
|
2793
|
+
state_path: node_fs_1.default.existsSync(statePath) ? toPosixPath(statePath) : null,
|
|
2794
|
+
roadmap_path: node_fs_1.default.existsSync(roadmapPath) ? toPosixPath(roadmapPath) : null,
|
|
2795
|
+
archive_dir: toPosixPath(archiveDir),
|
|
2796
|
+
milestones_path: node_fs_1.default.existsSync(milestonesPath) ? toPosixPath(milestonesPath) : null,
|
|
2797
|
+
project_path: node_fs_1.default.existsSync(projectPath) ? toPosixPath(projectPath) : null,
|
|
2798
|
+
requirements_path: node_fs_1.default.existsSync(requirementsPath) ? toPosixPath(requirementsPath) : null,
|
|
2476
2799
|
};
|
|
2477
2800
|
result['section_manifest'] = buildSectionManifestField(cwd, null, options, 'complete-milestone', {
|
|
2478
2801
|
gitCreateTag,
|
|
@@ -2880,7 +3203,10 @@ function cmdInitProgress(cwd, raw, options = {}) {
|
|
|
2880
3203
|
paused_at: pausedAt,
|
|
2881
3204
|
has_work_in_progress: !!currentPhase,
|
|
2882
3205
|
phase_mvp_mode: phaseMvpMode,
|
|
2883
|
-
|
|
3206
|
+
// #4455 follow-up (code-review finding): PROJECT.md is shared across
|
|
3207
|
+
// workstreams — see cmdInitCompleteMilestone's projectPath comment for
|
|
3208
|
+
// the full evidence.
|
|
3209
|
+
project_exists: pathExistsInternal(cwd, toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd, null), 'PROJECT.md')))),
|
|
2884
3210
|
roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
2885
3211
|
state_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
|
|
2886
3212
|
// #4040: partial-init discriminator (see buildInitCompletenessFields) —
|
|
@@ -2890,7 +3216,19 @@ function cmdInitProgress(cwd, raw, options = {}) {
|
|
|
2890
3216
|
// #2376: absolute — see comment on phase_dir in cmdInitExecutePhase.
|
|
2891
3217
|
state_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
|
|
2892
3218
|
roadmap_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
2893
|
-
|
|
3219
|
+
// #4455 follow-up: PROJECT.md is shared across workstreams.
|
|
3220
|
+
project_path: toPosixPath(node_path_1.default.join(planningDir(cwd, null), 'PROJECT.md')),
|
|
3221
|
+
// #4456 correction: an isolated review pass initially "fixed" this to
|
|
3222
|
+
// planningDir(cwd, null) on the assumption that config.json is shared
|
|
3223
|
+
// like PROJECT.md (workstream-flag.md's directory diagram marks it
|
|
3224
|
+
// `# Shared`) — but ADR-0006's own tests (tests/init.test.cjs, "init
|
|
3225
|
+
// handlers honor GSD_WORKSTREAM") assert config_path IS workstream-scoped
|
|
3226
|
+
// for execute-phase/new-project/new-milestone/progress, and gsd-test
|
|
3227
|
+
// caught the regression immediately. The diagram is stale for
|
|
3228
|
+
// config.json specifically (same class of staleness already found for
|
|
3229
|
+
// `milestones/` during the #4455 follow-up) — reverted to the
|
|
3230
|
+
// workstream-aware planningDir(cwd), matching the established,
|
|
3231
|
+
// ADR-governed, tested contract.
|
|
2894
3232
|
config_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'config.json')),
|
|
2895
3233
|
};
|
|
2896
3234
|
// #2992 (Phase 6.1): additive, optional field — degrades to null, never throws.
|
|
@@ -3125,12 +3463,9 @@ function buildAgentSkillsBlock(config, agentType, projectRoot, diagnostics) {
|
|
|
3125
3463
|
warn(`[agent-skills] WARNING: Global skill not found at "${displayPath}/SKILL.md" — skipping\n`);
|
|
3126
3464
|
continue;
|
|
3127
3465
|
}
|
|
3128
|
-
const
|
|
3129
|
-
if (
|
|
3130
|
-
const acceptedViaTrustedRoot = trustedGlobalRoots.some((root) =>
|
|
3131
|
-
const rootCheck = (0, security_cjs_1.validatePath)(globalSkillMd, root, { allowAbsolute: true });
|
|
3132
|
-
return Boolean(rootCheck['safe']);
|
|
3133
|
-
});
|
|
3466
|
+
const globalSkillMdContained = (0, security_cjs_1.tryWithinRoot)(globalSkillMd, globalSkillsBase, security_cjs_1.PathAcceptance.AbsoluteInsideRoot);
|
|
3467
|
+
if (globalSkillMdContained === null) {
|
|
3468
|
+
const acceptedViaTrustedRoot = trustedGlobalRoots.some((root) => (0, security_cjs_1.tryWithinRoot)(globalSkillMd, root, security_cjs_1.PathAcceptance.AbsoluteInsideRoot) !== null);
|
|
3134
3469
|
if (!acceptedViaTrustedRoot) {
|
|
3135
3470
|
warn(`[agent-skills] WARNING: Global skill "${skillName}" failed path check (symlink escape?) — skipping\n`);
|
|
3136
3471
|
continue;
|
|
@@ -3139,15 +3474,23 @@ function buildAgentSkillsBlock(config, agentType, projectRoot, diagnostics) {
|
|
|
3139
3474
|
// trace, not a skip, so it must not land in the diagnostics warnings[].
|
|
3140
3475
|
process.stderr.write(`[agent-skills] NOTE: Global skill "${skillName}" accepted via trusted_global_roots (resolves outside the default skills dir)\n`);
|
|
3141
3476
|
}
|
|
3477
|
+
// `ref` is an emitted display token, not a path anything reads or writes
|
|
3478
|
+
// through — the containment check above is a gate, not a path producer.
|
|
3479
|
+
// Emitting the validated (realpath-resolved, platform-separator) value
|
|
3480
|
+
// instead of this literal broke symlinked skill dirs and Windows output.
|
|
3481
|
+
// The only filesystem read here (existsSync above) already ran on the
|
|
3482
|
+
// lexical path before containment was checked, so ADR-4650's "use the
|
|
3483
|
+
// validated value" rule doesn't apply to this emission.
|
|
3142
3484
|
validEntries.push({ kind: 'include', ref: `${globalSkillDir}/SKILL.md`, display: displayPath });
|
|
3143
3485
|
continue;
|
|
3144
3486
|
}
|
|
3145
|
-
const
|
|
3146
|
-
if (
|
|
3147
|
-
warn(`[agent-skills] WARNING: Skipping unsafe path "${skillPath}":
|
|
3487
|
+
const skillPathContained = (0, security_cjs_1.tryWithinRoot)(skillPath, projectRoot);
|
|
3488
|
+
if (skillPathContained === null) {
|
|
3489
|
+
warn(`[agent-skills] WARNING: Skipping unsafe path "${skillPath}": not confined to the project directory\n`);
|
|
3148
3490
|
continue;
|
|
3149
3491
|
}
|
|
3150
|
-
|
|
3492
|
+
// ADR-4650: the validated value is the value used — never re-derive from raw input.
|
|
3493
|
+
const skillMdPath = node_path_1.default.join(skillPathContained, 'SKILL.md');
|
|
3151
3494
|
if (!node_fs_1.default.existsSync(skillMdPath)) {
|
|
3152
3495
|
// #2941: if the bare name matches a global skill, hint at the global: prefix.
|
|
3153
3496
|
// The bare name resolves as project-relative (which doesn't exist), but the
|
|
@@ -3203,20 +3546,37 @@ function cmdAgentSkills(cwd, agentType, raw, jsonMode) {
|
|
|
3203
3546
|
// persona fallback. Triggering the fallback for claude would change the
|
|
3204
3547
|
// documented "unconfigured → empty block" contract that agent-skills tests
|
|
3205
3548
|
// pin.
|
|
3549
|
+
//
|
|
3550
|
+
// #4407 (ADR-4139 stream 2): this is the one place GSD's own agent-persona
|
|
3551
|
+
// content is served through a real code seam rather than an eagerly
|
|
3552
|
+
// @-included file, so the compact/canonical choice is made here in code
|
|
3553
|
+
// (a real exit code) instead of a prose config-get gate. Compact is tried
|
|
3554
|
+
// first when requested; a missing compact sibling falls back to canonical
|
|
3555
|
+
// with the fallback disclosed in the payload itself, never a silent switch.
|
|
3556
|
+
let agentPayloadVariant = null;
|
|
3206
3557
|
if (!block) {
|
|
3207
3558
|
const runtime = (config && config['runtime']) || process.env['GSD_RUNTIME'] || 'claude';
|
|
3208
3559
|
if (runtime !== 'claude') {
|
|
3209
3560
|
const agentCheck = checkAgentsInstalled(runtime, projectRoot);
|
|
3210
3561
|
const agentsDir = agentCheck?.agents_dir;
|
|
3211
3562
|
if (typeof agentsDir === 'string' && agentsDir.length > 0) {
|
|
3212
|
-
const
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3563
|
+
const compactRequested = readConfigJsonBoolean(projectRoot, ['workflow', 'compact_content']);
|
|
3564
|
+
const compactContent = compactRequested
|
|
3565
|
+
? readNonEmptyFileOrNull(node_path_1.default.join(agentsDir, `${agentType}.compact.md`))
|
|
3566
|
+
: null;
|
|
3567
|
+
if (compactContent !== null) {
|
|
3568
|
+
block = compactContent;
|
|
3569
|
+
agentPayloadVariant = 'compact';
|
|
3570
|
+
}
|
|
3571
|
+
else {
|
|
3572
|
+
const canonicalContent = readNonEmptyFileOrNull(node_path_1.default.join(agentsDir, `${agentType}.md`));
|
|
3573
|
+
if (canonicalContent !== null) {
|
|
3574
|
+
block = compactRequested
|
|
3575
|
+
? `<!-- gsd: no compact payload registered for ${agentType}; serving canonical -->\n\n${canonicalContent}`
|
|
3576
|
+
: canonicalContent;
|
|
3577
|
+
agentPayloadVariant = 'canonical';
|
|
3217
3578
|
}
|
|
3218
3579
|
}
|
|
3219
|
-
catch { /* agent file not found — fall through to empty block */ }
|
|
3220
3580
|
}
|
|
3221
3581
|
}
|
|
3222
3582
|
}
|
|
@@ -3263,8 +3623,8 @@ function cmdAgentSkills(cwd, agentType, raw, jsonMode) {
|
|
|
3263
3623
|
if (jsonMode) {
|
|
3264
3624
|
// Build the Resolution<AgentSkillsValue> envelope and embed .value additively.
|
|
3265
3625
|
// Flat fields are retained unchanged for back-compat; value formalises the
|
|
3266
|
-
// Resolution convention (ADR-1411 P3, #1416). source/degraded
|
|
3267
|
-
// config-provenance extras, outside the Resolution<T> envelope.
|
|
3626
|
+
// Resolution convention (ADR-1411 P3, #1416). source/degraded/agent_payload_variant
|
|
3627
|
+
// remain config-provenance extras, outside the Resolution<T> envelope.
|
|
3268
3628
|
const resolution = (0, resolution_cjs_1.makeResolution)({ block: block || '', skills_count: normalizedPaths.length }, { configured, reason, warnings: diagnostics.warnings });
|
|
3269
3629
|
output({
|
|
3270
3630
|
agent_type: agentType,
|
|
@@ -3275,6 +3635,7 @@ function cmdAgentSkills(cwd, agentType, raw, jsonMode) {
|
|
|
3275
3635
|
reason,
|
|
3276
3636
|
source,
|
|
3277
3637
|
degraded,
|
|
3638
|
+
agent_payload_variant: agentPayloadVariant,
|
|
3278
3639
|
value: resolution.value,
|
|
3279
3640
|
}, raw);
|
|
3280
3641
|
return;
|
|
@@ -3563,4 +3924,5 @@ module.exports = {
|
|
|
3563
3924
|
cmdAgentSkills,
|
|
3564
3925
|
buildSkillManifest,
|
|
3565
3926
|
cmdSkillManifest,
|
|
3927
|
+
renderPendingTodosMarkdown,
|
|
3566
3928
|
};
|