@opengsd/gsd-core 1.7.0-rc.4 → 1.7.0-rc.5
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 +20 -0
- package/agents/gsd-doc-classifier.md +105 -0
- package/agents/gsd-doc-synthesizer.md +61 -0
- package/agents/gsd-ui-checker.md +28 -0
- package/bin/install.js +624 -456
- package/gsd-core/bin/gsd-tools.cjs +40 -1
- package/gsd-core/bin/lib/api-coverage.cjs +466 -0
- package/gsd-core/bin/lib/audit.cjs +6 -3
- package/gsd-core/bin/lib/capability-loader.cjs +11 -9
- package/gsd-core/bin/lib/capability-registry.cjs +325 -64
- package/gsd-core/bin/lib/capability-validator.cjs +1 -1
- package/gsd-core/bin/lib/capability-writer.cjs +10 -1
- package/gsd-core/bin/lib/check-command-router.cjs +242 -3
- package/gsd-core/bin/lib/commands.cjs +7 -5
- package/gsd-core/bin/lib/config-loader.cjs +1 -0
- package/gsd-core/bin/lib/config.cjs +96 -0
- package/gsd-core/bin/lib/core-utils.cjs +4 -1
- package/gsd-core/bin/lib/host-integration-adapters/cline-sdk-binding.cjs +234 -0
- package/gsd-core/bin/lib/host-integration-adapters/imperative-hook-bus.cjs +145 -0
- package/gsd-core/bin/lib/host-integration.cjs +16 -0
- package/gsd-core/bin/lib/init.cjs +74 -30
- package/gsd-core/bin/lib/install-effort-resolver.cjs +213 -0
- package/gsd-core/bin/lib/install-engine.cjs +151 -11
- package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
- package/gsd-core/bin/lib/loop-resolver.cjs +68 -17
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +50 -11
- package/gsd-core/bin/lib/milestone.cjs +3 -3
- package/gsd-core/bin/lib/normalize-test-command.cjs +187 -0
- package/gsd-core/bin/lib/phase-id.cjs +132 -3
- package/gsd-core/bin/lib/phase.cjs +78 -16
- package/gsd-core/bin/lib/planning-workspace.cjs +17 -0
- package/gsd-core/bin/lib/roadmap-command-router.cjs +5 -4
- package/gsd-core/bin/lib/roadmap-parser.cjs +21 -30
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +9 -9
- package/gsd-core/bin/lib/roadmap.cjs +42 -56
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +13 -6
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +2 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +17 -10
- package/gsd-core/bin/lib/runtime-homes.cjs +8 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +33 -25
- package/gsd-core/bin/lib/spec-section.cjs +111 -0
- package/gsd-core/bin/lib/state-transition.cjs +1 -1
- package/gsd-core/bin/lib/state.cjs +24 -24
- package/gsd-core/bin/lib/surface.cjs +30 -0
- package/gsd-core/bin/lib/uat.cjs +4 -1
- package/gsd-core/bin/lib/validate.cjs +15 -6
- package/gsd-core/bin/lib/verify.cjs +33 -37
- package/gsd-core/bin/shared/config-schema.manifest.json +2 -0
- package/gsd-core/bin/shared/model-catalog.json +6 -6
- package/gsd-core/references/api-coverage.md +104 -0
- package/gsd-core/references/model-profiles.md +2 -2
- package/gsd-core/references/planning-config.md +2 -0
- package/gsd-core/references/specless-probe-fallback.md +172 -0
- package/gsd-core/templates/config.json +2 -1
- package/gsd-core/workflows/audit-fix.md +9 -1
- package/gsd-core/workflows/code-review-fix.md +7 -3
- package/gsd-core/workflows/code-review.md +4 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +4 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +8 -4
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +42 -0
- package/gsd-core/workflows/execute-phase.md +1 -25
- package/gsd-core/workflows/plan-phase.md +31 -2
- package/gsd-core/workflows/quick.md +2 -2
- package/gsd-core/workflows/review.md +59 -13
- package/gsd-core/workflows/settings-advanced.md +5 -5
- package/gsd-core/workflows/settings.md +2 -2
- package/gsd-core/workflows/verify-phase.md +3 -2
- package/gsd-core/workflows/verify-work.md +38 -0
- package/hooks/dist/gsd-cursor-pre-tool.js +76 -0
- package/hooks/dist/gsd-cursor-stop.js +48 -0
- package/hooks/dist/gsd-cursor-subagent-start.js +50 -0
- package/hooks/dist/gsd-cursor-subagent-stop.js +40 -0
- package/hooks/dist/managed-hooks-registry.cjs +4 -0
- package/hooks/gsd-cursor-pre-tool.js +76 -0
- package/hooks/gsd-cursor-stop.js +48 -0
- package/hooks/gsd-cursor-subagent-start.js +50 -0
- package/hooks/gsd-cursor-subagent-stop.js +40 -0
- package/hooks/managed-hooks-registry.cjs +4 -0
- package/package.json +2 -1
- package/scripts/build-hooks.js +5 -1
- package/scripts/lint-phase-id-drift.cjs +150 -0
- package/scripts/run-tests.cjs +21 -1
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cline SDK binding — AgentPlugin + createAgentModel adapters
|
|
3
|
+
* (ADR-1239 Phase D / #2090).
|
|
4
|
+
*
|
|
5
|
+
* Two Context7-verified UPGRADES the file-convention projection ignored, now
|
|
6
|
+
* delivered through the negotiated `hookBus: host` + `modelMode: active`
|
|
7
|
+
* interface points:
|
|
8
|
+
*
|
|
9
|
+
* UPGRADE 1 — `AgentPlugin.hooks.beforeTool` planning-artifact guard.
|
|
10
|
+
* Re-implements the `.clinerules/hooks/PreToolUse` file-convention hook
|
|
11
|
+
* (issue #787) as a real Cline SDK AgentPlugin. Guard semantics are
|
|
12
|
+
* preserved EXACTLY: fail-open, cancel (skip) write-class calls targeting
|
|
13
|
+
* `.planning/`, pass through everything else. The SDK maps the file hook's
|
|
14
|
+
* `{cancel:true, errorMessage}` to `{skip:true, reason}` (beforeTool
|
|
15
|
+
* contract).
|
|
16
|
+
* Cite: https://github.com/cline/cline/blob/main/docs/sdk/plugins.mdx
|
|
17
|
+
* https://github.com/cline/cline/blob/main/sdk/packages/agents/README.md
|
|
18
|
+
*
|
|
19
|
+
* UPGRADE 2 — `DefaultGateway.createAgentModel({providerId, modelId})`.
|
|
20
|
+
* Resolves GSD's per-subagent `model_overrides` / `model_profile_overrides`
|
|
21
|
+
* (already used for OpenCode/Codex passive hosts) into the createAgentModel
|
|
22
|
+
* call params for cline's active model mode. The host gateway owns the
|
|
23
|
+
* actual model instantiation; this binding resolves WHICH model an
|
|
24
|
+
* overridden subagent should use.
|
|
25
|
+
* Cite: https://github.com/cline/cline/blob/main/docs/sdk/reference/gateway.mdx
|
|
26
|
+
* https://github.com/cline/cline/blob/main/sdk/packages/llms/README.md
|
|
27
|
+
*
|
|
28
|
+
* This module is PURE (no I/O, no SDK import): the real `@cline/sdk` is a
|
|
29
|
+
* fast-moving package set not linked at build/test time, so the binding exposes
|
|
30
|
+
* the decision functions a host plugin/gateway would call. Tests drive payloads
|
|
31
|
+
* through them directly (same mock-the-SDK pattern as the VS Code reference
|
|
32
|
+
* binding, tests/fixtures/vscode-host-binding.cjs).
|
|
33
|
+
*/
|
|
34
|
+
'use strict';
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.DEFAULT_CLINE_PROVIDER_ID = exports.clineGsdPlugin = exports.PLANNING_GUARD_REASON = exports.PLANNING_PATH_PATTERN = exports.WRITE_TOOL_PATTERN = void 0;
|
|
37
|
+
exports.evaluateBeforeTool = evaluateBeforeTool;
|
|
38
|
+
exports.inferProviderId = inferProviderId;
|
|
39
|
+
exports.resolveClineAgentModelParams = resolveClineAgentModelParams;
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// UPGRADE 1 — beforeTool planning-artifact guard
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
/**
|
|
44
|
+
* Write-class tool-verb detector. Matches the SAME regex as the #787
|
|
45
|
+
* PreToolUse file-convention hook so the guard behaves identically.
|
|
46
|
+
* Case-insensitive (the SDK delivers tool names in varying case).
|
|
47
|
+
*/
|
|
48
|
+
exports.WRITE_TOOL_PATTERN = /write|edit|replace|create|delete|remove|append|apply|patch|insert|mkdir/i;
|
|
49
|
+
/**
|
|
50
|
+
* `.planning/` path detector. Matches `.planning` preceded by start-of-string
|
|
51
|
+
* or a path separator (posix `/` or windows `\`) and followed by a separator or
|
|
52
|
+
* end-of-string — so `.planning-readme.txt` is NOT falsely matched. Mirrors the
|
|
53
|
+
* PreToolUse hook's `(^|[\\/])\.planning([\\/]|$)` exactly.
|
|
54
|
+
*/
|
|
55
|
+
exports.PLANNING_PATH_PATTERN = /(^|[\\/])\.planning([\\/]|$)/;
|
|
56
|
+
/**
|
|
57
|
+
* The user-visible reason returned when a `.planning/` write is blocked.
|
|
58
|
+
* Preserves the PreToolUse hook's errorMessage text so the guard behaves
|
|
59
|
+
* identically to the user (cancel→skip, errorMessage→reason).
|
|
60
|
+
*/
|
|
61
|
+
exports.PLANNING_GUARD_REASON = Object.freeze('GSD: .planning/ artifacts are managed by GSD workflows. Edit them only through a /gsd-* command, not directly.');
|
|
62
|
+
/**
|
|
63
|
+
* Path-bearing field-name detector. Only PATH-keyed field values are inspected,
|
|
64
|
+
* so a document that merely mentions ".planning/" in its body content is never
|
|
65
|
+
* falsely blocked. Mirrors the PreToolUse hook's PATH_KEY regex exactly.
|
|
66
|
+
*/
|
|
67
|
+
const PATH_KEY_PATTERN = /^(path|file|file_?path|filepath|target_?path|target|dir|directory|uri|filename)$/i;
|
|
68
|
+
/**
|
|
69
|
+
* Collect PATH-bearing string field values from an object tree, mirroring the
|
|
70
|
+
* PreToolUse hook's bounded walk. Pure; never throws.
|
|
71
|
+
*/
|
|
72
|
+
function collectPathValues(root) {
|
|
73
|
+
const paths = [];
|
|
74
|
+
const walk = (v, depth) => {
|
|
75
|
+
if (depth > 5 || paths.length > 64)
|
|
76
|
+
return;
|
|
77
|
+
if (Array.isArray(v)) {
|
|
78
|
+
for (const x of v)
|
|
79
|
+
walk(x, depth + 1);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (v && typeof v === 'object') {
|
|
83
|
+
const obj = v;
|
|
84
|
+
for (const k of Object.keys(obj)) {
|
|
85
|
+
const val = obj[k];
|
|
86
|
+
if (typeof val === 'string' && PATH_KEY_PATTERN.test(k)) {
|
|
87
|
+
paths.push(val);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
walk(val, depth + 1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
walk(root, 0);
|
|
96
|
+
return paths;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Resolve a tool name from a beforeTool payload's `tool` field, which may be a
|
|
100
|
+
* string or an object with a `name` property. Returns '' when absent (treated
|
|
101
|
+
* as non-write-class → allow, fail-open).
|
|
102
|
+
*/
|
|
103
|
+
function resolveToolName(tool) {
|
|
104
|
+
if (!tool)
|
|
105
|
+
return '';
|
|
106
|
+
if (typeof tool === 'string')
|
|
107
|
+
return tool;
|
|
108
|
+
const name = tool.name;
|
|
109
|
+
return typeof name === 'string' ? name : '';
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* The pure guard decision: given a beforeTool payload, decide skip (cancel) or
|
|
113
|
+
* allow. Fail-OPEN — any malformed input, missing tool, or thrown error returns
|
|
114
|
+
* 'allow' (the guard never blocks on a defect, mirroring the PreToolUse hook).
|
|
115
|
+
*
|
|
116
|
+
* @returns `{decision:'skip', reason}` for a write-class call targeting
|
|
117
|
+
* `.planning/`; `{decision:'allow'}` for everything else.
|
|
118
|
+
*/
|
|
119
|
+
function evaluateBeforeTool(payload) {
|
|
120
|
+
try {
|
|
121
|
+
if (!payload)
|
|
122
|
+
return { decision: 'allow' };
|
|
123
|
+
const toolName = resolveToolName(payload.tool);
|
|
124
|
+
if (!toolName)
|
|
125
|
+
return { decision: 'allow' };
|
|
126
|
+
const isWrite = exports.WRITE_TOOL_PATTERN.test(toolName);
|
|
127
|
+
if (!isWrite)
|
|
128
|
+
return { decision: 'allow' };
|
|
129
|
+
const paths = collectPathValues(payload.input);
|
|
130
|
+
const isPlanningPath = (s) => exports.PLANNING_PATH_PATTERN.test(s);
|
|
131
|
+
if (paths.some(isPlanningPath)) {
|
|
132
|
+
return { decision: 'skip', reason: exports.PLANNING_GUARD_REASON };
|
|
133
|
+
}
|
|
134
|
+
return { decision: 'allow' };
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
// Fail-open: a defect in the guard never blocks the user's operation.
|
|
138
|
+
return { decision: 'allow' };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* The Cline `AgentPlugin` shape (Context7 /cline/cline). A plugin implements
|
|
143
|
+
* `setup({agentId})` returning `{hooks, tools}`. The `beforeTool` hook returns
|
|
144
|
+
* `{skip:true, reason}` to block or `undefined` to allow.
|
|
145
|
+
*
|
|
146
|
+
* This object is the portable plugin a host loads from `~/.cline/plugins/`
|
|
147
|
+
* (analogous to `.opencode/plugins/gsd-core.js`). Its `beforeTool` delegates to
|
|
148
|
+
* the pure `evaluateBeforeTool` so the decision logic is testable without the
|
|
149
|
+
* SDK linked.
|
|
150
|
+
*/
|
|
151
|
+
exports.clineGsdPlugin = Object.freeze({
|
|
152
|
+
name: 'gsd-planning-guard',
|
|
153
|
+
setup(_ctx) {
|
|
154
|
+
return {
|
|
155
|
+
hooks: {
|
|
156
|
+
beforeTool(payload) {
|
|
157
|
+
const decision = evaluateBeforeTool(payload);
|
|
158
|
+
return decision.decision === 'skip' ? { skip: true, reason: decision.reason } : undefined;
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
// UPGRADE 2 — createAgentModel model-override resolution
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
/**
|
|
168
|
+
* The fallback provider id when a model id does not match a known provider
|
|
169
|
+
* family. Anthropic is cline's most common default; the host gateway retains
|
|
170
|
+
* the final say over provider resolution.
|
|
171
|
+
*/
|
|
172
|
+
exports.DEFAULT_CLINE_PROVIDER_ID = 'anthropic';
|
|
173
|
+
/**
|
|
174
|
+
* Infer a `providerId` (the createAgentModel first arg) from a model id by
|
|
175
|
+
* matching known provider families. Returns DEFAULT_CLINE_PROVIDER_ID for an
|
|
176
|
+
* unrecognized or empty id (fail-safe — the gateway applies its own default).
|
|
177
|
+
*
|
|
178
|
+
* Pure string-prefix classification; does not validate the id is a real model.
|
|
179
|
+
*/
|
|
180
|
+
function inferProviderId(modelId) {
|
|
181
|
+
if (typeof modelId !== 'string' || modelId.length === 0)
|
|
182
|
+
return exports.DEFAULT_CLINE_PROVIDER_ID;
|
|
183
|
+
const lower = modelId.toLowerCase();
|
|
184
|
+
if (lower.startsWith('claude'))
|
|
185
|
+
return 'anthropic';
|
|
186
|
+
if (lower.startsWith('gpt') || lower.startsWith('o1') || lower.startsWith('o3') || lower.startsWith('o4'))
|
|
187
|
+
return 'openai';
|
|
188
|
+
if (lower.startsWith('gemini'))
|
|
189
|
+
return 'google';
|
|
190
|
+
if (lower.startsWith('deepseek'))
|
|
191
|
+
return 'deepseek';
|
|
192
|
+
return exports.DEFAULT_CLINE_PROVIDER_ID;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Resolve the createAgentModel call params for a cline subagent from GSD's model
|
|
196
|
+
* override config. Mirrors the precedence OpenCode/Codex use (passive hosts
|
|
197
|
+
* embed the resolved model into agent frontmatter); for cline's active model
|
|
198
|
+
* mode the same resolution flows to `gateway.createAgentModel(params)`.
|
|
199
|
+
*
|
|
200
|
+
* Precedence (matches GSD's model_overrides > model_profile_overrides contract):
|
|
201
|
+
* 1. `modelOverrides[agentType]` — direct per-agent override
|
|
202
|
+
* 2. `modelProfileOverrides[profile][agentType]` — profile-scoped override
|
|
203
|
+
* 3. null — no override; the host gateway applies its own default
|
|
204
|
+
*
|
|
205
|
+
* Pure; never throws. Non-string / empty override values are ignored (fail-safe).
|
|
206
|
+
*
|
|
207
|
+
* @returns the `{providerId, modelId}` for createAgentModel, or null when no
|
|
208
|
+
* override is configured (the gateway default applies — GSD does NOT
|
|
209
|
+
* call createAgentModel in that case).
|
|
210
|
+
*/
|
|
211
|
+
function resolveClineAgentModelParams(args) {
|
|
212
|
+
const { agentType, modelOverrides, modelProfileOverrides, profile } = args;
|
|
213
|
+
if (!agentType || typeof agentType !== 'string')
|
|
214
|
+
return null;
|
|
215
|
+
// 1. Direct per-agent override wins.
|
|
216
|
+
if (modelOverrides && typeof modelOverrides === 'object') {
|
|
217
|
+
const direct = modelOverrides[agentType];
|
|
218
|
+
if (typeof direct === 'string' && direct.length > 0) {
|
|
219
|
+
return { providerId: inferProviderId(direct), modelId: direct };
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// 2. Profile-scoped override.
|
|
223
|
+
if (modelProfileOverrides && typeof modelProfileOverrides === 'object' && profile) {
|
|
224
|
+
const profileEntry = modelProfileOverrides[profile];
|
|
225
|
+
if (profileEntry && typeof profileEntry === 'object') {
|
|
226
|
+
const profileModel = profileEntry[agentType];
|
|
227
|
+
if (typeof profileModel === 'string' && profileModel.length > 0) {
|
|
228
|
+
return { providerId: inferProviderId(profileModel), modelId: profileModel };
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// 3. No override — gateway default applies.
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Imperative hook-bus adapter — descriptor-driven hooks.json binding
|
|
3
|
+
* (ADR-1239 Phase D / #2089).
|
|
4
|
+
*
|
|
5
|
+
* Generalizes the Cursor-specific `writeCursorHooksJson`/`removeCursorHooksJson`
|
|
6
|
+
* into a descriptor-driven hook-bus binding that reads the negotiated `hookBus`
|
|
7
|
+
* axis + the host's documented hook-event list (from
|
|
8
|
+
* `runtime.hostBehaviors.managedHookEvents`), NOT a hardcoded
|
|
9
|
+
* `sessionStart`/`postToolUse` pair.
|
|
10
|
+
*
|
|
11
|
+
* This module is PURE (no I/O): it resolves the event→script mapping and builds
|
|
12
|
+
* the hooks.json entry manifest. The actual file I/O (copying scripts, writing
|
|
13
|
+
* hooks.json) stays in `runtime-hooks-surface.cts`, which calls into the pure
|
|
14
|
+
* functions exported here. This separation makes the binding testable without a
|
|
15
|
+
* filesystem.
|
|
16
|
+
*
|
|
17
|
+
* Cursor hook-event universe (closed vocabulary per ADR-1239,
|
|
18
|
+
* https://cursor.com/docs/hooks):
|
|
19
|
+
* sessionStart, sessionEnd, preToolUse, postToolUse, subagentStart,
|
|
20
|
+
* subagentStop, beforeShellExecution, afterShellExecution,
|
|
21
|
+
* afterMCPExecution, afterFileEdit, preCompact, stop,
|
|
22
|
+
* beforeTabFileRead, afterTabFileEdit, workspaceOpen
|
|
23
|
+
*
|
|
24
|
+
* GSD registers for the 6 events in the portable floor + subagent lifecycle
|
|
25
|
+
* (AC4a upgrade, #2089):
|
|
26
|
+
* sessionStart, postToolUse, preToolUse, stop, subagentStart, subagentStop
|
|
27
|
+
*/
|
|
28
|
+
'use strict';
|
|
29
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
+
exports.GSD_HOOK_MARKER = exports.CURSOR_EVENT_SCRIPT_MAP = exports.CURSOR_HOOK_EVENTS = void 0;
|
|
31
|
+
exports.resolveManagedHookEvents = resolveManagedHookEvents;
|
|
32
|
+
exports.resolveHookScripts = resolveHookScripts;
|
|
33
|
+
exports.buildHookBusEntries = buildHookBusEntries;
|
|
34
|
+
/**
|
|
35
|
+
* The full set of Cursor hook events GSD can register for.
|
|
36
|
+
* Frozen closed vocabulary — adding an event requires updating both this set
|
|
37
|
+
* and the event→script mapping below.
|
|
38
|
+
*/
|
|
39
|
+
exports.CURSOR_HOOK_EVENTS = Object.freeze([
|
|
40
|
+
'sessionStart',
|
|
41
|
+
'postToolUse',
|
|
42
|
+
'preToolUse',
|
|
43
|
+
'stop',
|
|
44
|
+
'subagentStart',
|
|
45
|
+
'subagentStop',
|
|
46
|
+
]);
|
|
47
|
+
/**
|
|
48
|
+
* Event → hook-script mapping. Each event maps to a standalone `.js` script
|
|
49
|
+
* under `hooks/` that Cursor invokes via `hooks.json`.
|
|
50
|
+
*
|
|
51
|
+
* Convention: `gsd-cursor-<kebab-event>.js`. The script files are authored in
|
|
52
|
+
* `hooks/` and copied to `<configDir>/hooks/` during install by
|
|
53
|
+
* `runtime-hooks-surface.cts`.
|
|
54
|
+
*/
|
|
55
|
+
exports.CURSOR_EVENT_SCRIPT_MAP = Object.freeze({
|
|
56
|
+
sessionStart: 'gsd-cursor-session-start.js',
|
|
57
|
+
postToolUse: 'gsd-cursor-post-tool.js',
|
|
58
|
+
preToolUse: 'gsd-cursor-pre-tool.js',
|
|
59
|
+
stop: 'gsd-cursor-stop.js',
|
|
60
|
+
subagentStart: 'gsd-cursor-subagent-start.js',
|
|
61
|
+
subagentStop: 'gsd-cursor-subagent-stop.js',
|
|
62
|
+
});
|
|
63
|
+
/**
|
|
64
|
+
* The GSD-managed marker written into each hooks.json entry so the
|
|
65
|
+
* reconcile pass can distinguish GSD-owned entries from user-owned ones.
|
|
66
|
+
*/
|
|
67
|
+
exports.GSD_HOOK_MARKER = 'gsd-managed';
|
|
68
|
+
/**
|
|
69
|
+
* Resolve the managed hook events from a runtime descriptor's
|
|
70
|
+
* `hostBehaviors.managedHookEvents` list. Falls back to the full
|
|
71
|
+
* `CURSOR_HOOK_EVENTS` set when the descriptor does not declare the list
|
|
72
|
+
* (backward-compat for descriptors predating #2089).
|
|
73
|
+
*
|
|
74
|
+
* Pure: no I/O, never throws. Unknown event names are silently filtered
|
|
75
|
+
* (fail-closed — an unrecognized event is never registered). Falls back to
|
|
76
|
+
* the full CURSOR_HOOK_EVENTS set when the descriptor is absent or all entries
|
|
77
|
+
* are unrecognized (ensures the portable-event floor is always covered).
|
|
78
|
+
*
|
|
79
|
+
* @param managedHookEvents - the descriptor's `hostBehaviors.managedHookEvents` array
|
|
80
|
+
* @returns a deduplicated, validated array of event names
|
|
81
|
+
*/
|
|
82
|
+
function resolveManagedHookEvents(managedHookEvents) {
|
|
83
|
+
if (!Array.isArray(managedHookEvents) || managedHookEvents.length === 0) {
|
|
84
|
+
return exports.CURSOR_HOOK_EVENTS;
|
|
85
|
+
}
|
|
86
|
+
const valid = new Set(exports.CURSOR_HOOK_EVENTS);
|
|
87
|
+
const seen = new Set();
|
|
88
|
+
const result = [];
|
|
89
|
+
for (const ev of managedHookEvents) {
|
|
90
|
+
if (typeof ev === 'string' && valid.has(ev) && !seen.has(ev)) {
|
|
91
|
+
seen.add(ev);
|
|
92
|
+
result.push(ev);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return result.length > 0 ? result : exports.CURSOR_HOOK_EVENTS;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Build the list of hook script files that need to be copied for the given
|
|
99
|
+
* managed events. Each event maps to a script via `CURSOR_EVENT_SCRIPT_MAP`.
|
|
100
|
+
*
|
|
101
|
+
* Pure: returns a deduplicated array of script filenames.
|
|
102
|
+
*
|
|
103
|
+
* @param events - the managed event names (validated by `resolveManagedHookEvents`)
|
|
104
|
+
* @returns array of script filenames (e.g. `['gsd-cursor-session-start.js', ...]`)
|
|
105
|
+
*/
|
|
106
|
+
function resolveHookScripts(events) {
|
|
107
|
+
const scripts = [];
|
|
108
|
+
const seen = new Set();
|
|
109
|
+
for (const ev of events) {
|
|
110
|
+
const script = exports.CURSOR_EVENT_SCRIPT_MAP[ev];
|
|
111
|
+
if (script && !seen.has(script)) {
|
|
112
|
+
seen.add(script);
|
|
113
|
+
scripts.push(script);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return scripts;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Build the hooks.json managed-entry manifest for the given events.
|
|
120
|
+
* Each entry is `{ type: 'command', command: <cmd>, [GSD_HOOK_MARKER]: true }`.
|
|
121
|
+
*
|
|
122
|
+
* The `command` string is built by the caller (it requires platform-specific
|
|
123
|
+
* node-runner resolution from `runtime-hooks-surface.cts`). This function
|
|
124
|
+
* receives a pre-built `event → command` map and attaches the marker.
|
|
125
|
+
*
|
|
126
|
+
* Pure: no I/O.
|
|
127
|
+
*
|
|
128
|
+
* @param events - the managed event names
|
|
129
|
+
* @param commands - a map of event → command string (built by the caller)
|
|
130
|
+
* @returns a map of event → managed entry, ready for hooks.json reconciliation
|
|
131
|
+
*/
|
|
132
|
+
function buildHookBusEntries(events, commands) {
|
|
133
|
+
const entries = {};
|
|
134
|
+
for (const ev of events) {
|
|
135
|
+
const cmd = commands[ev];
|
|
136
|
+
if (cmd) {
|
|
137
|
+
entries[ev] = {
|
|
138
|
+
type: 'command',
|
|
139
|
+
command: cmd,
|
|
140
|
+
[exports.GSD_HOOK_MARKER]: true,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return entries;
|
|
145
|
+
}
|
|
@@ -434,6 +434,22 @@ const EXTENSION_EVENT_SURFACES = Object.freeze({
|
|
|
434
434
|
opencode: Object.freeze([
|
|
435
435
|
'session.created', 'session.idle', 'experimental.session.compacting',
|
|
436
436
|
'tool.execute.before', 'tool.execute.after', 'file.edited',
|
|
437
|
+
// #2087 — additional documented plugin events GSD binds (opencode.ai/docs/plugins):
|
|
438
|
+
// permission decisions + session error surface.
|
|
439
|
+
'permission.asked', 'permission.replied', 'session.error',
|
|
440
|
+
]),
|
|
441
|
+
// #2091 — Hermes Agent real plugin hook vocabulary (13 events).
|
|
442
|
+
// Cite: https://github.com/nousresearch/hermes-agent/blob/main/website/docs/user-guide/features/hooks.md
|
|
443
|
+
// Replaces the borrowed `hookEvents: "claude"` 6-event surface that silently
|
|
444
|
+
// never fired on Hermes.
|
|
445
|
+
hermes: Object.freeze([
|
|
446
|
+
'pre_tool_call', 'post_tool_call',
|
|
447
|
+
'pre_llm_call', 'post_llm_call',
|
|
448
|
+
'on_session_start', 'on_session_end',
|
|
449
|
+
'on_session_finalize', 'on_session_reset',
|
|
450
|
+
'subagent_start', 'subagent_stop',
|
|
451
|
+
'pre_gateway_dispatch', 'pre_approval_request',
|
|
452
|
+
'transform_tool_result',
|
|
437
453
|
]),
|
|
438
454
|
pi: Object.freeze(['tool_call']),
|
|
439
455
|
none: Object.freeze([]),
|
|
@@ -63,9 +63,9 @@ const { resolveModelInternal, resolveGranularityInternal, assertValidGranularity
|
|
|
63
63
|
const { findPhaseInternal } = phaseLocator;
|
|
64
64
|
const { getRoadmapPhaseInternal, getMilestoneInfo, getMilestonePhaseFilter, stripShippedMilestones, extractCurrentMilestone, } = roadmapParser;
|
|
65
65
|
const { pathExistsInternal, generateSlugInternal, toPosixPath } = coreUtils;
|
|
66
|
-
const { normalizePhaseName, phaseTokenMatches, stripProjectCodePrefix } = phaseId;
|
|
66
|
+
const { escapeRegex, normalizePhaseName, phaseTokenMatches, stripProjectCodePrefix, PHASE_NUMBER_TOKEN_SOURCE } = phaseId;
|
|
67
67
|
const { pruneOrphanedWorktrees } = worktreeSafety;
|
|
68
|
-
const { planningPaths, planningDir, planningRoot, getActiveWorkstream, findContextMdIn, } = planningWorkspace;
|
|
68
|
+
const { planningPaths, planningDir, planningRoot, listAvailableWorkstreams, getActiveWorkstream, findContextMdIn, } = planningWorkspace;
|
|
69
69
|
const { determinePhaseStatus } = commandsMod;
|
|
70
70
|
const { extractFrontmatter } = frontmatterMod;
|
|
71
71
|
const { readVerificationStatus } = verificationMod;
|
|
@@ -74,6 +74,35 @@ const { evaluateUatPassed } = uatPredicateMod;
|
|
|
74
74
|
void stripShippedMilestones;
|
|
75
75
|
// Accept all bold/colon variants of the Requirements header (#2769)
|
|
76
76
|
const REQUIREMENTS_HEADER_RE = /^\*\*Requirements:?\*\*[^\S\n]*:?[^\S\n]*([^\n]*)$/m;
|
|
77
|
+
// #2056: normalizePhaseName() strips ANY [A-Z][A-Z0-9_]*- prefix as a project
|
|
78
|
+
// code (e.g. 'MEM-01' → '01'), so a foreign-prefixed workstream/task id would
|
|
79
|
+
// collapse to its numeric suffix and resolve to an unrelated numeric phase via
|
|
80
|
+
// the dir/roadmap fallback. init plan-phase must require EXACT prefixed
|
|
81
|
+
// evidence — a phase dir whose token literally IS the prefixed query, or a
|
|
82
|
+
// roadmap entry literally headed with it — before accepting such a match. The
|
|
83
|
+
// configured project_code's own prefix (e.g. LKML-01 under project_code: LKML)
|
|
84
|
+
// is never foreign and always passes through.
|
|
85
|
+
function parsePhasePrefix(phase) {
|
|
86
|
+
const match = String(phase).match(/^([A-Z][A-Z0-9_]*)-(?=\d)/i);
|
|
87
|
+
return match ? match[1] : null;
|
|
88
|
+
}
|
|
89
|
+
function isForeignPrefixedPhaseQuery(phase, projectCode) {
|
|
90
|
+
const prefix = parsePhasePrefix(phase);
|
|
91
|
+
if (!prefix)
|
|
92
|
+
return false;
|
|
93
|
+
const configured = typeof projectCode === 'string' ? projectCode.trim() : '';
|
|
94
|
+
return !configured || prefix.toUpperCase() !== configured.toUpperCase();
|
|
95
|
+
}
|
|
96
|
+
function phaseInfoMatchesExactPrefix(phaseInfo, phase) {
|
|
97
|
+
const num = phaseInfo?.['phase_number'];
|
|
98
|
+
const numStr = typeof num === 'string' ? num : (typeof num === 'number' ? String(num) : '');
|
|
99
|
+
return numStr.toUpperCase() === phase.toUpperCase();
|
|
100
|
+
}
|
|
101
|
+
function roadmapPhaseMatchesExactPrefix(roadmapPhase, phase) {
|
|
102
|
+
const sectionRaw = roadmapPhase?.['section'];
|
|
103
|
+
const section = typeof sectionRaw === 'string' ? sectionRaw : '';
|
|
104
|
+
return new RegExp(`^#{2,4}\\s*Phase\\s+${escapeRegex(phase)}(?:\\b|\\s|:)`, 'i').test(section);
|
|
105
|
+
}
|
|
77
106
|
function listPhaseSummaryFiles(phaseDir) {
|
|
78
107
|
return scanPhasePlans(phaseDir)['summaryFiles'];
|
|
79
108
|
}
|
|
@@ -332,8 +361,18 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
|
|
|
332
361
|
error('phase required for init plan-phase');
|
|
333
362
|
}
|
|
334
363
|
const config = loadConfig(cwd);
|
|
364
|
+
const foreignPrefixedPhase = isForeignPrefixedPhaseQuery(phase, config.project_code);
|
|
335
365
|
let phaseInfo = findPhaseInternal(cwd, phase);
|
|
336
|
-
|
|
366
|
+
// #2056: a foreign-prefixed query must only match a phase whose token literally
|
|
367
|
+
// IS the prefixed query (e.g. a real 'MEM-01-*' dir); otherwise the numeric
|
|
368
|
+
// fallback ('MEM-01' → '01') would resolve to the unrelated numeric phase.
|
|
369
|
+
if (foreignPrefixedPhase && !phaseInfoMatchesExactPrefix(phaseInfo, phase)) {
|
|
370
|
+
phaseInfo = null;
|
|
371
|
+
}
|
|
372
|
+
let roadmapPhase = getRoadmapPhaseInternal(cwd, phase);
|
|
373
|
+
if (foreignPrefixedPhase && !roadmapPhaseMatchesExactPrefix(roadmapPhase, phase)) {
|
|
374
|
+
roadmapPhase = null;
|
|
375
|
+
}
|
|
337
376
|
if (phaseInfo?.['archived'] && roadmapPhase?.['found']) {
|
|
338
377
|
phaseInfo = null;
|
|
339
378
|
}
|
|
@@ -573,6 +612,9 @@ function cmdInitQuick(cwd, description, raw) {
|
|
|
573
612
|
executor_model: resolveModelInternal(cwd, 'gsd-executor'),
|
|
574
613
|
checker_model: resolveModelInternal(cwd, 'gsd-plan-checker'),
|
|
575
614
|
verifier_model: resolveModelInternal(cwd, 'gsd-verifier'),
|
|
615
|
+
// #2072: the quick review step spawns gsd-code-reviewer; resolve its own model
|
|
616
|
+
// so model_overrides / models.verification apply (was reusing executor_model).
|
|
617
|
+
reviewer_model: resolveModelInternal(cwd, 'gsd-code-reviewer'),
|
|
576
618
|
commit_docs: config.commit_docs,
|
|
577
619
|
branch_name: quickBranchName,
|
|
578
620
|
quick_id: quickId,
|
|
@@ -874,8 +916,8 @@ function cmdInitMilestoneOp(cwd, raw) {
|
|
|
874
916
|
const roadmapPath = node_path_1.default.join(planningDir(cwd), 'ROADMAP.md');
|
|
875
917
|
const roadmapRaw = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
|
|
876
918
|
const currentSection = extractCurrentMilestone(roadmapRaw, cwd);
|
|
877
|
-
// #1729: `(?:\s*\([^)\n]
|
|
878
|
-
const phasePattern =
|
|
919
|
+
// #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
|
|
920
|
+
const phasePattern = new RegExp(`#{2,4}\\s*Phase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:`, 'gi');
|
|
879
921
|
let m;
|
|
880
922
|
while ((m = phasePattern.exec(currentSection)) !== null) {
|
|
881
923
|
if (/^999(?:\.|$)/.test(m[1]))
|
|
@@ -896,7 +938,7 @@ function cmdInitMilestoneOp(cwd, raw) {
|
|
|
896
938
|
for (const e of entries) {
|
|
897
939
|
if (!e.isDirectory())
|
|
898
940
|
continue;
|
|
899
|
-
const m = stripProjectCodePrefix(e.name).match(
|
|
941
|
+
const m = stripProjectCodePrefix(e.name).match(new RegExp(`^(${PHASE_NUMBER_TOKEN_SOURCE})`));
|
|
900
942
|
if (!m)
|
|
901
943
|
continue;
|
|
902
944
|
diskPhaseDirs.set(canonicalizePhase(m[1]), e.name);
|
|
@@ -1024,13 +1066,13 @@ function cmdInitManager(cwd, raw) {
|
|
|
1024
1066
|
}
|
|
1025
1067
|
})();
|
|
1026
1068
|
const _checkboxStates = new Map();
|
|
1027
|
-
const _cbPattern =
|
|
1069
|
+
const _cbPattern = new RegExp(`-\\s*\\[(x| )\\]\\s*.*Phase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})[:\\s]`, 'gi');
|
|
1028
1070
|
let _cbMatch;
|
|
1029
1071
|
while ((_cbMatch = _cbPattern.exec(content)) !== null) {
|
|
1030
1072
|
_checkboxStates.set(_cbMatch[2], _cbMatch[1].toLowerCase() === 'x');
|
|
1031
1073
|
}
|
|
1032
|
-
// #1729: `(?:\s*\([^)\n]
|
|
1033
|
-
const phasePattern =
|
|
1074
|
+
// #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
|
|
1075
|
+
const phasePattern = new RegExp(`#{2,4}\\s*Phase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:\\s*([^\\n]+)`, 'gi');
|
|
1034
1076
|
const phases = [];
|
|
1035
1077
|
let match;
|
|
1036
1078
|
while ((match = phasePattern.exec(content)) !== null) {
|
|
@@ -1148,7 +1190,7 @@ function cmdInitManager(cwd, raw) {
|
|
|
1148
1190
|
.filter((p) => p['phase_complete'] === true)
|
|
1149
1191
|
.map((p) => normalizePhaseNumber(p['number'])));
|
|
1150
1192
|
const phaseMap = new Map(phases.map((p) => [normalizePhaseNumber(p['number']), p]));
|
|
1151
|
-
const _allCompletedPattern =
|
|
1193
|
+
const _allCompletedPattern = new RegExp(`-\\s*\\[x\\]\\s*.*Phase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})[:\\s]`, 'gi');
|
|
1152
1194
|
let _allMatch;
|
|
1153
1195
|
while ((_allMatch = _allCompletedPattern.exec(rawContent)) !== null) {
|
|
1154
1196
|
const phaseNum = normalizePhaseNumber(_allMatch[1]);
|
|
@@ -1180,7 +1222,7 @@ function cmdInitManager(cwd, raw) {
|
|
|
1180
1222
|
phase['deps_satisfied'] = true;
|
|
1181
1223
|
}
|
|
1182
1224
|
else {
|
|
1183
|
-
const depNums = phase['depends_on'].match(
|
|
1225
|
+
const depNums = phase['depends_on'].match(new RegExp(`${PHASE_NUMBER_TOKEN_SOURCE}`, 'gi')) || [];
|
|
1184
1226
|
phase['deps_satisfied'] = depNums.every((n) => completedNums.has(normalizePhaseNumber(n)));
|
|
1185
1227
|
phase['dep_phases'] = depNums;
|
|
1186
1228
|
}
|
|
@@ -1319,18 +1361,7 @@ function cmdInitProgress(cwd, raw) {
|
|
|
1319
1361
|
// reporting a stale root milestone. Require an explicit workstream instead.
|
|
1320
1362
|
// Mirror planningDir's resolution (GSD_WORKSTREAM env > stored active pointer) so
|
|
1321
1363
|
// an explicit --ws (which sets GSD_WORKSTREAM) satisfies the check.
|
|
1322
|
-
const
|
|
1323
|
-
let _availableWorkstreams = [];
|
|
1324
|
-
try {
|
|
1325
|
-
_availableWorkstreams = node_fs_1.default
|
|
1326
|
-
.readdirSync(_wsRoot, { withFileTypes: true })
|
|
1327
|
-
.filter((e) => e.isDirectory())
|
|
1328
|
-
.map((e) => e.name)
|
|
1329
|
-
.sort();
|
|
1330
|
-
}
|
|
1331
|
-
catch {
|
|
1332
|
-
/* no workstreams dir → flat mode */
|
|
1333
|
-
}
|
|
1364
|
+
const _availableWorkstreams = listAvailableWorkstreams(cwd);
|
|
1334
1365
|
const _resolvedWorkstream = process.env['GSD_WORKSTREAM'] || getActiveWorkstream(cwd);
|
|
1335
1366
|
if (_availableWorkstreams.length > 0 && !_resolvedWorkstream) {
|
|
1336
1367
|
error(`init.progress requires a workstream in workstream mode — no active workstream is set, so root STATE.md (likely stale) would be reported. ` +
|
|
@@ -1346,14 +1377,14 @@ function cmdInitProgress(cwd, raw) {
|
|
|
1346
1377
|
const roadmapCheckboxStates = new Map();
|
|
1347
1378
|
try {
|
|
1348
1379
|
const roadmapContent = extractCurrentMilestone(node_fs_1.default.readFileSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md'), 'utf-8'), cwd);
|
|
1349
|
-
// #1729: `(?:\s*\([^)\n]
|
|
1350
|
-
const headingPattern =
|
|
1380
|
+
// #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
|
|
1381
|
+
const headingPattern = new RegExp(`#{2,4}\\s*Phase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:\\s*([^\\n]+)`, 'gi');
|
|
1351
1382
|
let hm;
|
|
1352
1383
|
while ((hm = headingPattern.exec(roadmapContent)) !== null) {
|
|
1353
1384
|
roadmapPhaseNums.add(hm[1]);
|
|
1354
1385
|
roadmapPhaseNames.set(hm[1], hm[2].replace(/\(INSERTED\)/i, '').trim());
|
|
1355
1386
|
}
|
|
1356
|
-
const cbPattern =
|
|
1387
|
+
const cbPattern = new RegExp(`-\\s*\\[(x| )\\]\\s*.*Phase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})[:\\s]`, 'gi');
|
|
1357
1388
|
let cbm;
|
|
1358
1389
|
while ((cbm = cbPattern.exec(roadmapContent)) !== null) {
|
|
1359
1390
|
roadmapCheckboxStates.set(cbm[2], cbm[1].toLowerCase() === 'x');
|
|
@@ -1371,14 +1402,14 @@ function cmdInitProgress(cwd, raw) {
|
|
|
1371
1402
|
.map((e) => e.name)
|
|
1372
1403
|
.filter(isDirInMilestone)
|
|
1373
1404
|
.sort((a, b) => {
|
|
1374
|
-
const pa = a.match(
|
|
1375
|
-
const pb = b.match(
|
|
1405
|
+
const pa = a.match(new RegExp(`^(${PHASE_NUMBER_TOKEN_SOURCE})`, 'i'));
|
|
1406
|
+
const pb = b.match(new RegExp(`^(${PHASE_NUMBER_TOKEN_SOURCE})`, 'i'));
|
|
1376
1407
|
if (!pa || !pb)
|
|
1377
1408
|
return a.localeCompare(b);
|
|
1378
1409
|
return parseInt(pa[1], 10) - parseInt(pb[1], 10);
|
|
1379
1410
|
});
|
|
1380
1411
|
for (const dir of dirs) {
|
|
1381
|
-
const dirMatch = dir.match(
|
|
1412
|
+
const dirMatch = dir.match(new RegExp(`^(${PHASE_NUMBER_TOKEN_SOURCE})-?(.*)`, 'i'));
|
|
1382
1413
|
const phaseNumber = dirMatch ? dirMatch[1] : dir;
|
|
1383
1414
|
const phaseName = dirMatch && dirMatch[2] ? dirMatch[2] : null;
|
|
1384
1415
|
seenPhaseNums.add(phaseNumber.replace(/^0+/, '') || '0');
|
|
@@ -1870,11 +1901,24 @@ function buildSkillManifest(cwd, skillsDir = null) {
|
|
|
1870
1901
|
kind: 'skills',
|
|
1871
1902
|
},
|
|
1872
1903
|
{
|
|
1873
|
-
|
|
1904
|
+
// ADR-1239 upgrade 3 (#2088): Codex's canonical skill root is
|
|
1905
|
+
// $HOME/.agents/skills (per codex core-skills loader.rs), resolved via
|
|
1906
|
+
// the skills-kind `home` override in getGlobalSkillsBase.
|
|
1907
|
+
root: '~/.agents/skills',
|
|
1874
1908
|
path: (0, runtime_homes_cjs_1.getGlobalSkillsBase)('codex'),
|
|
1875
1909
|
scope: 'global',
|
|
1876
1910
|
kind: 'skills',
|
|
1877
1911
|
},
|
|
1912
|
+
{
|
|
1913
|
+
// Codex's deprecated fallback skill root ($CODEX_HOME/skills). Kept as a
|
|
1914
|
+
// discovery-only legacy root so pre-move installs remain inventoried;
|
|
1915
|
+
// GSD no longer installs here (#2088).
|
|
1916
|
+
root: '~/.codex/skills',
|
|
1917
|
+
path: node_path_1.default.join((0, runtime_homes_cjs_1.getGlobalConfigDir)('codex'), 'skills'),
|
|
1918
|
+
scope: 'global',
|
|
1919
|
+
kind: 'skills',
|
|
1920
|
+
deprecated: true,
|
|
1921
|
+
},
|
|
1878
1922
|
{
|
|
1879
1923
|
root: '.claude/gsd-core/skills',
|
|
1880
1924
|
path: node_path_1.default.join(node_os_1.default.homedir(), '.claude', 'gsd-core', 'skills'),
|