@opengsd/gsd-core 1.7.0-rc.4 → 1.7.0-rc.6
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 +20 -0
- package/agents/gsd-doc-classifier.md +105 -0
- package/agents/gsd-doc-synthesizer.md +61 -0
- package/agents/gsd-ui-checker.md +30 -0
- package/agents/gsd-ui-researcher.md +1 -0
- package/bin/install.js +1568 -622
- package/gsd-core/bin/gsd-tools.cjs +40 -1
- package/gsd-core/bin/lib/api-coverage.cjs +466 -0
- package/gsd-core/bin/lib/audit.cjs +6 -3
- package/gsd-core/bin/lib/capability-loader.cjs +11 -9
- package/gsd-core/bin/lib/capability-registry.cjs +761 -84
- package/gsd-core/bin/lib/capability-validator.cjs +56 -18
- package/gsd-core/bin/lib/capability-writer.cjs +10 -1
- package/gsd-core/bin/lib/check-command-router.cjs +242 -3
- package/gsd-core/bin/lib/commands.cjs +7 -5
- package/gsd-core/bin/lib/config-loader.cjs +1 -0
- package/gsd-core/bin/lib/config.cjs +96 -0
- package/gsd-core/bin/lib/core-utils.cjs +4 -1
- package/gsd-core/bin/lib/host-integration-adapters/cline-sdk-binding.cjs +234 -0
- package/gsd-core/bin/lib/host-integration-adapters/imperative-hook-bus.cjs +145 -0
- package/gsd-core/bin/lib/host-integration.cjs +45 -4
- package/gsd-core/bin/lib/init.cjs +76 -39
- package/gsd-core/bin/lib/install-effort-resolver.cjs +213 -0
- package/gsd-core/bin/lib/install-engine.cjs +228 -18
- package/gsd-core/bin/lib/installer-migration-report.cjs +7 -0
- package/gsd-core/bin/lib/loop-resolver.cjs +68 -17
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +50 -11
- package/gsd-core/bin/lib/mcp-server.cjs +18 -7
- package/gsd-core/bin/lib/milestone.cjs +3 -3
- package/gsd-core/bin/lib/normalize-test-command.cjs +187 -0
- package/gsd-core/bin/lib/phase-id.cjs +132 -3
- package/gsd-core/bin/lib/phase.cjs +78 -16
- package/gsd-core/bin/lib/planning-workspace.cjs +17 -0
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +24 -7
- package/gsd-core/bin/lib/roadmap-command-router.cjs +5 -4
- package/gsd-core/bin/lib/roadmap-parser.cjs +21 -30
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +9 -9
- package/gsd-core/bin/lib/roadmap.cjs +42 -56
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +248 -44
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +2 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +39 -23
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +19 -5
- package/gsd-core/bin/lib/runtime-homes.cjs +30 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +534 -37
- package/gsd-core/bin/lib/runtime-name-policy.cjs +63 -5
- package/gsd-core/bin/lib/security.cjs +6 -36
- package/gsd-core/bin/lib/shell-command-projection.cjs +115 -2
- package/gsd-core/bin/lib/spec-section.cjs +111 -0
- package/gsd-core/bin/lib/stale-bake-guard.cjs +30 -10
- package/gsd-core/bin/lib/state-transition.cjs +1 -1
- package/gsd-core/bin/lib/state.cjs +24 -24
- package/gsd-core/bin/lib/surface.cjs +40 -6
- package/gsd-core/bin/lib/uat.cjs +4 -1
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +249 -0
- package/gsd-core/bin/lib/validate.cjs +15 -6
- package/gsd-core/bin/lib/verify.cjs +33 -37
- package/gsd-core/bin/shared/config-schema.manifest.json +2 -0
- package/gsd-core/bin/shared/model-catalog.json +14 -9
- package/gsd-core/references/api-coverage.md +104 -0
- package/gsd-core/references/model-profiles.md +2 -2
- package/gsd-core/references/planning-config.md +2 -0
- package/gsd-core/references/specless-probe-fallback.md +172 -0
- package/gsd-core/references/ui-consideration-probe.md +73 -0
- package/gsd-core/templates/UI-SPEC.md +25 -0
- package/gsd-core/templates/VALIDATION.md +2 -0
- package/gsd-core/templates/config.json +2 -1
- package/gsd-core/workflows/audit-fix.md +9 -1
- package/gsd-core/workflows/audit-milestone.md +7 -4
- package/gsd-core/workflows/code-review-fix.md +7 -3
- package/gsd-core/workflows/code-review.md +4 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +4 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +8 -4
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +42 -0
- package/gsd-core/workflows/execute-phase.md +1 -25
- package/gsd-core/workflows/plan-phase.md +37 -2
- package/gsd-core/workflows/quick.md +2 -2
- package/gsd-core/workflows/review.md +59 -13
- package/gsd-core/workflows/settings-advanced.md +12 -9
- package/gsd-core/workflows/settings.md +2 -2
- package/gsd-core/workflows/ui-phase.md +146 -1
- package/gsd-core/workflows/validate-phase.md +2 -2
- package/gsd-core/workflows/verify-phase.md +3 -2
- package/gsd-core/workflows/verify-work.md +38 -0
- package/hooks/dist/gsd-cursor-pre-tool.js +76 -0
- package/hooks/dist/gsd-cursor-stop.js +48 -0
- package/hooks/dist/gsd-cursor-subagent-start.js +50 -0
- package/hooks/dist/gsd-cursor-subagent-stop.js +40 -0
- package/hooks/dist/gsd-windsurf-pre-command.js +275 -0
- package/hooks/dist/gsd-windsurf-pre-write.js +132 -0
- package/hooks/dist/managed-hooks-registry.cjs +6 -0
- package/hooks/gsd-cursor-pre-tool.js +76 -0
- package/hooks/gsd-cursor-stop.js +48 -0
- package/hooks/gsd-cursor-subagent-start.js +50 -0
- package/hooks/gsd-cursor-subagent-stop.js +40 -0
- package/hooks/gsd-windsurf-pre-command.js +275 -0
- package/hooks/gsd-windsurf-pre-write.js +132 -0
- package/hooks/managed-hooks-registry.cjs +6 -0
- package/package.json +9 -4
- package/pi/gsd.cjs +354 -0
- package/scripts/build-hooks.js +8 -1
- package/scripts/gen-golden-install-parity-zcode.cjs +11 -1
- package/scripts/gen-registry.cjs +128 -0
- package/scripts/lint-phase-id-drift.cjs +150 -0
- package/scripts/lint-test-file-count.allowlist.json +2 -1
- package/scripts/registry-schema.cjs +565 -0
- package/scripts/run-tests.cjs +21 -1
- package/scripts/validate-registry.cjs +117 -0
- package/vscode/browser.js +197 -0
- package/vscode/extension.js +383 -0
- package/vscode/host-binding.js +113 -0
- package/vscode/package.json +96 -0
|
@@ -63,9 +63,9 @@ const { resolveModelInternal, resolveGranularityInternal, assertValidGranularity
|
|
|
63
63
|
const { findPhaseInternal } = phaseLocator;
|
|
64
64
|
const { getRoadmapPhaseInternal, getMilestoneInfo, getMilestonePhaseFilter, stripShippedMilestones, extractCurrentMilestone, } = roadmapParser;
|
|
65
65
|
const { pathExistsInternal, generateSlugInternal, toPosixPath } = coreUtils;
|
|
66
|
-
const { normalizePhaseName, phaseTokenMatches, stripProjectCodePrefix } = phaseId;
|
|
66
|
+
const { escapeRegex, normalizePhaseName, phaseTokenMatches, stripProjectCodePrefix, PHASE_NUMBER_TOKEN_SOURCE, isForeignPrefixedPhaseQuery } = phaseId;
|
|
67
67
|
const { pruneOrphanedWorktrees } = worktreeSafety;
|
|
68
|
-
const { planningPaths, planningDir, planningRoot, getActiveWorkstream, findContextMdIn, } = planningWorkspace;
|
|
68
|
+
const { planningPaths, planningDir, planningRoot, listAvailableWorkstreams, getActiveWorkstream, findContextMdIn, } = planningWorkspace;
|
|
69
69
|
const { determinePhaseStatus } = commandsMod;
|
|
70
70
|
const { extractFrontmatter } = frontmatterMod;
|
|
71
71
|
const { readVerificationStatus } = verificationMod;
|
|
@@ -74,6 +74,37 @@ const { evaluateUatPassed } = uatPredicateMod;
|
|
|
74
74
|
void stripShippedMilestones;
|
|
75
75
|
// Accept all bold/colon variants of the Requirements header (#2769)
|
|
76
76
|
const REQUIREMENTS_HEADER_RE = /^\*\*Requirements:?\*\*[^\S\n]*:?[^\S\n]*([^\n]*)$/m;
|
|
77
|
+
// #2056/#2104: isForeignPrefixedPhaseQuery is imported from phase-id.cts
|
|
78
|
+
// (the canonical predicate). parsePhasePrefix is no longer needed locally.
|
|
79
|
+
// phaseInfoMatchesExactPrefix and roadmapPhaseMatchesExactPrefix are local
|
|
80
|
+
// helpers that post-filter the lookup results for foreign-prefix queries.
|
|
81
|
+
function phaseInfoMatchesExactPrefix(phaseInfo, phase) {
|
|
82
|
+
const num = phaseInfo?.['phase_number'];
|
|
83
|
+
const numStr = typeof num === 'string' ? num : (typeof num === 'number' ? String(num) : '');
|
|
84
|
+
return numStr.toUpperCase() === phase.toUpperCase();
|
|
85
|
+
}
|
|
86
|
+
function roadmapPhaseMatchesExactPrefix(roadmapPhase, phase) {
|
|
87
|
+
const sectionRaw = roadmapPhase?.['section'];
|
|
88
|
+
const section = typeof sectionRaw === 'string' ? sectionRaw : '';
|
|
89
|
+
return new RegExp(`^#{2,4}\\s*Phase\\s+${escapeRegex(phase)}(?:\\b|\\s|:)`, 'i').test(section);
|
|
90
|
+
}
|
|
91
|
+
// #2104: shared helpers that wrap findPhaseInternal / getRoadmapPhaseInternal
|
|
92
|
+
// with the #2056 foreign-prefix guard, so every init command gets the same
|
|
93
|
+
// protection without duplicating the guard logic at each call site.
|
|
94
|
+
function guardedFindPhase(cwd, phase, projectCode) {
|
|
95
|
+
let phaseInfo = findPhaseInternal(cwd, phase);
|
|
96
|
+
if (isForeignPrefixedPhaseQuery(phase, projectCode) && !phaseInfoMatchesExactPrefix(phaseInfo, phase)) {
|
|
97
|
+
phaseInfo = null;
|
|
98
|
+
}
|
|
99
|
+
return phaseInfo;
|
|
100
|
+
}
|
|
101
|
+
function guardedGetRoadmapPhase(cwd, phase, projectCode) {
|
|
102
|
+
let roadmapPhase = getRoadmapPhaseInternal(cwd, phase);
|
|
103
|
+
if (isForeignPrefixedPhaseQuery(phase, projectCode) && !roadmapPhaseMatchesExactPrefix(roadmapPhase, phase)) {
|
|
104
|
+
roadmapPhase = null;
|
|
105
|
+
}
|
|
106
|
+
return roadmapPhase;
|
|
107
|
+
}
|
|
77
108
|
function listPhaseSummaryFiles(phaseDir) {
|
|
78
109
|
return scanPhasePlans(phaseDir)['summaryFiles'];
|
|
79
110
|
}
|
|
@@ -229,9 +260,9 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
|
|
|
229
260
|
error('phase required for init execute-phase');
|
|
230
261
|
}
|
|
231
262
|
const config = loadConfig(cwd);
|
|
232
|
-
let phaseInfo =
|
|
263
|
+
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
|
|
233
264
|
const milestone = getMilestoneInfo(cwd);
|
|
234
|
-
const roadmapPhase =
|
|
265
|
+
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
|
|
235
266
|
if (phaseInfo?.['archived'] && roadmapPhase?.['found']) {
|
|
236
267
|
phaseInfo = null;
|
|
237
268
|
}
|
|
@@ -332,8 +363,9 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
|
|
|
332
363
|
error('phase required for init plan-phase');
|
|
333
364
|
}
|
|
334
365
|
const config = loadConfig(cwd);
|
|
335
|
-
|
|
336
|
-
|
|
366
|
+
// #2056/#2104: foreign-prefixed queries must not collapse to numeric phases.
|
|
367
|
+
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
|
|
368
|
+
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
|
|
337
369
|
if (phaseInfo?.['archived'] && roadmapPhase?.['found']) {
|
|
338
370
|
phaseInfo = null;
|
|
339
371
|
}
|
|
@@ -573,6 +605,9 @@ function cmdInitQuick(cwd, description, raw) {
|
|
|
573
605
|
executor_model: resolveModelInternal(cwd, 'gsd-executor'),
|
|
574
606
|
checker_model: resolveModelInternal(cwd, 'gsd-plan-checker'),
|
|
575
607
|
verifier_model: resolveModelInternal(cwd, 'gsd-verifier'),
|
|
608
|
+
// #2072: the quick review step spawns gsd-code-reviewer; resolve its own model
|
|
609
|
+
// so model_overrides / models.verification apply (was reusing executor_model).
|
|
610
|
+
reviewer_model: resolveModelInternal(cwd, 'gsd-code-reviewer'),
|
|
576
611
|
commit_docs: config.commit_docs,
|
|
577
612
|
branch_name: quickBranchName,
|
|
578
613
|
quick_id: quickId,
|
|
@@ -637,15 +672,15 @@ function cmdInitVerifyWork(cwd, phase, raw) {
|
|
|
637
672
|
}
|
|
638
673
|
const config = loadConfig(cwd);
|
|
639
674
|
const _slashRuntime = (0, runtime_slash_cjs_1.resolveRuntime)(cwd);
|
|
640
|
-
let phaseInfo =
|
|
675
|
+
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
|
|
641
676
|
if (phaseInfo?.['archived']) {
|
|
642
|
-
const roadmapPhase =
|
|
677
|
+
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
|
|
643
678
|
if (roadmapPhase?.['found']) {
|
|
644
679
|
phaseInfo = null;
|
|
645
680
|
}
|
|
646
681
|
}
|
|
647
682
|
if (!phaseInfo) {
|
|
648
|
-
const roadmapPhase =
|
|
683
|
+
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
|
|
649
684
|
if (roadmapPhase?.['found']) {
|
|
650
685
|
const phaseName = roadmapPhase['phase_name'];
|
|
651
686
|
phaseInfo = {
|
|
@@ -694,9 +729,9 @@ function cmdInitVerifyWork(cwd, phase, raw) {
|
|
|
694
729
|
}
|
|
695
730
|
function cmdInitPhaseOp(cwd, phase, raw) {
|
|
696
731
|
const config = loadConfig(cwd);
|
|
697
|
-
let phaseInfo =
|
|
732
|
+
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
|
|
698
733
|
if (phaseInfo?.['archived']) {
|
|
699
|
-
const roadmapPhase =
|
|
734
|
+
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
|
|
700
735
|
if (roadmapPhase?.['found']) {
|
|
701
736
|
const phaseName = roadmapPhase['phase_name'];
|
|
702
737
|
phaseInfo = {
|
|
@@ -717,7 +752,7 @@ function cmdInitPhaseOp(cwd, phase, raw) {
|
|
|
717
752
|
}
|
|
718
753
|
}
|
|
719
754
|
if (!phaseInfo) {
|
|
720
|
-
const roadmapPhase =
|
|
755
|
+
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
|
|
721
756
|
if (roadmapPhase?.['found']) {
|
|
722
757
|
const phaseName = roadmapPhase['phase_name'];
|
|
723
758
|
phaseInfo = {
|
|
@@ -874,8 +909,8 @@ function cmdInitMilestoneOp(cwd, raw) {
|
|
|
874
909
|
const roadmapPath = node_path_1.default.join(planningDir(cwd), 'ROADMAP.md');
|
|
875
910
|
const roadmapRaw = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
|
|
876
911
|
const currentSection = extractCurrentMilestone(roadmapRaw, cwd);
|
|
877
|
-
// #1729: `(?:\s*\([^)\n]
|
|
878
|
-
const phasePattern =
|
|
912
|
+
// #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
|
|
913
|
+
const phasePattern = new RegExp(`#{2,4}\\s*Phase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:`, 'gi');
|
|
879
914
|
let m;
|
|
880
915
|
while ((m = phasePattern.exec(currentSection)) !== null) {
|
|
881
916
|
if (/^999(?:\.|$)/.test(m[1]))
|
|
@@ -896,7 +931,7 @@ function cmdInitMilestoneOp(cwd, raw) {
|
|
|
896
931
|
for (const e of entries) {
|
|
897
932
|
if (!e.isDirectory())
|
|
898
933
|
continue;
|
|
899
|
-
const m = stripProjectCodePrefix(e.name).match(
|
|
934
|
+
const m = stripProjectCodePrefix(e.name).match(new RegExp(`^(${PHASE_NUMBER_TOKEN_SOURCE})`));
|
|
900
935
|
if (!m)
|
|
901
936
|
continue;
|
|
902
937
|
diskPhaseDirs.set(canonicalizePhase(m[1]), e.name);
|
|
@@ -1024,13 +1059,13 @@ function cmdInitManager(cwd, raw) {
|
|
|
1024
1059
|
}
|
|
1025
1060
|
})();
|
|
1026
1061
|
const _checkboxStates = new Map();
|
|
1027
|
-
const _cbPattern =
|
|
1062
|
+
const _cbPattern = new RegExp(`-\\s*\\[(x| )\\]\\s*.*Phase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})[:\\s]`, 'gi');
|
|
1028
1063
|
let _cbMatch;
|
|
1029
1064
|
while ((_cbMatch = _cbPattern.exec(content)) !== null) {
|
|
1030
1065
|
_checkboxStates.set(_cbMatch[2], _cbMatch[1].toLowerCase() === 'x');
|
|
1031
1066
|
}
|
|
1032
|
-
// #1729: `(?:\s*\([^)\n]
|
|
1033
|
-
const phasePattern =
|
|
1067
|
+
// #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
|
|
1068
|
+
const phasePattern = new RegExp(`#{2,4}\\s*Phase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:\\s*([^\\n]+)`, 'gi');
|
|
1034
1069
|
const phases = [];
|
|
1035
1070
|
let match;
|
|
1036
1071
|
while ((match = phasePattern.exec(content)) !== null) {
|
|
@@ -1148,7 +1183,7 @@ function cmdInitManager(cwd, raw) {
|
|
|
1148
1183
|
.filter((p) => p['phase_complete'] === true)
|
|
1149
1184
|
.map((p) => normalizePhaseNumber(p['number'])));
|
|
1150
1185
|
const phaseMap = new Map(phases.map((p) => [normalizePhaseNumber(p['number']), p]));
|
|
1151
|
-
const _allCompletedPattern =
|
|
1186
|
+
const _allCompletedPattern = new RegExp(`-\\s*\\[x\\]\\s*.*Phase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})[:\\s]`, 'gi');
|
|
1152
1187
|
let _allMatch;
|
|
1153
1188
|
while ((_allMatch = _allCompletedPattern.exec(rawContent)) !== null) {
|
|
1154
1189
|
const phaseNum = normalizePhaseNumber(_allMatch[1]);
|
|
@@ -1180,7 +1215,7 @@ function cmdInitManager(cwd, raw) {
|
|
|
1180
1215
|
phase['deps_satisfied'] = true;
|
|
1181
1216
|
}
|
|
1182
1217
|
else {
|
|
1183
|
-
const depNums = phase['depends_on'].match(
|
|
1218
|
+
const depNums = phase['depends_on'].match(new RegExp(`${PHASE_NUMBER_TOKEN_SOURCE}`, 'gi')) || [];
|
|
1184
1219
|
phase['deps_satisfied'] = depNums.every((n) => completedNums.has(normalizePhaseNumber(n)));
|
|
1185
1220
|
phase['dep_phases'] = depNums;
|
|
1186
1221
|
}
|
|
@@ -1319,18 +1354,7 @@ function cmdInitProgress(cwd, raw) {
|
|
|
1319
1354
|
// reporting a stale root milestone. Require an explicit workstream instead.
|
|
1320
1355
|
// Mirror planningDir's resolution (GSD_WORKSTREAM env > stored active pointer) so
|
|
1321
1356
|
// an explicit --ws (which sets GSD_WORKSTREAM) satisfies the check.
|
|
1322
|
-
const
|
|
1323
|
-
let _availableWorkstreams = [];
|
|
1324
|
-
try {
|
|
1325
|
-
_availableWorkstreams = node_fs_1.default
|
|
1326
|
-
.readdirSync(_wsRoot, { withFileTypes: true })
|
|
1327
|
-
.filter((e) => e.isDirectory())
|
|
1328
|
-
.map((e) => e.name)
|
|
1329
|
-
.sort();
|
|
1330
|
-
}
|
|
1331
|
-
catch {
|
|
1332
|
-
/* no workstreams dir → flat mode */
|
|
1333
|
-
}
|
|
1357
|
+
const _availableWorkstreams = listAvailableWorkstreams(cwd);
|
|
1334
1358
|
const _resolvedWorkstream = process.env['GSD_WORKSTREAM'] || getActiveWorkstream(cwd);
|
|
1335
1359
|
if (_availableWorkstreams.length > 0 && !_resolvedWorkstream) {
|
|
1336
1360
|
error(`init.progress requires a workstream in workstream mode — no active workstream is set, so root STATE.md (likely stale) would be reported. ` +
|
|
@@ -1346,14 +1370,14 @@ function cmdInitProgress(cwd, raw) {
|
|
|
1346
1370
|
const roadmapCheckboxStates = new Map();
|
|
1347
1371
|
try {
|
|
1348
1372
|
const roadmapContent = extractCurrentMilestone(node_fs_1.default.readFileSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md'), 'utf-8'), cwd);
|
|
1349
|
-
// #1729: `(?:\s*\([^)\n]
|
|
1350
|
-
const headingPattern =
|
|
1373
|
+
// #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
|
|
1374
|
+
const headingPattern = new RegExp(`#{2,4}\\s*Phase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:\\s*([^\\n]+)`, 'gi');
|
|
1351
1375
|
let hm;
|
|
1352
1376
|
while ((hm = headingPattern.exec(roadmapContent)) !== null) {
|
|
1353
1377
|
roadmapPhaseNums.add(hm[1]);
|
|
1354
1378
|
roadmapPhaseNames.set(hm[1], hm[2].replace(/\(INSERTED\)/i, '').trim());
|
|
1355
1379
|
}
|
|
1356
|
-
const cbPattern =
|
|
1380
|
+
const cbPattern = new RegExp(`-\\s*\\[(x| )\\]\\s*.*Phase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})[:\\s]`, 'gi');
|
|
1357
1381
|
let cbm;
|
|
1358
1382
|
while ((cbm = cbPattern.exec(roadmapContent)) !== null) {
|
|
1359
1383
|
roadmapCheckboxStates.set(cbm[2], cbm[1].toLowerCase() === 'x');
|
|
@@ -1371,14 +1395,14 @@ function cmdInitProgress(cwd, raw) {
|
|
|
1371
1395
|
.map((e) => e.name)
|
|
1372
1396
|
.filter(isDirInMilestone)
|
|
1373
1397
|
.sort((a, b) => {
|
|
1374
|
-
const pa = a.match(
|
|
1375
|
-
const pb = b.match(
|
|
1398
|
+
const pa = a.match(new RegExp(`^(${PHASE_NUMBER_TOKEN_SOURCE})`, 'i'));
|
|
1399
|
+
const pb = b.match(new RegExp(`^(${PHASE_NUMBER_TOKEN_SOURCE})`, 'i'));
|
|
1376
1400
|
if (!pa || !pb)
|
|
1377
1401
|
return a.localeCompare(b);
|
|
1378
1402
|
return parseInt(pa[1], 10) - parseInt(pb[1], 10);
|
|
1379
1403
|
});
|
|
1380
1404
|
for (const dir of dirs) {
|
|
1381
|
-
const dirMatch = dir.match(
|
|
1405
|
+
const dirMatch = dir.match(new RegExp(`^(${PHASE_NUMBER_TOKEN_SOURCE})-?(.*)`, 'i'));
|
|
1382
1406
|
const phaseNumber = dirMatch ? dirMatch[1] : dir;
|
|
1383
1407
|
const phaseName = dirMatch && dirMatch[2] ? dirMatch[2] : null;
|
|
1384
1408
|
seenPhaseNums.add(phaseNumber.replace(/^0+/, '') || '0');
|
|
@@ -1870,11 +1894,24 @@ function buildSkillManifest(cwd, skillsDir = null) {
|
|
|
1870
1894
|
kind: 'skills',
|
|
1871
1895
|
},
|
|
1872
1896
|
{
|
|
1873
|
-
|
|
1897
|
+
// ADR-1239 upgrade 3 (#2088): Codex's canonical skill root is
|
|
1898
|
+
// $HOME/.agents/skills (per codex core-skills loader.rs), resolved via
|
|
1899
|
+
// the skills-kind `home` override in getGlobalSkillsBase.
|
|
1900
|
+
root: '~/.agents/skills',
|
|
1874
1901
|
path: (0, runtime_homes_cjs_1.getGlobalSkillsBase)('codex'),
|
|
1875
1902
|
scope: 'global',
|
|
1876
1903
|
kind: 'skills',
|
|
1877
1904
|
},
|
|
1905
|
+
{
|
|
1906
|
+
// Codex's deprecated fallback skill root ($CODEX_HOME/skills). Kept as a
|
|
1907
|
+
// discovery-only legacy root so pre-move installs remain inventoried;
|
|
1908
|
+
// GSD no longer installs here (#2088).
|
|
1909
|
+
root: '~/.codex/skills',
|
|
1910
|
+
path: node_path_1.default.join((0, runtime_homes_cjs_1.getGlobalConfigDir)('codex'), 'skills'),
|
|
1911
|
+
scope: 'global',
|
|
1912
|
+
kind: 'skills',
|
|
1913
|
+
deprecated: true,
|
|
1914
|
+
},
|
|
1878
1915
|
{
|
|
1879
1916
|
root: '.claude/gsd-core/skills',
|
|
1880
1917
|
path: node_path_1.default.join(node_os_1.default.homedir(), '.claude', 'gsd-core', 'skills'),
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* install-effort-resolver — install-time effort resolution (#443), extracted from
|
|
7
|
+
* the package-root `bin/install.js` (#2071).
|
|
8
|
+
*
|
|
9
|
+
* `gsd-tools effort sync` (src/commands.cts `cmdEffortSync`) must mirror what the
|
|
10
|
+
* installer writes — home `~/.gsd/defaults.json` merged with the project's
|
|
11
|
+
* `.planning/config.json` — which the runtime resolver (`resolveEffortInternal`
|
|
12
|
+
* via `loadConfig`) does NOT do. It previously reached those two functions via
|
|
13
|
+
* `require('../../../bin/install.js')`, but the installer never copies the
|
|
14
|
+
* package-root `bin/install.js` into a runtime home, so `effort sync` crashed with
|
|
15
|
+
* MODULE_NOT_FOUND in every installed runtime (#2071). Moving the logic here — a
|
|
16
|
+
* `src/*.cts` module compiled into the shipped `gsd-core/bin/lib/` tree — lets both
|
|
17
|
+
* the installer AND `effort sync` require it from a location that is always present,
|
|
18
|
+
* keeping a single source of truth (no duplication / drift).
|
|
19
|
+
*
|
|
20
|
+
* Pure with respect to config: `readGsdEffectiveEffortConfig` performs the config
|
|
21
|
+
* reads; `resolveInstallTimeEffort` is pure given a pre-merged effort object.
|
|
22
|
+
*/
|
|
23
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
24
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
25
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- model-resolver.cjs is an export= CommonJS module
|
|
27
|
+
const modelResolver = require("./model-resolver.cjs");
|
|
28
|
+
const { EFFORT_SET: GSD_EFFORT_SET } = modelResolver;
|
|
29
|
+
/**
|
|
30
|
+
* #2517 — Read a single GSD config file (defaults.json or per-project
|
|
31
|
+
* config.json) into a plain object, returning null on missing/empty files
|
|
32
|
+
* and warning to stderr on JSON parse failures so silent corruption can't
|
|
33
|
+
* mask broken configs (review finding #5).
|
|
34
|
+
*/
|
|
35
|
+
function _readGsdConfigFile(absPath, label) {
|
|
36
|
+
if (!node_fs_1.default.existsSync(absPath))
|
|
37
|
+
return null;
|
|
38
|
+
let raw;
|
|
39
|
+
try {
|
|
40
|
+
raw = node_fs_1.default.readFileSync(absPath, 'utf-8');
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
process.stderr.write(`gsd: warning — could not read ${label} (${absPath}): ${err.message}\n`);
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse(raw);
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
process.stderr.write(`gsd: warning — invalid JSON in ${label} (${absPath}): ${err.message}\n`);
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// #443 — model-catalog and config-defaults.manifest.json exports needed only
|
|
55
|
+
// by effort-resolution code paths (resolveInstallTimeEffort /
|
|
56
|
+
// generateCodexAgentToml / Claude .md effort injection). Loaded lazily the
|
|
57
|
+
// first time they are needed so that requiring this module in test contexts that
|
|
58
|
+
// never trigger an install does NOT produce module-load-time side effects (the
|
|
59
|
+
// manifest read + hard throw) that could alter subprocess exit codes or stderr.
|
|
60
|
+
let _gsdEffortCatalogCache = null;
|
|
61
|
+
function _getGsdEffortCatalog() {
|
|
62
|
+
if (_gsdEffortCatalogCache)
|
|
63
|
+
return _gsdEffortCatalogCache;
|
|
64
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- model-catalog.cjs is an export= CommonJS module
|
|
65
|
+
const { AGENT_DEFAULT_TIERS, renderEffortForRuntime } = require('./model-catalog.cjs');
|
|
66
|
+
// This module lives in gsd-core/bin/lib/, so the shared manifest is one level
|
|
67
|
+
// up in gsd-core/bin/shared/ (bin/lib → bin → shared). (In bin/install.js this
|
|
68
|
+
// path was `.., gsd-core, bin, shared` relative to the package-root bin/.)
|
|
69
|
+
const manifestPath = node_path_1.default.join(__dirname, '..', 'shared', 'config-defaults.manifest.json');
|
|
70
|
+
let manifestData;
|
|
71
|
+
try {
|
|
72
|
+
manifestData = JSON.parse(node_fs_1.default.readFileSync(manifestPath, 'utf-8'));
|
|
73
|
+
}
|
|
74
|
+
catch (_err) {
|
|
75
|
+
// Fail loudly — a missing manifest is a broken install, not a soft degradation.
|
|
76
|
+
throw new Error(`gsd install: cannot load config-defaults.manifest.json at ${manifestPath}: ${_err.message}`);
|
|
77
|
+
}
|
|
78
|
+
const tierDefaults = (manifestData.effort &&
|
|
79
|
+
manifestData.effort.routing_tier_defaults &&
|
|
80
|
+
typeof manifestData.effort.routing_tier_defaults === 'object' &&
|
|
81
|
+
!Array.isArray(manifestData.effort.routing_tier_defaults))
|
|
82
|
+
? manifestData.effort.routing_tier_defaults
|
|
83
|
+
: { light: 'low', standard: 'high', heavy: 'xhigh' }; // guard: unreachable if manifest is valid
|
|
84
|
+
const effortDefault = (manifestData.effort && typeof manifestData.effort.default === 'string')
|
|
85
|
+
? manifestData.effort.default
|
|
86
|
+
: 'high'; // guard: unreachable if manifest is valid
|
|
87
|
+
_gsdEffortCatalogCache = {
|
|
88
|
+
AGENT_DEFAULT_TIERS,
|
|
89
|
+
renderEffortForRuntime,
|
|
90
|
+
EFFORT_MANIFEST_TIER_DEFAULTS: tierDefaults,
|
|
91
|
+
EFFORT_MANIFEST_DEFAULT: effortDefault,
|
|
92
|
+
};
|
|
93
|
+
return _gsdEffortCatalogCache;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* #443 — Read the merged `effort` config block for install-time effort resolution.
|
|
97
|
+
*
|
|
98
|
+
* Probes the same config sources as readGsdRuntimeProfileResolver (per-project
|
|
99
|
+
* `.planning/config.json` wins over `~/.gsd/defaults.json`) but extracts the
|
|
100
|
+
* `effort` object instead of the model-profile fields.
|
|
101
|
+
*
|
|
102
|
+
* Returns the merged `effort` object or null when neither source defines one.
|
|
103
|
+
* The caller can pass this to resolveInstallTimeEffort() which is pure and
|
|
104
|
+
* requires no filesystem access beyond what this helper already performs.
|
|
105
|
+
*
|
|
106
|
+
* @param targetDir Runtime install root (walks up to find .planning/).
|
|
107
|
+
*/
|
|
108
|
+
function readGsdEffectiveEffortConfig(targetDir = null) {
|
|
109
|
+
const homeDefaults = _readGsdConfigFile(node_path_1.default.join(node_os_1.default.homedir(), '.gsd', 'defaults.json'), '~/.gsd/defaults.json');
|
|
110
|
+
let projectConfig = null;
|
|
111
|
+
if (targetDir) {
|
|
112
|
+
let probeDir = node_path_1.default.resolve(targetDir);
|
|
113
|
+
for (let depth = 0; depth < 8; depth += 1) {
|
|
114
|
+
const candidate = node_path_1.default.join(probeDir, '.planning', 'config.json');
|
|
115
|
+
if (node_fs_1.default.existsSync(candidate)) {
|
|
116
|
+
projectConfig = _readGsdConfigFile(candidate, '.planning/config.json');
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
const parent = node_path_1.default.dirname(probeDir);
|
|
120
|
+
if (parent === probeDir)
|
|
121
|
+
break;
|
|
122
|
+
probeDir = parent;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const homeEffort = (homeDefaults && homeDefaults.effort && typeof homeDefaults.effort === 'object' && !Array.isArray(homeDefaults.effort))
|
|
126
|
+
? homeDefaults.effort
|
|
127
|
+
: null;
|
|
128
|
+
const projectEffort = (projectConfig && projectConfig.effort && typeof projectConfig.effort === 'object' && !Array.isArray(projectConfig.effort))
|
|
129
|
+
? projectConfig.effort
|
|
130
|
+
: null;
|
|
131
|
+
if (!homeEffort && !projectEffort)
|
|
132
|
+
return null;
|
|
133
|
+
// Per-project wins on conflict within each sub-field. Merge field-by-field so
|
|
134
|
+
// a project config that only sets agent_overrides still inherits global
|
|
135
|
+
// routing_tier_defaults and default.
|
|
136
|
+
return {
|
|
137
|
+
...(homeEffort || {}),
|
|
138
|
+
...(projectEffort || {}),
|
|
139
|
+
// Deep-merge agent_overrides (project wins per-key)
|
|
140
|
+
agent_overrides: {
|
|
141
|
+
...((homeEffort && homeEffort.agent_overrides) || {}),
|
|
142
|
+
...((projectEffort && projectEffort.agent_overrides) || {}),
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* #443 — Resolve install-time effort for a given agent, using the same
|
|
148
|
+
* precedence chain as resolveEffortInternal() in core.cjs, but operating
|
|
149
|
+
* on a pre-loaded effortCfg object (no loadConfig side-effects at install).
|
|
150
|
+
*
|
|
151
|
+
* Precedence (mirrors resolveEffortInternal):
|
|
152
|
+
* 1. effortCfg.agent_overrides[agentName]
|
|
153
|
+
* 2. effortCfg.routing_tier_defaults[agentTier] (if effortCfg present)
|
|
154
|
+
* — OR manifest tier defaults when effortCfg is null
|
|
155
|
+
* 3. effortCfg.default
|
|
156
|
+
* 4. 'high' (hardcoded fallback)
|
|
157
|
+
*
|
|
158
|
+
* @param effortCfg Result of readGsdEffectiveEffortConfig().
|
|
159
|
+
* @param agentName e.g. 'gsd-planner'
|
|
160
|
+
* @returns Universal effort string (low/medium/high/xhigh/max/minimal)
|
|
161
|
+
*/
|
|
162
|
+
function resolveInstallTimeEffort(effortCfg, agentName) {
|
|
163
|
+
// Validates each candidate against the canonical EFFORT_SET (sourced once
|
|
164
|
+
// from model-resolver.cjs) before accepting it, mirroring resolveEffortInternal
|
|
165
|
+
// exactly. Invalid values fall through to the next precedence layer; final
|
|
166
|
+
// fallback 'high'.
|
|
167
|
+
// Step 1: agent_overrides
|
|
168
|
+
if (effortCfg) {
|
|
169
|
+
const ao = effortCfg.agent_overrides;
|
|
170
|
+
if (ao && typeof ao === 'object' && !Array.isArray(ao)) {
|
|
171
|
+
const v = ao[agentName];
|
|
172
|
+
if (typeof v === 'string' && GSD_EFFORT_SET.has(v))
|
|
173
|
+
return v;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// Step 2: routing_tier_defaults keyed by the agent's catalog tier
|
|
177
|
+
const { AGENT_DEFAULT_TIERS, EFFORT_MANIFEST_TIER_DEFAULTS, EFFORT_MANIFEST_DEFAULT } = _getGsdEffortCatalog();
|
|
178
|
+
const agentTier = AGENT_DEFAULT_TIERS[agentName];
|
|
179
|
+
if (agentTier) {
|
|
180
|
+
if (effortCfg && effortCfg.routing_tier_defaults &&
|
|
181
|
+
typeof effortCfg.routing_tier_defaults === 'object' &&
|
|
182
|
+
!Array.isArray(effortCfg.routing_tier_defaults)) {
|
|
183
|
+
const v = effortCfg.routing_tier_defaults[agentTier];
|
|
184
|
+
if (typeof v === 'string' && GSD_EFFORT_SET.has(v))
|
|
185
|
+
return v;
|
|
186
|
+
}
|
|
187
|
+
else if (!effortCfg) {
|
|
188
|
+
// No effort config — use manifest tier defaults
|
|
189
|
+
const v = EFFORT_MANIFEST_TIER_DEFAULTS[agentTier];
|
|
190
|
+
if (typeof v === 'string' && GSD_EFFORT_SET.has(v))
|
|
191
|
+
return v;
|
|
192
|
+
}
|
|
193
|
+
// effortCfg exists but has no routing_tier_defaults — fall through
|
|
194
|
+
}
|
|
195
|
+
// Step 3: effort.default
|
|
196
|
+
if (effortCfg) {
|
|
197
|
+
const d = effortCfg.default;
|
|
198
|
+
if (typeof d === 'string' && GSD_EFFORT_SET.has(d))
|
|
199
|
+
return d;
|
|
200
|
+
}
|
|
201
|
+
// Step 4: manifest default (sourced from config-defaults.manifest.json effort.default)
|
|
202
|
+
// If even the manifest default is invalid, fall back to 'high'.
|
|
203
|
+
if (typeof EFFORT_MANIFEST_DEFAULT === 'string' && GSD_EFFORT_SET.has(EFFORT_MANIFEST_DEFAULT)) {
|
|
204
|
+
return EFFORT_MANIFEST_DEFAULT;
|
|
205
|
+
}
|
|
206
|
+
return 'high';
|
|
207
|
+
}
|
|
208
|
+
module.exports = {
|
|
209
|
+
readGsdEffectiveEffortConfig,
|
|
210
|
+
resolveInstallTimeEffort,
|
|
211
|
+
_getGsdEffortCatalog,
|
|
212
|
+
_readGsdConfigFile,
|
|
213
|
+
};
|