@event4u/agent-config 16.1.0 → 16.2.0
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/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +237 -248
- package/dist/agent-src/commands/roadmap/process-full.md +1 -1
- package/dist/agent-src/commands/work.md +4 -0
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +4 -8
- package/dist/agent-src/rules/ui-audit-gate.md +1 -2
- package/dist/agent-src/skills/accessibility-auditor/data/aria-patterns.csv +1 -1
- package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +2 -2
- package/dist/agent-src/skills/design-review/SKILL.md +30 -4
- package/dist/agent-src/skills/design-review/references/verification-automation.md +32 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +16 -1
- package/dist/agent-src/skills/existing-ui-audit/references/component-taxonomy-detection.md +56 -0
- package/dist/agent-src/skills/markitdown/SKILL.md +4 -0
- package/dist/agent-src/skills/playwright-testing/SKILL.md +30 -6
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +23 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +30 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
- package/dist/agent-src/templates/scripts/work_engine/state.ts +57 -7
- package/dist/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
- package/dist/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
- package/dist/cli/python/knowledge_ingest.js +1 -1
- package/dist/cli/python/workspace_drive.js +1 -1
- package/dist/cli-delegate/{chunk-S3VFXMTM.js → chunk-4XFP2Y3E.js} +1 -1
- package/dist/cli-delegate/{chunk-HDVVGPQW.js → chunk-7XGBX6ZH.js} +471 -387
- package/dist/cli-delegate/{chunk-GJCBB2UG.js → chunk-FP36SWJD.js} +432 -144
- package/dist/cli-delegate/{chunk-UNCRJBT3.js → chunk-IGD6ERX6.js} +31 -0
- package/dist/cli-delegate/{chunk-FXYUYU4N.js → chunk-JNMVLWLR.js} +42 -1
- package/dist/cli-delegate/{chunk-QJWAUMJW.js → chunk-YJ72UIAK.js} +730 -15
- package/dist/cli-delegate/cmd_conformance.js +902 -782
- package/dist/cli-delegate/cmd_doctor.js +2 -2
- package/dist/cli-delegate/cmd_export.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +4 -4
- package/dist/cli-delegate/cmd_memory_get.js +1 -1
- package/dist/cli-delegate/cmd_prune.js +2 -2
- package/dist/cli-delegate/cmd_route_audit.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +2 -2
- package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +2 -2
- package/dist/cli-delegate/cmd_uninstall.js +2 -2
- package/dist/cli-delegate/cmd_validate.js +2 -2
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +10 -10
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +6666 -2581
- package/dist/hooks/dispatch.sha256 +1 -0
- package/dist/install/install.mjs +848 -733
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +2747 -1510
- package/dist/server/schemas/settings.js +23 -0
- package/dist/ui/assets/index-Cwq4d0hX.js +23 -0
- package/dist/ui/assets/index-Cyjy8bWO.css +1 -0
- package/dist/ui/index.html +2 -2
- package/docs/contracts/settings-classes.md +6 -4
- package/package.json +4 -4
- package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
- package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
- package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
- package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
- package/src/agent-src/templates/scripts/work_engine/state.ts +57 -7
- package/src/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
- package/src/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
- package/src/config/agent-settings.template.yml +29 -0
- package/src/config/ci-local-parity.yml +18 -0
- package/src/config/continuity-surface.json +2 -2
- package/src/config/gate-coverage.yml +234 -3
- package/src/config/gate-violation-baselines.json +7 -3
- package/src/config/hook-latency-budget.json +21 -0
- package/src/config/hook-token-budget.json +2 -2
- package/src/config/metric-registry.yml +114 -0
- package/src/config/placeholder-drift-budget.json +10 -0
- package/src/config/routing-coverage-seed.json +18 -4
- package/src/scripts/_cli/cmd_doctor.ts +5 -19
- package/src/scripts/_cli/doctor_execution.ts +100 -17
- package/src/scripts/_cli/doctor_network_posture.ts +226 -0
- package/src/scripts/_lib/body_portable.ts +102 -0
- package/src/scripts/_lib/cc_transcript.ts +132 -1
- package/src/scripts/_lib/concern_sla_window.ts +200 -0
- package/src/scripts/_lib/forge_protection.ts +114 -0
- package/src/scripts/_lib/forge_reader.ts +558 -0
- package/src/scripts/_lib/host_env_write.ts +111 -0
- package/src/scripts/_lib/llm_proposer_transport.ts +14 -4
- package/src/scripts/_lib/obligations.ts +119 -8
- package/src/scripts/_lib/probe_inputs.ts +271 -0
- package/src/scripts/_lib/read_surface_scan.ts +322 -0
- package/src/scripts/_lib/release_highlights.ts +2 -1
- package/src/scripts/_lib/retrieval_sanitize.ts +77 -16
- package/src/scripts/_lib/shell_write_shapes.ts +108 -0
- package/src/scripts/_lib/structural_hiding.ts +622 -0
- package/src/scripts/_lib/turn_end_refusals.ts +525 -4
- package/src/scripts/_lib/turn_end_transcript.ts +216 -0
- package/src/scripts/_lib/verification_command.ts +324 -0
- package/src/scripts/_lib/verification_evidence.ts +647 -0
- package/src/scripts/_lib/verify_clause.ts +280 -0
- package/src/scripts/ai_team/team_dispatch.ts +4 -0
- package/src/scripts/before_complete_hook.ts +340 -23
- package/src/scripts/bench_hook_latency.ts +412 -5
- package/src/scripts/cache_realization_report.ts +61 -1
- package/src/scripts/check_argument_hint.ts +287 -0
- package/src/scripts/check_claims.ts +125 -1
- package/src/scripts/check_enforcement_coverage.ts +38 -9
- package/src/scripts/check_enforcement_matrix.ts +245 -3
- package/src/scripts/check_finding_dispositions.ts +22 -3
- package/src/scripts/check_generator_sync.ts +71 -0
- package/src/scripts/check_host_docs_digest.ts +786 -0
- package/src/scripts/check_host_format_column.ts +559 -0
- package/src/scripts/check_input_references.ts +228 -0
- package/src/scripts/check_inputs_parity.ts +235 -0
- package/src/scripts/check_kernel_edit_ratified.ts +47 -5
- package/src/scripts/check_kill_switch_table.ts +294 -0
- package/src/scripts/check_placeholder_drift.ts +221 -0
- package/src/scripts/check_read_surface_coverage.ts +515 -0
- package/src/scripts/check_release_holds.ts +2 -1
- package/src/scripts/check_routing_coverage.ts +378 -11
- package/src/scripts/check_static_parity.ts +17 -0
- package/src/scripts/ci_settle.ts +145 -19
- package/src/scripts/closure_scan.ts +115 -1
- package/src/scripts/compile_hook_manifest.ts +60 -16
- package/src/scripts/external_sources_denylist.json +29 -4
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +42 -5
- package/src/scripts/hooks/block_config_weakening.ts +229 -4
- package/src/scripts/hooks/block_kernel_rule_writes.ts +9 -78
- package/src/scripts/hooks/block_plumbing_writes.ts +314 -0
- package/src/scripts/hooks/bundle_integrity.ts +291 -0
- package/src/scripts/hooks/chain_nudge_hook.ts +1 -2
- package/src/scripts/hooks/code_graph_context_hook.ts +1 -2
- package/src/scripts/hooks/comment_discipline_hook.ts +1 -3
- package/src/scripts/hooks/concern_registry.ts +2 -0
- package/src/scripts/hooks/concern_timings.ts +94 -0
- package/src/scripts/hooks/delegation_nudge_hook.ts +1 -2
- package/src/scripts/hooks/design_pass_hook.ts +100 -9
- package/src/scripts/hooks/design_slop_hook.ts +1 -2
- package/src/scripts/hooks/dispatch_hook.ts +25 -19
- package/src/scripts/hooks/edit_shape_hook.ts +1 -2
- package/src/scripts/hooks/end_review_nudge_hook.ts +1 -1
- package/src/scripts/hooks/evidence_independence.ts +1 -2
- package/src/scripts/hooks/exit_codes.ts +128 -0
- package/src/scripts/hooks/host_lowering.json +1 -0
- package/src/scripts/hooks/host_lowering.ts +128 -3
- package/src/scripts/hooks/host_lowering.yaml +262 -46
- package/src/scripts/hooks/host_semantics.ts +30 -2
- package/src/scripts/hooks/injection_budget.ts +9 -2
- package/src/scripts/hooks/interruption_ledger_hook.ts +1 -2
- package/src/scripts/hooks/journal_record_hook.ts +1 -2
- package/src/scripts/hooks/memory_recall_hook.ts +1 -2
- package/src/scripts/hooks/obligation_settle_hook.ts +151 -5
- package/src/scripts/hooks/one_question_per_ask_hook.ts +25 -4
- package/src/scripts/hooks/orchestration_record_hook.ts +1 -2
- package/src/scripts/hooks/permission_gate.ts +1 -2
- package/src/scripts/hooks/push_settle_hook.ts +1 -3
- package/src/scripts/hooks/replay_hook.ts +2 -1
- package/src/scripts/hooks/reread_guard_hook.ts +1 -2
- package/src/scripts/hooks/review_baseline_hook.ts +1 -2
- package/src/scripts/hooks/rtk_wrap_hook.ts +1 -2
- package/src/scripts/hooks/rule_inject_hook.ts +8 -5
- package/src/scripts/hooks/run_continuation_hook.ts +13 -15
- package/src/scripts/hooks/skill_route_hook.ts +1 -2
- package/src/scripts/hooks/source_first_gate_hook.ts +1 -2
- package/src/scripts/hooks/spawn_guard_shadow_hook.ts +1 -2
- package/src/scripts/hooks/subagent_ledger_hook.ts +1 -2
- package/src/scripts/hooks/suggestion_capture_hook.ts +1 -1
- package/src/scripts/hooks/table_fingerprint.ts +49 -0
- package/src/scripts/hooks/telemetry_flush_hook.ts +1 -2
- package/src/scripts/hooks/telemetry_usage_hook.ts +1 -2
- package/src/scripts/hooks/tool_result_bytes_hook.ts +93 -9
- package/src/scripts/hooks/turn_end_gate_hook.ts +337 -324
- package/src/scripts/hooks/ui_route_nudge_hook.ts +1 -2
- package/src/scripts/install.ts +3 -4
- package/src/scripts/lint_deny_text.ts +419 -0
- package/src/scripts/lint_exit_codes.ts +262 -0
- package/src/scripts/lint_hook_manifest.ts +55 -0
- package/src/scripts/lint_roadmap_blockers.ts +78 -12
- package/src/scripts/lint_skill_descriptions.ts +1 -1
- package/src/scripts/lint_skill_scripts_readonly.ts +1 -1
- package/src/scripts/mcp_server/prompts.ts +142 -1
- package/src/scripts/measure_skill_ranker_baseline.ts +305 -51
- package/src/scripts/measure_turn_end_gate.ts +193 -0
- package/src/scripts/memory_lookup.ts +1 -1
- package/src/scripts/prepack-check.mjs +53 -0
- package/src/scripts/release_publication.ts +13 -1
- package/src/scripts/report_invocation_surface.ts +383 -0
- package/src/scripts/report_obligation_carriers.ts +699 -0
- package/src/scripts/report_obligation_writer_split.ts +236 -0
- package/src/scripts/report_skill_menu_census.ts +208 -0
- package/src/scripts/report_standing_payload_by_host.ts +1 -1
- package/src/scripts/roadmap_verify_share.ts +213 -0
- package/src/scripts/schemas/command.schema.json +44 -0
- package/src/scripts/schemas/review-findings.schema.json +4 -0
- package/src/scripts/schemas/skill.schema.json +44 -0
- package/src/scripts/self_review_gate.ts +359 -10
- package/src/scripts/semantic_parity_census.ts +314 -0
- package/src/scripts/session_eol_report.ts +54 -1
- package/src/scripts/sweep_dead_scan_roots.ts +1 -1
- package/src/scripts/trigger_eval_grandfather.json +0 -2
- package/src/scripts/trigger_eval_rotation.ts +122 -27
- package/src/scripts/ui_conformance_probe.ts +156 -4
- package/src/scripts/update_prices.ts +85 -3
- package/src/scripts/write_bundle_digest.mjs +74 -0
- package/src/server/schemas/settings.ts +31 -0
- package/src/templates/consumer-settings/ONBOARDING.md +1 -0
- package/dist/ui/assets/index-DH1xc62v.js +0 -29
- package/dist/ui/assets/index-jSfMW8_t.css +0 -1
- /package/dist/cli-delegate/{chunk-YUM5KURG.js → chunk-7RQP4UDH.js} +0 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Every in-body `${x}` on an artifact that declares `inputs:` is backed by a
|
|
4
|
+
* declaration.
|
|
5
|
+
*
|
|
6
|
+
* road-to-an-invocation-contract-that-reaches-the-wire 2.2.
|
|
7
|
+
*
|
|
8
|
+
* SCOPED TO DECLARING ARTIFACTS, ON PURPOSE
|
|
9
|
+
*
|
|
10
|
+
* An artifact with no `inputs:` block is not checked at all. That is what lets
|
|
11
|
+
* this arrive with zero findings over a corpus where nothing declares anything
|
|
12
|
+
* yet, and grow only with adoption — a check that fired on every artifact would
|
|
13
|
+
* have to be introduced together with a migration nobody asked for, which is
|
|
14
|
+
* exactly what the optional block was designed to avoid.
|
|
15
|
+
*
|
|
16
|
+
* THE COUNTING UNIT IS THE CENSUS'S, IMPORTED
|
|
17
|
+
*
|
|
18
|
+
* A reference counts when it appears in PROSE — outside fenced code blocks,
|
|
19
|
+
* indented blocks and inline code spans. `proseOnly` is imported from
|
|
20
|
+
* `report_invocation_surface.ts` rather than restated, because two copies of
|
|
21
|
+
* that definition would drift and the whole point of this phase is that the
|
|
22
|
+
* declaration and what depends on it cannot disagree.
|
|
23
|
+
*
|
|
24
|
+
* The consequence is stated rather than hidden: a `${target}` inside a fenced
|
|
25
|
+
* usage example is NOT checked. That is the conservative direction. A skill
|
|
26
|
+
* teaching Terraform has `${local.x}` in a fence and would otherwise be
|
|
27
|
+
* reported against a declaration it never claimed to satisfy — a false positive
|
|
28
|
+
* on working content, which is the failure that makes a young gate get
|
|
29
|
+
* disabled. When adoption makes in-fence usage examples common, widening the
|
|
30
|
+
* unit is a deliberate change with its own diff.
|
|
31
|
+
*
|
|
32
|
+
* Exit codes: 0 every reference backed · 1 an unbacked reference · 2 the corpus
|
|
33
|
+
* could not be read.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import fs from 'node:fs';
|
|
37
|
+
import os from 'node:os';
|
|
38
|
+
import path from 'node:path';
|
|
39
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
40
|
+
|
|
41
|
+
import * as yaml from 'js-yaml';
|
|
42
|
+
|
|
43
|
+
import { GateLedger } from './_lib/gate_ledger.js';
|
|
44
|
+
import { runGateCli, runSelfTest } from './_lib/gate_self_test.js';
|
|
45
|
+
import { assertScanned, reportScanned } from './_lib/scan_scope.js';
|
|
46
|
+
import { proseOnly } from './report_invocation_surface.js';
|
|
47
|
+
|
|
48
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
49
|
+
const ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
50
|
+
|
|
51
|
+
export const GATE = 'check_input_references';
|
|
52
|
+
|
|
53
|
+
/** The reference form this checks. Only the plurality syntax is a reference. */
|
|
54
|
+
const REFERENCE_RE = /\$\{([a-z][a-z0-9_]*)\}/g;
|
|
55
|
+
|
|
56
|
+
export interface Unbacked {
|
|
57
|
+
readonly file: string;
|
|
58
|
+
readonly name: string;
|
|
59
|
+
readonly declared: readonly string[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Declared input names, or `null` when the artifact declares no block at all. */
|
|
63
|
+
export function declaredInputs(text: string): string[] | null {
|
|
64
|
+
const m = /^---\n([\s\S]*?)\n---/.exec(text);
|
|
65
|
+
if (m === null) return null;
|
|
66
|
+
let fm: unknown;
|
|
67
|
+
try {
|
|
68
|
+
fm = yaml.load(m[1]!);
|
|
69
|
+
} catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
if (fm === null || typeof fm !== 'object') return null;
|
|
73
|
+
const inputs = (fm as Record<string, unknown>)['inputs'];
|
|
74
|
+
if (!Array.isArray(inputs)) return null;
|
|
75
|
+
const names: string[] = [];
|
|
76
|
+
for (const item of inputs) {
|
|
77
|
+
if (item === null || typeof item !== 'object') continue;
|
|
78
|
+
const name = (item as Record<string, unknown>)['name'];
|
|
79
|
+
if (typeof name === 'string') names.push(name);
|
|
80
|
+
}
|
|
81
|
+
return names;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Prose `${x}` references with no matching declaration.
|
|
86
|
+
*
|
|
87
|
+
* Returns `[]` for an artifact that declares no block — not because it has no
|
|
88
|
+
* references, but because it is out of scope.
|
|
89
|
+
*/
|
|
90
|
+
export function unbackedReferences(text: string, file = '<data>'): Unbacked[] {
|
|
91
|
+
const declared = declaredInputs(text);
|
|
92
|
+
if (declared === null) return [];
|
|
93
|
+
const seen = new Set<string>();
|
|
94
|
+
const out: Unbacked[] = [];
|
|
95
|
+
for (const m of proseOnly(text).matchAll(REFERENCE_RE)) {
|
|
96
|
+
const name = m[1]!;
|
|
97
|
+
if (declared.includes(name) || seen.has(name)) continue;
|
|
98
|
+
seen.add(name);
|
|
99
|
+
out.push({ file, name, declared });
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function walk(dir: string, match: (n: string) => boolean, out: string[] = []): string[] {
|
|
105
|
+
if (!fs.existsSync(dir)) return out;
|
|
106
|
+
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
107
|
+
const p = path.join(dir, e.name);
|
|
108
|
+
if (e.isDirectory()) walk(p, match, out);
|
|
109
|
+
else if (match(e.name)) out.push(p);
|
|
110
|
+
}
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function scan(root: string): { findings: Unbacked[]; scanned: number; declaring: number } {
|
|
115
|
+
const files = [
|
|
116
|
+
...walk(path.join(root, 'dist', 'agent-src', 'commands'), (n) => n.endsWith('.md')),
|
|
117
|
+
...walk(path.join(root, 'src', 'skills'), (n) => n === 'SKILL.md'),
|
|
118
|
+
].sort();
|
|
119
|
+
|
|
120
|
+
const findings: Unbacked[] = [];
|
|
121
|
+
let declaring = 0;
|
|
122
|
+
for (const f of files) {
|
|
123
|
+
const text = fs.readFileSync(f, 'utf8');
|
|
124
|
+
if (declaredInputs(text) !== null) declaring += 1;
|
|
125
|
+
findings.push(...unbackedReferences(text, path.relative(root, f)));
|
|
126
|
+
}
|
|
127
|
+
return { findings, scanned: files.length, declaring };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function main(argv: readonly string[]): number {
|
|
131
|
+
if (argv.includes('--self-test')) return selfTest();
|
|
132
|
+
const rootIdx = argv.indexOf('--root');
|
|
133
|
+
const root = rootIdx === -1 ? ROOT : (argv[rootIdx + 1] ?? ROOT);
|
|
134
|
+
|
|
135
|
+
const { findings, scanned, declaring } = scan(root);
|
|
136
|
+
const ledger = new GateLedger(GATE);
|
|
137
|
+
ledger.plan(findings.map((f) => `${f.file}:${f.name}`));
|
|
138
|
+
for (const f of findings) {
|
|
139
|
+
ledger.fail(`${f.file}:${f.name}`, `\`\${${f.name}}\` is not declared`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// A gate that read nothing has not passed. This fires BEFORE the verdict,
|
|
143
|
+
// because certifying against an empty corpus is the false green the coverage
|
|
144
|
+
// manifest exists to refuse — named by the provider-diverse ratification
|
|
145
|
+
// review as one condition that would have flipped its verdict.
|
|
146
|
+
assertScanned({ gate: GATE, scanned: scanned, units: 'artifact(s)', roots: ['dist/agent-src/commands', 'src/skills'] });
|
|
147
|
+
|
|
148
|
+
reportScanned({
|
|
149
|
+
gate: GATE,
|
|
150
|
+
scanned,
|
|
151
|
+
units: 'artifact(s)',
|
|
152
|
+
roots: ['dist/agent-src/commands', 'src/skills'],
|
|
153
|
+
});
|
|
154
|
+
ledger.report();
|
|
155
|
+
|
|
156
|
+
if (findings.length > 0) {
|
|
157
|
+
for (const f of findings) {
|
|
158
|
+
process.stdout.write(
|
|
159
|
+
`❌ ${GATE}: ${f.file} references \`\${${f.name}}\` with no \`inputs.${f.name}\` ` +
|
|
160
|
+
`(declared: ${f.declared.length > 0 ? f.declared.join(', ') : 'none'}).\n`,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
process.stdout.write(
|
|
164
|
+
'\nAdd the declaration, or drop the reference. Only artifacts that declare ' +
|
|
165
|
+
'`inputs:` at all are checked, so this never asks an artifact to start ' +
|
|
166
|
+
'declaring — it asks a declaration to be complete.\n',
|
|
167
|
+
);
|
|
168
|
+
return 1;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
process.stdout.write(
|
|
172
|
+
`✅ ${GATE}: every in-body reference is backed ` +
|
|
173
|
+
`(${String(declaring)} of ${String(scanned)} artifact(s) declare \`inputs:\`).\n`,
|
|
174
|
+
);
|
|
175
|
+
return 0;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* A self-test is what proves a gate DISCRIMINATES. An enforced scan floor only
|
|
180
|
+
* proves it read something; the verdict has to move.
|
|
181
|
+
*/
|
|
182
|
+
function selfTest(): number {
|
|
183
|
+
const plant = (name: string, body: string): string => {
|
|
184
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), `input-refs-${name}-`));
|
|
185
|
+
const skills = path.join(dir, 'src', 'skills', 'probe');
|
|
186
|
+
fs.mkdirSync(skills, { recursive: true });
|
|
187
|
+
fs.writeFileSync(path.join(skills, 'SKILL.md'), body, 'utf-8');
|
|
188
|
+
return dir;
|
|
189
|
+
};
|
|
190
|
+
const declaring = (ref: string): string =>
|
|
191
|
+
`---\nname: probe\ninputs:\n - name: a\n type: string\n---\n\nUses ${ref} here.\n`;
|
|
192
|
+
|
|
193
|
+
const run = (root: string): number =>
|
|
194
|
+
runGateCli(ROOT, path.join('src', 'scripts', 'check_input_references.ts'), ['--root', root], ROOT);
|
|
195
|
+
|
|
196
|
+
return runSelfTest({
|
|
197
|
+
gate: GATE,
|
|
198
|
+
minCases: 4,
|
|
199
|
+
minRejectCases: 2,
|
|
200
|
+
cases: [
|
|
201
|
+
{
|
|
202
|
+
name: 'an empty corpus is refused, not certified green',
|
|
203
|
+
expect: 'reject',
|
|
204
|
+
run: () => run(fs.mkdtempSync(path.join(os.tmpdir(), 'input-refs-empty-'))),
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
name: 'a reference the declaration backs passes',
|
|
208
|
+
expect: 'accept',
|
|
209
|
+
run: () => run(plant('backed', declaring('${a}'))),
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
name: 'a reference nothing declares fails',
|
|
213
|
+
expect: 'reject',
|
|
214
|
+
run: () => run(plant('unbacked', declaring('${b}'))),
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
name: 'an artifact with no declaration is out of scope, however it references',
|
|
218
|
+
expect: 'accept',
|
|
219
|
+
run: () =>
|
|
220
|
+
run(plant('noblock', '---\nname: probe\n---\n\nUses ${anything} here.\n')),
|
|
221
|
+
},
|
|
222
|
+
],
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {
|
|
227
|
+
process.exit(main(process.argv.slice(2)));
|
|
228
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* What the MCP server reads from an `inputs:` block equals what a real YAML
|
|
4
|
+
* parser reads from it.
|
|
5
|
+
*
|
|
6
|
+
* road-to-an-invocation-contract-that-reaches-the-wire — added after review.
|
|
7
|
+
*
|
|
8
|
+
* WHY THIS GATE EXISTS
|
|
9
|
+
*
|
|
10
|
+
* The server's `_parse_inputs` is a hand-rolled reader of a YAML subset,
|
|
11
|
+
* because the MCP server is stdlib-only and bundled and must not grow a YAML
|
|
12
|
+
* dependency to read one optional block. A hand-rolled reader of a format it
|
|
13
|
+
* does not fully implement has exactly one dangerous failure mode: reading
|
|
14
|
+
* something DIFFERENT from what the author wrote, silently. A host is then told
|
|
15
|
+
* about an argument that does not exist, or not told about one that does — and
|
|
16
|
+
* the schema validates either way, because the schema checks the YAML, not the
|
|
17
|
+
* reader.
|
|
18
|
+
*
|
|
19
|
+
* An independent review found that failure live: the reader's key scan ran at
|
|
20
|
+
* any depth, so a `default:` mapping whose first child was `name:` overwrote
|
|
21
|
+
* the parameter's own name. It also silently misread `required: True`, the
|
|
22
|
+
* flow form, a comment at column zero, and a block scalar. Every one of those
|
|
23
|
+
* is schema-valid.
|
|
24
|
+
*
|
|
25
|
+
* So the reader now DECLINES what it cannot model, and this gate makes every
|
|
26
|
+
* decline and every divergence a build failure with a named remedy. The
|
|
27
|
+
* contract is: write the block form, or the build tells you. That is strictly
|
|
28
|
+
* better than a reader that is clever and occasionally wrong, and it is why the
|
|
29
|
+
* reader is allowed to stay stdlib-only.
|
|
30
|
+
*
|
|
31
|
+
* Exit codes: 0 parity holds · 1 a divergence · 2 the corpus could not be read.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import fs from 'node:fs';
|
|
35
|
+
import os from 'node:os';
|
|
36
|
+
import path from 'node:path';
|
|
37
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
38
|
+
|
|
39
|
+
import * as yaml from 'js-yaml';
|
|
40
|
+
|
|
41
|
+
import { GateLedger } from './_lib/gate_ledger.js';
|
|
42
|
+
import { runGateCli, runSelfTest } from './_lib/gate_self_test.js';
|
|
43
|
+
import { assertScanned, reportScanned } from './_lib/scan_scope.js';
|
|
44
|
+
import { _frontmatter_text, _parse_inputs, type PromptInput } from './mcp_server/prompts.js';
|
|
45
|
+
|
|
46
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
47
|
+
const ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
48
|
+
|
|
49
|
+
export const GATE = 'check_inputs_parity';
|
|
50
|
+
|
|
51
|
+
/** The same reduction `_parse_inputs` produces, taken from a real YAML parse. */
|
|
52
|
+
export function yamlInputs(fmText: string): PromptInput[] | null {
|
|
53
|
+
let doc: unknown;
|
|
54
|
+
try {
|
|
55
|
+
doc = yaml.load(fmText);
|
|
56
|
+
} catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
if (doc === null || typeof doc !== 'object') return null;
|
|
60
|
+
const raw = (doc as Record<string, unknown>)['inputs'];
|
|
61
|
+
if (raw === undefined) return null;
|
|
62
|
+
if (!Array.isArray(raw)) return [];
|
|
63
|
+
const out: PromptInput[] = [];
|
|
64
|
+
for (const item of raw) {
|
|
65
|
+
if (item === null || typeof item !== 'object') continue;
|
|
66
|
+
const r = item as Record<string, unknown>;
|
|
67
|
+
if (typeof r['name'] !== 'string') continue;
|
|
68
|
+
out.push({
|
|
69
|
+
name: r['name'],
|
|
70
|
+
description: typeof r['description'] === 'string' ? r['description'] : '',
|
|
71
|
+
required: r['required'] === true,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface Divergence {
|
|
78
|
+
readonly file: string;
|
|
79
|
+
readonly server: PromptInput[];
|
|
80
|
+
readonly yaml: PromptInput[];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const same = (a: readonly PromptInput[], b: readonly PromptInput[]): boolean =>
|
|
84
|
+
a.length === b.length &&
|
|
85
|
+
a.every(
|
|
86
|
+
(x, i) =>
|
|
87
|
+
x.name === b[i]!.name &&
|
|
88
|
+
x.required === b[i]!.required &&
|
|
89
|
+
x.description === b[i]!.description,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
/** Compare both readers over one document. `null` when it declares no block. */
|
|
93
|
+
export function compare(text: string, file: string): Divergence | null {
|
|
94
|
+
const fm = _frontmatter_text(text);
|
|
95
|
+
if (fm === '') return null;
|
|
96
|
+
const expected = yamlInputs(fm);
|
|
97
|
+
if (expected === null) return null;
|
|
98
|
+
const actual = _parse_inputs(fm);
|
|
99
|
+
return same(actual, expected) ? null : { file, server: actual, yaml: expected };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function walk(dir: string, match: (n: string) => boolean, out: string[] = []): string[] {
|
|
103
|
+
if (!fs.existsSync(dir)) return out;
|
|
104
|
+
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
105
|
+
const p = path.join(dir, e.name);
|
|
106
|
+
if (e.isDirectory()) walk(p, match, out);
|
|
107
|
+
else if (match(e.name)) out.push(p);
|
|
108
|
+
}
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function scan(root: string): { findings: Divergence[]; scanned: number } {
|
|
113
|
+
const files = [
|
|
114
|
+
...walk(path.join(root, 'src', 'domains'), (n) => n === 'command.md'),
|
|
115
|
+
...walk(path.join(root, 'src', 'skills'), (n) => n === 'SKILL.md'),
|
|
116
|
+
...walk(path.join(root, 'dist', 'agent-src', 'commands'), (n) => n.endsWith('.md')),
|
|
117
|
+
].sort();
|
|
118
|
+
|
|
119
|
+
const findings: Divergence[] = [];
|
|
120
|
+
for (const f of files) {
|
|
121
|
+
const d = compare(fs.readFileSync(f, 'utf8'), path.relative(root, f));
|
|
122
|
+
if (d !== null) findings.push(d);
|
|
123
|
+
}
|
|
124
|
+
return { findings, scanned: files.length };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function main(argv: readonly string[]): number {
|
|
128
|
+
if (argv.includes('--self-test')) return selfTest();
|
|
129
|
+
const rootIdx = argv.indexOf('--root');
|
|
130
|
+
const root = rootIdx === -1 ? ROOT : (argv[rootIdx + 1] ?? ROOT);
|
|
131
|
+
|
|
132
|
+
const { findings, scanned } = scan(root);
|
|
133
|
+
const ledger = new GateLedger(GATE);
|
|
134
|
+
ledger.plan(findings.map((f) => f.file));
|
|
135
|
+
for (const f of findings) {
|
|
136
|
+
ledger.fail(f.file, `server read ${String(f.server.length)}, YAML read ${String(f.yaml.length)}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// A gate that read nothing has not passed. This fires BEFORE the verdict,
|
|
140
|
+
// because certifying against an empty corpus is the false green the coverage
|
|
141
|
+
// manifest exists to refuse — named by the provider-diverse ratification
|
|
142
|
+
// review as one condition that would have flipped its verdict.
|
|
143
|
+
assertScanned({ gate: GATE, scanned: scanned, units: 'artifact(s)', roots: ['src/domains', 'src/skills', 'dist/agent-src/commands'] });
|
|
144
|
+
|
|
145
|
+
reportScanned({
|
|
146
|
+
gate: GATE,
|
|
147
|
+
scanned,
|
|
148
|
+
units: 'artifact(s)',
|
|
149
|
+
roots: ['src/domains', 'src/skills', 'dist/agent-src/commands'],
|
|
150
|
+
});
|
|
151
|
+
ledger.report();
|
|
152
|
+
|
|
153
|
+
if (findings.length > 0) {
|
|
154
|
+
for (const f of findings) {
|
|
155
|
+
process.stdout.write(
|
|
156
|
+
`❌ ${GATE}: ${f.file} — the MCP reader and YAML disagree about \`inputs:\`.\n` +
|
|
157
|
+
` server: ${JSON.stringify(f.server)}\n` +
|
|
158
|
+
` yaml: ${JSON.stringify(f.yaml)}\n`,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
process.stdout.write(
|
|
162
|
+
'\nThe server reads one shape, deliberately — a block sequence of `- name:` items ' +
|
|
163
|
+
'with scalar values on their own lines. Rewrite the declaration in that shape. ' +
|
|
164
|
+
'The flow form, a block scalar and a nested mapping under an item key are ' +
|
|
165
|
+
'refused rather than guessed at, and this gate is how the refusal is visible ' +
|
|
166
|
+
'instead of becoming a wrong argument on the wire.\n',
|
|
167
|
+
);
|
|
168
|
+
return 1;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
process.stdout.write(
|
|
172
|
+
`✅ ${GATE}: the MCP reader and YAML agree on every declared \`inputs:\` block.\n`,
|
|
173
|
+
);
|
|
174
|
+
return 0;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* One rejecting case per way the reader and YAML can part company, plus the
|
|
179
|
+
* agreeing case — because a suite that only proves passes proves nothing.
|
|
180
|
+
*/
|
|
181
|
+
function selfTest(): number {
|
|
182
|
+
const plant = (name: string, fm: string): string => {
|
|
183
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), `inputs-parity-${name}-`));
|
|
184
|
+
const skills = path.join(dir, 'src', 'skills', 'probe');
|
|
185
|
+
fs.mkdirSync(skills, { recursive: true });
|
|
186
|
+
fs.writeFileSync(path.join(skills, 'SKILL.md'), `---\nname: probe\n${fm}---\n\nbody\n`, 'utf-8');
|
|
187
|
+
return dir;
|
|
188
|
+
};
|
|
189
|
+
const run = (root: string): number =>
|
|
190
|
+
runGateCli(ROOT, path.join('src', 'scripts', 'check_inputs_parity.ts'), ['--root', root], ROOT);
|
|
191
|
+
|
|
192
|
+
return runSelfTest({
|
|
193
|
+
gate: GATE,
|
|
194
|
+
minCases: 6,
|
|
195
|
+
minRejectCases: 5,
|
|
196
|
+
cases: [
|
|
197
|
+
{
|
|
198
|
+
name: 'an empty corpus is refused, not certified green',
|
|
199
|
+
expect: 'reject',
|
|
200
|
+
run: () => run(fs.mkdtempSync(path.join(os.tmpdir(), 'inputs-parity-empty-'))),
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
name: 'the supported block form agrees',
|
|
204
|
+
expect: 'accept',
|
|
205
|
+
run: () => run(plant('ok', 'inputs:\n - name: a\n type: string\n required: true\n')),
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
name: 'the flow form is a divergence',
|
|
209
|
+
expect: 'reject',
|
|
210
|
+
run: () => run(plant('flow', 'inputs: [{name: a, type: string}]\n')),
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: 'a nested mapping under an item key is a divergence',
|
|
214
|
+
expect: 'reject',
|
|
215
|
+
run: () =>
|
|
216
|
+
run(plant('nested', 'inputs:\n - name: opts\n type: string\n default:\n name: fallback\n')),
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
name: 'a block scalar is a divergence',
|
|
220
|
+
expect: 'reject',
|
|
221
|
+
run: () =>
|
|
222
|
+
run(plant('scalar', 'inputs:\n - name: a\n type: string\n description: >\n long\n')),
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
name: 'a dash on its own line is a divergence',
|
|
226
|
+
expect: 'reject',
|
|
227
|
+
run: () => run(plant('bareDash', 'inputs:\n -\n name: a\n type: string\n')),
|
|
228
|
+
},
|
|
229
|
+
],
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {
|
|
234
|
+
process.exit(main(process.argv.slice(2)));
|
|
235
|
+
}
|
|
@@ -30,6 +30,8 @@
|
|
|
30
30
|
*
|
|
31
31
|
* Gated: a diff touching a kernel rule under `src/rules/` (the nine of
|
|
32
32
|
* `_lib/kernel_rules.ts`), a governance hook (`src/scripts/hooks/block_*.ts`),
|
|
33
|
+
* a hook-plumbing SOURCE (`hook_manifest.yaml`, `host_lowering.yaml`, a
|
|
34
|
+
* `*-dispatcher.sh`, either hook budget file — see `PLUMBING_SOURCE_RE`),
|
|
33
35
|
* or this gate itself — so it cannot be weakened without its own record. Such
|
|
34
36
|
* a diff must carry a ratification artifact under
|
|
35
37
|
* the ratifications directory with `verdict: ratified`, valid per the
|
|
@@ -122,6 +124,31 @@ export const ANCHOR_PATHS: readonly string[] = [
|
|
|
122
124
|
/** Governance hooks: the blocking PreToolUse guards. */
|
|
123
125
|
const GOVERNANCE_HOOK_RE = /^src\/scripts\/hooks\/block_[a-z0-9_]+\.ts$/;
|
|
124
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Hook-plumbing SOURCES — the files that decide which concern runs, on which
|
|
129
|
+
* host, under which budget.
|
|
130
|
+
*
|
|
131
|
+
* `road-to-a-kernel-that-guards-its-plumbing` 1.1. The nine kernel rules were
|
|
132
|
+
* governed and the plumbing that decides whether a guard runs at all was not,
|
|
133
|
+
* so a diff deleting `block-no-verify` from the manifest carried no record
|
|
134
|
+
* while a typo in the rule it enforces carried one. The asymmetry was the
|
|
135
|
+
* defect: a concern removed from `hook_manifest.yaml` is a refusal that stops
|
|
136
|
+
* happening, which is the same authority change as loosening the rule behind
|
|
137
|
+
* it, reached one file earlier.
|
|
138
|
+
*
|
|
139
|
+
* This is the ADR-268 § 4 mechanism EXTENDED, not a second deny. Nothing new
|
|
140
|
+
* refuses a tool call here; the same CI gate simply reads a wider path set,
|
|
141
|
+
* and every property the gate already had — base-revision code, in-repo quorum
|
|
142
|
+
* policy, the whole diff as its unit — carries over unchanged.
|
|
143
|
+
*
|
|
144
|
+
* Sources only. `dist/hooks/dispatch.js` and `hooks/hooks.json` are BUILD
|
|
145
|
+
* OUTPUTS with no legitimate hand edit at all, so they are refused at tool-call
|
|
146
|
+
* time by `block_plumbing_writes.ts` rather than ratified here — a record for a
|
|
147
|
+
* file whose every hand edit is illegitimate would be a record of a mistake.
|
|
148
|
+
*/
|
|
149
|
+
const PLUMBING_SOURCE_RE =
|
|
150
|
+
/^(?:src\/scripts\/hook_manifest\.yaml|src\/scripts\/hooks\/host_lowering\.yaml|src\/scripts\/hooks\/[a-z0-9-]+-dispatcher\.sh|src\/config\/hook-(?:token|latency)-budget\.json)$/;
|
|
151
|
+
|
|
125
152
|
/**
|
|
126
153
|
* A kernel rule, in the source tree or in any projection.
|
|
127
154
|
*
|
|
@@ -141,6 +168,8 @@ const KERNEL_RULE_PATH_RE = /(?:^|\/)rules\/([a-z0-9-]+)\.md$/;
|
|
|
141
168
|
export interface GatedPaths {
|
|
142
169
|
kernelRules: string[];
|
|
143
170
|
governanceHooks: string[];
|
|
171
|
+
/** Hook-plumbing sources — manifest, host lowering, dispatchers, budgets. */
|
|
172
|
+
plumbing: string[];
|
|
144
173
|
self: boolean;
|
|
145
174
|
}
|
|
146
175
|
|
|
@@ -148,6 +177,7 @@ export interface GatedPaths {
|
|
|
148
177
|
export function classifyPaths(files: readonly string[]): GatedPaths {
|
|
149
178
|
const kernelRules: string[] = [];
|
|
150
179
|
const governanceHooks: string[] = [];
|
|
180
|
+
const plumbing: string[] = [];
|
|
151
181
|
let self = false;
|
|
152
182
|
for (const raw of files) {
|
|
153
183
|
const p = raw.replace(/\\/g, '/').trim();
|
|
@@ -171,14 +201,23 @@ export function classifyPaths(files: readonly string[]): GatedPaths {
|
|
|
171
201
|
}
|
|
172
202
|
if (GOVERNANCE_HOOK_RE.test(p)) {
|
|
173
203
|
governanceHooks.push(p);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (PLUMBING_SOURCE_RE.test(p)) {
|
|
207
|
+
plumbing.push(p);
|
|
174
208
|
}
|
|
175
209
|
}
|
|
176
|
-
return { kernelRules, governanceHooks, self };
|
|
210
|
+
return { kernelRules, governanceHooks, plumbing, self };
|
|
177
211
|
}
|
|
178
212
|
|
|
179
213
|
/** True when anything in the diff requires a ratification artifact. */
|
|
180
214
|
export function requiresRatification(gated: GatedPaths): boolean {
|
|
181
|
-
return
|
|
215
|
+
return (
|
|
216
|
+
gated.kernelRules.length > 0 ||
|
|
217
|
+
gated.governanceHooks.length > 0 ||
|
|
218
|
+
gated.plumbing.length > 0 ||
|
|
219
|
+
gated.self
|
|
220
|
+
);
|
|
182
221
|
}
|
|
183
222
|
|
|
184
223
|
/** Ratification artifacts present in the diff, as repo-relative paths. */
|
|
@@ -253,6 +292,7 @@ export function evaluate(
|
|
|
253
292
|
const gatedSet = new Set<string>([
|
|
254
293
|
...gated.kernelRules,
|
|
255
294
|
...gated.governanceHooks,
|
|
295
|
+
...gated.plumbing,
|
|
256
296
|
...(gated.self ? [SELF_PATH] : []),
|
|
257
297
|
]);
|
|
258
298
|
const artifactSet = new Set(ratificationArtifactsIn(files));
|
|
@@ -286,7 +326,8 @@ export function evaluate(
|
|
|
286
326
|
|
|
287
327
|
if (!requiresRatification(gated)) {
|
|
288
328
|
lines.push(
|
|
289
|
-
'✅ no kernel rule, governance hook or self edit in the
|
|
329
|
+
'✅ no kernel rule, governance hook, hook-plumbing source or self edit in the ' +
|
|
330
|
+
'diff — nothing to ratify',
|
|
290
331
|
);
|
|
291
332
|
close(true);
|
|
292
333
|
return { exitCode: 0, lines, scanned };
|
|
@@ -295,6 +336,7 @@ export function evaluate(
|
|
|
295
336
|
const touched = [
|
|
296
337
|
...gated.kernelRules.map((p) => `kernel rule ${p}`),
|
|
297
338
|
...gated.governanceHooks.map((p) => `governance hook ${p}`),
|
|
339
|
+
...gated.plumbing.map((p) => `hook-plumbing source ${p}`),
|
|
298
340
|
...(gated.self ? ['the ratification mechanism itself (gate, reader, policy or workflow)'] : []),
|
|
299
341
|
];
|
|
300
342
|
lines.push(`Gated surfaces in this diff (${touched.length}):`);
|
|
@@ -446,8 +488,8 @@ export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
|
446
488
|
roots: ['<the diff against the base ref>'],
|
|
447
489
|
allowEmpty:
|
|
448
490
|
'EMPTY_VALID: the corpus is one diff. A commit range that changed nothing is a ' +
|
|
449
|
-
'real state, and there is then no kernel rule, governance hook
|
|
450
|
-
'to ratify — the gate has read everything there was.',
|
|
491
|
+
'real state, and there is then no kernel rule, governance hook, plumbing source or ' +
|
|
492
|
+
'mechanism file to ratify — the gate has read everything there was.',
|
|
451
493
|
});
|
|
452
494
|
if (!quiet || result.exitCode !== 0) {
|
|
453
495
|
for (const l of result.lines) {
|