@opengsd/gsd-core 1.4.3 → 1.5.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/README.md +3 -3
- package/agents/gsd-executor.md +1 -1
- package/agents/gsd-plan-checker.md +11 -2
- package/agents/gsd-planner.md +11 -7
- package/agents/gsd-verifier.md +3 -3
- package/bin/install.js +825 -149
- package/commands/gsd/autonomous.md +5 -1
- package/commands/gsd/ns-manage.md +8 -1
- package/commands/gsd/ns-project.md +5 -0
- package/commands/gsd/ns-review.md +4 -1
- package/commands/gsd/ns-workflow.md +7 -1
- package/commands/gsd/plan-review-convergence.md +5 -4
- package/commands/gsd/surface.md +12 -5
- package/gemini-extension.json +1 -1
- package/gsd-core/bin/gsd-tools.cjs +198 -101
- package/gsd-core/bin/lib/audit-command-router.cjs +61 -0
- package/gsd-core/bin/lib/capability-registry.cjs +417 -0
- package/gsd-core/bin/lib/capability-state.cjs +336 -0
- package/gsd-core/bin/lib/cli-exit.cjs +22 -3
- package/gsd-core/bin/lib/config-loader.cjs +716 -0
- package/gsd-core/bin/lib/configuration.cjs +4 -34
- package/gsd-core/bin/lib/core-utils.cjs +198 -0
- package/gsd-core/bin/lib/core.cjs +57 -1817
- package/gsd-core/bin/lib/federated-config.cjs +182 -0
- package/gsd-core/bin/lib/graphify-command-router.cjs +74 -0
- package/gsd-core/bin/lib/init.cjs +58 -12
- package/gsd-core/bin/lib/install-profiles.cjs +157 -3
- package/gsd-core/bin/lib/intel-command-router.cjs +116 -0
- package/gsd-core/bin/lib/io.cjs +167 -0
- package/gsd-core/bin/lib/loop-host-contract.cjs +105 -0
- package/gsd-core/bin/lib/loop-resolver.cjs +460 -0
- package/gsd-core/bin/lib/model-resolver.cjs +426 -0
- package/gsd-core/bin/lib/phase-id.cjs +215 -0
- package/gsd-core/bin/lib/phase-locator.cjs +148 -0
- package/gsd-core/bin/lib/profile-pipeline.cjs +2 -2
- package/gsd-core/bin/lib/roadmap-parser.cjs +443 -0
- package/gsd-core/bin/lib/roadmap.cjs +4 -1
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +96 -9
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +2 -1
- package/gsd-core/bin/lib/runtime-homes.cjs +40 -0
- package/gsd-core/bin/lib/runtime-name-policy.cjs +1 -0
- package/gsd-core/bin/lib/shell-command-projection.cjs +13 -0
- package/gsd-core/bin/lib/state.cjs +114 -2
- package/gsd-core/bin/lib/surface.cjs +66 -14
- package/gsd-core/bin/lib/verify.cjs +1 -1
- package/gsd-core/bin/shared/model-catalog.json +5 -0
- package/gsd-core/bin/shared/runtime-aliases.manifest.json +3 -0
- package/gsd-core/references/planner-reviews.md +5 -2
- package/gsd-core/templates/phase-prompt.md +7 -7
- package/gsd-core/templates/project.md +19 -2
- package/gsd-core/templates/summary-complex.md +1 -0
- package/gsd-core/templates/summary-minimal.md +1 -0
- package/gsd-core/templates/summary-standard.md +1 -0
- package/gsd-core/templates/summary.md +1 -0
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +1 -1
- package/gsd-core/workflows/add-phase.md +1 -1
- package/gsd-core/workflows/add-tests.md +1 -1
- package/gsd-core/workflows/add-todo.md +1 -1
- package/gsd-core/workflows/ai-integration-phase.md +1 -1
- package/gsd-core/workflows/audit-fix.md +1 -1
- package/gsd-core/workflows/audit-milestone.md +1 -1
- package/gsd-core/workflows/audit-uat.md +1 -1
- package/gsd-core/workflows/autonomous.md +86 -15
- package/gsd-core/workflows/check-todos.md +1 -1
- package/gsd-core/workflows/cleanup.md +1 -1
- package/gsd-core/workflows/code-review-fix.md +6 -4
- package/gsd-core/workflows/code-review.md +3 -2
- package/gsd-core/workflows/complete-milestone.md +11 -5
- package/gsd-core/workflows/debug.md +1 -1
- package/gsd-core/workflows/diagnose-issues.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/auto.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
- package/gsd-core/workflows/discuss-phase.md +8 -1
- package/gsd-core/workflows/do.md +1 -1
- package/gsd-core/workflows/docs-update.md +1 -1
- package/gsd-core/workflows/edit-phase.md +1 -1
- package/gsd-core/workflows/eval-review.md +4 -1
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +1 -1
- package/gsd-core/workflows/execute-phase.md +8 -1
- package/gsd-core/workflows/execute-plan.md +1 -1
- package/gsd-core/workflows/explore.md +1 -1
- package/gsd-core/workflows/extract-learnings.md +1 -1
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +1 -1
- package/gsd-core/workflows/help/modes/full.md +1 -1
- package/gsd-core/workflows/import.md +1 -1
- package/gsd-core/workflows/ingest-docs.md +1 -1
- package/gsd-core/workflows/insert-phase.md +1 -1
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +1 -1
- package/gsd-core/workflows/map-codebase.md +1 -1
- package/gsd-core/workflows/milestone-summary.md +1 -1
- package/gsd-core/workflows/mvp-phase.md +1 -1
- package/gsd-core/workflows/new-milestone.md +1 -1
- package/gsd-core/workflows/new-project.md +1 -1
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +1 -1
- package/gsd-core/workflows/pause-work.md +1 -1
- package/gsd-core/workflows/plan-milestone-gaps.md +1 -1
- package/gsd-core/workflows/plan-phase.md +27 -2
- package/gsd-core/workflows/plan-review-convergence.md +60 -33
- package/gsd-core/workflows/plant-seed.md +1 -1
- package/gsd-core/workflows/profile-user.md +1 -1
- package/gsd-core/workflows/progress.md +1 -1
- package/gsd-core/workflows/quick.md +2 -2
- package/gsd-core/workflows/remove-phase.md +1 -1
- package/gsd-core/workflows/remove-workspace.md +1 -1
- package/gsd-core/workflows/resume-project.md +1 -1
- package/gsd-core/workflows/review.md +1 -1
- package/gsd-core/workflows/scan.md +1 -1
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/settings-advanced.md +1 -1
- package/gsd-core/workflows/settings-integrations.md +1 -1
- package/gsd-core/workflows/settings.md +1 -1
- package/gsd-core/workflows/ship.md +8 -1
- package/gsd-core/workflows/sketch-wrap-up.md +1 -1
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/spec-phase.md +1 -1
- package/gsd-core/workflows/spike-wrap-up.md +1 -1
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +1 -1
- package/gsd-core/workflows/thread.md +1 -1
- package/gsd-core/workflows/transition.md +1 -1
- package/gsd-core/workflows/ui-phase.md +1 -1
- package/gsd-core/workflows/ui-review.md +1 -1
- package/gsd-core/workflows/ultraplan-phase.md +1 -1
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-phase.md +1 -1
- package/gsd-core/workflows/verify-work.md +8 -1
- package/package.json +7 -2
- package/scripts/base64-scan.sh +1 -1
- package/scripts/changeset/cli.cjs +8 -1
- package/scripts/changeset/lint.cjs +38 -2
- package/scripts/ci-test-scope.cjs +21 -10
- package/scripts/gen-capability-registry.cjs +1808 -0
- package/scripts/gen-loop-host-contract.cjs +471 -0
- package/scripts/lint-regression-test-names.allowlist.json +269 -0
- package/scripts/lint-regression-test-names.cjs +117 -0
- package/scripts/lint-test-file-count.allowlist.json +12 -4
- package/scripts/prompt-injection-scan.sh +4 -4
- package/scripts/run-tests.cjs +79 -16
- package/scripts/secret-scan.sh +3 -3
- package/scripts/run-cross-platform-tests.cjs +0 -67
|
@@ -16,6 +16,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
16
16
|
*/
|
|
17
17
|
const node_path_1 = __importDefault(require("node:path"));
|
|
18
18
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
19
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
19
20
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
20
21
|
const installProfiles = require("./install-profiles.cjs");
|
|
21
22
|
const { stageSkillsForProfile, stageAgentsForProfile, stageSkillsForRuntimeAsSkills, stageCommandsForRuntimeFlat, } = installProfiles;
|
|
@@ -129,7 +130,7 @@ function findAgentsSourceRoot(runtimeConfigDir) {
|
|
|
129
130
|
const ALLOWED_RUNTIMES = new Set([
|
|
130
131
|
'claude', 'cursor', 'gemini', 'codex', 'copilot', 'antigravity',
|
|
131
132
|
'windsurf', 'augment', 'trae', 'qwen', 'hermes', 'codebuddy',
|
|
132
|
-
'cline', 'opencode', 'kilo',
|
|
133
|
+
'cline', 'kimi', 'opencode', 'kilo',
|
|
133
134
|
]);
|
|
134
135
|
// ---------------------------------------------------------------------------
|
|
135
136
|
// Layout table builders
|
|
@@ -150,6 +151,43 @@ function agentsKind(destSubpath, prefix, configDir) {
|
|
|
150
151
|
stage: (resolved) => stageAgentsForProfile(findAgentsSourceRoot(configDir), resolved),
|
|
151
152
|
};
|
|
152
153
|
}
|
|
154
|
+
function kimiAgentsKind(destSubpath, prefix, configDir) {
|
|
155
|
+
return {
|
|
156
|
+
kind: 'kimi-agents',
|
|
157
|
+
destSubpath,
|
|
158
|
+
prefix,
|
|
159
|
+
stage: (resolved) => {
|
|
160
|
+
const installExports = getInstallExports();
|
|
161
|
+
const buildKimiAgentArtifacts = installExports['buildKimiAgentArtifacts'];
|
|
162
|
+
const stagedAgents = stageAgentsForProfile(findAgentsSourceRoot(configDir), resolved);
|
|
163
|
+
const subagents = [];
|
|
164
|
+
if (node_fs_1.default.existsSync(stagedAgents)) {
|
|
165
|
+
for (const entry of node_fs_1.default.readdirSync(stagedAgents, { withFileTypes: true })) {
|
|
166
|
+
if (!entry.isFile() || !entry.name.endsWith('.md'))
|
|
167
|
+
continue;
|
|
168
|
+
const agentPath = node_path_1.default.join(stagedAgents, entry.name);
|
|
169
|
+
subagents.push({
|
|
170
|
+
path: node_path_1.default.join('agents', entry.name).replace(/\\/g, '/'),
|
|
171
|
+
content: node_fs_1.default.readFileSync(agentPath, 'utf8'),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const rootAgent = `---\nname: gsd\ndescription: Run GSD workflows in Kimi CLI.\ntools: Agent\n---\n\n# GSD for Kimi CLI\n\nCoordinate installed /skill:gsd-* workflows and route work to generated GSD subagents when a workflow requires an agent handoff.\n`;
|
|
176
|
+
const artifacts = buildKimiAgentArtifacts({ rootAgent, subagents });
|
|
177
|
+
const stageDir = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), 'gsd-kimi-agents-'));
|
|
178
|
+
installProfiles.STAGED_DIRS.add(stageDir);
|
|
179
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(stageDir, 'gsd.yaml'), artifacts.root.yaml);
|
|
180
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(stageDir, 'gsd.md'), artifacts.root.prompt);
|
|
181
|
+
const subagentsDir = node_path_1.default.join(stageDir, 'subagents');
|
|
182
|
+
node_fs_1.default.mkdirSync(subagentsDir, { recursive: true });
|
|
183
|
+
for (const artifact of artifacts.subagents) {
|
|
184
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(subagentsDir, `${artifact.name}.yaml`), artifact.yaml);
|
|
185
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(subagentsDir, `${artifact.name}.md`), artifact.prompt);
|
|
186
|
+
}
|
|
187
|
+
return stageDir;
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
}
|
|
153
191
|
/**
|
|
154
192
|
* Build a skills kind descriptor.
|
|
155
193
|
*
|
|
@@ -158,8 +196,9 @@ function agentsKind(destSubpath, prefix, configDir) {
|
|
|
158
196
|
* @param converterName name of converter function in bin/install.js exports
|
|
159
197
|
* @param runtime canonical runtime ID (gates Hermes/Qwen branding in converter)
|
|
160
198
|
* @param configDir runtime config dir (for .gsd-source marker resolution)
|
|
199
|
+
* @param nested if true, nest concrete skills under their ns-* routers (#69)
|
|
161
200
|
*/
|
|
162
|
-
function skillsKind(destSubpath, prefix, converterName, runtime, configDir) {
|
|
201
|
+
function skillsKind(destSubpath, prefix, converterName, runtime, configDir, nested = false) {
|
|
163
202
|
return {
|
|
164
203
|
kind: 'skills',
|
|
165
204
|
destSubpath,
|
|
@@ -171,7 +210,7 @@ function skillsKind(destSubpath, prefix, converterName, runtime, configDir) {
|
|
|
171
210
|
// Extra args are ignored by converters that don't need runtime/cmdNames.
|
|
172
211
|
const cmdNames = installExports.readGsdCommandNames();
|
|
173
212
|
const wrappedConverter = (content, skillName) => realConverter(content, skillName, runtime, cmdNames);
|
|
174
|
-
return stageSkillsForRuntimeAsSkills(findInstallSourceRoot(configDir), resolved, wrappedConverter, prefix);
|
|
213
|
+
return stageSkillsForRuntimeAsSkills(findInstallSourceRoot(configDir), resolved, wrappedConverter, prefix, nested);
|
|
175
214
|
},
|
|
176
215
|
};
|
|
177
216
|
}
|
|
@@ -206,6 +245,42 @@ function convertedCommandsKind(destSubpath, prefix, converterName, configDir) {
|
|
|
206
245
|
// ---------------------------------------------------------------------------
|
|
207
246
|
// Public API
|
|
208
247
|
// ---------------------------------------------------------------------------
|
|
248
|
+
// ---------------------------------------------------------------------------
|
|
249
|
+
// Nested skill-bundle support matrix (#69)
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
//
|
|
252
|
+
// When a runtime's skill loader scans only one level deep (non-recursive), a
|
|
253
|
+
// concrete skill nested at `<router>/skills/<name>/SKILL.md` drops out of the
|
|
254
|
+
// eager top-level listing yet stays readable by file path — which is exactly
|
|
255
|
+
// what namespace routing needs. Recursive loaders surface every nested SKILL.md
|
|
256
|
+
// as a peer (zero token saving), so they stay flat. Unconfirmed loaders stay
|
|
257
|
+
// flat conservatively. Verified June 2026:
|
|
258
|
+
//
|
|
259
|
+
// NEST (confirmed non-recursive / one-level scan):
|
|
260
|
+
// cline — cline/cline skills.ts scanSkillsDirectory uses flat fs.readdir
|
|
261
|
+
// qwen — QwenLM/qwen-code skill-load.ts flat readdir ("depth 2 enough")
|
|
262
|
+
// hermes — hermes-agent.nousresearch.com/docs/user-guide/features/skills
|
|
263
|
+
// (single-level subdir probe of the tap path)
|
|
264
|
+
// augment — https://docs.augmentcode.com/cli/skills (flat single-level)
|
|
265
|
+
// trae — docs.trae.ai/ide/skills + Trae-AI/TRAE#2253 (flat; nesting errors)
|
|
266
|
+
// antigravity— discuss.ai.google.dev/t/more-antigravity-issues/145875 ("will not recursive scan")
|
|
267
|
+
//
|
|
268
|
+
// FLAT (recursive loader → nesting gives no saving):
|
|
269
|
+
// cursor — https://cursor.com/docs/skills (walks skills root recursively)
|
|
270
|
+
// opencode — sst/opencode skill/index.ts glob "skills/**/SKILL.md"
|
|
271
|
+
// kilo — Kilo-Org/kilocode (opencode fork, same ** glob)
|
|
272
|
+
//
|
|
273
|
+
// FLAT (reverted from nested — nested skills not discoverable by Skill tool, #924):
|
|
274
|
+
// claude — https://code.claude.com/docs/en/skills + anthropics/claude-code#28266
|
|
275
|
+
// (one-level scan under ~/.claude/skills — but Skill-tool errors on unknown
|
|
276
|
+
// names rather than re-routing via the router; concrete skills must be
|
|
277
|
+
// at the top level so Skill(skill="gsd-plan-phase") succeeds)
|
|
278
|
+
//
|
|
279
|
+
// FLAT (nested-scan behaviour unconfirmed → conservative):
|
|
280
|
+
// codex — developers.openai.com/codex/skills/
|
|
281
|
+
// copilot — docs.github.com/en/copilot/concepts/agents/about-agent-skills
|
|
282
|
+
// windsurf — docs.devin.ai/desktop/cascade/skills
|
|
283
|
+
// codebuddy — codebuddy.ai/docs/cli/skills
|
|
209
284
|
/**
|
|
210
285
|
* Resolve the artifact layout for a given runtime and config directory.
|
|
211
286
|
*/
|
|
@@ -252,7 +327,7 @@ function resolveRuntimeArtifactLayout(runtime, configDir, scope = 'global') {
|
|
|
252
327
|
kinds = [skillsKind('skills', 'gsd-', 'convertClaudeCommandToCopilotSkill', 'copilot', configDir)];
|
|
253
328
|
break;
|
|
254
329
|
case 'antigravity':
|
|
255
|
-
kinds = [skillsKind('skills', 'gsd-', 'convertClaudeCommandToAntigravitySkill', 'antigravity', configDir)];
|
|
330
|
+
kinds = [skillsKind('skills', 'gsd-', 'convertClaudeCommandToAntigravitySkill', 'antigravity', configDir, true /* #69 nested */)];
|
|
256
331
|
break;
|
|
257
332
|
case 'windsurf':
|
|
258
333
|
kinds = [skillsKind('skills', 'gsd-', 'convertClaudeCommandToWindsurfSkill', 'windsurf', configDir)];
|
|
@@ -260,17 +335,21 @@ function resolveRuntimeArtifactLayout(runtime, configDir, scope = 'global') {
|
|
|
260
335
|
case 'augment':
|
|
261
336
|
kinds = [
|
|
262
337
|
commandsKind('commands', 'gsd-', configDir),
|
|
263
|
-
skillsKind('skills', 'gsd-', 'convertClaudeCommandToAugmentSkill', 'augment', configDir),
|
|
338
|
+
skillsKind('skills', 'gsd-', 'convertClaudeCommandToAugmentSkill', 'augment', configDir, true /* #69 nested */),
|
|
264
339
|
];
|
|
265
340
|
break;
|
|
266
341
|
case 'trae':
|
|
267
|
-
kinds = [skillsKind('skills', 'gsd-', 'convertClaudeCommandToTraeSkill', 'trae', configDir)];
|
|
342
|
+
kinds = [skillsKind('skills', 'gsd-', 'convertClaudeCommandToTraeSkill', 'trae', configDir, true /* #69 nested */)];
|
|
268
343
|
break;
|
|
269
344
|
case 'qwen':
|
|
270
|
-
kinds = [skillsKind('skills', 'gsd-', 'convertClaudeCommandToClaudeSkill', 'qwen', configDir)];
|
|
345
|
+
kinds = [skillsKind('skills', 'gsd-', 'convertClaudeCommandToClaudeSkill', 'qwen', configDir, true /* #69 nested */)];
|
|
271
346
|
break;
|
|
272
347
|
case 'hermes':
|
|
273
|
-
|
|
348
|
+
// #947: restore canonical gsd- prefix — skills land at skills/gsd/gsd-<stem>/SKILL.md
|
|
349
|
+
// and dispatch as /gsd-<stem>, consistent with every other runtime.
|
|
350
|
+
// The skills/gsd/ category bucket (introduced by #2841) is retained.
|
|
351
|
+
// Prior bare-stem layout (prefix='') used by #3664 is reversed here.
|
|
352
|
+
kinds = [skillsKind('skills/gsd', 'gsd-', 'convertClaudeCommandToClaudeSkill', 'hermes', configDir, true /* #69 nested */)];
|
|
274
353
|
break;
|
|
275
354
|
case 'codebuddy':
|
|
276
355
|
// CodeBuddy (Tencent) reads two user-level surfaces (codebuddy.ai/docs/cli):
|
|
@@ -286,7 +365,15 @@ function resolveRuntimeArtifactLayout(runtime, configDir, scope = 'global') {
|
|
|
286
365
|
];
|
|
287
366
|
break;
|
|
288
367
|
case 'cline':
|
|
289
|
-
kinds = scope === 'global' ? [skillsKind('skills', 'gsd-', 'convertClaudeCommandToClineSkill', 'cline', configDir)] : [];
|
|
368
|
+
kinds = scope === 'global' ? [skillsKind('skills', 'gsd-', 'convertClaudeCommandToClineSkill', 'cline', configDir, true /* #69 nested */)] : [];
|
|
369
|
+
break;
|
|
370
|
+
case 'kimi':
|
|
371
|
+
kinds = scope === 'global'
|
|
372
|
+
? [
|
|
373
|
+
skillsKind('skills', 'gsd-', 'convertClaudeCommandToKimiSkill', 'kimi', configDir),
|
|
374
|
+
kimiAgentsKind('agents', 'gsd', configDir),
|
|
375
|
+
]
|
|
376
|
+
: [];
|
|
290
377
|
break;
|
|
291
378
|
case 'opencode':
|
|
292
379
|
// OpenCode reads flat slash commands from command/ and on-demand skills
|
|
@@ -18,11 +18,12 @@ const REGISTRY = Object.freeze({
|
|
|
18
18
|
cursor: Object.freeze({ installSurface: 'cursor-hooks-json', writesSharedSettings: false, finishPermissionWriter: null }),
|
|
19
19
|
windsurf: Object.freeze({ installSurface: 'profile-marker-only', writesSharedSettings: false, finishPermissionWriter: null }),
|
|
20
20
|
trae: Object.freeze({ installSurface: 'profile-marker-only', writesSharedSettings: false, finishPermissionWriter: null }),
|
|
21
|
+
kimi: Object.freeze({ installSurface: 'profile-marker-only', writesSharedSettings: false, finishPermissionWriter: null }),
|
|
21
22
|
});
|
|
22
23
|
// ---------------------------------------------------------------------------
|
|
23
24
|
// Exports
|
|
24
25
|
// ---------------------------------------------------------------------------
|
|
25
|
-
/** The complete set of
|
|
26
|
+
/** The complete set of 16 supported runtimes for config-adapter dispatch. */
|
|
26
27
|
const ALLOWED_CONFIG_RUNTIMES = new Set(Object.keys(REGISTRY));
|
|
27
28
|
/** All valid installSurface values. */
|
|
28
29
|
const INSTALL_SURFACES = Object.freeze([
|
|
@@ -15,12 +15,18 @@
|
|
|
15
15
|
* cline — Skills-capable since v3.48.0 (#782). SKILL.md files live at
|
|
16
16
|
* ~/.cline/skills/<skillName>/SKILL.md (same flat layout as cursor/codex).
|
|
17
17
|
* .clinerules is also emitted (rules-based compatibility layer).
|
|
18
|
+
* kimi — Agent Skills are discovered from Kimi's generic user roots:
|
|
19
|
+
* ~/.config/agents/skills (recommended) then ~/.agents/skills,
|
|
20
|
+
* with Kimi selecting the first existing generic skills directory.
|
|
21
|
+
* ~/.kimi-code/skills is brand-specific and can be selected as a
|
|
22
|
+
* GSD write target with --config-dir or KIMI_CONFIG_DIR.
|
|
18
23
|
*/
|
|
19
24
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
20
25
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
21
26
|
};
|
|
22
27
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
28
|
exports.resolveAntigravityGlobalDir = resolveAntigravityGlobalDir;
|
|
29
|
+
exports.resolveKimiGlobalDir = resolveKimiGlobalDir;
|
|
24
30
|
exports.getGlobalConfigDir = getGlobalConfigDir;
|
|
25
31
|
exports.getGlobalSkillsBase = getGlobalSkillsBase;
|
|
26
32
|
exports.getGlobalSkillDir = getGlobalSkillDir;
|
|
@@ -59,6 +65,36 @@ function resolveAntigravityGlobalDir(opts = {}) {
|
|
|
59
65
|
}
|
|
60
66
|
return node_path_1.default.join(base, 'antigravity');
|
|
61
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Resolve Kimi's generic user root using Kimi CLI's documented first-existing
|
|
70
|
+
* generic skills directory policy:
|
|
71
|
+
*
|
|
72
|
+
* 1. ~/.config/agents/skills (recommended)
|
|
73
|
+
* 2. ~/.agents/skills
|
|
74
|
+
*
|
|
75
|
+
* If neither generic skills directory exists yet, install to the recommended
|
|
76
|
+
* ~/.config/agents root so the generated skills become the first generic
|
|
77
|
+
* candidate Kimi discovers.
|
|
78
|
+
*
|
|
79
|
+
* KIMI_CONFIG_DIR is a GSD installer write-location override. It is not Kimi's
|
|
80
|
+
* upstream data-root variable, and arbitrary roots are discoverable by Kimi only
|
|
81
|
+
* when the user also configures Kimi --skills-dir or extra_skill_dirs.
|
|
82
|
+
*/
|
|
83
|
+
function resolveKimiGlobalDir(opts = {}) {
|
|
84
|
+
const env = opts.env ?? process.env;
|
|
85
|
+
const home = opts.home ?? node_os_1.default.homedir();
|
|
86
|
+
const existsSyncFn = opts.existsSync ?? node_fs_1.default.existsSync;
|
|
87
|
+
if (env['KIMI_CONFIG_DIR'])
|
|
88
|
+
return expandTilde(env['KIMI_CONFIG_DIR']);
|
|
89
|
+
const recommendedRoot = node_path_1.default.join(home, '.config', 'agents');
|
|
90
|
+
const fallbackRoot = node_path_1.default.join(home, '.agents');
|
|
91
|
+
const candidates = [recommendedRoot, fallbackRoot];
|
|
92
|
+
for (const candidate of candidates) {
|
|
93
|
+
if (existsSyncFn(node_path_1.default.join(candidate, 'skills')))
|
|
94
|
+
return candidate;
|
|
95
|
+
}
|
|
96
|
+
return recommendedRoot;
|
|
97
|
+
}
|
|
62
98
|
/**
|
|
63
99
|
* Return the global config base directory for the given runtime.
|
|
64
100
|
* Respects the same env-var overrides as bin/install.js getGlobalDir().
|
|
@@ -122,6 +158,10 @@ function getGlobalConfigDir(runtime, explicitDir) {
|
|
|
122
158
|
// ── Cline ────────────────────────────────────────────────────────────────
|
|
123
159
|
case 'cline':
|
|
124
160
|
return env['CLINE_CONFIG_DIR'] ? expandTilde(env['CLINE_CONFIG_DIR']) : node_path_1.default.join(home, '.cline');
|
|
161
|
+
// ── Kimi CLI (generic agents user root) ────────────────────────────────
|
|
162
|
+
case 'kimi': {
|
|
163
|
+
return resolveKimiGlobalDir({ env, home });
|
|
164
|
+
}
|
|
125
165
|
// ── OpenCode (XDG) ───────────────────────────────────────────────────────
|
|
126
166
|
case 'opencode': {
|
|
127
167
|
if (env['OPENCODE_CONFIG_DIR'])
|
|
@@ -232,6 +232,19 @@ function isManagedHookCommand(commandText, opts = {}) {
|
|
|
232
232
|
const managedBasenames = managedHookCommandSurfaceSet(surface, includeLegacyAliases);
|
|
233
233
|
if (!managedBasenames || managedBasenames.size === 0)
|
|
234
234
|
return false;
|
|
235
|
+
// args-form check: the managed hook filename may appear in args[] rather than
|
|
236
|
+
// in command when a windowless launcher wraps the Node invocation. (#976)
|
|
237
|
+
// Only treat as managed when an arg basename matches the managed hook set —
|
|
238
|
+
// prevents false-positives for non-GSD entries that happen to share a path segment.
|
|
239
|
+
if (Array.isArray(opts.args) && opts.args.length > 0) {
|
|
240
|
+
for (const arg of opts.args) {
|
|
241
|
+
if (typeof arg !== 'string')
|
|
242
|
+
continue;
|
|
243
|
+
const argBasename = arg.replace(/\\/g, '/').split('/').pop() || '';
|
|
244
|
+
if (isManagedHookBasename(argBasename, { surface }))
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
235
248
|
const normalizedCommand = commandText.replace(/\\/g, '/');
|
|
236
249
|
if (typeof opts.configDir === 'string' && opts.configDir.length > 0) {
|
|
237
250
|
const normalizedHooksDir = `${node_path_1.default.join(opts.configDir, 'hooks').replace(/\\/g, '/')}/`;
|
|
@@ -607,6 +607,7 @@ function cmdStateRecordSession(cwd, options, raw) {
|
|
|
607
607
|
}
|
|
608
608
|
const now = clock_cjs_1.realClock.nowIso();
|
|
609
609
|
const updated = [];
|
|
610
|
+
let sessionCreated = false;
|
|
610
611
|
readModifyWriteStateMd(statePath, (content) => {
|
|
611
612
|
// Update Last session / Last Date
|
|
612
613
|
let result = (0, state_document_cjs_1.stateReplaceField)(content, 'Last session', now);
|
|
@@ -661,10 +662,80 @@ function cmdStateRecordSession(cwd, options, raw) {
|
|
|
661
662
|
}
|
|
662
663
|
}
|
|
663
664
|
}
|
|
665
|
+
// Bug #944: DWIM normalize/auto-create — when the caller supplied --stopped-at or
|
|
666
|
+
// --resume-file but the body lacks the canonical labels (in-place replace
|
|
667
|
+
// returned a miss), persist the values durably. Mirrors the DWIM pattern used
|
|
668
|
+
// by add-decision, add-blocker, and record-metric. Never silently drop
|
|
669
|
+
// caller-supplied values.
|
|
670
|
+
//
|
|
671
|
+
// Guard: only act when the caller actually supplied a value. When no
|
|
672
|
+
// --stopped-at / --resume-file are given and the body already had no session
|
|
673
|
+
// labels (nothing was updated), we return recorded:false — the existing
|
|
674
|
+
// behaviour for a no-op call that didn't supply any values.
|
|
675
|
+
//
|
|
676
|
+
// Correctness invariant: both buildStateFrontmatter and cmdStateSnapshot read
|
|
677
|
+
// only the FIRST `## Session` block (via a /##\s*Session\s*\n…/i regex).
|
|
678
|
+
// If we blindly append a second `## Session` block when one already exists, the
|
|
679
|
+
// newly-written Stopped at / Resume file end up in the second (invisible) block.
|
|
680
|
+
// Fix: when a `## Session` heading already exists, normalize THAT block in place
|
|
681
|
+
// (insert / replace canonical bold-label lines within the existing section).
|
|
682
|
+
// Only append a brand-new section when NO `## Session` heading exists at all.
|
|
683
|
+
const callerSuppliedValues = !!(options.stopped_at || (options.resume_file !== undefined && options.resume_file !== null));
|
|
684
|
+
const needsStoppedAt = options.stopped_at && !updated.includes('Stopped At');
|
|
685
|
+
const needsResumeFile = options.resume_file !== undefined && options.resume_file !== null && !updated.includes('Resume File');
|
|
686
|
+
const needsLastSession = !updated.includes('Last session') && !updated.includes('Last Date');
|
|
687
|
+
if (callerSuppliedValues && (needsStoppedAt || needsResumeFile || needsLastSession)) {
|
|
688
|
+
const resumeValue = (options.resume_file !== undefined && options.resume_file !== null)
|
|
689
|
+
? options.resume_file
|
|
690
|
+
: 'None';
|
|
691
|
+
const stoppedAtValue = options.stopped_at || 'None';
|
|
692
|
+
// Determine whether a ## Session heading already exists in the body.
|
|
693
|
+
const existingSessionHeading = /^## Session\s*$/im.test(content);
|
|
694
|
+
if (existingSessionHeading) {
|
|
695
|
+
// Normalize in place: replace the ENTIRE BODY of the existing ## Session
|
|
696
|
+
// section (heading + all content up to the next ## heading or EOF) with
|
|
697
|
+
// canonical bold-label lines. The negative-lookahead per-line pattern
|
|
698
|
+
// `(?!^## )[\s\S]` consumes every line that doesn't start with "## ",
|
|
699
|
+
// which correctly stops at the next section boundary without consuming it.
|
|
700
|
+
// A trailing blank line is added so the next ## heading keeps its spacing.
|
|
701
|
+
content = content.replace(/^(## Session[ \t]*\n(?:(?!^## )[\s\S])*)/m, [
|
|
702
|
+
'## Session',
|
|
703
|
+
'',
|
|
704
|
+
`**Last session:** ${now}`,
|
|
705
|
+
`**Stopped at:** ${stoppedAtValue}`,
|
|
706
|
+
`**Resume file:** ${resumeValue}`,
|
|
707
|
+
'',
|
|
708
|
+
'',
|
|
709
|
+
].join('\n'));
|
|
710
|
+
}
|
|
711
|
+
else {
|
|
712
|
+
// No ## Session heading exists at all — append a new canonical section.
|
|
713
|
+
const scaffold = [
|
|
714
|
+
'',
|
|
715
|
+
'## Session',
|
|
716
|
+
'',
|
|
717
|
+
`**Last session:** ${now}`,
|
|
718
|
+
`**Stopped at:** ${stoppedAtValue}`,
|
|
719
|
+
`**Resume file:** ${resumeValue}`,
|
|
720
|
+
'',
|
|
721
|
+
].join('\n');
|
|
722
|
+
content = content.trimEnd() + '\n' + scaffold;
|
|
723
|
+
}
|
|
724
|
+
sessionCreated = true;
|
|
725
|
+
if (needsLastSession)
|
|
726
|
+
updated.push('Last session');
|
|
727
|
+
if (needsStoppedAt)
|
|
728
|
+
updated.push('Stopped At');
|
|
729
|
+
if (needsResumeFile)
|
|
730
|
+
updated.push('Resume File');
|
|
731
|
+
}
|
|
664
732
|
return content;
|
|
665
733
|
}, cwd);
|
|
666
734
|
if (updated.length > 0) {
|
|
667
|
-
|
|
735
|
+
const result = { recorded: true, updated };
|
|
736
|
+
if (sessionCreated)
|
|
737
|
+
result['created'] = true;
|
|
738
|
+
output(result, raw, 'true');
|
|
668
739
|
}
|
|
669
740
|
else {
|
|
670
741
|
output({ recorded: false, reason: 'No session fields found in STATE.md' }, raw, 'false');
|
|
@@ -748,8 +819,12 @@ function cmdStateSnapshot(cwd, raw) {
|
|
|
748
819
|
const sessionMatch = body.match(/##\s*Session\s*\n([\s\S]*?)(?=\n##|$)/i);
|
|
749
820
|
if (sessionMatch) {
|
|
750
821
|
const sessionSection = sessionMatch[1];
|
|
822
|
+
// Accept both `**Last Date:**` (canonical template form) and `**Last session:**`
|
|
823
|
+
// (the form written by the DWIM auto-create / normalize path added for #944).
|
|
751
824
|
const lastDateMatch = sessionSection.match(/\*\*Last Date:\*\*\s*(.+)/i)
|
|
752
|
-
|| sessionSection.match(/^Last Date:\s*(.+)/im)
|
|
825
|
+
|| sessionSection.match(/^Last Date:\s*(.+)/im)
|
|
826
|
+
|| sessionSection.match(/\*\*Last session:\*\*\s*(.+)/i)
|
|
827
|
+
|| sessionSection.match(/^Last session:\s*(.+)/im);
|
|
753
828
|
const stoppedAtMatch = sessionSection.match(/\*\*Stopped At:\*\*\s*(.+)/i)
|
|
754
829
|
|| sessionSection.match(/^Stopped At:\s*(.+)/im);
|
|
755
830
|
const resumeFileMatch = sessionSection.match(/\*\*Resume File:\*\*\s*(.+)/i)
|
|
@@ -974,12 +1049,39 @@ function syncStateFrontmatter(content, cwd) {
|
|
|
974
1049
|
if (derivedFm['status'] === 'unknown' && existingFm['status'] && existingFm['status'] !== 'unknown') {
|
|
975
1050
|
derivedFm['status'] = existingFm['status'];
|
|
976
1051
|
}
|
|
1052
|
+
// Bug #948: preserve `milestone_name` / `milestone` when the derived value
|
|
1053
|
+
// is the template placeholder 'milestone'. getMilestoneInfo returns the
|
|
1054
|
+
// literal string 'milestone' when it cannot match the version from the roadmap
|
|
1055
|
+
// (e.g. no ROADMAP.md, roadmap lacks the heading for the stored version, or the
|
|
1056
|
+
// milestone version read from STATE.md itself triggers the lookup before the
|
|
1057
|
+
// file is fully written). A placeholder must never overwrite a real name that the
|
|
1058
|
+
// existing frontmatter already holds; only an empty derived value falls through
|
|
1059
|
+
// to this guard (the primary #905 preserve path below handles that).
|
|
1060
|
+
const MILESTONE_NAME_PLACEHOLDER = 'milestone';
|
|
1061
|
+
if (derivedFm['milestone_name'] === MILESTONE_NAME_PLACEHOLDER &&
|
|
1062
|
+
existingFm['milestone_name'] &&
|
|
1063
|
+
existingFm['milestone_name'] !== MILESTONE_NAME_PLACEHOLDER) {
|
|
1064
|
+
derivedFm['milestone_name'] = existingFm['milestone_name'];
|
|
1065
|
+
// Keep the stored milestone version consistent with the preserved name.
|
|
1066
|
+
if (existingFm['milestone']) {
|
|
1067
|
+
derivedFm['milestone'] = existingFm['milestone'];
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
977
1070
|
// Bug #905: preserve scalar fields that buildStateFrontmatter can only derive
|
|
978
1071
|
// from body annotations (Current Phase:, Current Plan:, etc.). When those
|
|
979
1072
|
// annotations are absent — e.g. after an agent or tool rewrites the body —
|
|
980
1073
|
// buildStateFrontmatter returns no value for those keys. Mirror the same
|
|
981
1074
|
// fallback pattern used in cmdStateJson so the existing frontmatter values
|
|
982
1075
|
// survive every writeStateMd call.
|
|
1076
|
+
//
|
|
1077
|
+
// For stopped_at / paused_at: the original #905 "fall back when derived is
|
|
1078
|
+
// absent" rule is preserved here. The stale-body-overwrites-frontmatter
|
|
1079
|
+
// scenario from #948 is prevented by the no-op guard in
|
|
1080
|
+
// readModifyWriteStateMd: when the transform produces no change the file is
|
|
1081
|
+
// never written, so syncStateFrontmatter never even runs. Attempting to
|
|
1082
|
+
// "always prefer frontmatter" here breaks legitimate callers like phase.complete
|
|
1083
|
+
// that intentionally write a new stopped_at value to the body and expect
|
|
1084
|
+
// syncStateFrontmatter to pick it up.
|
|
983
1085
|
if (!derivedFm['stopped_at'] && existingFm['stopped_at']) {
|
|
984
1086
|
derivedFm['stopped_at'] = existingFm['stopped_at'];
|
|
985
1087
|
}
|
|
@@ -1155,6 +1257,16 @@ function readModifyWriteStateMd(statePath, transformFn, cwd, options, clock) {
|
|
|
1155
1257
|
// restore it when resync is false.
|
|
1156
1258
|
const preFm = resync ? null : extractFrontmatter(content);
|
|
1157
1259
|
const modified = transformFn(content);
|
|
1260
|
+
// Bug #948: no-op guard — if the transform produced no change, do NOT write
|
|
1261
|
+
// the file. An unconditional write would bump `last_updated`, reset
|
|
1262
|
+
// `milestone_name` to the template placeholder, and resurrect stale
|
|
1263
|
+
// body-derived `stopped_at` values via syncStateFrontmatter. Skipping the
|
|
1264
|
+
// write when content is unchanged is safe because every caller that mutates
|
|
1265
|
+
// content already returns the mutated string, and callers that detect a
|
|
1266
|
+
// no-op explicitly return the original content unchanged.
|
|
1267
|
+
if (modified === content) {
|
|
1268
|
+
return;
|
|
1269
|
+
}
|
|
1158
1270
|
let synced = syncStateFrontmatter(modified, cwd);
|
|
1159
1271
|
if (!resync && preFm && preFm['progress']) {
|
|
1160
1272
|
// Re-apply the curated progress block that syncStateFrontmatter just
|
|
@@ -12,11 +12,18 @@
|
|
|
12
12
|
* Exports:
|
|
13
13
|
* readSurface(runtimeConfigDir)
|
|
14
14
|
* writeSurface(runtimeConfigDir, surfaceState)
|
|
15
|
-
* resolveSurface(runtimeConfigDir, manifest, clusterMap)
|
|
16
|
-
* applySurface(runtimeConfigDir, layout, manifest, clusterMap)
|
|
17
|
-
* listSurface(runtimeConfigDir, manifest, clusterMap)
|
|
15
|
+
* resolveSurface(runtimeConfigDir, manifest, clusterMap?, registry?)
|
|
16
|
+
* applySurface(runtimeConfigDir, layout, manifest, clusterMap?, registry?)
|
|
17
|
+
* listSurface(runtimeConfigDir, manifest, clusterMap?, registry?)
|
|
18
18
|
* pruneSkillDirs(skillsDir, retainedNames, prefix, manifest)
|
|
19
19
|
*
|
|
20
|
+
* The optional `registry` param (ADR-857 phase 4c) accepts the capability-registry
|
|
21
|
+
* object. When present, capability clusters are merged into the effective cluster
|
|
22
|
+
* map and the registry is threaded into resolveProfile so capability-contributed
|
|
23
|
+
* skills participate in the base set and disable-ability. Absent or undefined
|
|
24
|
+
* leaves behaviour identical to the pre-registry path (no-op for current registry
|
|
25
|
+
* where UI=full and the full profile returns '*' regardless).
|
|
26
|
+
*
|
|
20
27
|
* ADR-457 build-at-publish: the hand-written bin/lib/surface.cjs collapsed
|
|
21
28
|
* to a TypeScript source of truth. Behaviour is preserved byte-for-behaviour
|
|
22
29
|
* from the prior hand-written .cjs; only types are added.
|
|
@@ -86,10 +93,11 @@ function clustersToSkills(clusterNames, clusterMap) {
|
|
|
86
93
|
const result = new Set();
|
|
87
94
|
for (const name of clusterNames) {
|
|
88
95
|
const members = clusterMap[name];
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
96
|
+
// FIX 5: guard against non-iterable members — malformed registry must never throw
|
|
97
|
+
if (!Array.isArray(members))
|
|
98
|
+
continue;
|
|
99
|
+
for (const s of members)
|
|
100
|
+
result.add(s);
|
|
93
101
|
}
|
|
94
102
|
return result;
|
|
95
103
|
}
|
|
@@ -132,19 +140,63 @@ function normalizeSkillManifest(runtimeConfigDir, manifest) {
|
|
|
132
140
|
* 2. Remove skills in disabled clusters
|
|
133
141
|
* 3. Add explicitAdds (and their transitive closure)
|
|
134
142
|
* 4. Remove explicitRemoves (only the stem itself, no cascade)
|
|
143
|
+
*
|
|
144
|
+
* ADR-857 phase 4c: optional registry param. When present:
|
|
145
|
+
* - capability clusters are merged into the effective cluster map so
|
|
146
|
+
* capability-owned skill groups are disable-able.
|
|
147
|
+
* - registry is threaded into resolveProfile so capability skills
|
|
148
|
+
* participate in the base skill set and their requires: chains expand.
|
|
135
149
|
*/
|
|
136
|
-
function resolveSurface(runtimeConfigDir, manifest, clusterMap) {
|
|
137
|
-
|
|
150
|
+
function resolveSurface(runtimeConfigDir, manifest, clusterMap, registry) {
|
|
151
|
+
// Merge capability clusters into the cluster map when registry is provided.
|
|
152
|
+
// The ADR-857 phase 4a HARD gate guarantees that when a capId matches a CLUSTERS
|
|
153
|
+
// key, the values are EQUAL — so the spread is idempotent for matching names.
|
|
154
|
+
// Defense-in-depth: if a capId collides with a hand-authored CLUSTERS key AND
|
|
155
|
+
// the values DIFFER (future drift bypassing the gate), prefer the hand-authored
|
|
156
|
+
// value so disable behavior is never silently changed by a stale registry entry.
|
|
157
|
+
// Also guard: skip entries whose value is not a string[] (malformed registry).
|
|
158
|
+
let cm = clusterMap || clusters_cjs_1.CLUSTERS;
|
|
159
|
+
if (registry && registry.capabilityClusters && typeof registry.capabilityClusters === 'object') {
|
|
160
|
+
const baseCm = cm;
|
|
161
|
+
const capClusters = registry.capabilityClusters;
|
|
162
|
+
const merged = { ...baseCm };
|
|
163
|
+
for (const capId of Object.keys(capClusters)) {
|
|
164
|
+
const val = capClusters[capId];
|
|
165
|
+
// FIX 5: skip malformed (non-array) entries — never throw on bad registry
|
|
166
|
+
if (!Array.isArray(val))
|
|
167
|
+
continue;
|
|
168
|
+
// FIX 4: if the capId matches an existing cluster key, only override when
|
|
169
|
+
// the values are identical (guaranteed by 4a gate). If they differ, the
|
|
170
|
+
// hand-authored value wins — prefer known-correct disable behavior over
|
|
171
|
+
// a potentially stale registry entry.
|
|
172
|
+
if (Object.prototype.hasOwnProperty.call(baseCm, capId)) {
|
|
173
|
+
const existing = baseCm[capId];
|
|
174
|
+
if (!Array.isArray(existing)) {
|
|
175
|
+
merged[capId] = val;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
// Values differ → hand-authored wins (skip the override)
|
|
179
|
+
if (existing.length !== val.length || existing.some((v, i) => v !== val[i]))
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
// Prototype-pollution guard (parity with _capabilitySkillsForMode in install-profiles.cts)
|
|
183
|
+
if (capId === '__proto__' || capId === 'constructor' || capId === 'prototype')
|
|
184
|
+
continue;
|
|
185
|
+
merged[capId] = val;
|
|
186
|
+
}
|
|
187
|
+
cm = merged;
|
|
188
|
+
}
|
|
138
189
|
const skillManifest = normalizeSkillManifest(runtimeConfigDir, manifest);
|
|
139
190
|
const surface = readSurface(runtimeConfigDir);
|
|
140
191
|
// Determine base profile name: from surface state or from .gsd-profile marker
|
|
141
192
|
const baseProfileName = (surface && surface.baseProfile)
|
|
142
193
|
? surface.baseProfile
|
|
143
194
|
: (readActiveProfile(runtimeConfigDir) || 'full');
|
|
144
|
-
// Resolve base profile
|
|
195
|
+
// Resolve base profile — thread registry so capability skills are included.
|
|
145
196
|
const baseResolved = resolveProfile({
|
|
146
197
|
modes: baseProfileName.split(',').map((s) => s.trim()),
|
|
147
198
|
manifest: skillManifest,
|
|
199
|
+
registry,
|
|
148
200
|
});
|
|
149
201
|
// If full, we need to enumerate all skills from the manifest
|
|
150
202
|
let skills;
|
|
@@ -205,12 +257,12 @@ function resolveSurface(runtimeConfigDir, manifest, clusterMap) {
|
|
|
205
257
|
* Re-stage the active surface using the resolved layout.
|
|
206
258
|
* Iterates layout.kinds and syncs each artifact kind to its destination.
|
|
207
259
|
*/
|
|
208
|
-
function applySurface(runtimeConfigDir, layout, manifest, clusterMap) {
|
|
260
|
+
function applySurface(runtimeConfigDir, layout, manifest, clusterMap, registry) {
|
|
209
261
|
if (node_path_1.default.resolve(runtimeConfigDir) !== node_path_1.default.resolve(layout.configDir)) {
|
|
210
262
|
throw new TypeError('applySurface runtimeConfigDir must match layout.configDir');
|
|
211
263
|
}
|
|
212
264
|
const skillManifest = normalizeSkillManifest(layout.configDir, manifest);
|
|
213
|
-
const resolved = resolveSurface(layout.configDir, skillManifest, clusterMap);
|
|
265
|
+
const resolved = resolveSurface(layout.configDir, skillManifest, clusterMap, registry);
|
|
214
266
|
// Mirror installRuntimeArtifacts: skills kinds get per-runtime path rewrites
|
|
215
267
|
// so SKILL.md bodies reference the install target (pathPrefix), not the
|
|
216
268
|
// converter's default ~/.claude paths (#813). Computed lazily so command-only
|
|
@@ -409,9 +461,9 @@ function _syncGsdDir(stagedDir, destDir, kind, manifest) {
|
|
|
409
461
|
* Token cost = sum of description lengths ÷ 4 (mirrors audit script).
|
|
410
462
|
* Descriptions are read from the install source (findInstallSourceRoot).
|
|
411
463
|
*/
|
|
412
|
-
function listSurface(runtimeConfigDir, manifest, clusterMap) {
|
|
464
|
+
function listSurface(runtimeConfigDir, manifest, clusterMap, registry) {
|
|
413
465
|
const skillManifest = normalizeSkillManifest(runtimeConfigDir, manifest);
|
|
414
|
-
const resolved = resolveSurface(runtimeConfigDir, skillManifest, clusterMap);
|
|
466
|
+
const resolved = resolveSurface(runtimeConfigDir, skillManifest, clusterMap, registry);
|
|
415
467
|
// All known stems from manifest (exclude _calls_agents_ meta keys)
|
|
416
468
|
const allStems = [];
|
|
417
469
|
for (const [key] of skillManifest) {
|
|
@@ -383,7 +383,7 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
|
|
|
383
383
|
};
|
|
384
384
|
const sourceContent = (0, shell_command_projection_cjs_1.platformReadSync)(node_path_1.default.join(cwd, link['from'] || ''));
|
|
385
385
|
if (!sourceContent) {
|
|
386
|
-
check['detail'] = 'Source file not found';
|
|
386
|
+
check['detail'] = 'Source file not found (from: must be a relative file path; describe components/endpoints in via:)';
|
|
387
387
|
}
|
|
388
388
|
else if (link['pattern']) {
|
|
389
389
|
try {
|
|
@@ -4,6 +4,8 @@ Triggered when orchestrator sets Mode to `reviews`. Replanning from scratch with
|
|
|
4
4
|
|
|
5
5
|
**Mindset:** Fresh planner with review insights — not a surgeon making patches, but an architect who has read peer critiques.
|
|
6
6
|
|
|
7
|
+
**Execution contract:** REVIEWS.md is audit trail and feedback input, not a second execution contract. /gsd:execute-phase primarily consumes PLAN.md plus the normal phase context. Every current actionable review finding must therefore be incorporated into the relevant PLAN.md or explicitly deferred/rejected in that PLAN.md.
|
|
8
|
+
|
|
7
9
|
### Step 1: Load REVIEWS.md
|
|
8
10
|
Read the reviews file from `<files_to_read>`. Parse:
|
|
9
11
|
- Per-reviewer feedback (strengths, concerns, suggestions)
|
|
@@ -13,13 +15,14 @@ Read the reviews file from `<files_to_read>`. Parse:
|
|
|
13
15
|
### Step 2: Categorize Feedback
|
|
14
16
|
Group review feedback into:
|
|
15
17
|
- **Must address**: HIGH severity consensus concerns
|
|
16
|
-
- **
|
|
18
|
+
- **Must represent in PLAN.md**: actionable MEDIUM/LOW findings that require task, action, acceptance criteria, verify command, must_haves, threat-model, artifact, stale-path, or execution-contract changes
|
|
19
|
+
- **Should address**: MEDIUM severity concerns from 2+ reviewers that improve quality but do not change the executable contract
|
|
17
20
|
- **Consider**: Individual reviewer suggestions, LOW severity items
|
|
18
21
|
|
|
19
22
|
### Step 3: Plan Fresh with Review Context
|
|
20
23
|
Create new plans following the standard planning process, but with review feedback as additional constraints:
|
|
21
24
|
- Each HIGH severity consensus concern MUST have a task that addresses it
|
|
22
|
-
- MEDIUM
|
|
25
|
+
- Each current actionable MEDIUM/LOW finding MUST either appear in the relevant PLAN.md executable content or have a deferral/rejection rationale in that PLAN.md
|
|
23
26
|
- Note in task actions: "Addresses review concern: {concern}" for traceability
|
|
24
27
|
|
|
25
28
|
### Step 4: Return
|