@opengsd/gsd-core 1.4.4 → 1.5.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +3 -3
- package/agents/gsd-code-fixer.md +3 -2
- package/agents/gsd-debug-session-manager.md +2 -1
- package/agents/gsd-debugger.md +4 -3
- package/agents/gsd-executor.md +17 -16
- package/agents/gsd-intel-updater.md +38 -41
- package/agents/gsd-phase-researcher.md +8 -8
- package/agents/gsd-plan-checker.md +23 -13
- package/agents/gsd-planner.md +32 -188
- package/agents/gsd-project-researcher.md +5 -4
- package/agents/gsd-research-synthesizer.md +2 -1
- package/agents/gsd-ui-researcher.md +2 -1
- package/agents/gsd-verifier.md +12 -11
- package/bin/install.js +965 -1486
- 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/gsd_run +20 -0
- package/gsd-core/bin/lib/audit-command-router.cjs +61 -0
- package/gsd-core/bin/lib/capability-registry.cjs +2234 -0
- package/gsd-core/bin/lib/capability-state.cjs +336 -0
- package/gsd-core/bin/lib/check-command-router.cjs +133 -2
- 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 +107 -1817
- package/gsd-core/bin/lib/edge-probe.cjs +173 -0
- package/gsd-core/bin/lib/fallow-runner.cjs +63 -25
- 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/intel.cjs +3 -3
- package/gsd-core/bin/lib/io.cjs +222 -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-command-router.cjs +20 -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/phase.cjs +17 -0
- package/gsd-core/bin/lib/probe-core.cjs +257 -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 +44 -1
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +92 -95
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +68 -29
- package/gsd-core/bin/lib/runtime-homes.cjs +163 -87
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +1439 -0
- package/gsd-core/bin/lib/runtime-name-policy.cjs +2 -1
- package/gsd-core/bin/lib/runtime-slash.cjs +7 -2
- package/gsd-core/bin/lib/shell-command-projection.cjs +13 -0
- package/gsd-core/bin/lib/state-document.cjs +8 -0
- package/gsd-core/bin/lib/state.cjs +114 -2
- package/gsd-core/bin/lib/surface.cjs +66 -14
- package/gsd-core/bin/lib/uat-predicate.cjs +329 -0
- package/gsd-core/bin/lib/update-context.cjs +4 -1
- package/gsd-core/bin/lib/verify.cjs +104 -1
- package/gsd-core/bin/lib/worktree-base-ref.cjs +33 -8
- package/gsd-core/bin/shared/model-catalog.json +11 -6
- package/gsd-core/bin/shared/runtime-aliases.manifest.json +5 -1
- package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +7 -0
- package/gsd-core/references/edge-probe-fixtures/01-round-half-even/requirements.json +1 -0
- package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +8 -0
- package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/requirements.json +1 -0
- package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +7 -0
- package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/requirements.json +1 -0
- package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +7 -0
- package/gsd-core/references/edge-probe-fixtures/04-money-rounding/requirements.json +1 -0
- package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +8 -0
- package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/requirements.json +1 -0
- package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +8 -0
- package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/requirements.json +1 -0
- package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/resolutions.json +4 -0
- package/gsd-core/references/edge-probe.md +261 -0
- package/gsd-core/references/planner-antipatterns.md +41 -0
- package/gsd-core/references/planner-guidance.md +186 -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/spec.md +12 -0
- 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 +111 -51
- 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 +53 -17
- 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 +9 -1
- package/gsd-core/workflows/new-project.md +9 -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 +65 -28
- 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 +7 -7
- package/gsd-core/workflows/settings-integrations.md +1 -1
- package/gsd-core/workflows/settings.md +2 -2
- 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 +130 -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/update.md +2 -2
- 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 +11 -3
- 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 +2293 -0
- package/scripts/gen-loop-host-contract.cjs +471 -0
- package/scripts/lib/allowlist-ratchet.cjs +101 -1
- 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 +25 -4
- package/scripts/lint-windows-test-portability.cjs +178 -0
- package/scripts/prompt-injection-scan.sh +4 -4
- package/scripts/research-profiles.cjs +10 -10
- package/scripts/run-tests.cjs +133 -29
- package/scripts/secret-scan.sh +3 -3
- package/scripts/sync-next-version.cjs +133 -0
- package/scripts/sync-runtime-launcher.cjs +21 -5
- package/scripts/update-size-baseline.cjs +68 -0
- package/scripts/workflow-policy.cjs +42 -9
- package/scripts/workflow-size.cjs +90 -0
- package/scripts/run-cross-platform-tests.cjs +0 -67
|
@@ -157,10 +157,44 @@ function computeClosure(base, manifest) {
|
|
|
157
157
|
}
|
|
158
158
|
return closed;
|
|
159
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* Compute the capability skills to add for a given profile mode from the registry.
|
|
162
|
+
* Returns an array of skill stems contributed by capabilities whose profileMembership
|
|
163
|
+
* includes the given mode. Guards against prototype pollution and malformed registry.
|
|
164
|
+
*/
|
|
165
|
+
function _capabilitySkillsForMode(mode, registry) {
|
|
166
|
+
const BANNED = ['__proto__', 'constructor', 'prototype'];
|
|
167
|
+
const clusters = registry.capabilityClusters;
|
|
168
|
+
const membership = registry.profileMembership;
|
|
169
|
+
if (!clusters || typeof clusters !== 'object' || !membership || typeof membership !== 'object') {
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
172
|
+
const result = [];
|
|
173
|
+
for (const capId of Object.keys(clusters)) {
|
|
174
|
+
if (BANNED.includes(capId))
|
|
175
|
+
continue;
|
|
176
|
+
const mem = membership[capId];
|
|
177
|
+
if (!mem || typeof mem !== 'object')
|
|
178
|
+
continue;
|
|
179
|
+
const profiles = mem.profiles;
|
|
180
|
+
if (!Array.isArray(profiles))
|
|
181
|
+
continue;
|
|
182
|
+
if (!profiles.includes(mode))
|
|
183
|
+
continue;
|
|
184
|
+
const skills = clusters[capId];
|
|
185
|
+
if (!Array.isArray(skills))
|
|
186
|
+
continue;
|
|
187
|
+
for (const s of skills) {
|
|
188
|
+
if (typeof s === 'string' && s.length > 0)
|
|
189
|
+
result.push(s);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return result;
|
|
193
|
+
}
|
|
160
194
|
/**
|
|
161
195
|
* Resolve a profile (or composed profiles) to a typed result object.
|
|
162
196
|
*/
|
|
163
|
-
function resolveProfile({ modes, manifest, _profilesOverride } = {}) {
|
|
197
|
+
function resolveProfile({ modes, manifest, _profilesOverride, registry } = {}) {
|
|
164
198
|
const profiles = _profilesOverride || PROFILES;
|
|
165
199
|
const activeModes = (modes && modes.length > 0) ? modes : ['full'];
|
|
166
200
|
const normalizedModes = activeModes
|
|
@@ -185,7 +219,11 @@ function resolveProfile({ modes, manifest, _profilesOverride } = {}) {
|
|
|
185
219
|
// This profile is full — sentinel short-circuit
|
|
186
220
|
return { name: 'full', skills: '*', agents: new Set() };
|
|
187
221
|
}
|
|
188
|
-
|
|
222
|
+
// ADR-857 phase 4c: union capability skills for this mode BEFORE closure so
|
|
223
|
+
// their requires: chains expand too.
|
|
224
|
+
const capSkills = registry ? _capabilitySkillsForMode(mode, registry) : [];
|
|
225
|
+
const baseWithCap = [...base, ...capSkills];
|
|
226
|
+
const closure = computeClosure(baseWithCap, man);
|
|
189
227
|
for (const s of closure)
|
|
190
228
|
unionSkills.add(s);
|
|
191
229
|
}
|
|
@@ -318,9 +356,103 @@ function stageAgentsForProfile(srcAgentsDir, resolvedProfile) {
|
|
|
318
356
|
ensureExitCleanup();
|
|
319
357
|
return stageDir;
|
|
320
358
|
}
|
|
321
|
-
|
|
359
|
+
/**
|
|
360
|
+
* Build the namespace router → concrete sub-skill mapping (#69). The
|
|
361
|
+
* authoritative source is each `ns-*.md` router file's `requires:` frontmatter
|
|
362
|
+
* list. A concrete skill may be routed by more than one router (e.g. spec-phase
|
|
363
|
+
* is shared by ns-workflow and ns-ideate); it is nested — and physically
|
|
364
|
+
* duplicated — under every owning router.
|
|
365
|
+
*/
|
|
366
|
+
function buildNamespaceBundleMap(srcCommandsDir) {
|
|
367
|
+
const routerStems = new Set();
|
|
368
|
+
const routerChildren = new Map();
|
|
369
|
+
const childToRouters = new Map();
|
|
370
|
+
if (!node_fs_1.default.existsSync(srcCommandsDir)) {
|
|
371
|
+
return { routerStems, routerChildren, childToRouters };
|
|
372
|
+
}
|
|
373
|
+
for (const entry of node_fs_1.default.readdirSync(srcCommandsDir, { withFileTypes: true })) {
|
|
374
|
+
if (!entry.isFile() || !entry.name.endsWith('.md'))
|
|
375
|
+
continue;
|
|
376
|
+
if (!entry.name.startsWith('ns-'))
|
|
377
|
+
continue;
|
|
378
|
+
const stem = entry.name.slice(0, -3);
|
|
379
|
+
let children = [];
|
|
380
|
+
try {
|
|
381
|
+
children = parseRequires(node_fs_1.default.readFileSync(node_path_1.default.join(srcCommandsDir, entry.name), 'utf8'));
|
|
382
|
+
}
|
|
383
|
+
catch {
|
|
384
|
+
children = [];
|
|
385
|
+
}
|
|
386
|
+
routerStems.add(stem);
|
|
387
|
+
routerChildren.set(stem, children);
|
|
388
|
+
for (const child of children) {
|
|
389
|
+
const owners = childToRouters.get(child) || [];
|
|
390
|
+
owners.push(stem);
|
|
391
|
+
childToRouters.set(child, owners);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return { routerStems, routerChildren, childToRouters };
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Rewrite a converted namespace-router SKILL.md so its routing table points at
|
|
398
|
+
* nested sub-skill files instead of bare Skill-tool names (#69). Each table row
|
|
399
|
+
* whose final cell carries a `gsd-<stem>` token (optionally with `--flag`
|
|
400
|
+
* suffixes) is rewritten to `Read \`skills/<stem>/SKILL.md\`` (flags preserved
|
|
401
|
+
* as a note), the `Invoke` column header becomes `Read`, and the
|
|
402
|
+
* "Invoke … using the Skill tool" trailer becomes a file-read instruction.
|
|
403
|
+
* Only lines beginning with a table pipe are touched, so the `|` inside the
|
|
404
|
+
* `description:` frontmatter field is never matched.
|
|
405
|
+
*/
|
|
406
|
+
function transformRouterBodyToNested(converted) {
|
|
407
|
+
const lines = converted.split('\n');
|
|
408
|
+
const out = lines.map((line) => {
|
|
409
|
+
if (/Invoke the matched skill directly using the Skill tool\./.test(line)) {
|
|
410
|
+
return line.replace(/Invoke the matched skill directly using the Skill tool\./, "Read the matched sub-skill's SKILL.md and follow its instructions. The `skills/<name>/SKILL.md` paths in the right column are relative to this skill's own directory.");
|
|
411
|
+
}
|
|
412
|
+
if (!/^\s*\|/.test(line))
|
|
413
|
+
return line;
|
|
414
|
+
if (/^\s*\|[\s:|-]+\|\s*$/.test(line))
|
|
415
|
+
return line;
|
|
416
|
+
if (/\|\s*Invoke\s*\|/.test(line)) {
|
|
417
|
+
return line.replace(/\|\s*Invoke\s*\|/, '| Read |');
|
|
418
|
+
}
|
|
419
|
+
const cells = line.split('|');
|
|
420
|
+
const lastIdx = cells.length - 2;
|
|
421
|
+
if (lastIdx < 1)
|
|
422
|
+
return line;
|
|
423
|
+
const cell = cells[lastIdx];
|
|
424
|
+
const m = cell.match(/gsd-([a-z0-9-]+)((?:\s+--[a-z0-9-]+)*)/i);
|
|
425
|
+
if (!m)
|
|
426
|
+
return line;
|
|
427
|
+
const stem = m[1];
|
|
428
|
+
const flags = m[2].trim();
|
|
429
|
+
cells[lastIdx] = flags
|
|
430
|
+
? ` Read \`skills/${stem}/SKILL.md\` (${flags}) `
|
|
431
|
+
: ` Read \`skills/${stem}/SKILL.md\` `;
|
|
432
|
+
return cells.join('|');
|
|
433
|
+
});
|
|
434
|
+
return out.join('\n');
|
|
435
|
+
}
|
|
436
|
+
function stageSkillsForRuntimeAsSkills(srcCommandsDir, resolvedProfile, converter, prefix, nested = false) {
|
|
322
437
|
if (!node_fs_1.default.existsSync(srcCommandsDir))
|
|
323
438
|
return srcCommandsDir;
|
|
439
|
+
// Nesting applies to the `full` install AND to any surface whose skill set
|
|
440
|
+
// still contains every namespace router (a full/reset surface). It must NOT
|
|
441
|
+
// depend on the `'*'` sentinel alone: applySurface() materializes `full` into
|
|
442
|
+
// a concrete Set, so a sentinel-only gate would re-flatten the layout on every
|
|
443
|
+
// surface apply/reset (#69 adversarial-review finding). A partial surface that
|
|
444
|
+
// drops a whole router cluster falls back to flat automatically.
|
|
445
|
+
const bundles = nested ? buildNamespaceBundleMap(srcCommandsDir) : null;
|
|
446
|
+
let doNest = false;
|
|
447
|
+
if (nested && bundles && bundles.routerStems.size > 0) {
|
|
448
|
+
if (resolvedProfile.skills === '*') {
|
|
449
|
+
doNest = true;
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
const present = resolvedProfile.skills;
|
|
453
|
+
doNest = [...bundles.routerStems].every((r) => present.has(r));
|
|
454
|
+
}
|
|
455
|
+
}
|
|
324
456
|
const stageDir = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), 'gsd-profile-runtime-skills-'));
|
|
325
457
|
try {
|
|
326
458
|
const entries = node_fs_1.default.readdirSync(srcCommandsDir, { withFileTypes: true });
|
|
@@ -335,6 +467,27 @@ function stageSkillsForRuntimeAsSkills(srcCommandsDir, resolvedProfile, converte
|
|
|
335
467
|
const content = node_fs_1.default.readFileSync(node_path_1.default.join(srcCommandsDir, entry.name), 'utf8');
|
|
336
468
|
const skillName = `${prefix}${stem}`;
|
|
337
469
|
const converted = converter(content, skillName);
|
|
470
|
+
if (doNest && bundles.routerStems.has(stem)) {
|
|
471
|
+
// Router skill: rewrite its routing table to the nested Read pattern and
|
|
472
|
+
// emit it as the single top-level bundle entry.
|
|
473
|
+
const destDir = node_path_1.default.join(stageDir, skillName);
|
|
474
|
+
node_fs_1.default.mkdirSync(destDir, { recursive: true });
|
|
475
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(destDir, 'SKILL.md'), transformRouterBodyToNested(converted));
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
if (doNest && bundles.childToRouters.has(stem)) {
|
|
479
|
+
// Concrete skill routed by one or more namespace routers: nest a copy
|
|
480
|
+
// under each owning router's skills/ subdir so it drops out of the
|
|
481
|
+
// top-level eager listing while staying readable by file path (#69).
|
|
482
|
+
for (const routerStem of bundles.childToRouters.get(stem)) {
|
|
483
|
+
const destDir = node_path_1.default.join(stageDir, `${prefix}${routerStem}`, 'skills', stem);
|
|
484
|
+
node_fs_1.default.mkdirSync(destDir, { recursive: true });
|
|
485
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(destDir, 'SKILL.md'), converted);
|
|
486
|
+
}
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
// Flat top-level skill (default behaviour; also the unrouted fallback when
|
|
490
|
+
// nesting is active).
|
|
338
491
|
const destDir = node_path_1.default.join(stageDir, skillName);
|
|
339
492
|
node_fs_1.default.mkdirSync(destDir, { recursive: true });
|
|
340
493
|
node_fs_1.default.writeFileSync(node_path_1.default.join(destDir, 'SKILL.md'), converted);
|
|
@@ -575,6 +728,7 @@ module.exports = {
|
|
|
575
728
|
readActiveProfile,
|
|
576
729
|
writeActiveProfile,
|
|
577
730
|
// Shared internals
|
|
731
|
+
parseRequires,
|
|
578
732
|
cleanupStagedSkills,
|
|
579
733
|
// Back-compat / deprecated
|
|
580
734
|
MINIMAL_SKILL_ALLOWLIST,
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* Intel command router — CLI subcommand dispatcher for `gsd-tools intel`.
|
|
4
|
+
*
|
|
5
|
+
* ADR-959 (phase 4d-impl-4): intel command family cutover — last first-party
|
|
6
|
+
* command cutover in the initial capability rollout.
|
|
7
|
+
* Extracted from the hardcoded `case 'intel':` arm in gsd-tools.cjs.
|
|
8
|
+
* Behaviour is preserved byte-for-behaviour from the prior inline case;
|
|
9
|
+
* the dispatch path now flows: default → dispatchCapabilityCommand →
|
|
10
|
+
* require(intel-command-router.cjs) → routeIntelCommand.
|
|
11
|
+
*
|
|
12
|
+
* Router signature: { args, cwd, raw, error } — identical to the existing
|
|
13
|
+
* host routers. No new handler/arg convention; the capability registry
|
|
14
|
+
* discovers this router by name.
|
|
15
|
+
*
|
|
16
|
+
* Arg indexing (preserved exactly from the original case):
|
|
17
|
+
* args[0] = 'intel' (family — matched by dispatchCapabilityCommand)
|
|
18
|
+
* args[1] = subcommand (query | status | diff | snapshot | patch-meta |
|
|
19
|
+
* validate | extract-exports | update | api-surface)
|
|
20
|
+
* args[2] = term (query) | filePath (patch-meta | extract-exports)
|
|
21
|
+
*
|
|
22
|
+
* Notable: the `status` subcommand applies a `timeAgo` transform on
|
|
23
|
+
* `status.files[*].updated_at` in non-raw mode — preserved exactly.
|
|
24
|
+
*
|
|
25
|
+
* Test seams: pass `_intel` to inject a mock intel module; pass `_core` to
|
|
26
|
+
* inject a mock core module (captures `output` calls and provides a
|
|
27
|
+
* deterministic `timeAgo` without writing to real stdout). The `_`-prefix
|
|
28
|
+
* follows the repo's established seam convention (see audit-command-router.cts
|
|
29
|
+
* for the `_core` seam pattern). Production callers omit both.
|
|
30
|
+
*
|
|
31
|
+
* Note on `error(); return` pairs: in production `error()` calls
|
|
32
|
+
* `process.exit(1)` so the `return` is an equivalent no-op halt. The pairs
|
|
33
|
+
* are kept for lint/control-flow clarity; they do NOT change behaviour.
|
|
34
|
+
*
|
|
35
|
+
* Lazy require: intel.cjs is required INSIDE the route function so it is
|
|
36
|
+
* only loaded when an intel command is actually dispatched (preserves
|
|
37
|
+
* equivalence with the old inline case arm which required it at the top of
|
|
38
|
+
* the case block).
|
|
39
|
+
*/
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
41
|
+
const core = require("./core.cjs");
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
43
|
+
const io = require("./io.cjs");
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
45
|
+
const path = require("path");
|
|
46
|
+
const { ERROR_REASON } = io;
|
|
47
|
+
// ─── Implementation ───────────────────────────────────────────────────────────
|
|
48
|
+
function routeIntelCommand({ args, cwd, raw, error, _intel, _core }) {
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
|
|
50
|
+
const intel = _intel ?? require('./intel.cjs');
|
|
51
|
+
const c = _core ?? core;
|
|
52
|
+
const subcommand = args[1];
|
|
53
|
+
if (subcommand === 'query') {
|
|
54
|
+
const term = args[2];
|
|
55
|
+
if (!term) {
|
|
56
|
+
error('Usage: gsd-tools intel query <term>', ERROR_REASON.USAGE);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const planningDir = path.join(cwd, '.planning');
|
|
60
|
+
c.output(intel.intelQuery(term, planningDir), raw);
|
|
61
|
+
}
|
|
62
|
+
else if (subcommand === 'status') {
|
|
63
|
+
const planningDir = path.join(cwd, '.planning');
|
|
64
|
+
const status = intel.intelStatus(planningDir);
|
|
65
|
+
if (!raw && status.files) {
|
|
66
|
+
for (const file of Object.values(status.files)) {
|
|
67
|
+
if (file.updated_at) {
|
|
68
|
+
file.updated_at = c.timeAgo(new Date(file.updated_at));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
c.output(status, raw);
|
|
73
|
+
}
|
|
74
|
+
else if (subcommand === 'diff') {
|
|
75
|
+
const planningDir = path.join(cwd, '.planning');
|
|
76
|
+
c.output(intel.intelDiff(planningDir), raw);
|
|
77
|
+
}
|
|
78
|
+
else if (subcommand === 'snapshot') {
|
|
79
|
+
const planningDir = path.join(cwd, '.planning');
|
|
80
|
+
c.output(intel.intelSnapshot(planningDir), raw);
|
|
81
|
+
}
|
|
82
|
+
else if (subcommand === 'patch-meta') {
|
|
83
|
+
const filePath = args[2];
|
|
84
|
+
if (!filePath) {
|
|
85
|
+
error('Usage: gsd-tools intel patch-meta <file-path>', ERROR_REASON.USAGE);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
c.output(intel.intelPatchMeta(path.resolve(cwd, filePath)), raw);
|
|
89
|
+
}
|
|
90
|
+
else if (subcommand === 'validate') {
|
|
91
|
+
const planningDir = path.join(cwd, '.planning');
|
|
92
|
+
c.output(intel.intelValidate(planningDir), raw);
|
|
93
|
+
}
|
|
94
|
+
else if (subcommand === 'extract-exports') {
|
|
95
|
+
const filePath = args[2];
|
|
96
|
+
if (!filePath) {
|
|
97
|
+
error('Usage: gsd-tools intel extract-exports <file-path>', ERROR_REASON.USAGE);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
c.output(intel.intelExtractExports(path.resolve(cwd, filePath)), raw);
|
|
101
|
+
}
|
|
102
|
+
else if (subcommand === 'update') {
|
|
103
|
+
const planningDir = path.join(cwd, '.planning');
|
|
104
|
+
c.output(intel.intelUpdate(planningDir), raw);
|
|
105
|
+
}
|
|
106
|
+
else if (subcommand === 'api-surface') {
|
|
107
|
+
const planningDir = path.join(cwd, '.planning');
|
|
108
|
+
c.output(intel.intelApiSurface(planningDir), raw);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
error('Unknown intel subcommand. Available: query, status, update, diff, snapshot, patch-meta, validate, extract-exports, api-surface', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
module.exports = {
|
|
115
|
+
routeIntelCommand,
|
|
116
|
+
};
|
|
@@ -149,7 +149,7 @@ function matchesInValue(value, lowerTerm) {
|
|
|
149
149
|
}
|
|
150
150
|
/**
|
|
151
151
|
* Query intel files for a search term.
|
|
152
|
-
* Searches across all JSON intel files (keys and values)
|
|
152
|
+
* Searches across all JSON intel files in INTEL_FILES (keys and values), including arch-decisions.json (parsed as JSON, not as text).
|
|
153
153
|
*/
|
|
154
154
|
function intelQuery(term, planningDir) {
|
|
155
155
|
if (!isIntelEnabled(planningDir))
|
|
@@ -328,7 +328,7 @@ function intelValidate(planningDir) {
|
|
|
328
328
|
}
|
|
329
329
|
// Validate entries are objects with expected fields
|
|
330
330
|
if (data.entries && typeof data.entries === 'object') {
|
|
331
|
-
//
|
|
331
|
+
// file-roles.json (INTEL_FILES key 'files'): check exports are actual symbol names (no spaces)
|
|
332
332
|
if (key === 'files') {
|
|
333
333
|
for (const [entryPath, entry] of Object.entries(data.entries)) {
|
|
334
334
|
const entryObj = entry;
|
|
@@ -348,7 +348,7 @@ function intelValidate(planningDir) {
|
|
|
348
348
|
}
|
|
349
349
|
}
|
|
350
350
|
}
|
|
351
|
-
//
|
|
351
|
+
// dependency-graph.json (INTEL_FILES key 'deps'): check entries have version, type, used_by
|
|
352
352
|
if (key === 'deps') {
|
|
353
353
|
for (const [depName, entry] of Object.entries(data.entries)) {
|
|
354
354
|
const entryObj = entry;
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CLI I/O primitives — output(), error(), ERROR_REASON, JSON-error mode,
|
|
4
|
+
* and the temp-file helpers that output() depends on.
|
|
5
|
+
*
|
|
6
|
+
* Extracted from core.cts (ADR-857 rollout phase 1 / issue #859).
|
|
7
|
+
* The hand-written bodies are preserved byte-for-behaviour; only the module
|
|
8
|
+
* boundary moved. core.cts re-exports every symbol here under its own
|
|
9
|
+
* `export =` object so existing consumers are unaffected.
|
|
10
|
+
*
|
|
11
|
+
* New imports should pull I/O primitives from io.cjs directly.
|
|
12
|
+
*/
|
|
13
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
14
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
15
|
+
};
|
|
16
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
17
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
18
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
19
|
+
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
20
|
+
// ─── Temp-file helpers (needed by output()) ──────────────────────────────────
|
|
21
|
+
/**
|
|
22
|
+
* Dedicated GSD temp directory: path.join(os.tmpdir(), 'gsd').
|
|
23
|
+
* Created on first use. Keeps GSD temp files isolated from the system
|
|
24
|
+
* temp directory so reap scans only GSD files (#1975).
|
|
25
|
+
*/
|
|
26
|
+
const GSD_TEMP_DIR = node_path_1.default.join(node_os_1.default.tmpdir(), 'gsd');
|
|
27
|
+
function ensureGsdTempDir() {
|
|
28
|
+
(0, shell_command_projection_cjs_1.platformEnsureDir)(GSD_TEMP_DIR);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Remove stale gsd-* temp files/dirs older than maxAgeMs (default: 5 minutes).
|
|
32
|
+
* Runs opportunistically before each new temp file write to prevent unbounded accumulation.
|
|
33
|
+
* @param prefix - filename prefix to match (e.g., 'gsd-')
|
|
34
|
+
* @param opts
|
|
35
|
+
* @param opts.maxAgeMs - max age in ms before removal (default: 5 min)
|
|
36
|
+
* @param opts.dirsOnly - if true, only remove directories (default: false)
|
|
37
|
+
*/
|
|
38
|
+
function reapStaleTempFiles(prefix = 'gsd-', { maxAgeMs = 5 * 60 * 1000, dirsOnly = false } = {}) {
|
|
39
|
+
try {
|
|
40
|
+
ensureGsdTempDir();
|
|
41
|
+
const now = Date.now();
|
|
42
|
+
const entries = node_fs_1.default.readdirSync(GSD_TEMP_DIR);
|
|
43
|
+
for (const entry of entries) {
|
|
44
|
+
if (!entry.startsWith(prefix))
|
|
45
|
+
continue;
|
|
46
|
+
const fullPath = node_path_1.default.join(GSD_TEMP_DIR, entry);
|
|
47
|
+
try {
|
|
48
|
+
const stat = node_fs_1.default.statSync(fullPath);
|
|
49
|
+
if (now - stat.mtimeMs > maxAgeMs) {
|
|
50
|
+
if (stat.isDirectory()) {
|
|
51
|
+
node_fs_1.default.rmSync(fullPath, { recursive: true, force: true });
|
|
52
|
+
}
|
|
53
|
+
else if (!dirsOnly) {
|
|
54
|
+
node_fs_1.default.unlinkSync(fullPath);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// File may have been removed between readdir and stat — ignore
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// Non-critical — don't let cleanup failures break output
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// ─── Output helpers ───────────────────────────────────────────────────────────
|
|
68
|
+
/**
|
|
69
|
+
* Transient write errnos. When stdout/stderr is a NON-BLOCKING pipe — as it is
|
|
70
|
+
* under the parallel `node --test` runner on Linux CI — a full pipe buffer makes
|
|
71
|
+
* `fs.writeSync` throw EAGAIN, and a signal can interrupt it with EINTR. Both
|
|
72
|
+
* clear on retry once the reader drains. This is the same transient class the
|
|
73
|
+
* STATE.md lock path already retries (ACQUIRE_LOCK_RETRY_ERRNOS, #3776); #1008.
|
|
74
|
+
*/
|
|
75
|
+
const WRITE_RETRY_ERRNOS = new Set(['EAGAIN', 'EINTR']);
|
|
76
|
+
// Bounded so a pathological never-draining fd cannot spin forever. Each retry
|
|
77
|
+
// yields the thread for ~1ms via Atomics.wait (the project's sync-sleep idiom —
|
|
78
|
+
// see clock.cts realClock.sleep), so the cap is ~1s of total back-pressure wait.
|
|
79
|
+
const WRITE_MAX_RETRIES = 1000;
|
|
80
|
+
const WRITE_RETRY_BACKOFF_MS = 1;
|
|
81
|
+
// Sleep buffer is lazily allocated on the FIRST back-pressure retry (rare — only
|
|
82
|
+
// when a non-blocking pipe is full) and then reused. Keeping it out of module
|
|
83
|
+
// load costs nothing on the overwhelmingly common no-retry path and avoids
|
|
84
|
+
// perturbing SharedArrayBuffer-allocation accounting in other modules (perf-316).
|
|
85
|
+
let _writeSleepBuf = null;
|
|
86
|
+
function backoffOnce() {
|
|
87
|
+
if (_writeSleepBuf === null)
|
|
88
|
+
_writeSleepBuf = new Int32Array(new SharedArrayBuffer(4));
|
|
89
|
+
Atomics.wait(_writeSleepBuf, 0, 0, WRITE_RETRY_BACKOFF_MS);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Write the entire payload to `fd`, tolerating non-blocking-pipe back-pressure.
|
|
93
|
+
*
|
|
94
|
+
* `fs.writeSync` does NOT block on a non-blocking pipe: a full buffer throws
|
|
95
|
+
* EAGAIN, and a partially-drained buffer returns a SHORT count (fewer bytes than
|
|
96
|
+
* requested). The previous bare `fs.writeSync(fd, string)` call assumed it always
|
|
97
|
+
* blocked until the kernel accepted every byte — false under load, which both
|
|
98
|
+
* threw spurious errors and risked silently truncating output (#1008).
|
|
99
|
+
*
|
|
100
|
+
* This loops on short counts (advancing the offset) and retries EAGAIN/EINTR with
|
|
101
|
+
* a brief Atomics.wait backoff that yields the thread so the reader can drain.
|
|
102
|
+
* Non-transient errors (e.g. EPIPE) propagate unchanged.
|
|
103
|
+
*/
|
|
104
|
+
function writeAllSync(fd, data) {
|
|
105
|
+
const buf = Buffer.from(data, 'utf8');
|
|
106
|
+
let offset = 0;
|
|
107
|
+
let retries = 0;
|
|
108
|
+
while (offset < buf.length) {
|
|
109
|
+
try {
|
|
110
|
+
offset += node_fs_1.default.writeSync(fd, buf, offset, buf.length - offset);
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
const code = err.code ?? '';
|
|
114
|
+
if (WRITE_RETRY_ERRNOS.has(code) && retries < WRITE_MAX_RETRIES) {
|
|
115
|
+
retries += 1;
|
|
116
|
+
backoffOnce();
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
throw err;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function output(result, raw, rawValue) {
|
|
124
|
+
let data;
|
|
125
|
+
if (raw && rawValue !== undefined) {
|
|
126
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
127
|
+
data = String(rawValue);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
const json = JSON.stringify(result, null, 2);
|
|
131
|
+
// Large payloads exceed Claude Code's Bash tool buffer (~50KB).
|
|
132
|
+
// Write to tmpfile and output the path prefixed with @file: so callers can detect it.
|
|
133
|
+
if (json.length > 50000) {
|
|
134
|
+
reapStaleTempFiles();
|
|
135
|
+
ensureGsdTempDir();
|
|
136
|
+
const tmpPath = node_path_1.default.join(GSD_TEMP_DIR, `gsd-${Date.now()}.json`);
|
|
137
|
+
(0, shell_command_projection_cjs_1.platformWriteSync)(tmpPath, json);
|
|
138
|
+
data = '@file:' + tmpPath;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
data = json;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// process.stdout.write() is async when stdout is a pipe — process.exit()
|
|
145
|
+
// can tear down the process before the reader consumes the buffer. writeAllSync
|
|
146
|
+
// pushes every byte synchronously (looping short counts, retrying EAGAIN/EINTR),
|
|
147
|
+
// and skipping process.exit() lets the event loop drain naturally.
|
|
148
|
+
writeAllSync(1, data);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Frozen enum of typed reason codes used by error() for structured errors.
|
|
152
|
+
* Each subcommand contributes its own codes; the enum exists so tests can
|
|
153
|
+
* assert against typed values instead of grepping stderr (#2974).
|
|
154
|
+
*
|
|
155
|
+
* Adding a new code:
|
|
156
|
+
* - Pick a snake_case lowercase value (the JSON wire form)
|
|
157
|
+
* - Group by subsystem prefix (CONFIG_*, SDK_*, etc)
|
|
158
|
+
* - Pass it to error(msg, ERROR_REASON.NEW_CODE) at the call site
|
|
159
|
+
*/
|
|
160
|
+
const ERROR_REASON = Object.freeze({
|
|
161
|
+
// config-get / config-set
|
|
162
|
+
CONFIG_KEY_NOT_FOUND: 'config_key_not_found',
|
|
163
|
+
CONFIG_NO_FILE: 'config_no_file',
|
|
164
|
+
CONFIG_PARSE_FAILED: 'config_parse_failed',
|
|
165
|
+
CONFIG_INVALID_KEY: 'config_invalid_key',
|
|
166
|
+
// SDK / gsd-tools dispatch
|
|
167
|
+
SDK_FAIL_FAST: 'sdk_fail_fast',
|
|
168
|
+
SDK_UNKNOWN_COMMAND: 'sdk_unknown_command',
|
|
169
|
+
SDK_MISSING_ARG: 'sdk_missing_arg',
|
|
170
|
+
// workflow / phase
|
|
171
|
+
PHASE_NOT_FOUND: 'phase_not_found',
|
|
172
|
+
SUMMARY_NO_PLANNING: 'summary_no_planning',
|
|
173
|
+
// graphify
|
|
174
|
+
GRAPHIFY_NO_GRAPH: 'graphify_no_graph',
|
|
175
|
+
GRAPHIFY_INVALID_QUERY: 'graphify_invalid_query',
|
|
176
|
+
// hooks
|
|
177
|
+
HOOKS_OPT_OUT: 'hooks_opt_out',
|
|
178
|
+
// security-scan
|
|
179
|
+
SECURITY_SCAN_FAILED: 'security_scan_failed',
|
|
180
|
+
// generic
|
|
181
|
+
USAGE: 'usage',
|
|
182
|
+
UNKNOWN: 'unknown',
|
|
183
|
+
});
|
|
184
|
+
/**
|
|
185
|
+
* Process-level flag: when true, error() emits structured JSON to stderr
|
|
186
|
+
* instead of plain "Error: <message>" text. Set by gsd-tools.cjs when the
|
|
187
|
+
* CLI is invoked with `--json-errors`. Tests opt in to typed-IR error
|
|
188
|
+
* assertions by passing that flag and parsing the JSON.
|
|
189
|
+
*
|
|
190
|
+
* Default off so existing callers and human operators keep their plain-text
|
|
191
|
+
* diagnostics. The structured form is opt-in for tooling and tests (#2974).
|
|
192
|
+
*/
|
|
193
|
+
let _jsonErrorMode = false;
|
|
194
|
+
function setJsonErrorMode(v) { _jsonErrorMode = !!v; }
|
|
195
|
+
function getJsonErrorMode() { return _jsonErrorMode; }
|
|
196
|
+
/**
|
|
197
|
+
* Emit an error and exit. When the second argument is provided it must be
|
|
198
|
+
* a value from ERROR_REASON; tests can assert on `result.reason`. When the
|
|
199
|
+
* process is in JSON-error mode, stderr receives `{ ok: false, reason,
|
|
200
|
+
* message }` so callers can parse it; otherwise stderr keeps the plain
|
|
201
|
+
* text form for human operators.
|
|
202
|
+
*/
|
|
203
|
+
function error(message, reason = ERROR_REASON.UNKNOWN) {
|
|
204
|
+
if (_jsonErrorMode) {
|
|
205
|
+
const payload = JSON.stringify({ ok: false, reason, message }) + '\n';
|
|
206
|
+
writeAllSync(2, payload);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
writeAllSync(2, 'Error: ' + message + '\n');
|
|
210
|
+
}
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
module.exports = {
|
|
214
|
+
GSD_TEMP_DIR,
|
|
215
|
+
ensureGsdTempDir,
|
|
216
|
+
reapStaleTempFiles,
|
|
217
|
+
output,
|
|
218
|
+
ERROR_REASON,
|
|
219
|
+
setJsonErrorMode,
|
|
220
|
+
getJsonErrorMode,
|
|
221
|
+
error,
|
|
222
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* loop-host-contract.cjs — generated by scripts/gen-loop-host-contract.cjs
|
|
5
|
+
* DO NOT EDIT BY HAND. Run: node scripts/gen-loop-host-contract.cjs --write
|
|
6
|
+
* ADR-894 §3 — Loop Host Contract, generated from workflow markers.
|
|
7
|
+
* 12 points: discuss:pre/post, plan:pre/post, execute:pre/wave:pre/wave:post/post,
|
|
8
|
+
* verify:pre/post, ship:pre/post. Per-step agentRoles and coreArtifacts.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const LOOP_HOST_CONTRACT = [
|
|
12
|
+
{
|
|
13
|
+
"step": "discuss",
|
|
14
|
+
"points": [
|
|
15
|
+
"discuss:pre",
|
|
16
|
+
"discuss:post"
|
|
17
|
+
],
|
|
18
|
+
"agentRoles": [
|
|
19
|
+
"orchestrator"
|
|
20
|
+
],
|
|
21
|
+
"coreArtifacts": {
|
|
22
|
+
"produces": [
|
|
23
|
+
"CONTEXT.md"
|
|
24
|
+
],
|
|
25
|
+
"consumes": []
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"step": "plan",
|
|
30
|
+
"points": [
|
|
31
|
+
"plan:pre",
|
|
32
|
+
"plan:post"
|
|
33
|
+
],
|
|
34
|
+
"agentRoles": [
|
|
35
|
+
"researcher",
|
|
36
|
+
"planner",
|
|
37
|
+
"checker"
|
|
38
|
+
],
|
|
39
|
+
"coreArtifacts": {
|
|
40
|
+
"produces": [
|
|
41
|
+
"PLAN.md"
|
|
42
|
+
],
|
|
43
|
+
"consumes": [
|
|
44
|
+
"CONTEXT.md"
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"step": "execute",
|
|
50
|
+
"points": [
|
|
51
|
+
"execute:pre",
|
|
52
|
+
"execute:wave:pre",
|
|
53
|
+
"execute:wave:post",
|
|
54
|
+
"execute:post"
|
|
55
|
+
],
|
|
56
|
+
"agentRoles": [
|
|
57
|
+
"executor",
|
|
58
|
+
"verifier"
|
|
59
|
+
],
|
|
60
|
+
"coreArtifacts": {
|
|
61
|
+
"produces": [
|
|
62
|
+
"SUMMARY.md"
|
|
63
|
+
],
|
|
64
|
+
"consumes": [
|
|
65
|
+
"PLAN.md"
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"step": "verify",
|
|
71
|
+
"points": [
|
|
72
|
+
"verify:pre",
|
|
73
|
+
"verify:post"
|
|
74
|
+
],
|
|
75
|
+
"agentRoles": [
|
|
76
|
+
"orchestrator"
|
|
77
|
+
],
|
|
78
|
+
"coreArtifacts": {
|
|
79
|
+
"produces": [
|
|
80
|
+
"UAT.md"
|
|
81
|
+
],
|
|
82
|
+
"consumes": [
|
|
83
|
+
"SUMMARY.md"
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"step": "ship",
|
|
89
|
+
"points": [
|
|
90
|
+
"ship:pre",
|
|
91
|
+
"ship:post"
|
|
92
|
+
],
|
|
93
|
+
"agentRoles": [
|
|
94
|
+
"orchestrator"
|
|
95
|
+
],
|
|
96
|
+
"coreArtifacts": {
|
|
97
|
+
"produces": [],
|
|
98
|
+
"consumes": [
|
|
99
|
+
"UAT.md"
|
|
100
|
+
]
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
module.exports = { LOOP_HOST_CONTRACT };
|