@opengsd/gsd-core 1.7.0-rc.6 → 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/bin/install.js +5 -4
- 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 +57 -57
- 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/init.cjs +27 -13
- package/gsd-core/bin/lib/install-engine.cjs +3 -2
- package/gsd-core/bin/lib/installer-migration-authoring.cjs +2 -1
- 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/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/roadmap-parser.cjs +218 -13
- package/gsd-core/bin/lib/roadmap.cjs +100 -49
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +7 -6
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +3 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +2 -1
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +25 -17
- package/gsd-core/bin/lib/schema-detect.cjs +2 -1
- package/gsd-core/bin/lib/security.cjs +1 -1
- package/gsd-core/bin/lib/shell-command-projection.cjs +61 -25
- package/gsd-core/bin/lib/smart-entry.cjs +4 -3
- 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 +2 -2
- package/gsd-core/bin/lib/template.cjs +2 -1
- package/gsd-core/bin/lib/uat.cjs +54 -8
- 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/references/checkpoints.md +12 -0
- package/gsd-core/workflows/add-tests.md +1 -1
- 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/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/ship.md +8 -2
- package/gsd-core/workflows/spec-phase.md +1 -1
- package/gsd-core/workflows/transition.md +1 -1
- package/hooks/dist/gsd-statusline.js +164 -14
- package/hooks/gsd-statusline.js +164 -14
- package/package.json +4 -2
- package/scripts/ci-test-scope.cjs +39 -1
- package/scripts/gen-golden-install-parity-zcode.cjs +35 -45
- package/scripts/gen-install-tree-fixtures.cjs +75 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -1
- package/scripts/lint-table-schema-drift.cjs +157 -0
- package/skills/gsd-mempalace-capture/SKILL.md +27 -1
- package/skills/gsd-surface/SKILL.md +6 -6
- package/vscode/package.json +1 -1
|
@@ -45,6 +45,7 @@ const { extractFrontmatter } = frontmatter;
|
|
|
45
45
|
const modelProfiles = require("./model-profiles.cjs");
|
|
46
46
|
const { MODEL_PROFILES, VALID_PHASE_TYPES } = modelProfiles;
|
|
47
47
|
const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
|
|
48
|
+
const clock_cjs_1 = require("./clock.cjs");
|
|
48
49
|
// ─── Phase Status ─────────────────────────────────────────────────────────────
|
|
49
50
|
/**
|
|
50
51
|
* Determine phase status by checking plan/summary counts AND verification state.
|
|
@@ -592,6 +593,7 @@ function cmdCommit(cwd, message, files, raw, amend, noVerify) {
|
|
|
592
593
|
// Stage files
|
|
593
594
|
const explicitFiles = files && files.length > 0;
|
|
594
595
|
const filesToStage = explicitFiles ? files : ['.planning/'];
|
|
596
|
+
const stagedPaths = [];
|
|
595
597
|
for (const file of filesToStage) {
|
|
596
598
|
const fullPath = node_path_1.default.join(cwd, file);
|
|
597
599
|
if (!node_fs_1.default.existsSync(fullPath)) {
|
|
@@ -607,12 +609,32 @@ function cmdCommit(cwd, message, files, raw, amend, noVerify) {
|
|
|
607
609
|
}
|
|
608
610
|
else {
|
|
609
611
|
(0, shell_command_projection_cjs_1.execGit)(['add', file], { cwd });
|
|
612
|
+
stagedPaths.push(file);
|
|
610
613
|
}
|
|
611
614
|
}
|
|
612
|
-
// Commit
|
|
613
|
-
|
|
615
|
+
// Commit — when the caller declared a scope (--files), append a pathspec so
|
|
616
|
+
// only the declared files land in the commit, not the entire index (#2112).
|
|
617
|
+
// The pathspec uses stagedPaths (not filesToStage) so skipped missing files
|
|
618
|
+
// are excluded — otherwise git would record them as deletions (#2014).
|
|
619
|
+
// During a merge, git refuses partial commits — fall back to a bare commit.
|
|
620
|
+
// --amend is left without a pathspec: amending with -- <paths> is a different
|
|
621
|
+
// operation that rewrites the tip with only those paths.
|
|
622
|
+
if (explicitFiles && stagedPaths.length === 0 && !amend) {
|
|
623
|
+
const result = { committed: false, hash: null, reason: 'nothing_to_commit' };
|
|
624
|
+
output(result, raw, 'nothing');
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
const isMergeInProgress = (0, shell_command_projection_cjs_1.execGit)(['rev-parse', '-q', '--verify', 'MERGE_HEAD'], { cwd }).exitCode === 0;
|
|
628
|
+
const canScope = explicitFiles && stagedPaths.length > 0 && !amend
|
|
629
|
+
&& !isMergeInProgress;
|
|
630
|
+
const commitArgs = amend
|
|
631
|
+
? ['commit', '--amend', '--no-edit']
|
|
632
|
+
: ['commit', '-m', sanitizedMessage];
|
|
614
633
|
if (noVerify)
|
|
615
634
|
commitArgs.push('--no-verify');
|
|
635
|
+
if (canScope) {
|
|
636
|
+
commitArgs.push('--', ...stagedPaths);
|
|
637
|
+
}
|
|
616
638
|
const commitResult = (0, shell_command_projection_cjs_1.execGit)(commitArgs, { cwd });
|
|
617
639
|
if (commitResult.exitCode !== 0) {
|
|
618
640
|
if (commitResult.stdout.includes('nothing to commit') || commitResult.stderr.includes('nothing to commit')) {
|
|
@@ -710,12 +732,21 @@ function cmdCommitToSubrepo(cwd, message, files, raw) {
|
|
|
710
732
|
for (const [repo, repoFiles] of Object.entries(grouped)) {
|
|
711
733
|
const repoCwd = node_path_1.default.join(cwd, repo);
|
|
712
734
|
// Stage files (strip sub-repo prefix for paths relative to that repo)
|
|
735
|
+
const stagedRelPaths = [];
|
|
713
736
|
for (const file of repoFiles) {
|
|
714
737
|
const relativePath = file.slice(repo.length + 1);
|
|
715
|
-
(0, shell_command_projection_cjs_1.execGit)(['add', relativePath], { cwd: repoCwd });
|
|
738
|
+
const addResult = (0, shell_command_projection_cjs_1.execGit)(['add', relativePath], { cwd: repoCwd });
|
|
739
|
+
if (addResult.exitCode === 0) {
|
|
740
|
+
stagedRelPaths.push(relativePath);
|
|
741
|
+
}
|
|
716
742
|
}
|
|
717
|
-
// Commit
|
|
718
|
-
const
|
|
743
|
+
// Commit — pathspec limits the commit to the staged files only (#2112)
|
|
744
|
+
const isMergeInProgressSub = (0, shell_command_projection_cjs_1.execGit)(['rev-parse', '-q', '--verify', 'MERGE_HEAD'], { cwd: repoCwd }).exitCode === 0;
|
|
745
|
+
const canScopeSub = stagedRelPaths.length > 0 && !isMergeInProgressSub;
|
|
746
|
+
const commitArgs = canScopeSub
|
|
747
|
+
? ['commit', '-m', message, '--', ...stagedRelPaths]
|
|
748
|
+
: ['commit', '-m', message];
|
|
749
|
+
const commitResult = (0, shell_command_projection_cjs_1.execGit)(commitArgs, { cwd: repoCwd });
|
|
719
750
|
if (commitResult.exitCode !== 0) {
|
|
720
751
|
if (commitResult.stdout.includes('nothing to commit') || commitResult.stderr.includes('nothing to commit')) {
|
|
721
752
|
repos[repo] = { committed: false, hash: null, files: repoFiles, reason: 'nothing_to_commit' };
|
|
@@ -837,8 +868,16 @@ function cmdPrSubrepo(cwd, repo, branch, commitMessage, raw) {
|
|
|
837
868
|
error(`Failed to stage ${file} in ${repo}: ${addResult.stderr}`);
|
|
838
869
|
}
|
|
839
870
|
}
|
|
840
|
-
// 5. Commit
|
|
841
|
-
|
|
871
|
+
// 5. Commit — pathspec limits the commit to the staged files only (#2112).
|
|
872
|
+
// changedFiles includes both old and new paths for renames so the full
|
|
873
|
+
// rename is captured atomically (pathspec on newPath alone would leave the
|
|
874
|
+
// deletion of oldPath stranded in the index).
|
|
875
|
+
const isMergeInProgressPr = (0, shell_command_projection_cjs_1.execGit)(['rev-parse', '-q', '--verify', 'MERGE_HEAD'], { cwd: repoCwd }).exitCode === 0;
|
|
876
|
+
const canScopePr = changedFiles.length > 0 && !isMergeInProgressPr;
|
|
877
|
+
const commitArgs = canScopePr
|
|
878
|
+
? ['commit', '-m', commitMessage, '--', ...changedFiles]
|
|
879
|
+
: ['commit', '-m', commitMessage];
|
|
880
|
+
const commitResult = (0, shell_command_projection_cjs_1.execGit)(commitArgs, { cwd: repoCwd });
|
|
842
881
|
if (commitResult.exitCode !== 0) {
|
|
843
882
|
rollback();
|
|
844
883
|
error(`Failed to commit in ${repo}: ${commitResult.stderr}`);
|
|
@@ -1223,7 +1262,7 @@ function cmdTodoComplete(cwd, filename, raw) {
|
|
|
1223
1262
|
(0, shell_command_projection_cjs_1.platformEnsureDir)(completedDir);
|
|
1224
1263
|
// Read, add completion timestamp, move
|
|
1225
1264
|
let content = node_fs_1.default.readFileSync(sourcePath, 'utf-8');
|
|
1226
|
-
const today =
|
|
1265
|
+
const today = clock_cjs_1.realClock.localToday();
|
|
1227
1266
|
content = `completed: ${today}\n` + content;
|
|
1228
1267
|
(0, shell_command_projection_cjs_1.platformWriteSync)(node_path_1.default.join(completedDir, filename), content);
|
|
1229
1268
|
node_fs_1.default.unlinkSync(sourcePath);
|
|
@@ -1232,7 +1271,7 @@ function cmdTodoComplete(cwd, filename, raw) {
|
|
|
1232
1271
|
function cmdScaffold(cwd, type, options, raw) {
|
|
1233
1272
|
const { phase, name } = options;
|
|
1234
1273
|
const padded = phase ? normalizePhaseName(phase) : '00';
|
|
1235
|
-
const today =
|
|
1274
|
+
const today = clock_cjs_1.realClock.localToday();
|
|
1236
1275
|
// Find phase directory
|
|
1237
1276
|
const phaseInfo = phase ? findPhaseInternal(cwd, phase) : null;
|
|
1238
1277
|
const phaseDir = phaseInfo ? node_path_1.default.join(cwd, phaseInfo['directory']) : null;
|
|
@@ -158,11 +158,15 @@ const _warnedUnknownConfigKeys = new Set();
|
|
|
158
158
|
// ─── Git utilities ────────────────────────────────────────────────────────────
|
|
159
159
|
const _gitIgnoredCache = new Map();
|
|
160
160
|
function isGitIgnored(cwd, targetPath) {
|
|
161
|
-
|
|
161
|
+
// #2206: strip trailing slashes — `git check-ignore` has a quirk where a
|
|
162
|
+
// CRLF .gitignore with blank lines falsely reports a trailing-slash path
|
|
163
|
+
// (e.g. `.planning/`) as ignored. Normalizing here protects every call site.
|
|
164
|
+
const normalized = targetPath.replace(/\/+$/, '');
|
|
165
|
+
const key = cwd + '::' + normalized;
|
|
162
166
|
if (_gitIgnoredCache.has(key))
|
|
163
167
|
return _gitIgnoredCache.get(key);
|
|
164
168
|
// --no-index checks .gitignore rules regardless of whether the file is tracked.
|
|
165
|
-
const result = (0, shell_command_projection_cjs_1.execGit)(['check-ignore', '-q', '--no-index', '--',
|
|
169
|
+
const result = (0, shell_command_projection_cjs_1.execGit)(['check-ignore', '-q', '--no-index', '--', normalized], { cwd });
|
|
166
170
|
const ignored = result.exitCode === 0;
|
|
167
171
|
_gitIgnoredCache.set(key, ignored);
|
|
168
172
|
return ignored;
|
|
@@ -692,6 +692,18 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
|
|
|
692
692
|
const VALID_CONTEXT_POSITIONS = ['front', 'end'];
|
|
693
693
|
if (kp === 'statusline.context_position')
|
|
694
694
|
assertEnumValue(parsedValue, val, VALID_CONTEXT_POSITIONS, 'statusline.context_position');
|
|
695
|
+
// statusline.show_context_tokens — boolean only
|
|
696
|
+
if (kp === 'statusline.show_context_tokens') {
|
|
697
|
+
if (typeof parsedValue !== 'boolean') {
|
|
698
|
+
error(`Invalid statusline.show_context_tokens '${val}'. Must be a boolean (true or false).`);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
// statusline.show_git — boolean only
|
|
702
|
+
if (kp === 'statusline.show_git') {
|
|
703
|
+
if (typeof parsedValue !== 'boolean') {
|
|
704
|
+
error(`Invalid statusline.show_git '${val}'. Must be a boolean (true or false).`);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
695
707
|
// Fallow scope + profile enum validation (#3424)
|
|
696
708
|
const VALID_FALLOW_SCOPES = ['phase', 'repo'];
|
|
697
709
|
if (kp === 'code_quality.fallow.scope')
|
|
@@ -27,10 +27,16 @@ const { comparePhaseNum } = phaseIdModule;
|
|
|
27
27
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
28
28
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
29
29
|
const { findContextMdIn } = planningWorkspace;
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
31
|
+
const shellCommandProjection = require("./shell-command-projection.cjs");
|
|
30
32
|
// ─── Path helpers ────────────────────────────────────────────────────────────
|
|
31
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* Normalize a relative path to always use forward slashes (cross-platform).
|
|
35
|
+
* Delegates to the single separator seam in shell-command-projection so there is
|
|
36
|
+
* exactly one implementation of native→POSIX conversion across the codebase.
|
|
37
|
+
*/
|
|
32
38
|
function toPosixPath(p) {
|
|
33
|
-
return
|
|
39
|
+
return shellCommandProjection.toPosixPath(p);
|
|
34
40
|
}
|
|
35
41
|
/**
|
|
36
42
|
* Scan immediate child directories for separate git repos.
|
|
@@ -69,7 +69,7 @@ const SAFE_PATH_RE = /^(?!.*\.\.)(?:[A-Za-z0-9_.][A-Za-z0-9_.\-]*)(?:\/[A-Za-z0-
|
|
|
69
69
|
function classifyFile(file) {
|
|
70
70
|
if (typeof file !== 'string' || !file)
|
|
71
71
|
return null;
|
|
72
|
-
const norm =
|
|
72
|
+
const norm = (0, shell_command_projection_cjs_1.posixNormalize)(file);
|
|
73
73
|
if (MIGRATION_RES.some((r) => r.test(norm)))
|
|
74
74
|
return 'migration';
|
|
75
75
|
if (ROUTE_RES.some((r) => r.test(norm)))
|
|
@@ -87,7 +87,7 @@ function classifyFile(file) {
|
|
|
87
87
|
* check `structureMd.includes('src/lib')` holds.
|
|
88
88
|
*/
|
|
89
89
|
function isPathMapped(file, structureMd) {
|
|
90
|
-
const norm =
|
|
90
|
+
const norm = (0, shell_command_projection_cjs_1.posixNormalize)(file);
|
|
91
91
|
const parts = norm.split('/');
|
|
92
92
|
// Check prefixes from longest to shortest; any hit means "mapped".
|
|
93
93
|
for (let i = parts.length - 1; i >= 1; i--) {
|
|
@@ -130,7 +130,7 @@ function detectDrift(input) {
|
|
|
130
130
|
const elements = [];
|
|
131
131
|
const seen = new Map();
|
|
132
132
|
for (const rawFile of added) {
|
|
133
|
-
const file =
|
|
133
|
+
const file = (0, shell_command_projection_cjs_1.posixNormalize)(rawFile);
|
|
134
134
|
const specific = classifyFile(file);
|
|
135
135
|
let category = specific;
|
|
136
136
|
if (!category) {
|
|
@@ -251,7 +251,7 @@ function chooseAffectedPaths(paths) {
|
|
|
251
251
|
for (const raw of paths || []) {
|
|
252
252
|
if (typeof raw !== 'string' || !raw)
|
|
253
253
|
continue;
|
|
254
|
-
const file =
|
|
254
|
+
const file = (0, shell_command_projection_cjs_1.posixNormalize)(raw);
|
|
255
255
|
const parts = file.split('/');
|
|
256
256
|
if (parts.length === 0)
|
|
257
257
|
continue;
|
|
@@ -522,6 +522,27 @@ const FRONTMATTER_SCHEMAS = {
|
|
|
522
522
|
summary: { required: ['phase', 'plan', 'subsystem', 'tags', 'duration', 'completed'] },
|
|
523
523
|
verification: { required: ['phase', 'verified', 'status', 'score'] },
|
|
524
524
|
};
|
|
525
|
+
/**
|
|
526
|
+
* Strip ALL frontmatter blocks from the start of `content`.
|
|
527
|
+
*
|
|
528
|
+
* Handles CRLF line endings and multiple stacked blocks (corruption
|
|
529
|
+
* recovery): greedily strips consecutive `---...---` blocks separated by
|
|
530
|
+
* optional whitespace, so a doubled/tripled frontmatter header (e.g. from a
|
|
531
|
+
* botched merge) is fully removed, not just the first block.
|
|
532
|
+
*
|
|
533
|
+
* Canonical home for this primitive (#2143 audit dedup): previously
|
|
534
|
+
* duplicated byte-identically in both `state.cts` and `state-transition.cts`.
|
|
535
|
+
*/
|
|
536
|
+
function stripFrontmatter(content) {
|
|
537
|
+
let result = content;
|
|
538
|
+
while (true) {
|
|
539
|
+
const stripped = result.replace(/^\s*---\r?\n[\s\S]*?\r?\n---\s*/, '');
|
|
540
|
+
if (stripped === result)
|
|
541
|
+
break;
|
|
542
|
+
result = stripped;
|
|
543
|
+
}
|
|
544
|
+
return result;
|
|
545
|
+
}
|
|
525
546
|
function cmdFrontmatterGet(cwd, filePath, field, raw) {
|
|
526
547
|
if (!filePath) {
|
|
527
548
|
error('file path required');
|
|
@@ -654,6 +675,7 @@ module.exports = {
|
|
|
654
675
|
parseFrontmatter: extractFrontmatter,
|
|
655
676
|
reconstructFrontmatter,
|
|
656
677
|
spliceFrontmatter,
|
|
678
|
+
stripFrontmatter,
|
|
657
679
|
noOpObjectListSetError,
|
|
658
680
|
parseMustHavesBlock,
|
|
659
681
|
FRONTMATTER_SCHEMAS,
|
|
@@ -26,6 +26,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
26
26
|
const node_path_1 = __importDefault(require("node:path"));
|
|
27
27
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
28
28
|
const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
|
|
29
|
+
const clock_cjs_1 = require("./clock.cjs");
|
|
29
30
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
30
31
|
const ioMod = require("./io.cjs");
|
|
31
32
|
const { output } = ioMod;
|
|
@@ -276,7 +277,7 @@ function buildStateMd(phaseMap) {
|
|
|
276
277
|
const status = currentEntry ? 'Ready to plan' : 'All phases complete';
|
|
277
278
|
const filled = Math.round(pct / 10);
|
|
278
279
|
const bar = `[${'█'.repeat(filled)}${'░'.repeat(10 - filled)}]`;
|
|
279
|
-
const today =
|
|
280
|
+
const today = clock_cjs_1.realClock.localToday();
|
|
280
281
|
return [
|
|
281
282
|
'# Project State',
|
|
282
283
|
'',
|
|
@@ -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
|
|
@@ -203,7 +204,7 @@ function getInitGitState(cwd) {
|
|
|
203
204
|
}
|
|
204
205
|
resolved = node_path_1.default.resolve(resolved);
|
|
205
206
|
if (process.platform === 'win32') {
|
|
206
|
-
return
|
|
207
|
+
return (0, shell_command_projection_cjs_1.toNativePath)(resolved).toLowerCase();
|
|
207
208
|
}
|
|
208
209
|
return resolved;
|
|
209
210
|
};
|
|
@@ -213,7 +214,7 @@ function getInitGitState(cwd) {
|
|
|
213
214
|
try {
|
|
214
215
|
const prefixResult = (0, shell_command_projection_cjs_1.execGit)(['rev-parse', '--show-prefix'], { cwd, timeout: 5000 });
|
|
215
216
|
if (prefixResult['exitCode'] === 0) {
|
|
216
|
-
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());
|
|
217
218
|
inNestedSubdir = prefix.length > 0 && prefix !== '.' && prefix !== './';
|
|
218
219
|
resolvedByGitPrefix = true;
|
|
219
220
|
}
|
|
@@ -230,7 +231,7 @@ function getInitGitState(cwd) {
|
|
|
230
231
|
}
|
|
231
232
|
else {
|
|
232
233
|
const rel = node_path_1.default.relative(rootNorm, cwdNorm);
|
|
233
|
-
const relNorm =
|
|
234
|
+
const relNorm = (0, shell_command_projection_cjs_1.toNativePath)(rel);
|
|
234
235
|
inNestedSubdir =
|
|
235
236
|
relNorm !== '' &&
|
|
236
237
|
relNorm !== '.' &&
|
|
@@ -244,7 +245,7 @@ function getInitGitState(cwd) {
|
|
|
244
245
|
}
|
|
245
246
|
}
|
|
246
247
|
if (inNestedSubdir && typeof worktreeRoot === 'string') {
|
|
247
|
-
const toComparableRaw = (p) =>
|
|
248
|
+
const toComparableRaw = (p) => (0, shell_command_projection_cjs_1.posixNormalize)(p).replace(/\/+$/g, '').toLowerCase();
|
|
248
249
|
if (toComparableRaw(worktreeRoot) === toComparableRaw(String(cwd))) {
|
|
249
250
|
inNestedSubdir = false;
|
|
250
251
|
}
|
|
@@ -613,8 +614,8 @@ function cmdInitQuick(cwd, description, raw) {
|
|
|
613
614
|
quick_id: quickId,
|
|
614
615
|
slug: slug,
|
|
615
616
|
description: description || null,
|
|
616
|
-
date:
|
|
617
|
-
timestamp:
|
|
617
|
+
date: clock_cjs_1.realClock.localToday(),
|
|
618
|
+
timestamp: clock_cjs_1.realClock.nowIso(),
|
|
618
619
|
quick_dir: '.planning/quick',
|
|
619
620
|
task_dir: slug ? `.planning/quick/${quickId}-${slug}` : null,
|
|
620
621
|
roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
@@ -730,6 +731,21 @@ function cmdInitVerifyWork(cwd, phase, raw) {
|
|
|
730
731
|
function cmdInitPhaseOp(cwd, phase, raw) {
|
|
731
732
|
const config = loadConfig(cwd);
|
|
732
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
|
+
}
|
|
733
749
|
if (phaseInfo?.['archived']) {
|
|
734
750
|
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
|
|
735
751
|
if (roadmapPhase?.['found']) {
|
|
@@ -849,7 +865,6 @@ function cmdInitPhaseOp(cwd, phase, raw) {
|
|
|
849
865
|
}
|
|
850
866
|
function cmdInitTodos(cwd, area, raw) {
|
|
851
867
|
const config = loadConfig(cwd);
|
|
852
|
-
const now = new Date();
|
|
853
868
|
const pendingDir = node_path_1.default.join(planningDir(cwd), 'todos', 'pending');
|
|
854
869
|
let count = 0;
|
|
855
870
|
const todos = [];
|
|
@@ -885,8 +900,8 @@ function cmdInitTodos(cwd, area, raw) {
|
|
|
885
900
|
}
|
|
886
901
|
const result = {
|
|
887
902
|
commit_docs: config.commit_docs,
|
|
888
|
-
date:
|
|
889
|
-
timestamp:
|
|
903
|
+
date: clock_cjs_1.realClock.localToday(),
|
|
904
|
+
timestamp: clock_cjs_1.realClock.nowIso(),
|
|
890
905
|
todo_count: count,
|
|
891
906
|
todos,
|
|
892
907
|
area_filter: area || null,
|
|
@@ -1007,7 +1022,6 @@ function cmdInitMilestoneOp(cwd, raw) {
|
|
|
1007
1022
|
}
|
|
1008
1023
|
function cmdInitMapCodebase(cwd, raw) {
|
|
1009
1024
|
const config = loadConfig(cwd);
|
|
1010
|
-
const now = new Date();
|
|
1011
1025
|
const codebaseDir = node_path_1.default.join(planningRoot(cwd), 'codebase');
|
|
1012
1026
|
let existingMaps = [];
|
|
1013
1027
|
try {
|
|
@@ -1022,8 +1036,8 @@ function cmdInitMapCodebase(cwd, raw) {
|
|
|
1022
1036
|
search_gitignored: config.search_gitignored,
|
|
1023
1037
|
parallelization: config.parallelization,
|
|
1024
1038
|
subagent_timeout: config.subagent_timeout,
|
|
1025
|
-
date:
|
|
1026
|
-
timestamp:
|
|
1039
|
+
date: clock_cjs_1.realClock.localToday(),
|
|
1040
|
+
timestamp: clock_cjs_1.realClock.nowIso(),
|
|
1027
1041
|
codebase_dir: '.planning/codebase',
|
|
1028
1042
|
existing_maps: existingMaps,
|
|
1029
1043
|
has_maps: existingMaps.length > 0,
|
|
@@ -1763,7 +1777,7 @@ function buildAgentSkillsBlock(config, agentType, projectRoot, diagnostics) {
|
|
|
1763
1777
|
if (entry.kind === 'directive') {
|
|
1764
1778
|
return `- Load the \`${entry.name}\` skill via the Skill tool before proceeding (plugin-provided).`;
|
|
1765
1779
|
}
|
|
1766
|
-
return `- @${String(entry.ref)
|
|
1780
|
+
return `- @${(0, shell_command_projection_cjs_1.posixNormalize)(String(entry.ref))}`;
|
|
1767
1781
|
}).join('\n');
|
|
1768
1782
|
return `<agent_skills>\nRead these user-configured skills:\n${lines}\n</agent_skills>`;
|
|
1769
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.
|
|
@@ -883,8 +884,8 @@ function installOpencodeFamilyArtifacts(runtime, configDir, scope, resolvedProfi
|
|
|
883
884
|
isGlobal,
|
|
884
885
|
isOpencode: behaviors.skipHomePrefixSubstitution === true,
|
|
885
886
|
isWindowsHost: process.platform === 'win32',
|
|
886
|
-
resolvedTarget: node_path_1.default.resolve(configDir)
|
|
887
|
-
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()),
|
|
888
889
|
});
|
|
889
890
|
const commandDir = runtimeArtifactInstallPlan.assertDestWithinConfigHome(configDir, 'command');
|
|
890
891
|
installOpencodeFamilyCommands(runtime, commandDir, rawCommandsDir, pathPrefix, resolveAttribution);
|
|
@@ -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
|
}
|
|
@@ -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
|
}
|