@opengsd/gsd-core 1.5.0 → 1.6.0-rc.1
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/bin/install.js +108 -34
- package/gemini-extension.json +1 -1
- package/gsd-core/bin/gsd-tools.cjs +677 -2
- package/gsd-core/bin/lib/adr-parser.cjs +24 -17
- 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/config-loader.cjs +92 -84
- package/gsd-core/bin/lib/config-schema.cjs +26 -7
- package/gsd-core/bin/lib/config.cjs +1 -1
- package/gsd-core/bin/lib/decisions.cjs +149 -60
- 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-lifecycle.cjs +14 -5
- package/gsd-core/bin/lib/phase.cjs +29 -0
- 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-parser.cjs +44 -98
- package/gsd-core/bin/lib/runtime-homes.cjs +53 -1
- package/gsd-core/bin/lib/semver-compare.cjs +127 -0
- package/gsd-core/bin/lib/state-document.cjs +4 -2
- package/gsd-core/bin/lib/state.cjs +317 -161
- package/gsd-core/bin/lib/uat-predicate.cjs +7 -47
- package/gsd-core/bin/lib/uat.cjs +39 -26
- package/gsd-core/bin/lib/verify.cjs +29 -13
- package/gsd-core/bin/shared/config-defaults.manifest.json +4 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +4 -1
- package/gsd-core/references/execute-phase-between-wave-reset.md +43 -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 +3 -0
- package/gsd-core/references/scout-codebase.md +2 -2
- 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 +4 -6
- package/package.json +3 -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/run-tests.cjs +14 -0
- package/scripts/sync-manifest-versions.cjs +77 -5
|
@@ -17,6 +17,7 @@ const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs")
|
|
|
17
17
|
const io = require("./io.cjs");
|
|
18
18
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- config-loader.cjs is an export= CommonJS module
|
|
19
19
|
const configLoader = require("./config-loader.cjs");
|
|
20
|
+
const project_root_cjs_1 = require("./project-root.cjs");
|
|
20
21
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- model-resolver.cjs is an export= CommonJS module
|
|
21
22
|
const modelResolver = require("./model-resolver.cjs");
|
|
22
23
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- phase-locator.cjs is an export= CommonJS module
|
|
@@ -48,8 +49,9 @@ const { checkAgentsInstalled } = agentInstallCheck;
|
|
|
48
49
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- git-base-branch.cjs is an export= CommonJS module
|
|
49
50
|
const gitBaseBranch = require("./git-base-branch.cjs");
|
|
50
51
|
const { gitWorktreeInfoInternal } = gitBaseBranch;
|
|
52
|
+
const resolution_cjs_1 = require("./resolution.cjs");
|
|
51
53
|
const { output, error } = io;
|
|
52
|
-
const { loadConfig } = configLoader;
|
|
54
|
+
const { loadConfig, loadConfigResolved } = configLoader;
|
|
53
55
|
const { resolveModelInternal, resolveGranularityInternal, assertValidGranularityOverride } = modelResolver;
|
|
54
56
|
const { findPhaseInternal } = phaseLocator;
|
|
55
57
|
const { getRoadmapPhaseInternal, getMilestoneInfo, getMilestonePhaseFilter, stripShippedMilestones, extractCurrentMilestone, } = roadmapParser;
|
|
@@ -1542,7 +1544,12 @@ function cmdInitRemoveWorkspace(cwd, name, raw) {
|
|
|
1542
1544
|
};
|
|
1543
1545
|
output(result, raw);
|
|
1544
1546
|
}
|
|
1545
|
-
function buildAgentSkillsBlock(config, agentType, projectRoot) {
|
|
1547
|
+
function buildAgentSkillsBlock(config, agentType, projectRoot, diagnostics) {
|
|
1548
|
+
const warn = (message) => {
|
|
1549
|
+
process.stderr.write(message);
|
|
1550
|
+
if (diagnostics)
|
|
1551
|
+
diagnostics.warnings.push(message.replace(/\n+$/, ''));
|
|
1552
|
+
};
|
|
1546
1553
|
const runtime = (config && config['runtime']) || 'claude';
|
|
1547
1554
|
const globalSkillsBase = (0, runtime_homes_cjs_1.getGlobalSkillsBase)(runtime);
|
|
1548
1555
|
if (!config || !config['agent_skills'] || !agentType)
|
|
@@ -1552,7 +1559,11 @@ function buildAgentSkillsBlock(config, agentType, projectRoot) {
|
|
|
1552
1559
|
return '';
|
|
1553
1560
|
if (typeof skillPaths === 'string')
|
|
1554
1561
|
skillPaths = [skillPaths];
|
|
1555
|
-
if (!Array.isArray(skillPaths)
|
|
1562
|
+
if (!Array.isArray(skillPaths)) {
|
|
1563
|
+
warn(`[agent-skills] WARNING: Agent "${agentType}" has a malformed agent_skills value (expected string or array, got ${typeof skillPaths}) — ignoring\n`);
|
|
1564
|
+
return '';
|
|
1565
|
+
}
|
|
1566
|
+
if (skillPaths.length === 0)
|
|
1556
1567
|
return '';
|
|
1557
1568
|
// Hoist trusted roots computation before the loop: loadTrustedGlobalRoots does
|
|
1558
1569
|
// realpathSync I/O and should run at most once per call, not once per failing skill.
|
|
@@ -1563,18 +1574,20 @@ function buildAgentSkillsBlock(config, agentType, projectRoot) {
|
|
|
1563
1574
|
// Skill-tool directive ({ kind: 'directive', name }) for plugin-provided namespaced skills.
|
|
1564
1575
|
const validEntries = [];
|
|
1565
1576
|
for (const skillPath of skillPaths) {
|
|
1566
|
-
if (typeof skillPath !== 'string')
|
|
1577
|
+
if (typeof skillPath !== 'string') {
|
|
1578
|
+
warn(`[agent-skills] WARNING: Ignoring non-string skill entry (${typeof skillPath}) — skipping\n`);
|
|
1567
1579
|
continue;
|
|
1580
|
+
}
|
|
1568
1581
|
if (skillPath.startsWith('global:')) {
|
|
1569
1582
|
const skillName = skillPath.slice(7);
|
|
1570
1583
|
if (!skillName) {
|
|
1571
|
-
|
|
1584
|
+
warn(`[agent-skills] WARNING: "global:" prefix with empty skill name — skipping\n`);
|
|
1572
1585
|
continue;
|
|
1573
1586
|
}
|
|
1574
1587
|
// Accept: one or more [A-Za-z0-9_-]+ segments joined by single colons.
|
|
1575
1588
|
// Rejects: empty segments (::), leading/trailing colon, dots, slashes, backslashes.
|
|
1576
1589
|
if (!/^[A-Za-z0-9_-]+(:[A-Za-z0-9_-]+)*$/.test(skillName)) {
|
|
1577
|
-
|
|
1590
|
+
warn(`[agent-skills] WARNING: Invalid global skill name "${skillName}" — skipping\n`);
|
|
1578
1591
|
continue;
|
|
1579
1592
|
}
|
|
1580
1593
|
const isNamespaced = skillName.includes(':');
|
|
@@ -1585,20 +1598,20 @@ function buildAgentSkillsBlock(config, agentType, projectRoot) {
|
|
|
1585
1598
|
validEntries.push({ kind: 'directive', name: skillName });
|
|
1586
1599
|
}
|
|
1587
1600
|
else {
|
|
1588
|
-
|
|
1601
|
+
warn(`[agent-skills] WARNING: Plugin-namespaced skill "global:${skillName}" requires a Skill-tool-capable runtime (claude) — skipping on runtime "${runtime}"\n`);
|
|
1589
1602
|
}
|
|
1590
1603
|
continue;
|
|
1591
1604
|
}
|
|
1592
1605
|
// Non-namespaced bare name: attempt filesystem resolution as before.
|
|
1593
1606
|
if (globalSkillsBase === null) {
|
|
1594
|
-
|
|
1607
|
+
warn(`[agent-skills] WARNING: Runtime "${runtime}" does not use a skills directory — "global:${skillName}" is not supported on this runtime\n`);
|
|
1595
1608
|
continue;
|
|
1596
1609
|
}
|
|
1597
1610
|
const globalSkillDir = (0, runtime_homes_cjs_1.getGlobalSkillDir)(runtime, skillName);
|
|
1598
1611
|
const globalSkillMd = node_path_1.default.join(globalSkillDir, 'SKILL.md');
|
|
1599
1612
|
const displayPath = (0, runtime_homes_cjs_1.getGlobalSkillDisplayPath)(runtime, skillName);
|
|
1600
1613
|
if (!node_fs_1.default.existsSync(globalSkillMd)) {
|
|
1601
|
-
|
|
1614
|
+
warn(`[agent-skills] WARNING: Global skill not found at "${displayPath}/SKILL.md" — skipping\n`);
|
|
1602
1615
|
continue;
|
|
1603
1616
|
}
|
|
1604
1617
|
const pathCheck = (0, security_cjs_1.validatePath)(globalSkillMd, globalSkillsBase, { allowAbsolute: true });
|
|
@@ -1608,9 +1621,11 @@ function buildAgentSkillsBlock(config, agentType, projectRoot) {
|
|
|
1608
1621
|
return Boolean(rootCheck['safe']);
|
|
1609
1622
|
});
|
|
1610
1623
|
if (!acceptedViaTrustedRoot) {
|
|
1611
|
-
|
|
1624
|
+
warn(`[agent-skills] WARNING: Global skill "${skillName}" failed path check (symlink escape?) — skipping\n`);
|
|
1612
1625
|
continue;
|
|
1613
1626
|
}
|
|
1627
|
+
// Intentionally a direct stderr write, NOT warn(): this is an acceptance
|
|
1628
|
+
// trace, not a skip, so it must not land in the diagnostics warnings[].
|
|
1614
1629
|
process.stderr.write(`[agent-skills] NOTE: Global skill "${skillName}" accepted via trusted_global_roots (resolves outside the default skills dir)\n`);
|
|
1615
1630
|
}
|
|
1616
1631
|
validEntries.push({ kind: 'include', ref: `${globalSkillDir}/SKILL.md`, display: displayPath });
|
|
@@ -1618,18 +1633,20 @@ function buildAgentSkillsBlock(config, agentType, projectRoot) {
|
|
|
1618
1633
|
}
|
|
1619
1634
|
const pathCheck = (0, security_cjs_1.validatePath)(skillPath, projectRoot);
|
|
1620
1635
|
if (!pathCheck['safe']) {
|
|
1621
|
-
|
|
1636
|
+
warn(`[agent-skills] WARNING: Skipping unsafe path "${skillPath}": ${pathCheck['error']}\n`);
|
|
1622
1637
|
continue;
|
|
1623
1638
|
}
|
|
1624
1639
|
const skillMdPath = node_path_1.default.join(projectRoot, skillPath, 'SKILL.md');
|
|
1625
1640
|
if (!node_fs_1.default.existsSync(skillMdPath)) {
|
|
1626
|
-
|
|
1641
|
+
warn(`[agent-skills] WARNING: Skill not found at "${skillPath}/SKILL.md" — skipping\n`);
|
|
1627
1642
|
continue;
|
|
1628
1643
|
}
|
|
1629
1644
|
validEntries.push({ kind: 'include', ref: `${skillPath}/SKILL.md`, display: skillPath });
|
|
1630
1645
|
}
|
|
1631
|
-
if (validEntries.length === 0)
|
|
1646
|
+
if (validEntries.length === 0) {
|
|
1647
|
+
warn(`[agent-skills] WARNING: Agent "${agentType}" has ${skillPaths.length} configured skill path(s) but none resolved to a valid skill — all were skipped (see warnings above)\n`);
|
|
1632
1648
|
return '';
|
|
1649
|
+
}
|
|
1633
1650
|
const lines = validEntries.map((entry) => {
|
|
1634
1651
|
if (entry.kind === 'directive') {
|
|
1635
1652
|
return `- Load the \`${entry.name}\` skill via the Skill tool before proceeding (plugin-provided).`;
|
|
@@ -1643,16 +1660,68 @@ function cmdAgentSkills(cwd, agentType, raw, jsonMode) {
|
|
|
1643
1660
|
output('', raw, '');
|
|
1644
1661
|
return;
|
|
1645
1662
|
}
|
|
1646
|
-
|
|
1647
|
-
const
|
|
1663
|
+
// Anchor to project root before loading config (#1415/#1366 cwd-drift fix).
|
|
1664
|
+
const projectRoot = (0, project_root_cjs_1.findProjectRoot)(cwd);
|
|
1665
|
+
const { config, source, degraded } = loadConfigResolved(projectRoot);
|
|
1666
|
+
const diagnostics = { warnings: [] };
|
|
1667
|
+
const block = buildAgentSkillsBlock(config, agentType, projectRoot, diagnostics);
|
|
1668
|
+
// Compute configured + reason for diagnostic output.
|
|
1669
|
+
const agentSkillsMap = (config && config['agent_skills'] && typeof config['agent_skills'] === 'object')
|
|
1670
|
+
? config['agent_skills']
|
|
1671
|
+
: {};
|
|
1672
|
+
const configured = Object.prototype.hasOwnProperty.call(agentSkillsMap, agentType);
|
|
1673
|
+
let reason;
|
|
1674
|
+
let skillPaths = configured ? agentSkillsMap[agentType] : [];
|
|
1675
|
+
if (!configured) {
|
|
1676
|
+
reason = 'not_configured';
|
|
1677
|
+
skillPaths = [];
|
|
1678
|
+
}
|
|
1679
|
+
else {
|
|
1680
|
+
// Normalize paths to array
|
|
1681
|
+
if (typeof skillPaths === 'string')
|
|
1682
|
+
skillPaths = [skillPaths];
|
|
1683
|
+
if (!Array.isArray(skillPaths))
|
|
1684
|
+
skillPaths = [];
|
|
1685
|
+
const pathsArr = skillPaths;
|
|
1686
|
+
// Fix 3: treat "" (empty string) as configured_empty — all-blank entries = no meaningful paths.
|
|
1687
|
+
// An array of all empty/blank strings has length > 0 but zero meaningful paths.
|
|
1688
|
+
const nonBlankPaths = pathsArr.filter(p => typeof p === 'string' && p.trim().length > 0);
|
|
1689
|
+
if (pathsArr.length === 0 || nonBlankPaths.length === 0) {
|
|
1690
|
+
// configured with empty array / "" / all-blank entries
|
|
1691
|
+
reason = 'configured_empty';
|
|
1692
|
+
// Reflect zero meaningful paths in the normalized array used for skills_count
|
|
1693
|
+
skillPaths = [];
|
|
1694
|
+
try {
|
|
1695
|
+
process.stderr.write(`[agent-skills] WARNING: Agent "${agentType}" is configured in agent_skills but has no skill paths — skills_count will be 0\n`);
|
|
1696
|
+
}
|
|
1697
|
+
catch { /* stderr might be closed */ }
|
|
1698
|
+
}
|
|
1699
|
+
else if (!block) {
|
|
1700
|
+
// configured with paths but all failed to resolve (warnings already emitted by buildAgentSkillsBlock)
|
|
1701
|
+
reason = 'configured_unresolved';
|
|
1702
|
+
}
|
|
1703
|
+
else {
|
|
1704
|
+
reason = 'resolved';
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
const normalizedPaths = Array.isArray(skillPaths) ? skillPaths : [];
|
|
1648
1708
|
if (jsonMode) {
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1709
|
+
// Build the Resolution<AgentSkillsValue> envelope and embed .value additively.
|
|
1710
|
+
// Flat fields are retained unchanged for back-compat; value formalises the
|
|
1711
|
+
// Resolution convention (ADR-1411 P3, #1416). source/degraded remain
|
|
1712
|
+
// config-provenance extras, outside the Resolution<T> envelope.
|
|
1713
|
+
const resolution = (0, resolution_cjs_1.makeResolution)({ block: block || '', skills_count: normalizedPaths.length }, { configured, reason, warnings: diagnostics.warnings });
|
|
1714
|
+
output({
|
|
1715
|
+
agent_type: agentType,
|
|
1716
|
+
block: block || '',
|
|
1717
|
+
skills_count: normalizedPaths.length,
|
|
1718
|
+
warnings: diagnostics.warnings,
|
|
1719
|
+
configured,
|
|
1720
|
+
reason,
|
|
1721
|
+
source,
|
|
1722
|
+
degraded,
|
|
1723
|
+
value: resolution.value,
|
|
1724
|
+
}, raw);
|
|
1656
1725
|
return;
|
|
1657
1726
|
}
|
|
1658
1727
|
if (block) {
|
|
@@ -37,6 +37,33 @@ const OLD_PACKAGE_SIGNAL = 'gsd-core' + '-cc';
|
|
|
37
37
|
*/
|
|
38
38
|
const GSD_MANAGED_SUBTREES = ['hooks', 'commands'];
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Substring that identifies a skill file as referencing the pre-rename GSD
|
|
42
|
+
* runtime config subdirectory. Assembled from parts to avoid self-flagging.
|
|
43
|
+
*
|
|
44
|
+
* Old installs wrote skill bodies that embed the path to the GSD runtime
|
|
45
|
+
* directory — e.g. `@$HOME/.codex/get-shit-done/workflows/plan.md`. After // gsd-allow-legacy-name
|
|
46
|
+
* the rename to `gsd-core/` (#604), those embedded paths are stale and the
|
|
47
|
+
* skill file must be removed so the runtime does not pick up the wrong copy.
|
|
48
|
+
*
|
|
49
|
+
* Issue: #1453
|
|
50
|
+
*/
|
|
51
|
+
const LEGACY_SKILL_PATH_SIGNAL = 'get-shit-done'; // gsd-allow-legacy-name
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Prefix that identifies a skill directory as GSD-managed.
|
|
55
|
+
* Only `gsd-*` subdirectories under the `skills/` subtree are scanned; user
|
|
56
|
+
* skill directories with other prefixes are never touched.
|
|
57
|
+
*/
|
|
58
|
+
const GSD_SKILL_DIR_PREFIX = 'gsd-';
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* File extensions eligible for the stale-skill-path scan.
|
|
62
|
+
* SKILL.md is the only file in a codex/cursor/kilo/etc skill directory that
|
|
63
|
+
* embeds an @-import path to the GSD runtime config tree.
|
|
64
|
+
*/
|
|
65
|
+
const SKILL_MD_EXTENSIONS = new Set(['.md']);
|
|
66
|
+
|
|
40
67
|
/**
|
|
41
68
|
* Extensions eligible for the content-reference scan.
|
|
42
69
|
*
|
|
@@ -113,6 +140,24 @@ function fileContainsOldPackageSignal(absPath, fsMod) {
|
|
|
113
140
|
}
|
|
114
141
|
}
|
|
115
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Return true if the file at `absPath` contains the legacy skill path signal
|
|
145
|
+
* (`get-shit-done` as a path component inside an @-import or similar reference). // gsd-allow-legacy-name
|
|
146
|
+
* Skips unreadable files (returns false on any error).
|
|
147
|
+
*
|
|
148
|
+
* @param {string} absPath
|
|
149
|
+
* @param {object} fsMod
|
|
150
|
+
* @returns {boolean}
|
|
151
|
+
*/
|
|
152
|
+
function fileContainsLegacySkillPathSignal(absPath, fsMod) {
|
|
153
|
+
try {
|
|
154
|
+
const content = fsMod.readFileSync(absPath, 'utf8');
|
|
155
|
+
return content.includes('/' + LEGACY_SKILL_PATH_SIGNAL + '/'); // gsd-allow-legacy-name
|
|
156
|
+
} catch {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
116
161
|
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
117
162
|
|
|
118
163
|
/**
|
|
@@ -122,6 +167,9 @@ function fileContainsOldPackageSignal(absPath, fsMod) {
|
|
|
122
167
|
* Possible reasons in returned entries:
|
|
123
168
|
* - 'content-references-old-package': a code file whose content contains
|
|
124
169
|
* the old package name signal (hooks/ and commands/ subtrees only).
|
|
170
|
+
* - 'stale-get-shit-done-path': a skill markdown file whose content contains
|
|
171
|
+
* a path reference to the pre-rename `get-shit-done/` runtime directory // gsd-allow-legacy-name
|
|
172
|
+
* (skills/ subtree, gsd-* directories only). Issue #1453.
|
|
125
173
|
* - 'legacy-shared-cache': the old package's shared update-check cache file.
|
|
126
174
|
*
|
|
127
175
|
* @param {string[]} configDirs - absolute paths to runtime config dirs to scan
|
|
@@ -164,6 +212,54 @@ function planLegacyCleanup(configDirs, opts = {}) {
|
|
|
164
212
|
}
|
|
165
213
|
}
|
|
166
214
|
}
|
|
215
|
+
|
|
216
|
+
// #1453: Scan skills/gsd-* directories for stale get-shit-done path references. // gsd-allow-legacy-name
|
|
217
|
+
//
|
|
218
|
+
// Background: older GSD installs wrote SKILL.md files that embedded a path to
|
|
219
|
+
// the GSD runtime config directory, e.g.:
|
|
220
|
+
// @$HOME/.codex/get-shit-done/workflows/docs-update.md // gsd-allow-legacy-name
|
|
221
|
+
//
|
|
222
|
+
// After the rename to gsd-core/ (#604), the correct path is:
|
|
223
|
+
// @$HOME/.codex/gsd-core/workflows/docs-update.md
|
|
224
|
+
//
|
|
225
|
+
// When Codex upgrades to gsd-core 1.5.0 it writes fresh skill files to
|
|
226
|
+
// ~/.codex/skills/ but does NOT remove stale copies that an older install
|
|
227
|
+
// may have placed under OTHER discoverable skill roots (e.g. ~/.agents/skills/,
|
|
228
|
+
// ~/.config/agents/skills/). Codex can pick up either copy and the stale one
|
|
229
|
+
// breaks the session (#1453).
|
|
230
|
+
//
|
|
231
|
+
// This scan removes GSD-managed skill files (under gsd-* subdirs) that still
|
|
232
|
+
// reference the old path. Only .md files are scanned (SKILL.md is the sole
|
|
233
|
+
// embedded-path carrier in a skill dir). The skills/ dir itself is not deleted;
|
|
234
|
+
// user-owned non-gsd-* skill dirs are never touched.
|
|
235
|
+
const skillsDir = path.join(configDir, 'skills');
|
|
236
|
+
let skillDirEntries;
|
|
237
|
+
try {
|
|
238
|
+
skillDirEntries = fsMod.readdirSync(skillsDir, { withFileTypes: true });
|
|
239
|
+
} catch {
|
|
240
|
+
skillDirEntries = null;
|
|
241
|
+
}
|
|
242
|
+
if (skillDirEntries) {
|
|
243
|
+
for (const entry of skillDirEntries) {
|
|
244
|
+
// Only process gsd-* subdirectories (GSD-managed skill dirs).
|
|
245
|
+
if (!entry.isDirectory()) continue;
|
|
246
|
+
if (!entry.name.startsWith(GSD_SKILL_DIR_PREFIX)) continue;
|
|
247
|
+
|
|
248
|
+
const skillDir = path.join(skillsDir, entry.name);
|
|
249
|
+
const files = collectFilesUnder(skillDir, fsMod);
|
|
250
|
+
|
|
251
|
+
for (const absPath of files) {
|
|
252
|
+
// Never flag user-authored dev-preferences artifacts
|
|
253
|
+
if (isDevPreferencesPath(absPath)) continue;
|
|
254
|
+
|
|
255
|
+
// Only scan .md files for the stale path signal.
|
|
256
|
+
const ext = path.extname(absPath).toLowerCase();
|
|
257
|
+
if (SKILL_MD_EXTENSIONS.has(ext) && fileContainsLegacySkillPathSignal(absPath, fsMod)) {
|
|
258
|
+
addCandidate(absPath, 'stale-get-shit-done-path'); // gsd-allow-legacy-name
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
167
263
|
}
|
|
168
264
|
|
|
169
265
|
// Legacy shared cache (fixed name from the old package)
|
|
@@ -430,9 +430,13 @@ function cmdLoopRenderHooks(cwd, point, raw, options = {}) {
|
|
|
430
430
|
config = {};
|
|
431
431
|
}
|
|
432
432
|
const state = resolveCapabilityRuntimeState(cwd, runtimeConfigDir, config);
|
|
433
|
-
//
|
|
433
|
+
// Load overlay-aware registry (ADR-1244 D2 wiring) so installed third-party
|
|
434
|
+
// capabilities are visible to loop rendering exactly like first-party ones.
|
|
434
435
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
435
|
-
const
|
|
436
|
+
const { loadRegistry } = require('./capability-loader.cjs');
|
|
437
|
+
// #1459 IC-04: thread the consent home (process.env.GSD_HOME) EXPLICITLY so a consented project cap's
|
|
438
|
+
// loop surfaces (steps/gates/contributions) render here at the SAME home that gated its activation.
|
|
439
|
+
const registry = loadRegistry({ includeInstalled: true, cwd, gsdHome: process.env['GSD_HOME'] });
|
|
436
440
|
const capabilityStatesById = new Map();
|
|
437
441
|
for (const cap of state.capabilities || []) {
|
|
438
442
|
capabilityStatesById.set(cap.id, cap);
|
|
@@ -446,6 +450,26 @@ function cmdLoopRenderHooks(cwd, point, raw, options = {}) {
|
|
|
446
450
|
coreError(msg);
|
|
447
451
|
return;
|
|
448
452
|
}
|
|
453
|
+
// ── ADR-1244 D2 fail-closed gate injection ────────────────────────────────────
|
|
454
|
+
// For every skipped overlay capability that declared a gate at this point,
|
|
455
|
+
// inject a synthetic BLOCKING gate into the resolved output so the loop HALTS
|
|
456
|
+
// rather than silently proceeding as if the gate had passed. step/contribution
|
|
457
|
+
// overlays that were skipped are left open (skip-open is correct for them).
|
|
458
|
+
const overlayMeta = registry['_overlay'];
|
|
459
|
+
if (overlayMeta && Array.isArray(overlayMeta.blockedGates)) {
|
|
460
|
+
for (const blocked of overlayMeta.blockedGates) {
|
|
461
|
+
if (blocked.point === point) {
|
|
462
|
+
const syntheticGate = {
|
|
463
|
+
capId: blocked.capId,
|
|
464
|
+
kind: 'gate',
|
|
465
|
+
blocking: true,
|
|
466
|
+
onError: 'halt',
|
|
467
|
+
check: `capability "${blocked.capId}" was skipped at load (${blocked.reason}); its gate at ${point} cannot be evaluated — failing closed`,
|
|
468
|
+
};
|
|
469
|
+
resolved.activeHooks.push(syntheticGate);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
449
473
|
// --active-cap mode: print exactly 'true' or 'false' with no envelope
|
|
450
474
|
if (activeCapId !== undefined) {
|
|
451
475
|
const isActive = resolved.activeHooks.some((h) => h.capId === activeCapId);
|