@opengsd/gsd-core 1.5.0 → 1.6.0-rc.2
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/plugin.json +1 -1
- package/agents/gsd-plan-checker.md +34 -0
- package/agents/gsd-planner.md +2 -0
- package/agents/gsd-roadmapper.md +6 -0
- package/bin/install.js +199 -365
- package/commands/gsd/capture.md +5 -1
- package/gemini-extension.json +1 -1
- package/gsd-core/bin/gsd-tools.cjs +695 -5
- package/gsd-core/bin/lib/adr-parser.cjs +45 -23
- package/gsd-core/bin/lib/audit.cjs +2 -2
- package/gsd-core/bin/lib/capability-consent.cjs +763 -0
- package/gsd-core/bin/lib/capability-ledger.cjs +831 -0
- package/gsd-core/bin/lib/capability-lifecycle.cjs +1551 -0
- package/gsd-core/bin/lib/capability-loader.cjs +764 -0
- package/gsd-core/bin/lib/capability-lock.cjs +553 -0
- package/gsd-core/bin/lib/capability-registry.cjs +198 -4
- package/gsd-core/bin/lib/capability-source.cjs +1242 -0
- package/gsd-core/bin/lib/capability-state.cjs +9 -6
- package/gsd-core/bin/lib/capability-trust.cjs +550 -0
- package/gsd-core/bin/lib/capability-validator.cjs +2066 -0
- package/gsd-core/bin/lib/capability-writer.cjs +14 -5
- package/gsd-core/bin/lib/check-command-router.cjs +69 -18
- package/gsd-core/bin/lib/command-aliases.cjs +8 -0
- package/gsd-core/bin/lib/commands.cjs +247 -0
- package/gsd-core/bin/lib/config-loader.cjs +98 -84
- package/gsd-core/bin/lib/config-schema.cjs +26 -7
- package/gsd-core/bin/lib/config.cjs +7 -1
- package/gsd-core/bin/lib/decisions.cjs +149 -60
- package/gsd-core/bin/lib/frontmatter.cjs +7 -3
- package/gsd-core/bin/lib/gap-checker.cjs +126 -11
- package/gsd-core/bin/lib/init.cjs +91 -22
- package/gsd-core/bin/lib/legacy-cleanup.cjs +96 -0
- package/gsd-core/bin/lib/loop-resolver.cjs +26 -2
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +471 -0
- package/gsd-core/bin/lib/milestone.cjs +41 -2
- package/gsd-core/bin/lib/phase-command-router.cjs +5 -0
- package/gsd-core/bin/lib/phase-id.cjs +25 -11
- package/gsd-core/bin/lib/phase-lifecycle.cjs +14 -5
- package/gsd-core/bin/lib/phase.cjs +33 -4
- package/gsd-core/bin/lib/probe-core.cjs +7 -0
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +59 -26
- package/gsd-core/bin/lib/project-root.cjs +89 -2
- package/gsd-core/bin/lib/resolution.cjs +26 -0
- package/gsd-core/bin/lib/roadmap-command-router.cjs +16 -3
- package/gsd-core/bin/lib/roadmap-parser.cjs +73 -106
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +47 -17
- package/gsd-core/bin/lib/roadmap.cjs +5 -2
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +423 -3
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +77 -0
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +1 -28
- package/gsd-core/bin/lib/runtime-homes.cjs +53 -1
- package/gsd-core/bin/lib/runtime-name-policy.cjs +44 -0
- package/gsd-core/bin/lib/semver-compare.cjs +127 -0
- package/gsd-core/bin/lib/shell-command-projection.cjs +55 -1
- package/gsd-core/bin/lib/state-document.cjs +4 -2
- package/gsd-core/bin/lib/state.cjs +317 -161
- package/gsd-core/bin/lib/surface.cjs +12 -19
- package/gsd-core/bin/lib/uat-predicate.cjs +7 -47
- package/gsd-core/bin/lib/uat.cjs +39 -26
- package/gsd-core/bin/lib/validate.cjs +5 -2
- package/gsd-core/bin/lib/verify.cjs +40 -15
- package/gsd-core/bin/lib/worktree-safety.cjs +202 -0
- package/gsd-core/bin/shared/config-defaults.manifest.json +6 -1
- package/gsd-core/bin/shared/config-schema.manifest.json +5 -1
- package/gsd-core/references/context-budget.md +8 -8
- package/gsd-core/references/execute-phase-between-wave-reset.md +43 -0
- package/gsd-core/references/execute-phase-context-guard.md +16 -0
- package/gsd-core/references/execute-phase-wave-guard.md +33 -0
- package/gsd-core/references/planner-antipatterns.md +48 -0
- package/gsd-core/references/planning-config.md +4 -0
- package/gsd-core/references/prohibition-probe.md +15 -9
- package/gsd-core/references/scout-codebase.md +2 -2
- package/gsd-core/workflows/autonomous.md +33 -33
- package/gsd-core/workflows/diagnose-issues.md +6 -1
- package/gsd-core/workflows/discuss-phase/templates/context.md +1 -1
- package/gsd-core/workflows/discuss-phase.md +1 -2
- package/gsd-core/workflows/execute-phase.md +12 -12
- package/gsd-core/workflows/help/modes/full.md +10 -0
- package/gsd-core/workflows/list-seeds.md +63 -0
- package/gsd-core/workflows/manager.md +37 -37
- package/gsd-core/workflows/pr-branch.md +156 -0
- package/gsd-core/workflows/quick.md +6 -1
- package/gsd-core/workflows/review.md +10 -2
- package/gsd-core/workflows/spec-phase.md +8 -3
- package/gsd-core/workflows/verify-phase.md +2 -2
- package/package.json +6 -3
- package/scripts/gen-capability-matrix.cjs +284 -0
- package/scripts/gen-capability-registry.cjs +96 -1853
- package/scripts/lint-regression-test-names.allowlist.json +1 -0
- package/scripts/lint-resolution-provenance.allowlist.json +1 -0
- package/scripts/lint-resolution-provenance.cjs +192 -0
- package/scripts/lint-test-file-count.allowlist.json +9 -0
- package/scripts/prompt-injection-scan.sh +1 -0
- package/scripts/run-tests.cjs +14 -0
- package/scripts/sync-manifest-versions.cjs +77 -5
|
@@ -19,9 +19,52 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
19
19
|
* Installer Module for conversion behavior.
|
|
20
20
|
*/
|
|
21
21
|
const node_path_1 = __importDefault(require("node:path"));
|
|
22
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
23
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
22
24
|
const commandRoster = require("./command-roster.cjs");
|
|
23
25
|
const { readGsdCommandNames, transformContentToHyphen } = commandRoster;
|
|
24
|
-
const
|
|
26
|
+
const runtimeNamePolicy = require("./runtime-name-policy.cjs");
|
|
27
|
+
const { getDirName } = runtimeNamePolicy;
|
|
28
|
+
// #1383: resolve GSD's version WITHOUT a top-level
|
|
29
|
+
// `require('../../../package.json')`. That require ran at module load on every
|
|
30
|
+
// gsd-tools invocation (this module sits in the gsd-tools loader chain) and
|
|
31
|
+
// threw `Cannot find module '../../../package.json'` on runtimes whose root has
|
|
32
|
+
// no package.json — notably Codex, where the installer omits the synthetic root
|
|
33
|
+
// package.json — taking the entire CLI down before it did anything. And even
|
|
34
|
+
// where it resolved (Claude's synthetic `{"type":"commonjs"}`), there is no
|
|
35
|
+
// `version` field, so the single consumer below already emitted
|
|
36
|
+
// `version: undefined`. Resolve lazily and defensively instead:
|
|
37
|
+
// 1. Installed trees carry <root>/gsd-core/VERSION (written by the installer);
|
|
38
|
+
// this module lives at <root>/gsd-core/bin/lib, so VERSION is two dirs up.
|
|
39
|
+
// 2. The source / npm-package tree has no gsd-core/VERSION but carries a real
|
|
40
|
+
// package.json three dirs up — read it lazily, never at module-load time.
|
|
41
|
+
// A failed/invalid lookup degrades to '' (the caller omits the field) rather
|
|
42
|
+
// than crashing or emitting `version: undefined`. Both sources are validated
|
|
43
|
+
// against the same semver shape the repo's other VERSION reader enforces
|
|
44
|
+
// (src/update-context.cts) so a garbled VERSION file is never emitted verbatim.
|
|
45
|
+
// Exported for the #1383 regression.
|
|
46
|
+
const SEMVER_PREFIX = /^\d+\.\d+\.\d+/; // mirrors src/update-context.cts SEMVER_PREFIX
|
|
47
|
+
function resolveVersionFrom(libDir) {
|
|
48
|
+
try {
|
|
49
|
+
const v = node_fs_1.default.readFileSync(node_path_1.default.join(libDir, '..', '..', 'VERSION'), 'utf8').trim();
|
|
50
|
+
if (SEMVER_PREFIX.test(v))
|
|
51
|
+
return v;
|
|
52
|
+
}
|
|
53
|
+
catch { /* not an installed tree (no gsd-core/VERSION) */ }
|
|
54
|
+
try {
|
|
55
|
+
const pkg = require(node_path_1.default.join(libDir, '..', '..', '..', 'package.json'));
|
|
56
|
+
if (pkg && typeof pkg.version === 'string' && SEMVER_PREFIX.test(pkg.version))
|
|
57
|
+
return pkg.version;
|
|
58
|
+
}
|
|
59
|
+
catch { /* runtime root has no package.json (e.g. Codex) */ }
|
|
60
|
+
return '';
|
|
61
|
+
}
|
|
62
|
+
let cachedVersion;
|
|
63
|
+
function gsdVersion() {
|
|
64
|
+
if (cachedVersion === undefined)
|
|
65
|
+
cachedVersion = resolveVersionFrom(__dirname);
|
|
66
|
+
return cachedVersion;
|
|
67
|
+
}
|
|
25
68
|
const colorNameToHex = {
|
|
26
69
|
cyan: '#00FFFF',
|
|
27
70
|
red: '#FF0000',
|
|
@@ -363,8 +406,11 @@ function convertClaudeCommandToClaudeSkill(content, skillName, runtime = null, c
|
|
|
363
406
|
// Hermes' SKILL.md spec lists `version` as a required frontmatter field.
|
|
364
407
|
// Track GSD's package version so Hermes' skill_view() reports a stable
|
|
365
408
|
// identifier per install.
|
|
366
|
-
if (runtime === 'hermes')
|
|
367
|
-
|
|
409
|
+
if (runtime === 'hermes') {
|
|
410
|
+
const version = gsdVersion();
|
|
411
|
+
if (version)
|
|
412
|
+
fm += `version: ${yamlQuote(version)}\n`;
|
|
413
|
+
}
|
|
368
414
|
// #778 (b) — Qwen-only numeric priority for /skills ordering. Scoped to qwen
|
|
369
415
|
// so Claude/Hermes skill frontmatter is unchanged (they ignore the field, but
|
|
370
416
|
// we keep their output byte-stable). skillName is the `gsd-<stem>` dir name.
|
|
@@ -1895,7 +1941,366 @@ function convertClaudeCommandToOpencodeSkill(content, skillName) {
|
|
|
1895
1941
|
function convertClaudeCommandToKiloSkill(content, skillName) {
|
|
1896
1942
|
return convertClaudeCommandToOpencodeFamilySkill(content, skillName, (c) => convertClaudeToKiloFrontmatter(c));
|
|
1897
1943
|
}
|
|
1944
|
+
// ── Rewrite engine — ADR-1508 Phase 2 ───────────────────────────────────────
|
|
1945
|
+
// Relocated from bin/install.js (#1511). Behavior is byte-for-behavior identical
|
|
1946
|
+
// to the originals; the only change is the injected `attribution` 5th param in
|
|
1947
|
+
// _applyRuntimeRewrites (replacing the internal getCommitAttribution() call).
|
|
1948
|
+
/**
|
|
1949
|
+
* Compute the path prefix for a runtime install.
|
|
1950
|
+
* Global installs under $HOME use $HOME/... form; others use the resolved target.
|
|
1951
|
+
* isOpencode excludes OpenCode (uses ~/.config/opencode which breaks $HOME shorthand).
|
|
1952
|
+
* isWindowsHost is not used today but reserved for future Windows-specific logic.
|
|
1953
|
+
*
|
|
1954
|
+
* @private — exported as `_computePathPrefix` for tests.
|
|
1955
|
+
*/
|
|
1956
|
+
function computePathPrefix({ isGlobal, isOpencode, isWindowsHost: _isWindowsHost, resolvedTarget, homeDir }) {
|
|
1957
|
+
if (isGlobal && resolvedTarget.startsWith(homeDir) && !isOpencode) {
|
|
1958
|
+
return '$HOME' + resolvedTarget.slice(homeDir.length) + '/';
|
|
1959
|
+
}
|
|
1960
|
+
return `${resolvedTarget}/`;
|
|
1961
|
+
}
|
|
1962
|
+
/**
|
|
1963
|
+
* Canonical list of every non-Claude runtime that gsd-core emits artifacts for.
|
|
1964
|
+
* Exported so test files can import this single source of truth rather than
|
|
1965
|
+
* maintaining divergent hand-rolled arrays (#1521).
|
|
1966
|
+
*
|
|
1967
|
+
* Keep in sync with the runtime flags in bin/install.js and getDirName().
|
|
1968
|
+
*/
|
|
1969
|
+
const NON_CLAUDE_RUNTIMES = [
|
|
1970
|
+
'codex', 'opencode', 'kilo', 'gemini', 'copilot', 'antigravity',
|
|
1971
|
+
'cursor', 'windsurf', 'augment', 'trae', 'qwen', 'hermes', 'kimi',
|
|
1972
|
+
'codebuddy', 'cline',
|
|
1973
|
+
];
|
|
1974
|
+
/**
|
|
1975
|
+
* #1521: Every non-Claude runtime resolves its own runtime identity from a
|
|
1976
|
+
* runtime-neutral config, and defaults workflow.use_worktrees to false —
|
|
1977
|
+
* GSD's worktree isolation uses Claude Code's isolation="worktree" spawn
|
|
1978
|
+
* parameter, which no other runtime honors. Stamped into the emitted
|
|
1979
|
+
* workflow runtime-resolution blocks. (Generalizes the Codex-only #1515 fix.)
|
|
1980
|
+
*
|
|
1981
|
+
* @private — exported as `_stampNonClaudeRuntimeDefaults` for tests.
|
|
1982
|
+
*/
|
|
1983
|
+
function _stampNonClaudeRuntimeDefaults(content, runtime) {
|
|
1984
|
+
content = content.replace(/config-get workflow\.use_worktrees --raw 2>\/dev\/null \|\| echo "true"/g, 'config-get workflow.use_worktrees --default false --raw 2>/dev/null || echo "false"');
|
|
1985
|
+
content = content.replace(/config-get runtime --default claude --raw 2>\/dev\/null \|\| echo "claude"/g, `config-get runtime --default ${runtime} --raw 2>/dev/null || echo "${runtime}"`);
|
|
1986
|
+
return content;
|
|
1987
|
+
}
|
|
1988
|
+
/**
|
|
1989
|
+
* Apply the per-runtime rewrite table to a single content string.
|
|
1990
|
+
* Relocated from bin/install.js `_applyRuntimeRewrites`.
|
|
1991
|
+
*
|
|
1992
|
+
* The 5th `attribution` param replaces the internal getCommitAttribution() call
|
|
1993
|
+
* so the function is pure (no config I/O). Pass the resolved attribution value
|
|
1994
|
+
* from the installer; pass `undefined` to leave Co-Authored-By lines untouched.
|
|
1995
|
+
*
|
|
1996
|
+
* @private — exported as `_applyRuntimeRewrites` for tests.
|
|
1997
|
+
*/
|
|
1998
|
+
function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, attribution = undefined) {
|
|
1999
|
+
const dirName = getDirName(runtime);
|
|
2000
|
+
const normalizedPathPrefix = pathPrefix.replace(/\/$/, '');
|
|
2001
|
+
// #1521: stamp runtime identity + use_worktrees=false for every non-Claude runtime
|
|
2002
|
+
// before brand-specific path rewrites, so the replace operates on the pristine
|
|
2003
|
+
// source line and is idempotent regardless of subsequent path substitutions.
|
|
2004
|
+
if (runtime !== 'claude') {
|
|
2005
|
+
content = _stampNonClaudeRuntimeDefaults(content, runtime);
|
|
2006
|
+
}
|
|
2007
|
+
switch (runtime) {
|
|
2008
|
+
case 'codex':
|
|
2009
|
+
content = content.replace(/~\/\.claude\//g, pathPrefix);
|
|
2010
|
+
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
|
|
2011
|
+
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
|
|
2012
|
+
content = content.replace(/~\/\.codex\//g, pathPrefix);
|
|
2013
|
+
// #1515 stamp moved to _stampNonClaudeRuntimeDefaults (#1521 generalisation).
|
|
2014
|
+
content = processAttribution(content, attribution);
|
|
2015
|
+
break;
|
|
2016
|
+
case 'cline':
|
|
2017
|
+
content = content.replace(/~\/\.claude\//g, pathPrefix);
|
|
2018
|
+
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
|
|
2019
|
+
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
|
|
2020
|
+
content = content.replace(/~\/\.cline\//g, pathPrefix);
|
|
2021
|
+
content = content.replace(/\$HOME\/\.cline\//g, pathPrefix);
|
|
2022
|
+
content = content.replace(/~\/\.claude\b/g, normalizedPathPrefix);
|
|
2023
|
+
content = content.replace(/\$HOME\/\.claude\b/g, normalizedPathPrefix);
|
|
2024
|
+
content = content.replace(/~\/\.cline\b/g, normalizedPathPrefix);
|
|
2025
|
+
content = content.replace(/\$HOME\/\.cline\b/g, normalizedPathPrefix);
|
|
2026
|
+
content = processAttribution(content, attribution);
|
|
2027
|
+
break;
|
|
2028
|
+
case 'cursor':
|
|
2029
|
+
content = content.replace(/~\/\.claude\//g, pathPrefix);
|
|
2030
|
+
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
|
|
2031
|
+
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
|
|
2032
|
+
content = content.replace(/~\/\.claude(?![\w-])/g, normalizedPathPrefix);
|
|
2033
|
+
content = content.replace(/\$HOME\/\.claude(?![\w-])/g, normalizedPathPrefix);
|
|
2034
|
+
content = content.replace(/\.\/\.claude(?![\w-])/g, `./${dirName}`);
|
|
2035
|
+
content = content.replace(/~\/\.cursor\//g, pathPrefix);
|
|
2036
|
+
content = processAttribution(content, attribution);
|
|
2037
|
+
break;
|
|
2038
|
+
case 'windsurf': {
|
|
2039
|
+
content = content.replace(/~\/\.claude\//g, pathPrefix);
|
|
2040
|
+
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
|
|
2041
|
+
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
|
|
2042
|
+
content = content.replace(/~\/\.claude(?![\w-])/g, normalizedPathPrefix);
|
|
2043
|
+
content = content.replace(/\$HOME\/\.claude(?![\w-])/g, normalizedPathPrefix);
|
|
2044
|
+
content = content.replace(/~\/\.codeium\/windsurf\//g, pathPrefix);
|
|
2045
|
+
if (isGlobal) {
|
|
2046
|
+
content = content.replace(/\.devin\/skills\//g, `${pathPrefix}skills/`);
|
|
2047
|
+
content = content.replace(/\.\/\.devin\//g, pathPrefix);
|
|
2048
|
+
content = content.replace(/~\/\.devin(?![\w-])/g, normalizedPathPrefix);
|
|
2049
|
+
content = content.replace(/\$HOME\/\.devin(?![\w-])/g, normalizedPathPrefix);
|
|
2050
|
+
}
|
|
2051
|
+
content = processAttribution(content, attribution);
|
|
2052
|
+
break;
|
|
2053
|
+
}
|
|
2054
|
+
case 'augment':
|
|
2055
|
+
content = content.replace(/~\/\.claude\//g, pathPrefix);
|
|
2056
|
+
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
|
|
2057
|
+
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
|
|
2058
|
+
content = content.replace(/~\/\.claude(?![\w-])/g, normalizedPathPrefix);
|
|
2059
|
+
content = content.replace(/\$HOME\/\.claude(?![\w-])/g, normalizedPathPrefix);
|
|
2060
|
+
content = content.replace(/\.\/\.claude(?![\w-])/g, `./${dirName}`);
|
|
2061
|
+
content = content.replace(/~\/\.augment\//g, pathPrefix);
|
|
2062
|
+
content = content.replace(/\$HOME\/\.augment\//g, pathPrefix);
|
|
2063
|
+
content = content.replace(/~\/\.augment(?![\w-])/g, normalizedPathPrefix);
|
|
2064
|
+
content = content.replace(/\$HOME\/\.augment(?![\w-])/g, normalizedPathPrefix);
|
|
2065
|
+
content = processAttribution(content, attribution);
|
|
2066
|
+
break;
|
|
2067
|
+
case 'trae':
|
|
2068
|
+
content = content.replace(/~\/\.claude\//g, pathPrefix);
|
|
2069
|
+
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
|
|
2070
|
+
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
|
|
2071
|
+
content = content.replace(/~\/\.claude\b/g, normalizedPathPrefix);
|
|
2072
|
+
content = content.replace(/\$HOME\/\.claude\b/g, normalizedPathPrefix);
|
|
2073
|
+
content = content.replace(/\.\/\.claude\b/g, `./${dirName}`);
|
|
2074
|
+
content = content.replace(/~\/\.trae\//g, pathPrefix);
|
|
2075
|
+
content = processAttribution(content, attribution);
|
|
2076
|
+
break;
|
|
2077
|
+
case 'codebuddy':
|
|
2078
|
+
content = content.replace(/~\/\.claude\//g, pathPrefix);
|
|
2079
|
+
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
|
|
2080
|
+
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
|
|
2081
|
+
content = content.replace(/~\/\.claude\b/g, normalizedPathPrefix);
|
|
2082
|
+
content = content.replace(/\$HOME\/\.claude\b/g, normalizedPathPrefix);
|
|
2083
|
+
content = content.replace(/\.\/\.claude\b/g, `./${dirName}`);
|
|
2084
|
+
content = content.replace(/~\/\.codebuddy\//g, pathPrefix);
|
|
2085
|
+
content = content.replace(/\$HOME\/\.codebuddy\//g, pathPrefix);
|
|
2086
|
+
content = content.replace(/~\/\.codebuddy\b/g, normalizedPathPrefix);
|
|
2087
|
+
content = content.replace(/\$HOME\/\.codebuddy\b/g, normalizedPathPrefix);
|
|
2088
|
+
content = processAttribution(content, attribution);
|
|
2089
|
+
break;
|
|
2090
|
+
case 'copilot':
|
|
2091
|
+
content = processAttribution(content, attribution);
|
|
2092
|
+
break;
|
|
2093
|
+
case 'antigravity':
|
|
2094
|
+
content = processAttribution(content, attribution);
|
|
2095
|
+
break;
|
|
2096
|
+
case 'claude':
|
|
2097
|
+
content = content.replace(/~\/\.claude\//g, pathPrefix);
|
|
2098
|
+
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
|
|
2099
|
+
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
|
|
2100
|
+
content = processAttribution(content, attribution);
|
|
2101
|
+
break;
|
|
2102
|
+
case 'qwen':
|
|
2103
|
+
content = content.replace(/CLAUDE\.md/g, 'QWEN.md');
|
|
2104
|
+
content = content.replace(/\bClaude Code\b/g, 'Qwen Code');
|
|
2105
|
+
content = content.replace(/~\/\.claude\//g, pathPrefix);
|
|
2106
|
+
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
|
|
2107
|
+
content = content.replace(/~\/\.qwen\//g, pathPrefix);
|
|
2108
|
+
content = content.replace(/\$HOME\/\.qwen\//g, pathPrefix);
|
|
2109
|
+
content = content.replace(/~\/\.claude(?![\w-])/g, normalizedPathPrefix);
|
|
2110
|
+
content = content.replace(/\$HOME\/\.claude(?![\w-])/g, normalizedPathPrefix);
|
|
2111
|
+
content = content.replace(/~\/\.qwen(?![\w-])/g, normalizedPathPrefix);
|
|
2112
|
+
content = content.replace(/\$HOME\/\.qwen(?![\w-])/g, normalizedPathPrefix);
|
|
2113
|
+
content = content.replace(/\.claude\//g, '.qwen/');
|
|
2114
|
+
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
|
|
2115
|
+
content = content.replace(/\.\/\.qwen\//g, `./${dirName}/`);
|
|
2116
|
+
content = processAttribution(content, attribution);
|
|
2117
|
+
break;
|
|
2118
|
+
case 'hermes':
|
|
2119
|
+
content = content.replace(/CLAUDE\.md/g, 'HERMES.md');
|
|
2120
|
+
content = content.replace(/\bClaude Code\b/g, 'Hermes Agent');
|
|
2121
|
+
content = content.replace(/~\/\.claude\//g, pathPrefix);
|
|
2122
|
+
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
|
|
2123
|
+
content = content.replace(/~\/\.hermes\//g, pathPrefix);
|
|
2124
|
+
content = content.replace(/\$HOME\/\.hermes\//g, pathPrefix);
|
|
2125
|
+
content = content.replace(/~\/\.claude(?![\w-])/g, normalizedPathPrefix);
|
|
2126
|
+
content = content.replace(/\$HOME\/\.claude(?![\w-])/g, normalizedPathPrefix);
|
|
2127
|
+
content = content.replace(/~\/\.hermes(?![\w-])/g, normalizedPathPrefix);
|
|
2128
|
+
content = content.replace(/\$HOME\/\.hermes(?![\w-])/g, normalizedPathPrefix);
|
|
2129
|
+
content = content.replace(/\.claude\//g, '.hermes/');
|
|
2130
|
+
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
|
|
2131
|
+
content = content.replace(/\.\/\.hermes\//g, `./${dirName}/`);
|
|
2132
|
+
content = processAttribution(content, attribution);
|
|
2133
|
+
break;
|
|
2134
|
+
case 'kimi':
|
|
2135
|
+
content = content.replace(/~\/\.claude\//g, pathPrefix);
|
|
2136
|
+
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
|
|
2137
|
+
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
|
|
2138
|
+
content = content.replace(/~\/\.claude\b/g, normalizedPathPrefix);
|
|
2139
|
+
content = content.replace(/\$HOME\/\.claude\b/g, normalizedPathPrefix);
|
|
2140
|
+
content = content.replace(/\.\/\.claude\b/g, `./${dirName}`);
|
|
2141
|
+
content = processAttribution(content, attribution);
|
|
2142
|
+
break;
|
|
2143
|
+
default:
|
|
2144
|
+
// Unknown runtime — no rewrites (OpenCode/Kilo handled by their own install path).
|
|
2145
|
+
break;
|
|
2146
|
+
}
|
|
2147
|
+
return content;
|
|
2148
|
+
}
|
|
2149
|
+
/**
|
|
2150
|
+
* LOW-LEVEL: In-place fs walk: rewrite all .md files under stagedDir.
|
|
2151
|
+
*
|
|
2152
|
+
* pathPrefix and attribution are passed in (already resolved by the caller).
|
|
2153
|
+
* Single owner of the walk loop — both the high-level rewriteStagedSkillBodies
|
|
2154
|
+
* and the install.js compat wrapper delegate here.
|
|
2155
|
+
*
|
|
2156
|
+
* @param stagedDir directory of staged skill/agent files
|
|
2157
|
+
* @param runtime canonical runtime ID
|
|
2158
|
+
* @param pathPrefix trailing-slash path prefix (e.g. '$HOME/.cursor/')
|
|
2159
|
+
* @param isGlobal true for global scope installs
|
|
2160
|
+
* @param attribution Co-Authored-By value (string | null | undefined)
|
|
2161
|
+
*/
|
|
2162
|
+
function applyRuntimeContentRewritesInPlace(stagedDir, runtime, pathPrefix, isGlobal = false, attribution = undefined) {
|
|
2163
|
+
if (!node_fs_1.default.existsSync(stagedDir))
|
|
2164
|
+
return;
|
|
2165
|
+
const walkAndRewrite = (dir) => {
|
|
2166
|
+
for (const entry of node_fs_1.default.readdirSync(dir, { withFileTypes: true })) {
|
|
2167
|
+
const fullPath = node_path_1.default.join(dir, entry.name);
|
|
2168
|
+
if (entry.isDirectory()) {
|
|
2169
|
+
walkAndRewrite(fullPath);
|
|
2170
|
+
}
|
|
2171
|
+
else if (entry.name.endsWith('.md')) {
|
|
2172
|
+
let content = node_fs_1.default.readFileSync(fullPath, 'utf8');
|
|
2173
|
+
content = _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal, attribution);
|
|
2174
|
+
node_fs_1.default.writeFileSync(fullPath, content);
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
};
|
|
2178
|
+
walkAndRewrite(stagedDir);
|
|
2179
|
+
}
|
|
2180
|
+
/**
|
|
2181
|
+
* LOW-LEVEL: Copy-to-temp then rewrite all .md files.
|
|
2182
|
+
*
|
|
2183
|
+
* pathPrefix and attribution are passed in (already resolved by the caller).
|
|
2184
|
+
* Single owner of the copy+rewrite loop — both the high-level
|
|
2185
|
+
* rewriteStagedCommandBodies and the install.js compat wrapper delegate here.
|
|
2186
|
+
*
|
|
2187
|
+
* IMPORTANT: always copies to a fresh mkdtemp dir — never mutates the source dir
|
|
2188
|
+
* (stageSkillsForProfile returns the source dir on full profile; mutation would
|
|
2189
|
+
* corrupt the package source).
|
|
2190
|
+
*
|
|
2191
|
+
* @param stagedDir directory of staged flat .md command files
|
|
2192
|
+
* @param runtime canonical runtime ID
|
|
2193
|
+
* @param pathPrefix trailing-slash path prefix
|
|
2194
|
+
* @param isGlobal true for global scope installs
|
|
2195
|
+
* @param attribution Co-Authored-By value (string | null | undefined)
|
|
2196
|
+
* @returns {string} path to the temp dir (caller is responsible for cleanup)
|
|
2197
|
+
*/
|
|
2198
|
+
function applyRuntimeContentRewritesForCommandsInPlace(stagedDir, runtime, pathPrefix, isGlobal = false, attribution = undefined) {
|
|
2199
|
+
if (!node_fs_1.default.existsSync(stagedDir))
|
|
2200
|
+
return stagedDir;
|
|
2201
|
+
const tempDir = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), 'gsd-cmd-rewrites-'));
|
|
2202
|
+
try {
|
|
2203
|
+
for (const entry of node_fs_1.default.readdirSync(stagedDir, { withFileTypes: true })) {
|
|
2204
|
+
if (!entry.isFile() || !entry.name.endsWith('.md'))
|
|
2205
|
+
continue;
|
|
2206
|
+
let content = node_fs_1.default.readFileSync(node_path_1.default.join(stagedDir, entry.name), 'utf8');
|
|
2207
|
+
content = _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal, attribution);
|
|
2208
|
+
if (runtime === 'augment') {
|
|
2209
|
+
content = convertClaudeToAugmentMarkdown(content);
|
|
2210
|
+
}
|
|
2211
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(tempDir, entry.name), content);
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
catch (err) {
|
|
2215
|
+
try {
|
|
2216
|
+
node_fs_1.default.rmSync(tempDir, { recursive: true, force: true });
|
|
2217
|
+
}
|
|
2218
|
+
catch { /* best-effort */ }
|
|
2219
|
+
throw err;
|
|
2220
|
+
}
|
|
2221
|
+
return tempDir;
|
|
2222
|
+
}
|
|
2223
|
+
/**
|
|
2224
|
+
* HIGH-LEVEL: In-place fs walk: rewrite all .md files under stagedDir for the given runtime.
|
|
2225
|
+
*
|
|
2226
|
+
* Deep public seam (ADR-1508 Phase 2). Derives resolvedTarget/homeDir/isGlobal/pathPrefix/
|
|
2227
|
+
* attribution from opts, then delegates to applyRuntimeContentRewritesInPlace (single walk owner).
|
|
2228
|
+
*
|
|
2229
|
+
* @param stagedDir directory of staged skill/agent files
|
|
2230
|
+
* @param opts.runtime canonical runtime ID
|
|
2231
|
+
* @param opts.configDir runtime config directory (absolute path)
|
|
2232
|
+
* @param opts.scope 'global' | 'local'
|
|
2233
|
+
* @param opts.homedir optional homedir resolver (injectable for tests; defaults to os.homedir)
|
|
2234
|
+
* @param opts.platform optional platform string (injectable for tests; defaults to process.platform)
|
|
2235
|
+
* @param opts.resolveAttribution optional fn(runtime)→string|null|undefined; called once per invocation
|
|
2236
|
+
*/
|
|
2237
|
+
function rewriteStagedSkillBodies(stagedDir, opts) {
|
|
2238
|
+
const { runtime, configDir, scope = 'global', homedir = () => node_os_1.default.homedir(), platform = process.platform, resolveAttribution, } = opts;
|
|
2239
|
+
if (!node_fs_1.default.existsSync(stagedDir))
|
|
2240
|
+
return;
|
|
2241
|
+
const resolvedTarget = node_path_1.default.resolve(configDir).replace(/\\/g, '/');
|
|
2242
|
+
const homeDir = homedir().replace(/\\/g, '/');
|
|
2243
|
+
const isGlobal = scope === 'global';
|
|
2244
|
+
const isOpencode = runtime === 'opencode';
|
|
2245
|
+
const isWindowsHost = platform === 'win32';
|
|
2246
|
+
const pathPrefix = computePathPrefix({ isGlobal, isOpencode, isWindowsHost, resolvedTarget, homeDir });
|
|
2247
|
+
const attribution = resolveAttribution ? resolveAttribution(runtime) : undefined;
|
|
2248
|
+
applyRuntimeContentRewritesInPlace(stagedDir, runtime, pathPrefix, isGlobal, attribution);
|
|
2249
|
+
}
|
|
2250
|
+
/**
|
|
2251
|
+
* HIGH-LEVEL: Copy-to-temp then rewrite all .md files for the given runtime.
|
|
2252
|
+
*
|
|
2253
|
+
* Deep public seam (ADR-1508 Phase 2). Derives resolvedTarget/homeDir/isGlobal/pathPrefix/
|
|
2254
|
+
* attribution from opts, then delegates to applyRuntimeContentRewritesForCommandsInPlace
|
|
2255
|
+
* (single copy+rewrite owner).
|
|
2256
|
+
*
|
|
2257
|
+
* @internal — symmetric companion to rewriteStagedSkillBodies; retained as the deep-seam
|
|
2258
|
+
* API for command bodies. No production caller today (install rewrites commands via
|
|
2259
|
+
* copyWithPathReplacement → applyRuntimeContentRewritesForCommandsInPlace). Kept for
|
|
2260
|
+
* API symmetry + test coverage.
|
|
2261
|
+
*
|
|
2262
|
+
* @returns {string} path to the temp dir (caller is responsible for cleanup)
|
|
2263
|
+
*/
|
|
2264
|
+
function rewriteStagedCommandBodies(stagedDir, opts) {
|
|
2265
|
+
const { runtime, configDir, scope = 'global', homedir = () => node_os_1.default.homedir(), platform = process.platform, resolveAttribution, } = opts;
|
|
2266
|
+
if (!node_fs_1.default.existsSync(stagedDir))
|
|
2267
|
+
return stagedDir;
|
|
2268
|
+
const resolvedTarget = node_path_1.default.resolve(configDir).replace(/\\/g, '/');
|
|
2269
|
+
const homeDir = homedir().replace(/\\/g, '/');
|
|
2270
|
+
const isGlobal = scope === 'global';
|
|
2271
|
+
const isOpencode = runtime === 'opencode';
|
|
2272
|
+
const isWindowsHost = platform === 'win32';
|
|
2273
|
+
const pathPrefix = computePathPrefix({ isGlobal, isOpencode, isWindowsHost, resolvedTarget, homeDir });
|
|
2274
|
+
const attribution = resolveAttribution ? resolveAttribution(runtime) : undefined;
|
|
2275
|
+
return applyRuntimeContentRewritesForCommandsInPlace(stagedDir, runtime, pathPrefix, isGlobal, attribution);
|
|
2276
|
+
}
|
|
2277
|
+
// ── End rewrite engine ────────────────────────────────────────────────────────
|
|
2278
|
+
/**
|
|
2279
|
+
* Apply Co-Authored-By attribution policy to file content.
|
|
2280
|
+
* - null -> remove the Co-Authored-By line and its preceding blank line
|
|
2281
|
+
* - undefined -> leave content unchanged
|
|
2282
|
+
* - string -> replace the value ($ escaped to block backreference injection)
|
|
2283
|
+
*
|
|
2284
|
+
* Pure content transform, relocated from bin/install.js per ADR-1508
|
|
2285
|
+
* (epic #1507, #1510 Phase 1). NOTE: getCommitAttribution stays in the
|
|
2286
|
+
* installer — it is impure install-time config I/O (reads runtime
|
|
2287
|
+
* settings.json, uses the install-time config-dir + cache), not a content
|
|
2288
|
+
* transform, so it does not belong behind this content-conversion seam.
|
|
2289
|
+
*/
|
|
2290
|
+
function processAttribution(content, attribution) {
|
|
2291
|
+
if (attribution === null) {
|
|
2292
|
+
// Remove Co-Authored-By lines and the preceding blank line
|
|
2293
|
+
return content.replace(/(\r?\n){2}Co-Authored-By:.*$/gim, '');
|
|
2294
|
+
}
|
|
2295
|
+
if (attribution === undefined) {
|
|
2296
|
+
return content;
|
|
2297
|
+
}
|
|
2298
|
+
// Replace with custom attribution (escape $ to prevent backreference injection)
|
|
2299
|
+
const safeAttribution = attribution.replace(/\$/g, '$$$$');
|
|
2300
|
+
return content.replace(/Co-Authored-By:.*$/gim, `Co-Authored-By: ${safeAttribution}`);
|
|
2301
|
+
}
|
|
1898
2302
|
module.exports = {
|
|
2303
|
+
processAttribution,
|
|
1899
2304
|
yamlIdentifier,
|
|
1900
2305
|
yamlQuote,
|
|
1901
2306
|
toSingleLine,
|
|
@@ -1932,6 +2337,9 @@ module.exports = {
|
|
|
1932
2337
|
convertClaudeCommandToKiloSkill,
|
|
1933
2338
|
readGsdCommandNames,
|
|
1934
2339
|
transformContentToHyphen,
|
|
2340
|
+
// #1383: version resolver (exported for regression test of the Codex
|
|
2341
|
+
// missing-package.json crash + the VERSION-file source of truth).
|
|
2342
|
+
resolveVersionFrom,
|
|
1935
2343
|
// #1182: agent converters + tool-name table dependency closure
|
|
1936
2344
|
claudeToCopilotTools,
|
|
1937
2345
|
convertCopilotToolName,
|
|
@@ -1946,4 +2354,16 @@ module.exports = {
|
|
|
1946
2354
|
convertClaudeAgentToCodebuddyAgent,
|
|
1947
2355
|
convertClaudeAgentToClineAgent,
|
|
1948
2356
|
convertClaudeAgentToCodexAgent,
|
|
2357
|
+
// #1511 ADR-1508 Phase 2: rewrite engine deep seam
|
|
2358
|
+
// Low-level walkers (pathPrefix + attribution pre-resolved by caller):
|
|
2359
|
+
applyRuntimeContentRewritesInPlace,
|
|
2360
|
+
applyRuntimeContentRewritesForCommandsInPlace,
|
|
2361
|
+
// High-level wrappers (derive pathPrefix + attribution from opts):
|
|
2362
|
+
rewriteStagedSkillBodies,
|
|
2363
|
+
rewriteStagedCommandBodies,
|
|
2364
|
+
_computePathPrefix: computePathPrefix,
|
|
2365
|
+
_applyRuntimeRewrites,
|
|
2366
|
+
_stampNonClaudeRuntimeDefaults,
|
|
2367
|
+
// #1521: canonical non-Claude runtime list for test files and tooling
|
|
2368
|
+
NON_CLAUDE_RUNTIMES,
|
|
1949
2369
|
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* Runtime Artifact Install Plan Module.
|
|
4
|
+
*
|
|
5
|
+
* Turns a pre-resolved runtime artifact layout into staged copy inputs. The
|
|
6
|
+
* installer adapter still owns pruning, copying, migrations, output, and final
|
|
7
|
+
* cleanup execution.
|
|
8
|
+
*/
|
|
9
|
+
// In .cts (CommonJS output) files, `require` is available as a global.
|
|
10
|
+
const _require = require;
|
|
11
|
+
const path = _require('node:path');
|
|
12
|
+
function errorMessage(err) {
|
|
13
|
+
if (err instanceof Error)
|
|
14
|
+
return err.message;
|
|
15
|
+
return String(err);
|
|
16
|
+
}
|
|
17
|
+
function addCleanupDir(cleanupDirs, stagedDir, rewrittenDir) {
|
|
18
|
+
const sourceDir = rewrittenDir ?? stagedDir;
|
|
19
|
+
if (sourceDir !== stagedDir)
|
|
20
|
+
cleanupDirs.push(sourceDir);
|
|
21
|
+
return sourceDir;
|
|
22
|
+
}
|
|
23
|
+
function createRuntimeArtifactInstallPlan(args) {
|
|
24
|
+
const { layout, resolvedProfile, homedir, platform, resolveAttribution, deps = {}, } = args;
|
|
25
|
+
const conversionExports = _require('./runtime-artifact-conversion.cjs');
|
|
26
|
+
const rewriteStagedSkillBodies = deps.rewriteStagedSkillBodies ?? conversionExports.rewriteStagedSkillBodies;
|
|
27
|
+
const rewriteStagedCommandBodies = deps.rewriteStagedCommandBodies ?? conversionExports.rewriteStagedCommandBodies;
|
|
28
|
+
const cleanupDirs = [];
|
|
29
|
+
const items = [];
|
|
30
|
+
const scope = layout.scope ?? 'global';
|
|
31
|
+
const rewriteOpts = {
|
|
32
|
+
runtime: layout.runtime,
|
|
33
|
+
configDir: layout.configDir,
|
|
34
|
+
scope,
|
|
35
|
+
homedir,
|
|
36
|
+
platform,
|
|
37
|
+
resolveAttribution,
|
|
38
|
+
};
|
|
39
|
+
for (const kind of layout.kinds) {
|
|
40
|
+
let stagedDir;
|
|
41
|
+
try {
|
|
42
|
+
stagedDir = kind.stage(resolvedProfile);
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
return { ok: false, kind: 'stage_failed', message: errorMessage(err), cleanupDirs, failedKind: kind.kind };
|
|
46
|
+
}
|
|
47
|
+
let sourceDir = stagedDir;
|
|
48
|
+
try {
|
|
49
|
+
if (kind.kind === 'commands') {
|
|
50
|
+
const rewrittenDir = rewriteStagedCommandBodies(stagedDir, rewriteOpts);
|
|
51
|
+
sourceDir = addCleanupDir(cleanupDirs, stagedDir, rewrittenDir);
|
|
52
|
+
}
|
|
53
|
+
else if (kind.kind === 'skills' || kind.kind === 'kimi-agents') {
|
|
54
|
+
const rewrittenDir = rewriteStagedSkillBodies(stagedDir, rewriteOpts);
|
|
55
|
+
sourceDir = addCleanupDir(cleanupDirs, stagedDir, rewrittenDir);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
return { ok: false, kind: 'rewrite_failed', message: errorMessage(err), cleanupDirs, failedKind: kind.kind };
|
|
60
|
+
}
|
|
61
|
+
items.push({
|
|
62
|
+
kind: kind.kind,
|
|
63
|
+
sourceDir,
|
|
64
|
+
destDir: path.join(layout.configDir, kind.destSubpath),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return { ok: true, plan: { items, cleanupDirs } };
|
|
68
|
+
}
|
|
69
|
+
function createRuntimeArtifactUninstallPlan(layout) {
|
|
70
|
+
return {
|
|
71
|
+
items: layout.kinds.map((kind) => ({
|
|
72
|
+
kind: kind.kind,
|
|
73
|
+
destDir: path.join(layout.configDir, kind.destSubpath),
|
|
74
|
+
})),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
module.exports = { createRuntimeArtifactInstallPlan, createRuntimeArtifactUninstallPlan };
|
|
@@ -25,33 +25,6 @@ const runtimeArtifactConversion = require("./runtime-artifact-conversion.cjs");
|
|
|
25
25
|
const conversionExports = runtimeArtifactConversion;
|
|
26
26
|
// In .cts (CommonJS output) files, `require` is available as a global.
|
|
27
27
|
const _require = require;
|
|
28
|
-
/**
|
|
29
|
-
* Load bin/install.js exports in a test-safe way.
|
|
30
|
-
* Sets GSD_TEST_MODE only for the duration of the require() call and only if
|
|
31
|
-
* it was not already set, restoring the original value in a finally block so
|
|
32
|
-
* the module-level environment is never permanently mutated.
|
|
33
|
-
*/
|
|
34
|
-
function loadInstallExports() {
|
|
35
|
-
const savedTestMode = process.env['GSD_TEST_MODE'];
|
|
36
|
-
if (savedTestMode === undefined)
|
|
37
|
-
process.env['GSD_TEST_MODE'] = '1';
|
|
38
|
-
try {
|
|
39
|
-
return _require('../../../bin/install.js');
|
|
40
|
-
}
|
|
41
|
-
finally {
|
|
42
|
-
if (savedTestMode === undefined)
|
|
43
|
-
delete process.env['GSD_TEST_MODE'];
|
|
44
|
-
else
|
|
45
|
-
process.env['GSD_TEST_MODE'] = savedTestMode;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
/** Cache after first successful load. */
|
|
49
|
-
let _installExports = null;
|
|
50
|
-
function getInstallExports() {
|
|
51
|
-
if (!_installExports)
|
|
52
|
-
_installExports = loadInstallExports();
|
|
53
|
-
return _installExports;
|
|
54
|
-
}
|
|
55
28
|
// ---------------------------------------------------------------------------
|
|
56
29
|
// Source root finders
|
|
57
30
|
// ---------------------------------------------------------------------------
|
|
@@ -330,4 +303,4 @@ function resolveRuntimeArtifactLayoutFromRegistry(registry, runtime, configDir,
|
|
|
330
303
|
const kinds = entries.map((entry) => dispatchKindEntry(entry, runtime, configDir, scope));
|
|
331
304
|
return { runtime, configDir, scope, kinds };
|
|
332
305
|
}
|
|
333
|
-
module.exports = { resolveRuntimeArtifactLayout, resolveRuntimeArtifactLayoutFromRegistry, findInstallSourceRoot
|
|
306
|
+
module.exports = { resolveRuntimeArtifactLayout, resolveRuntimeArtifactLayoutFromRegistry, findInstallSourceRoot };
|
|
@@ -36,6 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
37
|
exports.resolveConfigHomeFromDescriptor = resolveConfigHomeFromDescriptor;
|
|
38
38
|
exports.resolveAntigravityGlobalDir = resolveAntigravityGlobalDir;
|
|
39
|
+
exports.detectAntigravityDirAmbiguity = detectAntigravityDirAmbiguity;
|
|
39
40
|
exports.resolveKimiGlobalDir = resolveKimiGlobalDir;
|
|
40
41
|
exports.getGlobalConfigDir = getGlobalConfigDir;
|
|
41
42
|
exports.resolveSkillsBaseFromDescriptor = resolveSkillsBaseFromDescriptor;
|
|
@@ -97,7 +98,20 @@ function resolveConfigHomeFromDescriptor(configHome, opts = {}) {
|
|
|
97
98
|
}
|
|
98
99
|
const base = node_path_1.default.join(home, configHome.parent);
|
|
99
100
|
if (configHome.probe && configHome.probe.length > 0) {
|
|
100
|
-
//
|
|
101
|
+
// Pass 1 (marker-priority): when probeExists is declared, prefer the
|
|
102
|
+
// candidate GSD actually owns (its `<candidate>/<probeExists>` exists).
|
|
103
|
+
// This disambiguates an active-but-shadowing sibling dir (e.g. the
|
|
104
|
+
// Antigravity-IDE `~/.gemini/antigravity` dir) from the dir GSD was
|
|
105
|
+
// installed into, instead of blindly taking the first dir that exists.
|
|
106
|
+
if (configHome.probeExists) {
|
|
107
|
+
for (const candidate of configHome.probe) {
|
|
108
|
+
const resolved = node_path_1.default.join(base, candidate);
|
|
109
|
+
if (existsSyncFn(node_path_1.default.join(resolved, configHome.probeExists))) {
|
|
110
|
+
return resolved;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Pass 2 (legacy bare-existence): first candidate dir that exists.
|
|
101
115
|
for (const candidate of configHome.probe) {
|
|
102
116
|
const resolved = node_path_1.default.join(base, candidate);
|
|
103
117
|
if (existsSyncFn(resolved))
|
|
@@ -171,8 +185,46 @@ function resolveAntigravityGlobalDir(opts = {}) {
|
|
|
171
185
|
parent: '.gemini',
|
|
172
186
|
env: ['ANTIGRAVITY_CONFIG_DIR'],
|
|
173
187
|
probe: ['antigravity', 'antigravity-ide', 'antigravity-cli'],
|
|
188
|
+
// Prefer the candidate GSD installed into (carries gsd-core/VERSION) over
|
|
189
|
+
// a bare-existing sibling. Without this, a CLI user (antigravity-cli) who
|
|
190
|
+
// also has the IDE's ~/.gemini/antigravity dir is shadowed to the legacy
|
|
191
|
+
// dir because it is probed first. See #213/#217. The posix-slash literal
|
|
192
|
+
// matches capabilities/antigravity/capability.json; both normalize via
|
|
193
|
+
// path.join at the check site, so Windows backslash handling is covered.
|
|
194
|
+
probeExists: 'gsd-core/VERSION',
|
|
174
195
|
}, { env, home, existsSync: existsSyncFn });
|
|
175
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Detect whether the Antigravity config-dir resolution is ambiguous — i.e. more
|
|
199
|
+
* than one of ~/.gemini/{antigravity,antigravity-ide,antigravity-cli} exists, so
|
|
200
|
+
* a user upgrading from a pre-#217 install may have had GSD written into the
|
|
201
|
+
* wrong sibling dir (the legacy/IDE dir shadowing an active CLI dir).
|
|
202
|
+
*
|
|
203
|
+
* This is a pure, side-effect-free probe intended for the installer and
|
|
204
|
+
* /gsd-update to surface operator guidance (set ANTIGRAVITY_CONFIG_DIR or move
|
|
205
|
+
* gsd-core/ into the intended dir). The migration framework cannot relocate an
|
|
206
|
+
* install across sibling config dirs (it is bounded to a single configDir and
|
|
207
|
+
* has no cross-dir move primitive — see installer-migrations 004), so existing
|
|
208
|
+
* misinstalls are corrected by re-detection + operator guidance, not an
|
|
209
|
+
* automatic move.
|
|
210
|
+
*/
|
|
211
|
+
function detectAntigravityDirAmbiguity(opts = {}) {
|
|
212
|
+
const env = opts.env ?? process.env;
|
|
213
|
+
const home = opts.home ?? node_os_1.default.homedir();
|
|
214
|
+
const existsSyncFn = opts.existsSync ?? node_fs_1.default.existsSync;
|
|
215
|
+
const marker = node_path_1.default.join('gsd-core', 'VERSION');
|
|
216
|
+
const base = node_path_1.default.join(home, '.gemini');
|
|
217
|
+
const candidates = ['antigravity', 'antigravity-ide', 'antigravity-cli'].map((c) => node_path_1.default.join(base, c));
|
|
218
|
+
const presentDirs = candidates.filter((dir) => existsSyncFn(dir));
|
|
219
|
+
const gsdMarkedDirs = candidates.filter((dir) => existsSyncFn(node_path_1.default.join(dir, marker)));
|
|
220
|
+
return {
|
|
221
|
+
ambiguous: presentDirs.length > 1,
|
|
222
|
+
resolved: resolveAntigravityGlobalDir({ env, home, existsSync: existsSyncFn }),
|
|
223
|
+
presentDirs,
|
|
224
|
+
gsdMarkedDirs,
|
|
225
|
+
envOverridden: Boolean(env['ANTIGRAVITY_CONFIG_DIR']),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
176
228
|
/**
|
|
177
229
|
* Resolve Kimi's generic user root using Kimi CLI's documented first-existing
|
|
178
230
|
* generic skills directory policy:
|