@opengsd/gsd-core 1.7.0-rc.5 → 1.7.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-executor.md +2 -1
- package/agents/gsd-security-auditor.md +13 -15
- package/agents/gsd-ui-checker.md +2 -0
- package/agents/gsd-ui-researcher.md +1 -0
- package/bin/install.js +975 -196
- package/commands/gsd/mempalace-capture.md +27 -1
- package/commands/gsd/surface.md +6 -6
- package/gsd-core/bin/gsd-tools.cjs +63 -2
- package/gsd-core/bin/lib/api-coverage.cjs +3 -4
- package/gsd-core/bin/lib/audit.cjs +7 -6
- package/gsd-core/bin/lib/capability-registry.cjs +503 -87
- package/gsd-core/bin/lib/capability-validator.cjs +56 -18
- package/gsd-core/bin/lib/check-command-router.cjs +1 -1
- package/gsd-core/bin/lib/clock.cjs +19 -0
- package/gsd-core/bin/lib/commands.cjs +48 -9
- package/gsd-core/bin/lib/config-loader.cjs +6 -2
- package/gsd-core/bin/lib/config.cjs +12 -0
- package/gsd-core/bin/lib/core-utils.cjs +8 -2
- package/gsd-core/bin/lib/drift.cjs +4 -4
- package/gsd-core/bin/lib/frontmatter.cjs +22 -0
- package/gsd-core/bin/lib/gsd2-import.cjs +2 -1
- package/gsd-core/bin/lib/host-integration.cjs +33 -8
- package/gsd-core/bin/lib/init.cjs +60 -53
- package/gsd-core/bin/lib/install-engine.cjs +93 -22
- package/gsd-core/bin/lib/installer-migration-authoring.cjs +2 -1
- package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +1 -1
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +342 -0
- package/gsd-core/bin/lib/markdown-table.cjs +698 -0
- package/gsd-core/bin/lib/mcp-server.cjs +18 -7
- package/gsd-core/bin/lib/milestone.cjs +217 -31
- package/gsd-core/bin/lib/phase-command-router.cjs +50 -2
- package/gsd-core/bin/lib/phase-lifecycle.cjs +62 -36
- package/gsd-core/bin/lib/phase-locator.cjs +23 -2
- package/gsd-core/bin/lib/phase.cjs +436 -61
- package/gsd-core/bin/lib/plan-scan.cjs +3 -0
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +24 -7
- package/gsd-core/bin/lib/roadmap-parser.cjs +218 -13
- package/gsd-core/bin/lib/roadmap.cjs +100 -49
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +242 -44
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +3 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +24 -14
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +19 -5
- package/gsd-core/bin/lib/runtime-homes.cjs +22 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +526 -29
- package/gsd-core/bin/lib/runtime-name-policy.cjs +63 -5
- package/gsd-core/bin/lib/schema-detect.cjs +2 -1
- package/gsd-core/bin/lib/security.cjs +7 -37
- package/gsd-core/bin/lib/shell-command-projection.cjs +176 -27
- package/gsd-core/bin/lib/smart-entry.cjs +4 -3
- package/gsd-core/bin/lib/stale-bake-guard.cjs +30 -10
- package/gsd-core/bin/lib/state-transition.cjs +100 -45
- package/gsd-core/bin/lib/state.cjs +391 -126
- package/gsd-core/bin/lib/surface.cjs +12 -8
- package/gsd-core/bin/lib/template.cjs +2 -1
- package/gsd-core/bin/lib/uat.cjs +54 -8
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +249 -0
- package/gsd-core/bin/lib/ui-safety-gate.cjs +23 -1
- package/gsd-core/bin/lib/verify.cjs +4 -3
- package/gsd-core/bin/lib/workstream.cjs +3 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +1 -1
- package/gsd-core/bin/lib/write-set.cjs +38 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +2 -0
- package/gsd-core/bin/shared/model-catalog.json +8 -3
- package/gsd-core/references/checkpoints.md +12 -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/workflows/add-tests.md +1 -1
- package/gsd-core/workflows/audit-milestone.md +7 -4
- package/gsd-core/workflows/debug.md +2 -0
- package/gsd-core/workflows/execute-phase.md +5 -3
- package/gsd-core/workflows/fast.md +8 -22
- package/gsd-core/workflows/plan-phase.md +6 -0
- package/gsd-core/workflows/progress.md +2 -2
- package/gsd-core/workflows/quick.md +2 -0
- package/gsd-core/workflows/review.md +42 -3
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/settings-advanced.md +7 -4
- package/gsd-core/workflows/ship.md +8 -2
- package/gsd-core/workflows/spec-phase.md +1 -1
- package/gsd-core/workflows/transition.md +1 -1
- package/gsd-core/workflows/ui-phase.md +146 -1
- package/gsd-core/workflows/validate-phase.md +2 -2
- package/hooks/dist/gsd-statusline.js +164 -14
- 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 +2 -0
- package/hooks/gsd-statusline.js +164 -14
- package/hooks/gsd-windsurf-pre-command.js +275 -0
- package/hooks/gsd-windsurf-pre-write.js +132 -0
- package/hooks/managed-hooks-registry.cjs +2 -0
- package/package.json +10 -4
- package/pi/gsd.cjs +354 -0
- package/scripts/build-hooks.js +3 -0
- package/scripts/ci-test-scope.cjs +39 -1
- package/scripts/gen-golden-install-parity-zcode.cjs +35 -35
- package/scripts/gen-install-tree-fixtures.cjs +75 -0
- package/scripts/gen-registry.cjs +128 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -1
- package/scripts/lint-table-schema-drift.cjs +157 -0
- package/scripts/lint-test-file-count.allowlist.json +2 -1
- package/scripts/registry-schema.cjs +565 -0
- package/scripts/validate-registry.cjs +117 -0
- package/skills/gsd-mempalace-capture/SKILL.md +27 -1
- package/skills/gsd-surface/SKILL.md +6 -6
- 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
|
@@ -13,6 +13,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
13
13
|
const node_path_1 = __importDefault(require("node:path"));
|
|
14
14
|
const node_os_1 = __importDefault(require("node:os"));
|
|
15
15
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
16
|
+
const clock_cjs_1 = require("./clock.cjs");
|
|
16
17
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- io.cjs is an export= CommonJS module
|
|
17
18
|
const io = require("./io.cjs");
|
|
18
19
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- config-loader.cjs is an export= CommonJS module
|
|
@@ -63,7 +64,7 @@ const { resolveModelInternal, resolveGranularityInternal, assertValidGranularity
|
|
|
63
64
|
const { findPhaseInternal } = phaseLocator;
|
|
64
65
|
const { getRoadmapPhaseInternal, getMilestoneInfo, getMilestonePhaseFilter, stripShippedMilestones, extractCurrentMilestone, } = roadmapParser;
|
|
65
66
|
const { pathExistsInternal, generateSlugInternal, toPosixPath } = coreUtils;
|
|
66
|
-
const { escapeRegex, normalizePhaseName, phaseTokenMatches, stripProjectCodePrefix, PHASE_NUMBER_TOKEN_SOURCE } = phaseId;
|
|
67
|
+
const { escapeRegex, normalizePhaseName, phaseTokenMatches, stripProjectCodePrefix, PHASE_NUMBER_TOKEN_SOURCE, isForeignPrefixedPhaseQuery } = phaseId;
|
|
67
68
|
const { pruneOrphanedWorktrees } = worktreeSafety;
|
|
68
69
|
const { planningPaths, planningDir, planningRoot, listAvailableWorkstreams, getActiveWorkstream, findContextMdIn, } = planningWorkspace;
|
|
69
70
|
const { determinePhaseStatus } = commandsMod;
|
|
@@ -74,25 +75,10 @@ const { evaluateUatPassed } = uatPredicateMod;
|
|
|
74
75
|
void stripShippedMilestones;
|
|
75
76
|
// Accept all bold/colon variants of the Requirements header (#2769)
|
|
76
77
|
const REQUIREMENTS_HEADER_RE = /^\*\*Requirements:?\*\*[^\S\n]*:?[^\S\n]*([^\n]*)$/m;
|
|
77
|
-
// #2056:
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
// evidence — a phase dir whose token literally IS the prefixed query, or a
|
|
82
|
-
// roadmap entry literally headed with it — before accepting such a match. The
|
|
83
|
-
// configured project_code's own prefix (e.g. LKML-01 under project_code: LKML)
|
|
84
|
-
// is never foreign and always passes through.
|
|
85
|
-
function parsePhasePrefix(phase) {
|
|
86
|
-
const match = String(phase).match(/^([A-Z][A-Z0-9_]*)-(?=\d)/i);
|
|
87
|
-
return match ? match[1] : null;
|
|
88
|
-
}
|
|
89
|
-
function isForeignPrefixedPhaseQuery(phase, projectCode) {
|
|
90
|
-
const prefix = parsePhasePrefix(phase);
|
|
91
|
-
if (!prefix)
|
|
92
|
-
return false;
|
|
93
|
-
const configured = typeof projectCode === 'string' ? projectCode.trim() : '';
|
|
94
|
-
return !configured || prefix.toUpperCase() !== configured.toUpperCase();
|
|
95
|
-
}
|
|
78
|
+
// #2056/#2104: isForeignPrefixedPhaseQuery is imported from phase-id.cts
|
|
79
|
+
// (the canonical predicate). parsePhasePrefix is no longer needed locally.
|
|
80
|
+
// phaseInfoMatchesExactPrefix and roadmapPhaseMatchesExactPrefix are local
|
|
81
|
+
// helpers that post-filter the lookup results for foreign-prefix queries.
|
|
96
82
|
function phaseInfoMatchesExactPrefix(phaseInfo, phase) {
|
|
97
83
|
const num = phaseInfo?.['phase_number'];
|
|
98
84
|
const numStr = typeof num === 'string' ? num : (typeof num === 'number' ? String(num) : '');
|
|
@@ -103,6 +89,23 @@ function roadmapPhaseMatchesExactPrefix(roadmapPhase, phase) {
|
|
|
103
89
|
const section = typeof sectionRaw === 'string' ? sectionRaw : '';
|
|
104
90
|
return new RegExp(`^#{2,4}\\s*Phase\\s+${escapeRegex(phase)}(?:\\b|\\s|:)`, 'i').test(section);
|
|
105
91
|
}
|
|
92
|
+
// #2104: shared helpers that wrap findPhaseInternal / getRoadmapPhaseInternal
|
|
93
|
+
// with the #2056 foreign-prefix guard, so every init command gets the same
|
|
94
|
+
// protection without duplicating the guard logic at each call site.
|
|
95
|
+
function guardedFindPhase(cwd, phase, projectCode) {
|
|
96
|
+
let phaseInfo = findPhaseInternal(cwd, phase);
|
|
97
|
+
if (isForeignPrefixedPhaseQuery(phase, projectCode) && !phaseInfoMatchesExactPrefix(phaseInfo, phase)) {
|
|
98
|
+
phaseInfo = null;
|
|
99
|
+
}
|
|
100
|
+
return phaseInfo;
|
|
101
|
+
}
|
|
102
|
+
function guardedGetRoadmapPhase(cwd, phase, projectCode) {
|
|
103
|
+
let roadmapPhase = getRoadmapPhaseInternal(cwd, phase);
|
|
104
|
+
if (isForeignPrefixedPhaseQuery(phase, projectCode) && !roadmapPhaseMatchesExactPrefix(roadmapPhase, phase)) {
|
|
105
|
+
roadmapPhase = null;
|
|
106
|
+
}
|
|
107
|
+
return roadmapPhase;
|
|
108
|
+
}
|
|
106
109
|
function listPhaseSummaryFiles(phaseDir) {
|
|
107
110
|
return scanPhasePlans(phaseDir)['summaryFiles'];
|
|
108
111
|
}
|
|
@@ -201,7 +204,7 @@ function getInitGitState(cwd) {
|
|
|
201
204
|
}
|
|
202
205
|
resolved = node_path_1.default.resolve(resolved);
|
|
203
206
|
if (process.platform === 'win32') {
|
|
204
|
-
return
|
|
207
|
+
return (0, shell_command_projection_cjs_1.toNativePath)(resolved).toLowerCase();
|
|
205
208
|
}
|
|
206
209
|
return resolved;
|
|
207
210
|
};
|
|
@@ -211,7 +214,7 @@ function getInitGitState(cwd) {
|
|
|
211
214
|
try {
|
|
212
215
|
const prefixResult = (0, shell_command_projection_cjs_1.execGit)(['rev-parse', '--show-prefix'], { cwd, timeout: 5000 });
|
|
213
216
|
if (prefixResult['exitCode'] === 0) {
|
|
214
|
-
const prefix = (typeof prefixResult['stdout'] === 'string' ? prefixResult['stdout'] : '').trim()
|
|
217
|
+
const prefix = (0, shell_command_projection_cjs_1.posixNormalize)((typeof prefixResult['stdout'] === 'string' ? prefixResult['stdout'] : '').trim());
|
|
215
218
|
inNestedSubdir = prefix.length > 0 && prefix !== '.' && prefix !== './';
|
|
216
219
|
resolvedByGitPrefix = true;
|
|
217
220
|
}
|
|
@@ -228,7 +231,7 @@ function getInitGitState(cwd) {
|
|
|
228
231
|
}
|
|
229
232
|
else {
|
|
230
233
|
const rel = node_path_1.default.relative(rootNorm, cwdNorm);
|
|
231
|
-
const relNorm =
|
|
234
|
+
const relNorm = (0, shell_command_projection_cjs_1.toNativePath)(rel);
|
|
232
235
|
inNestedSubdir =
|
|
233
236
|
relNorm !== '' &&
|
|
234
237
|
relNorm !== '.' &&
|
|
@@ -242,7 +245,7 @@ function getInitGitState(cwd) {
|
|
|
242
245
|
}
|
|
243
246
|
}
|
|
244
247
|
if (inNestedSubdir && typeof worktreeRoot === 'string') {
|
|
245
|
-
const toComparableRaw = (p) =>
|
|
248
|
+
const toComparableRaw = (p) => (0, shell_command_projection_cjs_1.posixNormalize)(p).replace(/\/+$/g, '').toLowerCase();
|
|
246
249
|
if (toComparableRaw(worktreeRoot) === toComparableRaw(String(cwd))) {
|
|
247
250
|
inNestedSubdir = false;
|
|
248
251
|
}
|
|
@@ -258,9 +261,9 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
|
|
|
258
261
|
error('phase required for init execute-phase');
|
|
259
262
|
}
|
|
260
263
|
const config = loadConfig(cwd);
|
|
261
|
-
let phaseInfo =
|
|
264
|
+
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
|
|
262
265
|
const milestone = getMilestoneInfo(cwd);
|
|
263
|
-
const roadmapPhase =
|
|
266
|
+
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
|
|
264
267
|
if (phaseInfo?.['archived'] && roadmapPhase?.['found']) {
|
|
265
268
|
phaseInfo = null;
|
|
266
269
|
}
|
|
@@ -361,18 +364,9 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
|
|
|
361
364
|
error('phase required for init plan-phase');
|
|
362
365
|
}
|
|
363
366
|
const config = loadConfig(cwd);
|
|
364
|
-
|
|
365
|
-
let phaseInfo =
|
|
366
|
-
|
|
367
|
-
// IS the prefixed query (e.g. a real 'MEM-01-*' dir); otherwise the numeric
|
|
368
|
-
// fallback ('MEM-01' → '01') would resolve to the unrelated numeric phase.
|
|
369
|
-
if (foreignPrefixedPhase && !phaseInfoMatchesExactPrefix(phaseInfo, phase)) {
|
|
370
|
-
phaseInfo = null;
|
|
371
|
-
}
|
|
372
|
-
let roadmapPhase = getRoadmapPhaseInternal(cwd, phase);
|
|
373
|
-
if (foreignPrefixedPhase && !roadmapPhaseMatchesExactPrefix(roadmapPhase, phase)) {
|
|
374
|
-
roadmapPhase = null;
|
|
375
|
-
}
|
|
367
|
+
// #2056/#2104: foreign-prefixed queries must not collapse to numeric phases.
|
|
368
|
+
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
|
|
369
|
+
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
|
|
376
370
|
if (phaseInfo?.['archived'] && roadmapPhase?.['found']) {
|
|
377
371
|
phaseInfo = null;
|
|
378
372
|
}
|
|
@@ -620,8 +614,8 @@ function cmdInitQuick(cwd, description, raw) {
|
|
|
620
614
|
quick_id: quickId,
|
|
621
615
|
slug: slug,
|
|
622
616
|
description: description || null,
|
|
623
|
-
date:
|
|
624
|
-
timestamp:
|
|
617
|
+
date: clock_cjs_1.realClock.localToday(),
|
|
618
|
+
timestamp: clock_cjs_1.realClock.nowIso(),
|
|
625
619
|
quick_dir: '.planning/quick',
|
|
626
620
|
task_dir: slug ? `.planning/quick/${quickId}-${slug}` : null,
|
|
627
621
|
roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
@@ -679,15 +673,15 @@ function cmdInitVerifyWork(cwd, phase, raw) {
|
|
|
679
673
|
}
|
|
680
674
|
const config = loadConfig(cwd);
|
|
681
675
|
const _slashRuntime = (0, runtime_slash_cjs_1.resolveRuntime)(cwd);
|
|
682
|
-
let phaseInfo =
|
|
676
|
+
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
|
|
683
677
|
if (phaseInfo?.['archived']) {
|
|
684
|
-
const roadmapPhase =
|
|
678
|
+
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
|
|
685
679
|
if (roadmapPhase?.['found']) {
|
|
686
680
|
phaseInfo = null;
|
|
687
681
|
}
|
|
688
682
|
}
|
|
689
683
|
if (!phaseInfo) {
|
|
690
|
-
const roadmapPhase =
|
|
684
|
+
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
|
|
691
685
|
if (roadmapPhase?.['found']) {
|
|
692
686
|
const phaseName = roadmapPhase['phase_name'];
|
|
693
687
|
phaseInfo = {
|
|
@@ -736,9 +730,24 @@ function cmdInitVerifyWork(cwd, phase, raw) {
|
|
|
736
730
|
}
|
|
737
731
|
function cmdInitPhaseOp(cwd, phase, raw) {
|
|
738
732
|
const config = loadConfig(cwd);
|
|
739
|
-
let phaseInfo =
|
|
733
|
+
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
|
|
734
|
+
// #2237: surface ambiguous phase-directory collisions instead of silently
|
|
735
|
+
// taking the first match when unrelated projects share a .planning/phases/ tree.
|
|
736
|
+
if (phaseInfo?.['ambiguous_matches']) {
|
|
737
|
+
const matches = phaseInfo['ambiguous_matches'];
|
|
738
|
+
const result = {
|
|
739
|
+
phase_found: false,
|
|
740
|
+
phase_dir: null,
|
|
741
|
+
phase_number: null,
|
|
742
|
+
phase_name: null,
|
|
743
|
+
ambiguous_matches: matches,
|
|
744
|
+
warning: `Phase ${phase} is ambiguous: ${matches.length} directories match (${matches.map((m) => `"${m}"`).join(', ')}). Set a distinct project_code in .planning/config.json to scope resolution.`,
|
|
745
|
+
};
|
|
746
|
+
output(withProjectRoot(cwd, result), raw);
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
740
749
|
if (phaseInfo?.['archived']) {
|
|
741
|
-
const roadmapPhase =
|
|
750
|
+
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
|
|
742
751
|
if (roadmapPhase?.['found']) {
|
|
743
752
|
const phaseName = roadmapPhase['phase_name'];
|
|
744
753
|
phaseInfo = {
|
|
@@ -759,7 +768,7 @@ function cmdInitPhaseOp(cwd, phase, raw) {
|
|
|
759
768
|
}
|
|
760
769
|
}
|
|
761
770
|
if (!phaseInfo) {
|
|
762
|
-
const roadmapPhase =
|
|
771
|
+
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
|
|
763
772
|
if (roadmapPhase?.['found']) {
|
|
764
773
|
const phaseName = roadmapPhase['phase_name'];
|
|
765
774
|
phaseInfo = {
|
|
@@ -856,7 +865,6 @@ function cmdInitPhaseOp(cwd, phase, raw) {
|
|
|
856
865
|
}
|
|
857
866
|
function cmdInitTodos(cwd, area, raw) {
|
|
858
867
|
const config = loadConfig(cwd);
|
|
859
|
-
const now = new Date();
|
|
860
868
|
const pendingDir = node_path_1.default.join(planningDir(cwd), 'todos', 'pending');
|
|
861
869
|
let count = 0;
|
|
862
870
|
const todos = [];
|
|
@@ -892,8 +900,8 @@ function cmdInitTodos(cwd, area, raw) {
|
|
|
892
900
|
}
|
|
893
901
|
const result = {
|
|
894
902
|
commit_docs: config.commit_docs,
|
|
895
|
-
date:
|
|
896
|
-
timestamp:
|
|
903
|
+
date: clock_cjs_1.realClock.localToday(),
|
|
904
|
+
timestamp: clock_cjs_1.realClock.nowIso(),
|
|
897
905
|
todo_count: count,
|
|
898
906
|
todos,
|
|
899
907
|
area_filter: area || null,
|
|
@@ -1014,7 +1022,6 @@ function cmdInitMilestoneOp(cwd, raw) {
|
|
|
1014
1022
|
}
|
|
1015
1023
|
function cmdInitMapCodebase(cwd, raw) {
|
|
1016
1024
|
const config = loadConfig(cwd);
|
|
1017
|
-
const now = new Date();
|
|
1018
1025
|
const codebaseDir = node_path_1.default.join(planningRoot(cwd), 'codebase');
|
|
1019
1026
|
let existingMaps = [];
|
|
1020
1027
|
try {
|
|
@@ -1029,8 +1036,8 @@ function cmdInitMapCodebase(cwd, raw) {
|
|
|
1029
1036
|
search_gitignored: config.search_gitignored,
|
|
1030
1037
|
parallelization: config.parallelization,
|
|
1031
1038
|
subagent_timeout: config.subagent_timeout,
|
|
1032
|
-
date:
|
|
1033
|
-
timestamp:
|
|
1039
|
+
date: clock_cjs_1.realClock.localToday(),
|
|
1040
|
+
timestamp: clock_cjs_1.realClock.nowIso(),
|
|
1034
1041
|
codebase_dir: '.planning/codebase',
|
|
1035
1042
|
existing_maps: existingMaps,
|
|
1036
1043
|
has_maps: existingMaps.length > 0,
|
|
@@ -1770,7 +1777,7 @@ function buildAgentSkillsBlock(config, agentType, projectRoot, diagnostics) {
|
|
|
1770
1777
|
if (entry.kind === 'directive') {
|
|
1771
1778
|
return `- Load the \`${entry.name}\` skill via the Skill tool before proceeding (plugin-provided).`;
|
|
1772
1779
|
}
|
|
1773
|
-
return `- @${String(entry.ref)
|
|
1780
|
+
return `- @${(0, shell_command_projection_cjs_1.posixNormalize)(String(entry.ref))}`;
|
|
1774
1781
|
}).join('\n');
|
|
1775
1782
|
return `<agent_skills>\nRead these user-configured skills:\n${lines}\n</agent_skills>`;
|
|
1776
1783
|
}
|
|
@@ -27,6 +27,7 @@ const runtimeArtifactLayout = require("./runtime-artifact-layout.cjs");
|
|
|
27
27
|
const runtimeArtifactInstallPlan = require("./runtime-artifact-install-plan.cjs");
|
|
28
28
|
const runtimeNamePolicy = require("./runtime-name-policy.cjs");
|
|
29
29
|
const installProfiles = require("./install-profiles.cjs");
|
|
30
|
+
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
30
31
|
const { processAttribution } = runtimeArtifactConversion;
|
|
31
32
|
// resolveRuntimeArtifactLayout: accessed via module ref (not destructured) so
|
|
32
33
|
// test stubs that monkeypatch the module's exports are seen at call time.
|
|
@@ -102,6 +103,17 @@ function convertClaudeCommandToOpencodeSkill(content, skillName) {
|
|
|
102
103
|
function convertClaudeCommandToKiloSkill(content, skillName) {
|
|
103
104
|
return runtimeArtifactConversion.convertClaudeCommandToKiloSkill(content, skillName);
|
|
104
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Converter-name registry for the OpenCode-family combined skills installer
|
|
108
|
+
* (ADR-1239 / #2093). Maps the `converter` string declared on each runtime's
|
|
109
|
+
* artifactLayout skills-kind descriptor (capabilities/<runtime>/capability.json)
|
|
110
|
+
* to the actual conversion function, so `installOpencodeFamilySkills` dispatches
|
|
111
|
+
* off the descriptor instead of a `frontmatterDialect === 'kilo'` runtime check.
|
|
112
|
+
*/
|
|
113
|
+
const SKILLS_CONVERTER_REGISTRY = {
|
|
114
|
+
convertClaudeCommandToOpencodeSkill,
|
|
115
|
+
convertClaudeCommandToKiloSkill,
|
|
116
|
+
};
|
|
105
117
|
// ---------------------------------------------------------------------------
|
|
106
118
|
// User-artifact preservation helpers
|
|
107
119
|
// ---------------------------------------------------------------------------
|
|
@@ -302,9 +314,13 @@ function _copyStaged(stagedDir, destDir, kind, configDir, runtime) {
|
|
|
302
314
|
let destName;
|
|
303
315
|
if (kind.kind === 'agents') {
|
|
304
316
|
// Agent files already carry the gsd- prefix in the source dir.
|
|
305
|
-
// #
|
|
306
|
-
|
|
307
|
-
|
|
317
|
+
// #2099: descriptor-driven via hostBehaviors.agentFileExtension (was
|
|
318
|
+
// hardcoded `runtime === 'copilot'`). copilot declares '.agent.md';
|
|
319
|
+
// every other runtime's descriptor leaves this unset, so destName falls
|
|
320
|
+
// back to entry.name unchanged (byte-parity, #1575 origin comment).
|
|
321
|
+
const _agentExt = runtime ? _hostBehaviors(runtime).agentFileExtension : undefined;
|
|
322
|
+
destName = _agentExt
|
|
323
|
+
? entry.name.replace(/\.md$/, _agentExt)
|
|
308
324
|
: entry.name;
|
|
309
325
|
}
|
|
310
326
|
else if (namespacedByDir) {
|
|
@@ -440,7 +456,7 @@ function _runLegacyInstallMigrations(runtime, configDir, scope = 'global') {
|
|
|
440
456
|
// that for Hermes the flat skills/gsd-*/ removal (below) does not delete the freshly
|
|
441
457
|
// created skills/gsd-dev-preferences/ skill dir.
|
|
442
458
|
let savedLegacyArtifacts = null;
|
|
443
|
-
if (runtime
|
|
459
|
+
if (_hostBehaviors(runtime).legacyCommandsGsdInstallMigration) {
|
|
444
460
|
if (node_fs_1.default.existsSync(legacyCommandsGsd)) {
|
|
445
461
|
savedLegacyArtifacts = preserveUserArtifacts(legacyCommandsGsd, ['dev-preferences.md']);
|
|
446
462
|
node_fs_1.default.rmSync(legacyCommandsGsd, { recursive: true });
|
|
@@ -497,7 +513,8 @@ function _runLegacyUninstallCleanup(runtime, configDir, scope = 'global') {
|
|
|
497
513
|
// commands/gsd/ for claude local, preserving dev-preferences.md by restoring it
|
|
498
514
|
// to the same location (#1423). Using migrateLegacyDevPreferencesToSkill here
|
|
499
515
|
// (which would redirect to skills/) conflicts with the test contract for local installs.
|
|
500
|
-
const
|
|
516
|
+
const _lu = _hostBehaviors(runtime).legacyCommandsGsdUninstall;
|
|
517
|
+
const isLegacyCommandsGsd = _lu === true || (_lu === 'global' && scope === 'global');
|
|
501
518
|
if (isLegacyCommandsGsd) {
|
|
502
519
|
const legacyCommandsGsd = node_path_1.default.join(configDir, 'commands', 'gsd');
|
|
503
520
|
if (node_fs_1.default.existsSync(legacyCommandsGsd)) {
|
|
@@ -651,6 +668,18 @@ function installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile, res
|
|
|
651
668
|
const nestedGsdDirForCleanup = node_path_1.default.join(configDir, 'skills', 'gsd');
|
|
652
669
|
_removeHermesBareStemDirs(nestedGsdDirForCleanup);
|
|
653
670
|
}
|
|
671
|
+
// Generic-branch nativePlugin staging (ADR-1239 / #2102 Stage 1): runtimes
|
|
672
|
+
// outside the OpenCode/Kilo combined-family install (e.g. pi, whose
|
|
673
|
+
// artifactLayout is empty and which never sets combinedFamilyInstall) still
|
|
674
|
+
// need their declared hostBehaviors.nativePlugin file copied into configDir.
|
|
675
|
+
// findInstallSourceRoot resolves the repo/package root independent of
|
|
676
|
+
// configDir contents (marker check, then a walk-up from __dirname), so this
|
|
677
|
+
// is safe even when configDir has no .gsd-source marker (artifactLayout: []).
|
|
678
|
+
if (behaviors.nativePlugin) {
|
|
679
|
+
const commandsGsdDir = runtimeArtifactLayout.findInstallSourceRoot(configDir);
|
|
680
|
+
const src = node_path_1.default.dirname(node_path_1.default.dirname(commandsGsdDir));
|
|
681
|
+
_installNativePluginIfDeclared(runtime, configDir, behaviors, src);
|
|
682
|
+
}
|
|
654
683
|
}
|
|
655
684
|
// ---------------------------------------------------------------------------
|
|
656
685
|
// installOpencodeFamilySkills
|
|
@@ -679,9 +708,16 @@ function installOpencodeFamilySkills(runtime, targetDir, rawCommandsDir, pathPre
|
|
|
679
708
|
const rawDir = rawCommandsDir;
|
|
680
709
|
if (!rawDir || !node_fs_1.default.existsSync(rawDir))
|
|
681
710
|
return 0;
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
711
|
+
// #2093: descriptor-driven — dispatch off the skills-kind entry's `converter`
|
|
712
|
+
// string (capabilities/<runtime>/capability.json artifactLayout) via the
|
|
713
|
+
// SKILLS_CONVERTER_REGISTRY, instead of a `frontmatterDialect === 'kilo'`
|
|
714
|
+
// runtime check. Fail loud if the descriptor names an unregistered converter
|
|
715
|
+
// (mirrors the converter=null throw in runtime-artifact-layout.cts).
|
|
716
|
+
const converterName = skillsKindEntry.converter;
|
|
717
|
+
const converter = converterName ? SKILLS_CONVERTER_REGISTRY[converterName] : undefined;
|
|
718
|
+
if (!converter) {
|
|
719
|
+
throw new TypeError(`installOpencodeFamilySkills: unknown skills converter '${String(converterName)}' for runtime '${runtime}'`);
|
|
720
|
+
}
|
|
685
721
|
const dest = runtimeArtifactInstallPlan.assertDestWithinConfigHome(targetDir, skillsKindEntry.destSubpath);
|
|
686
722
|
// Symlink-escape guard: reject if any path component between targetDir and
|
|
687
723
|
// dest is a symlink that would redirect writes outside the config root.
|
|
@@ -769,6 +805,16 @@ function installOpencodeFamilyCommands(runtime, destDir, srcDir, pathPrefix, res
|
|
|
769
805
|
let content = node_fs_1.default.readFileSync(srcPath, 'utf8');
|
|
770
806
|
content = applyOpencodeFamilyPathPrefix(content, runtime, pathPrefix);
|
|
771
807
|
content = processAttribution(content, resolveAttribution(runtime));
|
|
808
|
+
// #2093: this commands-kind entry's descriptor `converter` field is
|
|
809
|
+
// intentionally `null` (see capabilities/{kilo,opencode}/capability.json —
|
|
810
|
+
// the flattened-command writer above applies its own path/attribution
|
|
811
|
+
// rewrites and has no per-file converter slot to key on), so there is no
|
|
812
|
+
// descriptor string to dispatch through here. `frontmatterDialect` is the
|
|
813
|
+
// documented, intentional dispatch key for frontmatter-shape selection —
|
|
814
|
+
// it is itself descriptor-driven (not a `runtime === 'kilo'` check), so it
|
|
815
|
+
// already satisfies the fold-to-descriptor requirement. Only the SKILLS
|
|
816
|
+
// converter site above (installOpencodeFamilySkills) has a real
|
|
817
|
+
// `converter` string to key on via SKILLS_CONVERTER_REGISTRY.
|
|
772
818
|
content = _hostBehaviors(runtime).frontmatterDialect === 'kilo'
|
|
773
819
|
? runtimeArtifactConversion.convertClaudeToKiloFrontmatter(content)
|
|
774
820
|
: runtimeArtifactConversion.convertClaudeToOpencodeFrontmatter(content);
|
|
@@ -777,12 +823,44 @@ function installOpencodeFamilyCommands(runtime, destDir, srcDir, pathPrefix, res
|
|
|
777
823
|
}
|
|
778
824
|
}
|
|
779
825
|
// ---------------------------------------------------------------------------
|
|
826
|
+
// _installNativePluginIfDeclared
|
|
827
|
+
// ---------------------------------------------------------------------------
|
|
828
|
+
/**
|
|
829
|
+
* Copy a runtime's declared native-extension/plugin file (hostBehaviors.nativePlugin)
|
|
830
|
+
* into its resolved config dir, when the runtime descriptor declares one.
|
|
831
|
+
*
|
|
832
|
+
* Extracted (ADR-1239 / #2102 Stage 1) from the body previously inlined in
|
|
833
|
+
* installOpencodeFamilyArtifacts so a runtime that is NOT part of the
|
|
834
|
+
* OpenCode/Kilo combined-family install (e.g. pi, whose artifactLayout is
|
|
835
|
+
* empty and which never sets combinedFamilyInstall) can still get its
|
|
836
|
+
* nativePlugin file staged via the generic installRuntimeArtifacts branch.
|
|
837
|
+
* Behavior for opencode/kilo is unchanged — same source resolution, same
|
|
838
|
+
* mkdir + copyFileSync call, same silent no-op when the source is missing.
|
|
839
|
+
*
|
|
840
|
+
* @param runtime - canonical runtime id (only used for the assertDestWithinConfigHome guard)
|
|
841
|
+
* @param configDir - resolved runtime config directory
|
|
842
|
+
* @param behaviors - the runtime's hostBehaviors descriptor
|
|
843
|
+
* @param src - repo/package root (two levels up from the commands/gsd source dir)
|
|
844
|
+
*/
|
|
845
|
+
function _installNativePluginIfDeclared(runtime, configDir, behaviors, src) {
|
|
846
|
+
const np = behaviors.nativePlugin;
|
|
847
|
+
if (np && np.source) {
|
|
848
|
+
const pluginSrc = node_path_1.default.join(src, np.source);
|
|
849
|
+
if (node_fs_1.default.existsSync(pluginSrc)) {
|
|
850
|
+
const destDir = runtimeArtifactInstallPlan.assertDestWithinConfigHome(configDir, np.dir);
|
|
851
|
+
node_fs_1.default.mkdirSync(destDir, { recursive: true });
|
|
852
|
+
node_fs_1.default.copyFileSync(pluginSrc, node_path_1.default.join(destDir, np.file));
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
// ---------------------------------------------------------------------------
|
|
780
857
|
// installOpencodeFamilyArtifacts
|
|
781
858
|
// ---------------------------------------------------------------------------
|
|
782
859
|
/**
|
|
783
|
-
* Combined-family install orchestrator for OpenCode/Kilo (ADR-1239 / #2087
|
|
784
|
-
* Stages the flattened commands surface + skills surface + (
|
|
785
|
-
*
|
|
860
|
+
* Combined-family install orchestrator for OpenCode/Kilo (ADR-1239 / #2087,
|
|
861
|
+
* #2093). Stages the flattened commands surface + skills surface + (any
|
|
862
|
+
* runtime whose hostBehaviors declares `nativePlugin` — OpenCode and, since
|
|
863
|
+
* #2093, Kilo) native plugin adapter, mirroring the bespoke `else if (isOpencode ||
|
|
786
864
|
* isKilo)` block previously inlined in bin/install.js.
|
|
787
865
|
*
|
|
788
866
|
* @param runtime - 'opencode' or 'kilo'
|
|
@@ -806,21 +884,13 @@ function installOpencodeFamilyArtifacts(runtime, configDir, scope, resolvedProfi
|
|
|
806
884
|
isGlobal,
|
|
807
885
|
isOpencode: behaviors.skipHomePrefixSubstitution === true,
|
|
808
886
|
isWindowsHost: process.platform === 'win32',
|
|
809
|
-
resolvedTarget: node_path_1.default.resolve(configDir)
|
|
810
|
-
homeDir: node_os_1.default.homedir()
|
|
887
|
+
resolvedTarget: (0, shell_command_projection_cjs_1.posixNormalize)(node_path_1.default.resolve(configDir)),
|
|
888
|
+
homeDir: (0, shell_command_projection_cjs_1.posixNormalize)(node_os_1.default.homedir()),
|
|
811
889
|
});
|
|
812
890
|
const commandDir = runtimeArtifactInstallPlan.assertDestWithinConfigHome(configDir, 'command');
|
|
813
891
|
installOpencodeFamilyCommands(runtime, commandDir, rawCommandsDir, pathPrefix, resolveAttribution);
|
|
814
892
|
installOpencodeFamilySkills(runtime, configDir, rawCommandsDir, pathPrefix, resolveAttribution);
|
|
815
|
-
|
|
816
|
-
if (np && np.source) {
|
|
817
|
-
const pluginSrc = node_path_1.default.join(src, np.source);
|
|
818
|
-
if (node_fs_1.default.existsSync(pluginSrc)) {
|
|
819
|
-
const destDir = runtimeArtifactInstallPlan.assertDestWithinConfigHome(configDir, np.dir);
|
|
820
|
-
node_fs_1.default.mkdirSync(destDir, { recursive: true });
|
|
821
|
-
node_fs_1.default.copyFileSync(pluginSrc, node_path_1.default.join(destDir, np.file));
|
|
822
|
-
}
|
|
823
|
-
}
|
|
893
|
+
_installNativePluginIfDeclared(runtime, configDir, behaviors, src);
|
|
824
894
|
}
|
|
825
895
|
// ---------------------------------------------------------------------------
|
|
826
896
|
// uninstallRuntimeArtifacts
|
|
@@ -879,6 +949,7 @@ module.exports = {
|
|
|
879
949
|
installOpencodeFamilySkills,
|
|
880
950
|
installOpencodeFamilyCommands,
|
|
881
951
|
installOpencodeFamilyArtifacts,
|
|
952
|
+
_installNativePluginIfDeclared,
|
|
882
953
|
_hostBehaviors,
|
|
883
954
|
_copyStaged,
|
|
884
955
|
hasExistingSymlinkBetween,
|
|
@@ -14,6 +14,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
14
14
|
exports.validateInstallerMigrationRecord = validateInstallerMigrationRecord;
|
|
15
15
|
exports.validateInstallerMigrationActions = validateInstallerMigrationActions;
|
|
16
16
|
const node_path_1 = __importDefault(require("node:path"));
|
|
17
|
+
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
17
18
|
function getStr(record, field) {
|
|
18
19
|
const v = record[field];
|
|
19
20
|
return typeof v === 'string' ? v : '';
|
|
@@ -56,7 +57,7 @@ function requireActionEvidence(action, field, migration) {
|
|
|
56
57
|
}
|
|
57
58
|
function validateSafeRelPath(relPath, migration, actionType) {
|
|
58
59
|
const source = actionSource(migration, { relPath });
|
|
59
|
-
const normalized =
|
|
60
|
+
const normalized = (0, shell_command_projection_cjs_1.posixNormalize)(relPath);
|
|
60
61
|
if (node_path_1.default.isAbsolute(normalized) || node_path_1.default.win32.isAbsolute(normalized)) {
|
|
61
62
|
throw new Error(`migration action ${actionType} relPath must stay inside configDir: ${source}`);
|
|
62
63
|
}
|
|
@@ -42,6 +42,9 @@ exports.BUNDLED_GSD_HOOK_FILES = Object.freeze(new Set([
|
|
|
42
42
|
'hooks/gsd-cursor-stop.js',
|
|
43
43
|
'hooks/gsd-cursor-subagent-start.js',
|
|
44
44
|
'hooks/gsd-cursor-subagent-stop.js',
|
|
45
|
+
// Windsurf/Cascade blocking hooks — registered by writeWindsurfHooksJson (#2100).
|
|
46
|
+
'hooks/gsd-windsurf-pre-write.js',
|
|
47
|
+
'hooks/gsd-windsurf-pre-command.js',
|
|
45
48
|
'hooks/gsd-ensure-canonical-path.js',
|
|
46
49
|
'hooks/gsd-graphify-update.sh',
|
|
47
50
|
'hooks/gsd-phase-boundary.sh',
|
|
@@ -118,7 +118,7 @@ function normalizeRelPath(relPath) {
|
|
|
118
118
|
if (typeof relPath !== 'string' || relPath.trim() === '') {
|
|
119
119
|
throw new Error('migration action relPath must be a non-empty string');
|
|
120
120
|
}
|
|
121
|
-
const normalized =
|
|
121
|
+
const normalized = (0, shell_command_projection_cjs_1.posixNormalize)(relPath);
|
|
122
122
|
if (node_path_1.default.isAbsolute(normalized) || node_path_1.default.win32.isAbsolute(normalized)) {
|
|
123
123
|
throw new Error(`migration action relPath must stay inside configDir: ${relPath}`);
|
|
124
124
|
}
|