@opengsd/gsd-core 1.7.0-rc.6 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.opencode/plugins/gsd-core.js +14 -0
- package/README.md +2 -0
- package/agents/gsd-debug-session-manager.md +42 -4
- package/agents/gsd-debugger.md +87 -29
- package/agents/gsd-executor.md +31 -3
- package/agents/gsd-planner.md +29 -36
- package/agents/gsd-security-auditor.md +13 -15
- package/agents/gsd-verifier.md +2 -2
- package/bin/install.js +1157 -84
- package/commands/gsd/ai-integration-phase.md +1 -1
- package/commands/gsd/mempalace-capture.md +31 -1
- package/commands/gsd/new-milestone.md +1 -1
- package/commands/gsd/plan-phase.md +5 -3
- package/commands/gsd/plan-review-convergence.md +3 -2
- package/commands/gsd/surface.md +6 -6
- package/gsd-core/bin/gsd-tools.cjs +1866 -2434
- package/gsd-core/bin/lib/adapter-imperative.cjs +8 -1
- package/gsd-core/bin/lib/agent-command-router.cjs +20 -5
- package/gsd-core/bin/lib/api-coverage.cjs +341 -49
- package/gsd-core/bin/lib/audit.cjs +7 -6
- package/gsd-core/bin/lib/broken-windows.cjs +716 -0
- package/gsd-core/bin/lib/capability-command-router.cjs +733 -0
- package/gsd-core/bin/lib/capability-registry.cjs +157 -88
- package/gsd-core/bin/lib/capability-writer.cjs +6 -1
- package/gsd-core/bin/lib/check-command-router.cjs +129 -26
- package/gsd-core/bin/lib/claude-orchestration-command-router.cjs +115 -27
- package/gsd-core/bin/lib/claude-orchestration.cjs +84 -9
- package/gsd-core/bin/lib/clock.cjs +19 -0
- package/gsd-core/bin/lib/command-aliases.cjs +14 -0
- package/gsd-core/bin/lib/commands.cjs +129 -13
- package/gsd-core/bin/lib/config-loader.cjs +20 -4
- package/gsd-core/bin/lib/config.cjs +81 -18
- package/gsd-core/bin/lib/core-utils.cjs +14 -3
- package/gsd-core/bin/lib/decisions.cjs +32 -8
- package/gsd-core/bin/lib/docs.cjs +6 -0
- package/gsd-core/bin/lib/drift.cjs +4 -4
- package/gsd-core/bin/lib/external-descriptor-trust.cjs +14 -2
- package/gsd-core/bin/lib/frontmatter.cjs +22 -0
- package/gsd-core/bin/lib/gap-checker.cjs +17 -2
- package/gsd-core/bin/lib/gsd2-import.cjs +2 -1
- package/gsd-core/bin/lib/init.cjs +138 -60
- package/gsd-core/bin/lib/install-engine.cjs +301 -25
- package/gsd-core/bin/lib/install-profiles.cjs +239 -1
- package/gsd-core/bin/lib/installer-migration-authoring.cjs +2 -1
- package/gsd-core/bin/lib/installer-migrations/005-opencode-baseline-commands-dir.cjs +146 -0
- package/gsd-core/bin/lib/installer-migrations/006-pi-extension-cjs-to-js.cjs +91 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +45 -6
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +449 -0
- package/gsd-core/bin/lib/markdown-table.cjs +698 -0
- package/gsd-core/bin/lib/milestone.cjs +463 -43
- package/gsd-core/bin/lib/model-catalog.cjs +19 -4
- package/gsd-core/bin/lib/model-resolver.cjs +189 -7
- package/gsd-core/bin/lib/onboard-projection.cjs +11 -8
- package/gsd-core/bin/lib/phase-command-router.cjs +50 -2
- package/gsd-core/bin/lib/phase-id.cjs +26 -4
- 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 +636 -72
- package/gsd-core/bin/lib/plan-scan.cjs +73 -2
- package/gsd-core/bin/lib/roadmap-parser.cjs +225 -17
- package/gsd-core/bin/lib/roadmap.cjs +113 -52
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +14 -7
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +3 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +24 -9
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +41 -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 +73 -7
- package/gsd-core/bin/lib/state-document.cjs +7 -4
- package/gsd-core/bin/lib/state-transition.cjs +122 -46
- package/gsd-core/bin/lib/state.cjs +456 -137
- package/gsd-core/bin/lib/surface.cjs +53 -11
- package/gsd-core/bin/lib/template.cjs +2 -1
- package/gsd-core/bin/lib/uat.cjs +474 -13
- package/gsd-core/bin/lib/ui-safety-gate.cjs +23 -1
- package/gsd-core/bin/lib/validate.cjs +12 -8
- package/gsd-core/bin/lib/verification.cjs +112 -17
- package/gsd-core/bin/lib/verify.cjs +224 -25
- 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 +5 -2
- package/gsd-core/references/api-coverage.md +37 -7
- package/gsd-core/references/checkpoints.md +13 -1
- package/gsd-core/references/common-bug-patterns.md +13 -0
- package/gsd-core/references/debugger-bug-taxonomy.md +111 -0
- package/gsd-core/references/debugger-fix-acceptance.md +157 -0
- package/gsd-core/references/debugger-philosophy.md +1 -0
- package/gsd-core/references/debugger-prevention.md +98 -0
- package/gsd-core/references/debugger-rca-branching.md +98 -0
- package/gsd-core/references/debugger-repro-hardening.md +130 -0
- package/gsd-core/references/debugger-sbfl.md +110 -0
- package/gsd-core/references/debugger-semantic-recall.md +81 -0
- package/gsd-core/references/execute-phase-quota-recovery.md +55 -0
- package/gsd-core/references/execute-phase-requirement-revert.md +8 -0
- package/gsd-core/references/execute-phase-response-language.md +7 -0
- package/gsd-core/references/planner-antipatterns.md +6 -0
- package/gsd-core/references/planner-mvp-mode.md +12 -13
- package/gsd-core/references/planner-preconditions.md +156 -0
- package/gsd-core/references/planner-reversibility.md +132 -0
- package/gsd-core/references/reviewer-instances.md +9 -7
- package/gsd-core/references/skeleton-template.md +1 -1
- package/gsd-core/references/thinking-models-planning.md +3 -1
- package/gsd-core/templates/DEBUG.md +5 -3
- package/gsd-core/workflows/add-phase.md +2 -0
- package/gsd-core/workflows/add-tests.md +4 -2
- package/gsd-core/workflows/add-todo.md +32 -1
- package/gsd-core/workflows/ai-integration-phase.md +4 -2
- package/gsd-core/workflows/audit-fix.md +2 -2
- package/gsd-core/workflows/check-todos.md +3 -1
- package/gsd-core/workflows/cleanup.md +7 -1
- package/gsd-core/workflows/code-review.md +17 -5
- package/gsd-core/workflows/complete-milestone.md +3 -0
- package/gsd-core/workflows/debug.md +27 -5
- package/gsd-core/workflows/diagnose-issues.md +1 -1
- package/gsd-core/workflows/discovery-phase.md +7 -0
- package/gsd-core/workflows/discuss-phase/templates/context.md +16 -2
- package/gsd-core/workflows/discuss-phase-assumptions.md +3 -0
- package/gsd-core/workflows/do.md +7 -1
- package/gsd-core/workflows/docs-update.md +1 -0
- package/gsd-core/workflows/eval-review.md +3 -0
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +4 -4
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +2 -2
- package/gsd-core/workflows/execute-phase.md +30 -37
- package/gsd-core/workflows/execute-plan.md +15 -4
- package/gsd-core/workflows/fast.md +8 -22
- package/gsd-core/workflows/graduation.md +3 -0
- package/gsd-core/workflows/health.md +7 -1
- package/gsd-core/workflows/help/modes/full.md +6 -2
- package/gsd-core/workflows/import.md +8 -2
- package/gsd-core/workflows/inbox.md +7 -0
- package/gsd-core/workflows/ingest-docs.md +15 -10
- package/gsd-core/workflows/manager.md +3 -1
- package/gsd-core/workflows/map-codebase.md +4 -4
- package/gsd-core/workflows/mvp-phase.md +3 -0
- package/gsd-core/workflows/new-milestone.md +69 -21
- package/gsd-core/workflows/new-project.md +17 -15
- package/gsd-core/workflows/new-workspace.md +3 -1
- package/gsd-core/workflows/onboard.md +3 -0
- package/gsd-core/workflows/plan-phase.md +14 -5
- package/gsd-core/workflows/plan-review-convergence.md +48 -3
- package/gsd-core/workflows/plant-seed.md +3 -0
- package/gsd-core/workflows/profile-user.md +7 -1
- package/gsd-core/workflows/progress.md +33 -5
- package/gsd-core/workflows/quick.md +21 -7
- package/gsd-core/workflows/remove-workspace.md +3 -0
- package/gsd-core/workflows/review.md +123 -68
- package/gsd-core/workflows/scan.md +1 -1
- package/gsd-core/workflows/secure-phase.md +4 -1
- package/gsd-core/workflows/settings-integrations.md +3 -0
- package/gsd-core/workflows/settings.md +3 -0
- package/gsd-core/workflows/ship.md +58 -5
- package/gsd-core/workflows/sketch.md +3 -0
- package/gsd-core/workflows/smart-entry.md +3 -0
- package/gsd-core/workflows/spec-phase.md +1 -1
- package/gsd-core/workflows/spike.md +7 -1
- package/gsd-core/workflows/transition.md +1 -1
- package/gsd-core/workflows/ui-phase.md +3 -1
- package/gsd-core/workflows/ui-review.md +3 -0
- package/gsd-core/workflows/undo.md +7 -0
- package/gsd-core/workflows/update.md +2 -0
- package/gsd-core/workflows/validate-phase.md +3 -0
- package/gsd-core/workflows/verify-phase.md +2 -2
- package/gsd-core/workflows/verify-work.md +7 -3
- package/hooks/dist/gsd-context-monitor.js +27 -9
- package/hooks/dist/gsd-statusline.js +252 -17
- package/hooks/gsd-context-monitor.js +27 -9
- package/hooks/gsd-statusline.js +252 -17
- package/package.json +8 -4
- package/pi/gsd.cjs +8 -2
- package/scripts/changeset/lint.cjs +1 -0
- package/scripts/changeset/parse.cjs +26 -0
- package/scripts/check-glossary-refs.cjs +220 -0
- package/scripts/ci-rebase-check.cjs +48 -4
- package/scripts/ci-test-scope.cjs +39 -1
- package/scripts/gen-adr-index.cjs +526 -0
- package/scripts/gen-golden-install-parity-zcode.cjs +35 -45
- package/scripts/gen-install-tree-fixtures.cjs +75 -0
- package/scripts/gen-test-timings.cjs +201 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -1
- package/scripts/lint-portable-timeout.cjs +140 -0
- package/scripts/lint-table-schema-drift.cjs +157 -0
- package/scripts/lint-test-file-count.allowlist.json +1 -0
- package/scripts/release-tarball-smoke.cjs +18 -11
- package/scripts/run-tests.cjs +420 -58
- package/skills/gsd-ai-integration-phase/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +31 -1
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-plan-phase/SKILL.md +5 -3
- package/skills/gsd-plan-review-convergence/SKILL.md +3 -2
- package/skills/gsd-surface/SKILL.md +6 -6
- package/vscode/package.json +1 -1
|
@@ -26,6 +26,7 @@ const { readGsdCommandNames, transformContentToHyphen } = commandRoster;
|
|
|
26
26
|
const runtimeNamePolicy = require("./runtime-name-policy.cjs");
|
|
27
27
|
const { getDirName } = runtimeNamePolicy;
|
|
28
28
|
const capabilityRegistry = require("./capability-registry.cjs");
|
|
29
|
+
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
29
30
|
// #1383: resolve GSD's version WITHOUT a top-level
|
|
30
31
|
// `require('../../../package.json')`. That require ran at module load on every
|
|
31
32
|
// gsd-tools invocation (this module sits in the gsd-tools loader chain) and
|
|
@@ -867,7 +868,13 @@ function convertClaudeCommandToCursorSkill(content, skillName) {
|
|
|
867
868
|
description = toSingleLine(description);
|
|
868
869
|
const shortDescription = description.length > 180 ? `${description.slice(0, 177)}...` : description;
|
|
869
870
|
const adapter = getCursorSkillAdapterHeader(skillName);
|
|
870
|
-
|
|
871
|
+
// #2341: mark user-invocable:false so the skill is NOT shown in Cursor's '/'
|
|
872
|
+
// menu (it defaults to true). Cursor also writes a commands/ surface (#785),
|
|
873
|
+
// and surfacing both duplicated every /gsd-* entry. This mirrors the #789
|
|
874
|
+
// CodeBuddy de-dup: the commands/ surface is the sole '/' entry point; skills
|
|
875
|
+
// stay model-invocable background knowledge. (user-invocable:false hides from
|
|
876
|
+
// '/' while keeping model invocation — distinct from disable-model-invocation.)
|
|
877
|
+
return `---\nname: ${yamlIdentifier(skillName)}\ndescription: ${yamlQuote(shortDescription)}\nuser-invocable: false\n---\n\n${adapter}\n\n${body.trimStart()}`;
|
|
871
878
|
}
|
|
872
879
|
/**
|
|
873
880
|
* Convert a Claude Code command to a Cursor 1.6 slash command (#785).
|
|
@@ -2143,8 +2150,8 @@ function computePathPrefix({ isGlobal, isOpencode, isWindowsHost: _isWindowsHost
|
|
|
2143
2150
|
// Without this, path.join on Windows produces a backslash prefix that
|
|
2144
2151
|
// leaks into markdown content and breaks cross-platform substring checks.
|
|
2145
2152
|
// See DEFECT.WINDOWS-PATH-LEAK-IN-MARKDOWN-CONTENT in CONTEXT.md.
|
|
2146
|
-
const posixTarget = String(resolvedTarget)
|
|
2147
|
-
const posixHome = homeDir ? String(homeDir)
|
|
2153
|
+
const posixTarget = (0, shell_command_projection_cjs_1.posixNormalize)(String(resolvedTarget));
|
|
2154
|
+
const posixHome = homeDir ? (0, shell_command_projection_cjs_1.posixNormalize)(String(homeDir)) : homeDir;
|
|
2148
2155
|
if (isGlobal && posixTarget.startsWith(posixHome) && !isOpencode) {
|
|
2149
2156
|
return '$HOME' + posixTarget.slice(posixHome.length) + '/';
|
|
2150
2157
|
}
|
|
@@ -2461,8 +2468,8 @@ function rewriteStagedSkillBodies(stagedDir, opts) {
|
|
|
2461
2468
|
const { runtime, configDir, scope = 'global', homedir = () => node_os_1.default.homedir(), platform = process.platform, resolveAttribution, } = opts;
|
|
2462
2469
|
if (!node_fs_1.default.existsSync(stagedDir))
|
|
2463
2470
|
return;
|
|
2464
|
-
const resolvedTarget = node_path_1.default.resolve(configDir)
|
|
2465
|
-
const homeDir =
|
|
2471
|
+
const resolvedTarget = (0, shell_command_projection_cjs_1.posixNormalize)(node_path_1.default.resolve(configDir));
|
|
2472
|
+
const homeDir = (0, shell_command_projection_cjs_1.posixNormalize)(homedir());
|
|
2466
2473
|
const isGlobal = scope === 'global';
|
|
2467
2474
|
const isOpencode = false; // #2087: opencode installs via the combined-family engine path, never through the generic rewrite
|
|
2468
2475
|
const isWindowsHost = platform === 'win32';
|
|
@@ -2490,8 +2497,8 @@ function rewriteStagedCommandBodies(stagedDir, opts) {
|
|
|
2490
2497
|
const { runtime, configDir, scope = 'global', homedir = () => node_os_1.default.homedir(), platform = process.platform, resolveAttribution, } = opts;
|
|
2491
2498
|
if (!node_fs_1.default.existsSync(stagedDir))
|
|
2492
2499
|
return stagedDir;
|
|
2493
|
-
const resolvedTarget = node_path_1.default.resolve(configDir)
|
|
2494
|
-
const homeDir =
|
|
2500
|
+
const resolvedTarget = (0, shell_command_projection_cjs_1.posixNormalize)(node_path_1.default.resolve(configDir));
|
|
2501
|
+
const homeDir = (0, shell_command_projection_cjs_1.posixNormalize)(homedir());
|
|
2495
2502
|
const isGlobal = scope === 'global';
|
|
2496
2503
|
const isOpencode = false; // #2087: opencode installs via the combined-family engine path, never through the generic rewrite
|
|
2497
2504
|
const isWindowsHost = platform === 'win32';
|
|
@@ -67,9 +67,10 @@ function createRuntimeArtifactInstallPlan(args) {
|
|
|
67
67
|
// to kind.stage() for agents kind entries with a converter (convertedAgentsKind).
|
|
68
68
|
// NO _stampNonClaudeRuntimeDefaults — agents are NOT stamped in the inline loop.
|
|
69
69
|
const os = _require('node:os');
|
|
70
|
+
const { posixNormalize } = _require('./shell-command-projection.cjs');
|
|
70
71
|
const homedirFn = homedir ?? (() => os.homedir());
|
|
71
|
-
const resolvedTarget = path.resolve(layout.configDir)
|
|
72
|
-
const homeDir = homedirFn()
|
|
72
|
+
const resolvedTarget = posixNormalize(path.resolve(layout.configDir));
|
|
73
|
+
const homeDir = posixNormalize(homedirFn());
|
|
73
74
|
const isGlobal = scope === 'global';
|
|
74
75
|
const isOpencode = layout.runtime === 'opencode';
|
|
75
76
|
const isWindowsHost = (platform ?? process.platform) === 'win32';
|
|
@@ -23,6 +23,7 @@ const { stageSkillsForProfile, stageAgentsForProfile, stageAgentsForRuntimeWithC
|
|
|
23
23
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
24
24
|
const runtimeArtifactConversion = require("./runtime-artifact-conversion.cjs");
|
|
25
25
|
const conversionExports = runtimeArtifactConversion;
|
|
26
|
+
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
26
27
|
// In .cts (CommonJS output) files, `require` is available as a global.
|
|
27
28
|
const _require = require;
|
|
28
29
|
// ---------------------------------------------------------------------------
|
|
@@ -192,7 +193,7 @@ function kimiAgentsKind(destSubpath, prefix, configDir) {
|
|
|
192
193
|
continue;
|
|
193
194
|
const agentPath = node_path_1.default.join(stagedAgents, entry.name);
|
|
194
195
|
subagents.push({
|
|
195
|
-
path: node_path_1.default.join('agents', entry.name)
|
|
196
|
+
path: (0, shell_command_projection_cjs_1.posixNormalize)(node_path_1.default.join('agents', entry.name)),
|
|
196
197
|
content: node_fs_1.default.readFileSync(agentPath, 'utf8'),
|
|
197
198
|
});
|
|
198
199
|
}
|
|
@@ -226,8 +227,12 @@ function kimiAgentsKind(destSubpath, prefix, configDir) {
|
|
|
226
227
|
* arg so scope-aware converters (antigravity, copilot) can choose
|
|
227
228
|
* between global home paths and workspace-relative paths without
|
|
228
229
|
* colliding with the `runtime` string at position 3.
|
|
230
|
+
* @param capabilityRegistry #2322: optional capability registry — captured in the
|
|
231
|
+
* stage() closure so third-party capability skills are bound to
|
|
232
|
+
* their declaring capId at staging time. Absent -> stage() stages
|
|
233
|
+
* nothing third-party (fail closed).
|
|
229
234
|
*/
|
|
230
|
-
function skillsKind(destSubpath, prefix, converterName, runtime, configDir, nested = false, scope = 'global') {
|
|
235
|
+
function skillsKind(destSubpath, prefix, converterName, runtime, configDir, nested = false, scope = 'global', capabilityRegistry) {
|
|
231
236
|
return {
|
|
232
237
|
kind: 'skills',
|
|
233
238
|
destSubpath,
|
|
@@ -246,7 +251,7 @@ function skillsKind(destSubpath, prefix, converterName, runtime, configDir, nest
|
|
|
246
251
|
: [];
|
|
247
252
|
const isGlobal = scope === 'global';
|
|
248
253
|
const wrappedConverter = (content, skillName) => realConverter(content, skillName, runtime, cmdNames, isGlobal);
|
|
249
|
-
return stageSkillsForRuntimeAsSkills(findInstallSourceRoot(configDir), resolved, wrappedConverter, prefix, nested);
|
|
254
|
+
return stageSkillsForRuntimeAsSkills(findInstallSourceRoot(configDir), resolved, wrappedConverter, prefix, nested, capabilityRegistry);
|
|
250
255
|
},
|
|
251
256
|
};
|
|
252
257
|
}
|
|
@@ -284,7 +289,7 @@ function getRegistry() {
|
|
|
284
289
|
* Map a single ArtifactKindDescriptor entry to an ArtifactKind using the
|
|
285
290
|
* matching builder function. Mirrors the hand-built calls in the old switch.
|
|
286
291
|
*/
|
|
287
|
-
function dispatchKindEntry(entry, runtime, configDir, scope) {
|
|
292
|
+
function dispatchKindEntry(entry, runtime, configDir, scope, capabilityRegistry) {
|
|
288
293
|
const { kind, destSubpath, prefix, nesting, converter } = entry;
|
|
289
294
|
const nested = nesting === 'nested';
|
|
290
295
|
let result;
|
|
@@ -303,7 +308,7 @@ function dispatchKindEntry(entry, runtime, configDir, scope) {
|
|
|
303
308
|
if (converter == null) {
|
|
304
309
|
throw new TypeError(`resolveRuntimeArtifactLayout: skills entry for '${runtime}' has converter=null (converter is required for skills)`);
|
|
305
310
|
}
|
|
306
|
-
result = skillsKind(destSubpath, prefix, converter, runtime, configDir, nested, scope);
|
|
311
|
+
result = skillsKind(destSubpath, prefix, converter, runtime, configDir, nested, scope, capabilityRegistry);
|
|
307
312
|
break;
|
|
308
313
|
case 'kimi-agents':
|
|
309
314
|
result = kimiAgentsKind(destSubpath, prefix, configDir);
|
|
@@ -321,11 +326,21 @@ function dispatchKindEntry(entry, runtime, configDir, scope) {
|
|
|
321
326
|
*
|
|
322
327
|
* ADR-857 phase 5d: driven by the capability-registry artifactLayout descriptor
|
|
323
328
|
* instead of a hardcoded switch statement.
|
|
329
|
+
*
|
|
330
|
+
* @param capabilityRegistry #2322: optional — when the caller has a composed
|
|
331
|
+
* capability registry in scope (e.g. capability-writer.cts's `capability set`
|
|
332
|
+
* path, or a fresh install's registry-aware profile resolution), pass it here
|
|
333
|
+
* so the skills kind's stage() closure can materialize installed third-party
|
|
334
|
+
* capability skills bound to their declaring capId. Both call paths (surface
|
|
335
|
+
* apply AND the installer) must pass their registry here — resolveProfile's
|
|
336
|
+
* own `'*'` (full profile) short-circuit never carries a registry, so if it
|
|
337
|
+
* is not threaded in at layout-build time a `full`-profile install stages no
|
|
338
|
+
* third-party capability skills regardless of registration (#2322 blocker 2).
|
|
324
339
|
*/
|
|
325
|
-
function resolveRuntimeArtifactLayout(runtime, configDir, scope = 'global') {
|
|
326
|
-
return resolveRuntimeArtifactLayoutFromRegistry(getRegistry(), runtime, configDir, scope);
|
|
340
|
+
function resolveRuntimeArtifactLayout(runtime, configDir, scope = 'global', capabilityRegistry) {
|
|
341
|
+
return resolveRuntimeArtifactLayoutFromRegistry(getRegistry(), runtime, configDir, scope, capabilityRegistry);
|
|
327
342
|
}
|
|
328
|
-
function resolveRuntimeArtifactLayoutFromRegistry(registry, runtime, configDir, scope = 'global') {
|
|
343
|
+
function resolveRuntimeArtifactLayoutFromRegistry(registry, runtime, configDir, scope = 'global', capabilityRegistry) {
|
|
329
344
|
if (typeof configDir !== 'string' || configDir === '') {
|
|
330
345
|
throw new TypeError('configDir must be a non-empty string');
|
|
331
346
|
}
|
|
@@ -337,7 +352,7 @@ function resolveRuntimeArtifactLayoutFromRegistry(registry, runtime, configDir,
|
|
|
337
352
|
throw new TypeError(`Unknown runtime: '${runtime}' — add to runtime-artifact-layout.cjs table`);
|
|
338
353
|
}
|
|
339
354
|
const entries = desc[scope] ?? [];
|
|
340
|
-
const kinds = entries.map((entry) => dispatchKindEntry(entry, runtime, configDir, scope));
|
|
355
|
+
const kinds = entries.map((entry) => dispatchKindEntry(entry, runtime, configDir, scope, capabilityRegistry));
|
|
341
356
|
return { runtime, configDir, scope, kinds };
|
|
342
357
|
}
|
|
343
358
|
module.exports = { resolveRuntimeArtifactLayout, resolveRuntimeArtifactLayoutFromRegistry, findInstallSourceRoot };
|
|
@@ -267,7 +267,7 @@ function normalizeNodePath(execPath, opts) {
|
|
|
267
267
|
return execPath;
|
|
268
268
|
const env = (opts && opts.env) || process.env;
|
|
269
269
|
const existsSync = (opts && opts.existsSync) || node_fs_1.default.existsSync;
|
|
270
|
-
const normalizedForMatch =
|
|
270
|
+
const normalizedForMatch = shellCmdProjection.posixNormalize(execPath);
|
|
271
271
|
if (/\/fnm_multishells\/[0-9]+_[0-9]+\/node(\.exe)?$/i.test(normalizedForMatch)) {
|
|
272
272
|
const candidates = [];
|
|
273
273
|
if (env.FNM_DIR) {
|
|
@@ -283,11 +283,16 @@ function normalizeNodePath(execPath, opts) {
|
|
|
283
283
|
}
|
|
284
284
|
return execPath;
|
|
285
285
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
286
|
+
// Homebrew (macOS Intel /usr/local, Apple Silicon /opt/homebrew, Linuxbrew
|
|
287
|
+
// /home/linuxbrew/.linuxbrew, and any custom HOMEBREW_PREFIX) pins node at
|
|
288
|
+
// <prefix>/Cellar/node(<@ver>)?/<ver>/bin/node, then deletes prior versions on
|
|
289
|
+
// `brew upgrade node`. Rewrite to the stable <prefix>/bin/node symlink, which
|
|
290
|
+
// survives the upgrade. Derive <prefix> from the path itself (more reliable
|
|
291
|
+
// than HOMEBREW_PREFIX env — the path IS the install location) so every layout
|
|
292
|
+
// is covered by one branch instead of one per known prefix (#2185).
|
|
293
|
+
const homebrewMatch = normalizedForMatch.match(/^(.+)\/Cellar\/node(@\d+)?\/[^/]+\/bin\/node(\.exe)?$/i);
|
|
294
|
+
if (homebrewMatch) {
|
|
295
|
+
return `${homebrewMatch[1]}/bin/node${homebrewMatch[3] || ''}`;
|
|
291
296
|
}
|
|
292
297
|
// mise pins a concrete node version at <data>/installs/node/<ver>/bin/node
|
|
293
298
|
// (Windows: <data>/installs/node/<ver>/node.exe). Node realpaths
|
|
@@ -304,6 +309,22 @@ function normalizeNodePath(execPath, opts) {
|
|
|
304
309
|
if (existsSync(shim))
|
|
305
310
|
return shim;
|
|
306
311
|
}
|
|
312
|
+
// volta pins a concrete node image at <VOLTA_HOME>/tools/image/node/<ver>/bin/node
|
|
313
|
+
// (Windows: <VOLTA_HOME>/tools/image/node/<ver>/node.exe — volta's own layout
|
|
314
|
+
// puts node.exe at the image root, no bin/). `volta uninstall node@<ver>` prunes
|
|
315
|
+
// that image, so a baked hook command 404s — the same ephemeral-path failure
|
|
316
|
+
// #977 fixed for fnm and #1619 for mise. The stable alias is the shim
|
|
317
|
+
// <VOLTA_HOME>/bin/node, a symlink to volta-shim that always resolves to the
|
|
318
|
+
// active pin. Derive <VOLTA_HOME> from execPath rather than the env so a custom
|
|
319
|
+
// VOLTA_HOME and the Windows %LOCALAPPDATA%\Volta default both work (#2185's
|
|
320
|
+
// reasoning), and only rewrite when the shim exists — otherwise fall back to
|
|
321
|
+
// the raw execPath unchanged.
|
|
322
|
+
const voltaMatch = normalizedForMatch.match(/^(.*)\/tools\/image\/node\/[^/]+\/(?:bin\/)?node(\.exe)?$/);
|
|
323
|
+
if (voltaMatch) {
|
|
324
|
+
const shim = `${voltaMatch[1]}/bin/node${voltaMatch[2] || ''}`;
|
|
325
|
+
if (existsSync(shim))
|
|
326
|
+
return shim;
|
|
327
|
+
}
|
|
307
328
|
return execPath;
|
|
308
329
|
}
|
|
309
330
|
function resolveNodeRunner(opts) {
|
|
@@ -311,7 +332,7 @@ function resolveNodeRunner(opts) {
|
|
|
311
332
|
if (!execPath)
|
|
312
333
|
return null;
|
|
313
334
|
const stablePath = normalizeNodePath(execPath, opts);
|
|
314
|
-
return JSON.stringify(
|
|
335
|
+
return JSON.stringify(shellCmdProjection.posixNormalize(stablePath));
|
|
315
336
|
}
|
|
316
337
|
function resolveBashRunner(opts) {
|
|
317
338
|
const platform = (opts && opts.platform) || process.platform;
|
|
@@ -332,7 +353,7 @@ function resolveBashRunner(opts) {
|
|
|
332
353
|
}
|
|
333
354
|
for (const candidate of candidates) {
|
|
334
355
|
if (candidate && exists(candidate)) {
|
|
335
|
-
return JSON.stringify(
|
|
356
|
+
return JSON.stringify(shellCmdProjection.posixNormalize(candidate));
|
|
336
357
|
}
|
|
337
358
|
}
|
|
338
359
|
return null;
|
|
@@ -372,7 +393,7 @@ function rewriteLegacyManagedNodeHookCommands(settings, absoluteRunner, opts) {
|
|
|
372
393
|
}
|
|
373
394
|
else {
|
|
374
395
|
_runnerToken = m[1];
|
|
375
|
-
const runnerPath = (m[2] || m[3] || m[4] || '')
|
|
396
|
+
const runnerPath = shellCmdProjection.posixNormalize(m[2] || m[3] || m[4] || '');
|
|
376
397
|
const stableRunner = normalizeNodePath(runnerPath);
|
|
377
398
|
if (stableRunner === runnerPath && platform !== 'win32')
|
|
378
399
|
continue;
|
|
@@ -562,10 +583,10 @@ function buildCodexHookWindowsShimIR(scriptAbsPath, absoluteRunnerToken) {
|
|
|
562
583
|
catch {
|
|
563
584
|
interpreter = absoluteRunnerToken;
|
|
564
585
|
}
|
|
565
|
-
const targetAbs =
|
|
586
|
+
const targetAbs = shellCmdProjection.posixNormalize(scriptAbsPath);
|
|
566
587
|
const scriptQuoted = JSON.stringify(targetAbs);
|
|
567
588
|
const cmdPath = scriptAbsPath.replace(/\.js$/, '.cmd');
|
|
568
|
-
const hookCommand = JSON.stringify(
|
|
589
|
+
const hookCommand = JSON.stringify(shellCmdProjection.posixNormalize(cmdPath));
|
|
569
590
|
const runnerQuoted = JSON.stringify(interpreter);
|
|
570
591
|
return {
|
|
571
592
|
invocation: { interpreter, target: scriptAbsPath },
|
|
@@ -584,7 +605,7 @@ function ensureCodexHooksJsonSessionStart(targetDir, opts = {}) {
|
|
|
584
605
|
const hooksJsonPath = node_path_1.default.join(targetDir, 'hooks.json');
|
|
585
606
|
if (!absoluteRunner)
|
|
586
607
|
return { changed: false, wrote: false, path: hooksJsonPath };
|
|
587
|
-
const scriptPath = node_path_1.default.resolve(targetDir, 'hooks', 'gsd-check-update.js')
|
|
608
|
+
const scriptPath = shellCmdProjection.posixNormalize(node_path_1.default.resolve(targetDir, 'hooks', 'gsd-check-update.js'));
|
|
588
609
|
const cmdShimPath = scriptPath.replace(/\.js$/, '.cmd');
|
|
589
610
|
let managedCommand;
|
|
590
611
|
if (platform === 'win32') {
|
|
@@ -614,7 +635,7 @@ function ensureCodexHooksJsonSessionStart(targetDir, opts = {}) {
|
|
|
614
635
|
if (!managedCommand)
|
|
615
636
|
return { changed: false, wrote: false, path: hooksJsonPath };
|
|
616
637
|
const commandWindows = platform === 'win32'
|
|
617
|
-
? JSON.stringify(
|
|
638
|
+
? JSON.stringify(shellCmdProjection.posixNormalize(cmdShimPath))
|
|
618
639
|
: undefined;
|
|
619
640
|
return reconcileCodexHooksJsonSessionStart(targetDir, { managedCommand, commandWindows });
|
|
620
641
|
}
|
|
@@ -624,7 +645,7 @@ function ensureCodexHooksJsonEvent(targetDir, eventName, opts = {}) {
|
|
|
624
645
|
const hooksJsonPath = node_path_1.default.join(targetDir, 'hooks.json');
|
|
625
646
|
if (!absoluteRunner)
|
|
626
647
|
return { changed: false, wrote: false, path: hooksJsonPath };
|
|
627
|
-
const scriptPath = node_path_1.default.resolve(targetDir, 'hooks', 'gsd-context-monitor.js')
|
|
648
|
+
const scriptPath = shellCmdProjection.posixNormalize(node_path_1.default.resolve(targetDir, 'hooks', 'gsd-context-monitor.js'));
|
|
628
649
|
let managedCommand;
|
|
629
650
|
if (platform === 'win32') {
|
|
630
651
|
const shimIR = buildCodexHookWindowsShimIR(scriptPath, absoluteRunner);
|
|
@@ -667,6 +688,7 @@ function buildHookCommand(configDir, hookName, opts) {
|
|
|
667
688
|
opts = {};
|
|
668
689
|
const platform = opts.platform || process.platform;
|
|
669
690
|
const runtime = opts.runtime || 'generic';
|
|
691
|
+
const hookShell = opts.hookShell;
|
|
670
692
|
const isShellHook = hookName.endsWith('.sh');
|
|
671
693
|
if (shellHookOmitsBashRunner({ platform, runtime, isShellHook })) {
|
|
672
694
|
if (opts.portableHooks) {
|
|
@@ -676,7 +698,7 @@ function buildHookCommand(configDir, hookName, opts) {
|
|
|
676
698
|
});
|
|
677
699
|
return JSON.stringify(`${portableBaseDir}/hooks/${hookName}`);
|
|
678
700
|
}
|
|
679
|
-
return JSON.stringify(
|
|
701
|
+
return JSON.stringify(shellCmdProjection.posixNormalize(configDir) + '/hooks/' + hookName);
|
|
680
702
|
}
|
|
681
703
|
const nodeRunner = resolveNodeRunner();
|
|
682
704
|
const runner = isShellHook ? resolveBashRunner(opts) : nodeRunner;
|
|
@@ -692,14 +714,16 @@ function buildHookCommand(configDir, hookName, opts) {
|
|
|
692
714
|
scriptPath: `${portableBaseDir}/hooks/${hookName}`,
|
|
693
715
|
runtime: opts.runtime || 'generic',
|
|
694
716
|
platform,
|
|
717
|
+
hookShell,
|
|
695
718
|
});
|
|
696
719
|
}
|
|
697
|
-
const hooksPath =
|
|
720
|
+
const hooksPath = shellCmdProjection.posixNormalize(configDir) + '/hooks/' + hookName;
|
|
698
721
|
return projectManagedHookCommand({
|
|
699
722
|
absoluteRunner: runner,
|
|
700
723
|
scriptPath: hooksPath,
|
|
701
724
|
runtime,
|
|
702
725
|
platform,
|
|
726
|
+
hookShell,
|
|
703
727
|
});
|
|
704
728
|
}
|
|
705
729
|
// ---------------------------------------------------------------------------
|
|
@@ -844,7 +868,7 @@ function writeClineArtifacts(targetDir, isGlobalInstall) {
|
|
|
844
868
|
function buildCursorHookEntry(scriptPath) {
|
|
845
869
|
return {
|
|
846
870
|
type: 'command',
|
|
847
|
-
command:
|
|
871
|
+
command: shellCmdProjection.posixNormalize(scriptPath),
|
|
848
872
|
[GSD_CURSOR_HOOK_MARKER]: true,
|
|
849
873
|
};
|
|
850
874
|
}
|
|
@@ -13,6 +13,7 @@ exports.ORM_INFO = exports.SCHEMA_PATTERNS = void 0;
|
|
|
13
13
|
exports.detectSchemaFiles = detectSchemaFiles;
|
|
14
14
|
exports.detectSchemaOrm = detectSchemaOrm;
|
|
15
15
|
exports.checkSchemaDrift = checkSchemaDrift;
|
|
16
|
+
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
16
17
|
exports.SCHEMA_PATTERNS = [
|
|
17
18
|
{ pattern: /^src\/collections\/.*\.ts$/, orm: 'payload' },
|
|
18
19
|
{ pattern: /^src\/globals\/.*\.ts$/, orm: 'payload' },
|
|
@@ -61,7 +62,7 @@ function detectSchemaFiles(files) {
|
|
|
61
62
|
const matches = [];
|
|
62
63
|
const orms = new Set();
|
|
63
64
|
for (const rawFile of files) {
|
|
64
|
-
const file =
|
|
65
|
+
const file = (0, shell_command_projection_cjs_1.posixNormalize)(rawFile);
|
|
65
66
|
for (const { pattern, orm } of exports.SCHEMA_PATTERNS) {
|
|
66
67
|
if (pattern.test(file)) {
|
|
67
68
|
matches.push(rawFile);
|
|
@@ -341,7 +341,7 @@ function sanitizeForDisplay(text) {
|
|
|
341
341
|
let sanitized = sanitizeForPrompt(text);
|
|
342
342
|
const protocolLeakPatterns = [
|
|
343
343
|
/^\s*(?:assistant|user|system)\s+to=[^:\s]+:[^\n]+$/i,
|
|
344
|
-
/^\s*<\|(?:assistant|user|system)[^|]*\|>\s*$/i,
|
|
344
|
+
/^\s*<\|(?:assistant|user|system)[^|]*\|>\s*$/i, // allow-adhoc-markdown: not a GFM table-cell scan — matches `<|role|>` protocol-leak marker tokens (prompt-injection sanitization), a false-positive on the table-regex pipe+cell-class fingerprint
|
|
345
345
|
];
|
|
346
346
|
sanitized = sanitized
|
|
347
347
|
.split('\n')
|
|
@@ -15,6 +15,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
15
15
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
16
16
|
};
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.toPosixPath = toPosixPath;
|
|
19
|
+
exports.toNativePath = toNativePath;
|
|
20
|
+
exports.posixNormalize = posixNormalize;
|
|
18
21
|
exports.hookCommandNeedsPowerShellCallOperator = hookCommandNeedsPowerShellCallOperator;
|
|
19
22
|
exports.formatHookCommandForRuntime = formatHookCommandForRuntime;
|
|
20
23
|
exports.shellHookOmitsBashRunner = shellHookOmitsBashRunner;
|
|
@@ -52,26 +55,58 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
52
55
|
// can intercept calls from this seam — destructured imports capture references
|
|
53
56
|
// at load time and become un-mockable.
|
|
54
57
|
const node_child_process_1 = __importDefault(require("node:child_process"));
|
|
58
|
+
/**
|
|
59
|
+
* Convert a filesystem path to POSIX form (forward slashes) by translating the
|
|
60
|
+
* platform-native separator. Single seam for native→POSIX conversion.
|
|
61
|
+
*
|
|
62
|
+
* Prefer this over `p.replace(/\\/g, '/')`: the regex form hardcodes both
|
|
63
|
+
* separators and corrupts POSIX paths containing a literal backslash (a legal
|
|
64
|
+
* filename character). Splitting on `path.sep` only ever touches real
|
|
65
|
+
* separators — a no-op on POSIX, `\`→`/` on Windows.
|
|
66
|
+
*/
|
|
67
|
+
function toPosixPath(p) {
|
|
68
|
+
return p.split(node_path_1.default.sep).join(node_path_1.default.posix.sep);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Convert a filesystem path to the platform-native separator form. No-op on
|
|
72
|
+
* POSIX; `/`→`\` on Windows. Prefer this over a
|
|
73
|
+
* `process.platform === 'win32' ? p.replace(/\//g, '\\') : p` ternary.
|
|
74
|
+
*/
|
|
75
|
+
function toNativePath(p) {
|
|
76
|
+
return p.split(node_path_1.default.posix.sep).join(node_path_1.default.sep);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Normalize ALL backslashes to forward slashes, unconditionally and independent
|
|
80
|
+
* of the running OS. Use this when emitting a path into a POSIX/bash target
|
|
81
|
+
* (which may differ from the running platform — e.g. generating a Windows config
|
|
82
|
+
* on a Linux runner) or when parsing input whose separators are unpredictable.
|
|
83
|
+
*
|
|
84
|
+
* Contrast `toPosixPath`, which is running-OS-relative (splits on `path.sep`) and
|
|
85
|
+
* is for *this machine's* filesystem paths. Do NOT use `toPosixPath` for
|
|
86
|
+
* target-platform projection — on a Linux runner it would not convert a
|
|
87
|
+
* Windows-target path's backslashes.
|
|
88
|
+
*/
|
|
89
|
+
function posixNormalize(p) {
|
|
90
|
+
return p.replace(/\\/g, '/');
|
|
91
|
+
}
|
|
55
92
|
/**
|
|
56
93
|
* Return true when a managed hook command must be prefixed with PowerShell's
|
|
57
94
|
* call operator so a quoted executable token is invokable by the target
|
|
58
95
|
* runtime/shell combination.
|
|
59
96
|
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* one-line re-enable, per the conservative policy below. Note: Antigravity —
|
|
68
|
-
* the Gemini-backend successor — never matched the old `runtime === 'gemini'`
|
|
69
|
-
* check, so its behavior (no prefix) is unchanged.
|
|
97
|
+
* The `&`/no-`&` decision is keyed on the **effective hook-execution shell**
|
|
98
|
+
* (`opts.hookShell`), not on runtime alone — a single runtime (Claude Code)
|
|
99
|
+
* can host either Git Bash or PowerShell on Windows, and no single static
|
|
100
|
+
* command string is valid in both (#2236):
|
|
101
|
+
* - Git Bash: `"node.exe" "hook.js"` works; `& "node.exe" …` → syntax error.
|
|
102
|
+
* - PowerShell: `& "node.exe" "hook.js"` works; bare `"node.exe" …` →
|
|
103
|
+
* `Unexpected token`.
|
|
70
104
|
*
|
|
71
|
-
*
|
|
105
|
+
* Default is `false` (Git Bash form) for backward compatibility. Set
|
|
106
|
+
* `opts.hookShell = 'powershell'` to emit the PowerShell call-operator form.
|
|
72
107
|
*/
|
|
73
|
-
function hookCommandNeedsPowerShellCallOperator(
|
|
74
|
-
return
|
|
108
|
+
function hookCommandNeedsPowerShellCallOperator(opts = {}) {
|
|
109
|
+
return opts.hookShell === 'powershell';
|
|
75
110
|
}
|
|
76
111
|
/**
|
|
77
112
|
* Project a fully-assembled hook command string for the target runtime.
|
|
@@ -122,7 +157,7 @@ function formatManagedHookScriptToken(scriptPath, opts = {}) {
|
|
|
122
157
|
const platform = opts.platform || process.platform;
|
|
123
158
|
if (platform !== 'win32')
|
|
124
159
|
return null;
|
|
125
|
-
return JSON.stringify(scriptPath
|
|
160
|
+
return JSON.stringify(posixNormalize(scriptPath));
|
|
126
161
|
}
|
|
127
162
|
function projectLocalHookPrefix({ runtime: _runtime = 'claude', dirName, hookPathStyle }) {
|
|
128
163
|
if (!dirName)
|
|
@@ -140,29 +175,30 @@ function projectLocalHookPrefix({ runtime: _runtime = 'claude', dirName, hookPat
|
|
|
140
175
|
: `"$CLAUDE_PROJECT_DIR"/${dirName}`;
|
|
141
176
|
}
|
|
142
177
|
function projectPortableHookBaseDir({ configDir, homeDir }) {
|
|
143
|
-
const normalizedConfigDir = String(configDir || '')
|
|
144
|
-
const normalizedHome = String(homeDir || '')
|
|
178
|
+
const normalizedConfigDir = posixNormalize(String(configDir || ''));
|
|
179
|
+
const normalizedHome = posixNormalize(String(homeDir || ''));
|
|
145
180
|
if (!normalizedConfigDir || !normalizedHome)
|
|
146
181
|
return normalizedConfigDir;
|
|
147
182
|
return normalizedConfigDir.startsWith(normalizedHome)
|
|
148
183
|
? '$HOME' + normalizedConfigDir.slice(normalizedHome.length)
|
|
149
184
|
: normalizedConfigDir;
|
|
150
185
|
}
|
|
151
|
-
function projectShellCommandText({ runnerToken, argTokens = [], runtime = 'generic', platform = process.platform, }) {
|
|
186
|
+
function projectShellCommandText({ runnerToken, argTokens = [], runtime = 'generic', platform = process.platform, hookShell, }) {
|
|
152
187
|
if (!runnerToken)
|
|
153
188
|
return null;
|
|
154
189
|
const parts = [runnerToken, ...argTokens.filter(Boolean)];
|
|
155
|
-
return formatHookCommandForRuntime(parts.join(' '), { platform, runtime });
|
|
190
|
+
return formatHookCommandForRuntime(parts.join(' '), { platform, runtime, hookShell });
|
|
156
191
|
}
|
|
157
|
-
function projectManagedHookCommand({ absoluteRunner, scriptPath, runtime = 'generic', platform = process.platform }) {
|
|
192
|
+
function projectManagedHookCommand({ absoluteRunner, scriptPath, runtime = 'generic', platform = process.platform, hookShell }) {
|
|
158
193
|
if (!absoluteRunner || !scriptPath)
|
|
159
194
|
return null;
|
|
160
|
-
const normalizedScriptPath = platform === 'win32' ? scriptPath
|
|
195
|
+
const normalizedScriptPath = platform === 'win32' ? posixNormalize(scriptPath) : scriptPath;
|
|
161
196
|
return projectShellCommandText({
|
|
162
197
|
runnerToken: absoluteRunner,
|
|
163
198
|
argTokens: [JSON.stringify(normalizedScriptPath)],
|
|
164
199
|
runtime,
|
|
165
200
|
platform,
|
|
201
|
+
hookShell,
|
|
166
202
|
});
|
|
167
203
|
}
|
|
168
204
|
const MANAGED_HOOK_BASENAMES_BY_SURFACE = {
|
|
@@ -255,14 +291,14 @@ function isManagedHookCommand(commandText, opts = {}) {
|
|
|
255
291
|
for (const arg of opts.args) {
|
|
256
292
|
if (typeof arg !== 'string')
|
|
257
293
|
continue;
|
|
258
|
-
const argBasename = arg
|
|
294
|
+
const argBasename = posixNormalize(arg).split('/').pop() || '';
|
|
259
295
|
if (isManagedHookBasename(argBasename, { surface }))
|
|
260
296
|
return true;
|
|
261
297
|
}
|
|
262
298
|
}
|
|
263
|
-
const normalizedCommand = commandText
|
|
299
|
+
const normalizedCommand = posixNormalize(commandText);
|
|
264
300
|
if (typeof opts.configDir === 'string' && opts.configDir.length > 0) {
|
|
265
|
-
const normalizedHooksDir = `${node_path_1.default.join(opts.configDir, 'hooks')
|
|
301
|
+
const normalizedHooksDir = `${posixNormalize(node_path_1.default.join(opts.configDir, 'hooks'))}/`;
|
|
266
302
|
if (!normalizedCommand.includes(normalizedHooksDir))
|
|
267
303
|
return false;
|
|
268
304
|
}
|
|
@@ -292,7 +328,7 @@ const ANCHORED_HOOK_SCRIPT_TOKEN = /^"\$[A-Za-z_][A-Za-z0-9_]*"\//;
|
|
|
292
328
|
function projectLegacySettingsHookCommand({ absoluteRunner, scriptPath, scriptToken, runtime = 'generic', platform = process.platform, }) {
|
|
293
329
|
if (!absoluteRunner || !scriptPath)
|
|
294
330
|
return null;
|
|
295
|
-
const normalizedScriptPath = platform === 'win32' ? scriptPath
|
|
331
|
+
const normalizedScriptPath = platform === 'win32' ? posixNormalize(scriptPath) : scriptPath;
|
|
296
332
|
// #1693: a script path already carrying a `"$CLAUDE_PROJECT_DIR"`-anchored
|
|
297
333
|
// quoted prefix (local installs) is already a valid shell token — only the
|
|
298
334
|
// variable is quoted, the rest is bare. JSON.stringify-ing it on Windows
|
|
@@ -350,7 +386,7 @@ function projectPathActionProjection({ mode = 'repair', targetDir, platform = pr
|
|
|
350
386
|
let shellActions;
|
|
351
387
|
if (isWin32) {
|
|
352
388
|
const psTargetDir = escapePowerShellSingleQuoted(targetDir);
|
|
353
|
-
const bashTargetDir = escapeSingleQuotedShellLiteral(String(targetDir)
|
|
389
|
+
const bashTargetDir = escapeSingleQuotedShellLiteral(posixNormalize(String(targetDir)));
|
|
354
390
|
shellActions = [
|
|
355
391
|
{
|
|
356
392
|
label: 'PowerShell',
|
|
@@ -42,6 +42,7 @@ exports.runSmartEntry = runSmartEntry;
|
|
|
42
42
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
43
43
|
const node_path_1 = __importDefault(require("node:path"));
|
|
44
44
|
const node_child_process_1 = require("node:child_process");
|
|
45
|
+
const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
|
|
45
46
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
46
47
|
const ioMod = require("./io.cjs");
|
|
47
48
|
const { output } = ioMod;
|
|
@@ -51,6 +52,9 @@ const { planningPaths } = planningWorkspace;
|
|
|
51
52
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
52
53
|
const frontmatter = require("./frontmatter.cjs");
|
|
53
54
|
const { extractFrontmatter } = frontmatter;
|
|
55
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- phase-lifecycle.cjs is an export= CommonJS module
|
|
56
|
+
const phaseLifecycle = require("./phase-lifecycle.cjs");
|
|
57
|
+
const { deriveProgressFromRoadmap } = phaseLifecycle;
|
|
54
58
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
55
59
|
const stateDocument = require("./state-document.cjs");
|
|
56
60
|
const { stateExtractField } = stateDocument;
|
|
@@ -244,6 +248,8 @@ function detectSignals(cwd, now = Date.now) {
|
|
|
244
248
|
has_git: git.has_git,
|
|
245
249
|
verify_failed: false,
|
|
246
250
|
stale_activity: false,
|
|
251
|
+
roadmap_total_phases: null,
|
|
252
|
+
roadmap_completed_phases: null,
|
|
247
253
|
};
|
|
248
254
|
if (!hasPlanning)
|
|
249
255
|
return empty;
|
|
@@ -274,9 +280,9 @@ function detectSignals(cwd, now = Date.now) {
|
|
|
274
280
|
fmScalar(fm, body, 'progress', 'Progress');
|
|
275
281
|
// Blockers list: `- <text>` items under a `## Blockers` heading.
|
|
276
282
|
const blockers = [];
|
|
277
|
-
const
|
|
278
|
-
if (
|
|
279
|
-
const items =
|
|
283
|
+
const blockersSection = (0, markdown_sectionizer_cjs_1.collectSection)(body, (h) => h.level === 2 && h.text.trim().toLowerCase() === 'blockers', { levelBounded: true });
|
|
284
|
+
if (blockersSection) {
|
|
285
|
+
const items = blockersSection.body.match(/^-\s+(.+)$/gm) || [];
|
|
280
286
|
for (const item of items)
|
|
281
287
|
blockers.push(item.replace(/^-\s+/, '').trim());
|
|
282
288
|
}
|
|
@@ -290,6 +296,24 @@ function detectSignals(cwd, now = Date.now) {
|
|
|
290
296
|
// STATUS: marker on the current phase's summary/verify artifact.
|
|
291
297
|
const verifyFailed = /\bverify-fail(ed)?|verification-fail|uat-fail\b/i.test(statusRaw || '') ||
|
|
292
298
|
detectVerifyFailed(cwd, currentPhaseRaw);
|
|
299
|
+
// #2427: derive global phase counts from ROADMAP.md's Progress table. These
|
|
300
|
+
// are preferred over STATE.md's cached milestone-scoped `total_phases` (which
|
|
301
|
+
// goes stale when phases are appended after a milestone switch) for the
|
|
302
|
+
// completion check. Null when ROADMAP.md is absent or has no parseable
|
|
303
|
+
// Progress table — isComplete falls back to the legacy comparison in that case.
|
|
304
|
+
let roadmapTotalPhases = null;
|
|
305
|
+
let roadmapCompletedPhases = null;
|
|
306
|
+
if (hasRoadmap) {
|
|
307
|
+
try {
|
|
308
|
+
const roadmapContent = node_fs_1.default.readFileSync(paths.roadmap, 'utf8');
|
|
309
|
+
const derived = deriveProgressFromRoadmap(roadmapContent);
|
|
310
|
+
roadmapTotalPhases = derived.totalPhases;
|
|
311
|
+
roadmapCompletedPhases = derived.completedPhases;
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
/* ROADMAP.md unreadable — leave null; isComplete falls back to legacy. */
|
|
315
|
+
}
|
|
316
|
+
}
|
|
293
317
|
return {
|
|
294
318
|
current_phase: parseIntOrNull(currentPhaseRaw),
|
|
295
319
|
total_phases: parseIntOrNull(totalPhasesRaw),
|
|
@@ -304,14 +328,56 @@ function detectSignals(cwd, now = Date.now) {
|
|
|
304
328
|
has_git: git.has_git,
|
|
305
329
|
verify_failed: verifyFailed,
|
|
306
330
|
stale_activity: staleActivity,
|
|
331
|
+
roadmap_total_phases: roadmapTotalPhases,
|
|
332
|
+
roadmap_completed_phases: roadmapCompletedPhases,
|
|
307
333
|
};
|
|
308
334
|
}
|
|
309
335
|
// ─── Situation classification ─────────────────────────────────────────────────
|
|
310
|
-
/**
|
|
336
|
+
/**
|
|
337
|
+
* True when the workflow has fully completed all phases.
|
|
338
|
+
*
|
|
339
|
+
* #2427: completion is grounded in ROADMAP.md's Progress table (global,
|
|
340
|
+
* authoritative, never stale) when available, with a legacy fallback to
|
|
341
|
+
* STATE.md's cached `total_phases` when the roadmap has no parseable Progress
|
|
342
|
+
* table (e.g. a fresh project or a non-standard roadmap layout). The status
|
|
343
|
+
* regex was tightened to require milestone-level completion language
|
|
344
|
+
* (`milestone complete` / `all phases complete` / `complete(d)`) and no longer
|
|
345
|
+
* matches per-phase messages like "Phase X shipped — PR #N" that falsely
|
|
346
|
+
* satisfied the pre-fix alternation (`\bcomplete(d)?|done|shipped\b`).
|
|
347
|
+
*/
|
|
311
348
|
function isComplete(s) {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
349
|
+
// Prefer ROADMAP-derived counts (global, authoritative) over STATE.md's
|
|
350
|
+
// cached milestone-scoped total_phases (stale-prone). Fall back to legacy
|
|
351
|
+
// when the roadmap has no Progress table.
|
|
352
|
+
if (s.roadmap_total_phases !== null && s.roadmap_completed_phases !== null) {
|
|
353
|
+
if (s.roadmap_total_phases === 0)
|
|
354
|
+
return false;
|
|
355
|
+
if (s.roadmap_completed_phases < s.roadmap_total_phases)
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
// Legacy path: STATE.md comparison. Still subject to the two-scale bug,
|
|
360
|
+
// but only fires when ROADMAP.md is absent or has no Progress table.
|
|
361
|
+
if (s.total_phases === null || s.current_phase === null)
|
|
362
|
+
return false;
|
|
363
|
+
if (s.current_phase < s.total_phases)
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
// Status regex: require milestone-level completion language. The pre-fix
|
|
367
|
+
// regex matched any "shipped" / "done" substring (per-phase language).
|
|
368
|
+
// Tightened to match:
|
|
369
|
+
// - "milestone complete" (ADR-2207 terminal status — usually written as
|
|
370
|
+
// "<version> milestone complete", e.g. "v1.0 milestone complete"; the
|
|
371
|
+
// substring match handles both forms)
|
|
372
|
+
// - "all phases complete" (ADR-2207 intermediate terminal)
|
|
373
|
+
// - "complete" / "completed" (legacy short form — STATE.md milestone
|
|
374
|
+
// status is a single value, not a per-phase log)
|
|
375
|
+
// Intentionally does NOT match "done" alone even though normalizeStateStatus
|
|
376
|
+
// (state-document.cts) treats "done" as "completed" — in the milestone
|
|
377
|
+
// status field, "done" is per-phase noise (e.g. "Phase X done"), not a
|
|
378
|
+
// milestone-completion signal. Mirrors workstream-inventory-builder.cts's
|
|
379
|
+
// terminal pattern \bmilestone\s+complete\b.
|
|
380
|
+
return /\b(milestone\s+complete|all\s+phases\s+complete|complete(d)?)\b/i.test(s.status);
|
|
315
381
|
}
|
|
316
382
|
/** Idle-stranded: clean tree, committed work not shipped, optionally stale. */
|
|
317
383
|
function isIdleStranded(s) {
|