@opengsd/gsd-core 1.7.0-rc.4 → 1.7.0-rc.6
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 +30 -0
- package/agents/gsd-ui-researcher.md +1 -0
- package/bin/install.js +1568 -622
- 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 +761 -84
- package/gsd-core/bin/lib/capability-validator.cjs +56 -18
- 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 +45 -4
- package/gsd-core/bin/lib/init.cjs +76 -39
- package/gsd-core/bin/lib/install-effort-resolver.cjs +213 -0
- package/gsd-core/bin/lib/install-engine.cjs +228 -18
- package/gsd-core/bin/lib/installer-migration-report.cjs +7 -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/mcp-server.cjs +18 -7
- 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/review-reviewer-selection.cjs +24 -7
- 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 +248 -44
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +2 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +39 -23
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +19 -5
- package/gsd-core/bin/lib/runtime-homes.cjs +30 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +534 -37
- package/gsd-core/bin/lib/runtime-name-policy.cjs +63 -5
- package/gsd-core/bin/lib/security.cjs +6 -36
- package/gsd-core/bin/lib/shell-command-projection.cjs +115 -2
- package/gsd-core/bin/lib/spec-section.cjs +111 -0
- package/gsd-core/bin/lib/stale-bake-guard.cjs +30 -10
- 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 +40 -6
- package/gsd-core/bin/lib/uat.cjs +4 -1
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +249 -0
- 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 +14 -9
- 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/references/ui-consideration-probe.md +73 -0
- package/gsd-core/templates/UI-SPEC.md +25 -0
- package/gsd-core/templates/VALIDATION.md +2 -0
- package/gsd-core/templates/config.json +2 -1
- package/gsd-core/workflows/audit-fix.md +9 -1
- package/gsd-core/workflows/audit-milestone.md +7 -4
- 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 +37 -2
- package/gsd-core/workflows/quick.md +2 -2
- package/gsd-core/workflows/review.md +59 -13
- package/gsd-core/workflows/settings-advanced.md +12 -9
- package/gsd-core/workflows/settings.md +2 -2
- package/gsd-core/workflows/ui-phase.md +146 -1
- package/gsd-core/workflows/validate-phase.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/gsd-windsurf-pre-command.js +275 -0
- package/hooks/dist/gsd-windsurf-pre-write.js +132 -0
- package/hooks/dist/managed-hooks-registry.cjs +6 -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/gsd-windsurf-pre-command.js +275 -0
- package/hooks/gsd-windsurf-pre-write.js +132 -0
- package/hooks/managed-hooks-registry.cjs +6 -0
- package/package.json +9 -4
- package/pi/gsd.cjs +354 -0
- package/scripts/build-hooks.js +8 -1
- package/scripts/gen-golden-install-parity-zcode.cjs +11 -1
- package/scripts/gen-registry.cjs +128 -0
- package/scripts/lint-phase-id-drift.cjs +150 -0
- package/scripts/lint-test-file-count.allowlist.json +2 -1
- package/scripts/registry-schema.cjs +565 -0
- package/scripts/run-tests.cjs +21 -1
- package/scripts/validate-registry.cjs +117 -0
- package/vscode/browser.js +197 -0
- package/vscode/extension.js +383 -0
- package/vscode/host-binding.js +113 -0
- package/vscode/package.json +96 -0
|
@@ -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
|
+
}
|
|
@@ -430,12 +430,53 @@ function hookEventSurfaceFor(hookEvents) {
|
|
|
430
430
|
// events (session/tool/file/permission); pi ~30 fine-grained extension events;
|
|
431
431
|
// 'none' = the host exposes no extension surface and the engine owns the bus
|
|
432
432
|
// (VS Code). Declarative hosts (no plugin API) do not set `extensionEvents`.
|
|
433
|
+
// OpenCode's plugin event surface (ADR-1239 §research; ~25 documented events,
|
|
434
|
+
// GSD binds this subset). Hoisted to a named const — rather than duplicated
|
|
435
|
+
// object literals — so the `kilo` dialect below (#2093) can reuse the IDENTICAL
|
|
436
|
+
// array instead of a copy-pasted one that could silently drift out of sync.
|
|
437
|
+
const OPENCODE_EXTENSION_EVENTS = Object.freeze([
|
|
438
|
+
'session.created', 'session.idle', 'experimental.session.compacting',
|
|
439
|
+
'tool.execute.before', 'tool.execute.after', 'file.edited',
|
|
440
|
+
// #2087 — additional documented plugin events GSD binds (opencode.ai/docs/plugins):
|
|
441
|
+
// permission decisions + session error surface.
|
|
442
|
+
'permission.asked', 'permission.replied', 'session.error',
|
|
443
|
+
]);
|
|
433
444
|
const EXTENSION_EVENT_SURFACES = Object.freeze({
|
|
434
|
-
opencode:
|
|
435
|
-
|
|
436
|
-
|
|
445
|
+
opencode: OPENCODE_EXTENSION_EVENTS,
|
|
446
|
+
// #2093 — Kilo Code is an OpenCode fork sharing the same plugin/extension
|
|
447
|
+
// event bus (host hook bus, UPGRADE 1): reuses OPENCODE_EXTENSION_EVENTS
|
|
448
|
+
// verbatim (not a re-derivation), so the two dialects stay pinned together
|
|
449
|
+
// by construction. See .kilo/plugins/gsd-core.js (copied verbatim from
|
|
450
|
+
// .opencode/plugins/gsd-core.js).
|
|
451
|
+
kilo: OPENCODE_EXTENSION_EVENTS,
|
|
452
|
+
// #2091 — Hermes Agent real plugin hook vocabulary (13 events).
|
|
453
|
+
// Cite: https://github.com/nousresearch/hermes-agent/blob/main/website/docs/user-guide/features/hooks.md
|
|
454
|
+
// Replaces the borrowed `hookEvents: "claude"` 6-event surface that silently
|
|
455
|
+
// never fired on Hermes.
|
|
456
|
+
hermes: Object.freeze([
|
|
457
|
+
'pre_tool_call', 'post_tool_call',
|
|
458
|
+
'pre_llm_call', 'post_llm_call',
|
|
459
|
+
'on_session_start', 'on_session_end',
|
|
460
|
+
'on_session_finalize', 'on_session_reset',
|
|
461
|
+
'subagent_start', 'subagent_stop',
|
|
462
|
+
'pre_gateway_dispatch', 'pre_approval_request',
|
|
463
|
+
'transform_tool_result',
|
|
464
|
+
]),
|
|
465
|
+
// #2102 Stage 2 — pi's real ExtensionAPI event vocabulary (~30 fine-grained
|
|
466
|
+
// extension events; documentation-sourced, ADR-1239 §research). Replaces the
|
|
467
|
+
// placeholder single-event ['tool_call'] surface — the Stage 1 value only
|
|
468
|
+
// covered the one event pi/gsd.cjs happened to bind at the time, not the
|
|
469
|
+
// full declared surface.
|
|
470
|
+
pi: Object.freeze([
|
|
471
|
+
'session_start', 'project_trust', 'resources_discover', 'input',
|
|
472
|
+
'before_agent_start', 'agent_start', 'message_start', 'message_update',
|
|
473
|
+
'message_end', 'turn_start', 'context', 'before_provider_request',
|
|
474
|
+
'after_provider_response', 'tool_execution_start', 'tool_execution_update',
|
|
475
|
+
'tool_execution_end', 'tool_call', 'tool_result', 'turn_end', 'agent_end',
|
|
476
|
+
'session_before_switch', 'session_shutdown', 'session_before_fork',
|
|
477
|
+
'session_info_changed', 'session_before_compact', 'session_compact',
|
|
478
|
+
'session_before_tree', 'session_tree', 'thinking_level_select', 'model_select',
|
|
437
479
|
]),
|
|
438
|
-
pi: Object.freeze(['tool_call']),
|
|
439
480
|
none: Object.freeze([]),
|
|
440
481
|
});
|
|
441
482
|
/**
|