* fix(#1196): wire discuss loop step for capability hooks discuss was contract-declared (gsd:loop-host marker, in POINT_ORDER and LOOP_HOST_CONTRACT) but structurally unwireable: discuss-phase.md had no `loop render-hooks` dispatch and was absent from the conformance gate's HOST_LOOP_FILES, so capabilities could never wire discuss:pre/discuss:post. - discuss-phase.md: add minimal discuss:pre (before analyze_phase) and discuss:post (after write_context) render-hooks dispatch steps that delegate consumption to a new shared reference (kept under the 32KB #2551 budget; no inline subagent dispatch token). - references/loop-hook-dispatch.md: new canonical, point-agnostic contract for consuming `loop render-hooks --raw` activeHooks (contribution/step/ gate) — single source for hook consumption across host loops. - gen-loop-host-contract.cjs: derive HOST_LOOP_FILES from STEP_WORKFLOWS and export scanWiredPoints()/getWiredLoopPoints() (throws on a missing host file) — one source of truth for the host-loop file + wired-point set. - phase6-capstone-conformance.test.cjs: consume the derived HOST_LOOP_FILES and shared scanWiredPoints (was a hand-maintained duplicate omitting discuss-phase.md + a duplicated regex). - gen-capability-registry.cjs: add validateHooksWired() gen-time guard that rejects a capability hook declared at a valid-but-unwired loop point, with a clear remediation message — failure now surfaces at gen --check/--write time instead of deep in the full conformance suite. - tests (capability-registry.test.cjs): regression + anti-pattern parity guards (every loop-host marker is in STEP_WORKFLOWS/HOST_LOOP_FILES; POINT_ORDER === flattened LOOP_HOST_CONTRACT) so no step can drift into the discuss-class gap again. - docs/INVENTORY*: register the new reference. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(#1196): backfill changeset PR number (#1199) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -34,6 +34,9 @@ const SCHEMA_VERSION = '1';
|
||||
// registry generator and the loop-host-contract generator share one source of truth.
|
||||
const { LOOP_HOST_CONTRACT } = require('../gsd-core/bin/lib/loop-host-contract.cjs');
|
||||
|
||||
// Wired-points helper — tells us which points actually have render-hooks call sites.
|
||||
const { getWiredLoopPoints } = require('./gen-loop-host-contract.cjs');
|
||||
|
||||
// Canonical point order — explicit constant (do NOT rely on Set insertion order).
|
||||
// Used for point-ordering semantics in consumes-satisfiability validation and topo-sort.
|
||||
const POINT_ORDER = [
|
||||
@@ -1961,6 +1964,54 @@ function runConfigFormatParityGate(capMap) {
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Gen-time wired guard ─────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Validate that every hook point declared by a capability has a corresponding
|
||||
* `loop render-hooks <point>` call site in one of the host-loop workflow files.
|
||||
*
|
||||
* Only valid loop points (in VALID_LOOP_POINTS) are checked here. Invalid points
|
||||
* are already caught by validateStep/validateContribution/validateGate — do not
|
||||
* double-report.
|
||||
*
|
||||
* @param {object} cap Validated capability object.
|
||||
* @param {Set<string>} wiredSet Set of points that have call sites in host workflows.
|
||||
* @returns {string[]} Array of error strings; empty means all points are wired.
|
||||
*/
|
||||
function validateHooksWired(cap, wiredSet) {
|
||||
const errors = [];
|
||||
const capId = cap.id || '(unknown)';
|
||||
|
||||
function checkPoint(point, groupName, idx) {
|
||||
// Only flag valid points that are unwired — invalid points are schema-validator's job.
|
||||
if (!VALID_LOOP_POINTS.has(point)) return;
|
||||
if (!wiredSet.has(point)) {
|
||||
errors.push(
|
||||
'capability "' + capId + '" ' + groupName + '[' + idx + '].point "' + point +
|
||||
'" is declared but not wired in any host-loop workflow ' +
|
||||
'(no `loop render-hooks ' + point + '` call site). ' +
|
||||
'Wire the call site in the host workflow ' +
|
||||
'(see scripts/gen-loop-host-contract.cjs STEP_WORKFLOWS) or remove the hook.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < (cap.steps || []).length; i++) {
|
||||
const hook = cap.steps[i];
|
||||
if (hook.point !== undefined) checkPoint(hook.point, 'steps', i);
|
||||
}
|
||||
for (let i = 0; i < (cap.contributions || []).length; i++) {
|
||||
const hook = cap.contributions[i];
|
||||
if (hook.point !== undefined) checkPoint(hook.point, 'contributions', i);
|
||||
}
|
||||
for (let i = 0; i < (cap.gates || []).length; i++) {
|
||||
const hook = cap.gates[i];
|
||||
if (hook.point !== undefined) checkPoint(hook.point, 'gates', i);
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
// ─── Registry builder ─────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
@@ -1982,6 +2033,10 @@ function loadAndValidate(centralKeys, capabilitiesDir) {
|
||||
return { capMap, errors };
|
||||
}
|
||||
|
||||
// Compute wired points ONCE before iterating capabilities so the filesystem
|
||||
// scan is not repeated per-capability. ROOT is the repo root (defined at top of file).
|
||||
const wiredSet = getWiredLoopPoints(ROOT);
|
||||
|
||||
const folderEntries = fs.readdirSync(resolvedCapDir, { withFileTypes: true })
|
||||
.filter((e) => e.isDirectory())
|
||||
.map((e) => e.name)
|
||||
@@ -2013,6 +2068,13 @@ function loadAndValidate(centralKeys, capabilitiesDir) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Gen-time wired guard: reject hooks that declare a valid point with no call site.
|
||||
const wiredErrors = validateHooksWired(cap, wiredSet);
|
||||
if (wiredErrors.length > 0) {
|
||||
for (const e of wiredErrors) errors.push(folderId + '/capability.json: ' + e);
|
||||
continue;
|
||||
}
|
||||
|
||||
const fragmentErrors = materializeHookFragments(cap, path.dirname(capPath));
|
||||
if (fragmentErrors.length > 0) {
|
||||
for (const e of fragmentErrors) errors.push(folderId + '/capability.json: ' + e);
|
||||
@@ -2499,6 +2561,7 @@ module.exports = {
|
||||
POINT_TO_CONTRACT,
|
||||
HOST_ARTIFACT_EARLIEST_POINT_IDX,
|
||||
SCHEMA_VERSION,
|
||||
validateHooksWired,
|
||||
// ADR-857 phase 4a: derived views + gates
|
||||
deriveCapabilityClusters,
|
||||
deriveProfileMembership,
|
||||
|
||||
Reference in New Issue
Block a user