@event4u/agent-config 9.17.0 → 9.18.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/marketplace.json +1 -1
- package/CHANGELOG.md +122 -171
- package/dist/agent-src/commands/challenge-me/vision.md +5 -0
- package/dist/agent-src/commands/evals/rule-compliance-audit.json +46 -0
- package/dist/agent-src/commands/feature/plan.md +20 -0
- package/dist/agent-src/commands/feature/roadmap.md +19 -0
- package/dist/agent-src/commands/pr/create.md +29 -0
- package/dist/agent-src/commands/roadmap/create.md +29 -0
- package/dist/agent-src/commands/rule-compliance-audit.md +28 -5
- package/dist/agent-src/contexts/execution/plan-confidence-gate.md +133 -0
- package/dist/agent-src/rules/active-remediation.md +2 -0
- package/dist/agent-src/rules/architecture.md +2 -0
- package/dist/agent-src/rules/augment-edit-discipline.md +2 -0
- package/dist/agent-src/rules/brand-consistency.md +10 -62
- package/dist/agent-src/rules/brand-source-of-truth.md +62 -11
- package/dist/agent-src/rules/broken-access-control.md +4 -0
- package/dist/agent-src/rules/cli-output-handling.md +5 -0
- package/dist/agent-src/rules/code-comment-discipline.md +3 -0
- package/dist/agent-src/rules/commit-conventions.md +2 -0
- package/dist/agent-src/rules/cross-source-consistency.md +3 -0
- package/dist/agent-src/rules/design-fidelity.md +2 -0
- package/dist/agent-src/rules/doc-screenshot-hygiene.md +2 -0
- package/dist/agent-src/rules/docker-commands.md +4 -0
- package/dist/agent-src/rules/domain-adoption-policy.md +2 -0
- package/dist/agent-src/rules/domain-safety-disclaimer.md +5 -2
- package/dist/agent-src/rules/engineering-safety-floor.md +2 -0
- package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +9 -0
- package/dist/agent-src/rules/image-likeness-and-rights.md +4 -0
- package/dist/agent-src/rules/improve-before-implement.md +4 -0
- package/dist/agent-src/rules/laravel-routing.md +4 -0
- package/dist/agent-src/rules/legal-safety-floor.md +3 -0
- package/dist/agent-src/rules/lethal-trifecta-guard.md +3 -0
- package/dist/agent-src/rules/media-governance-routing.md +5 -0
- package/dist/agent-src/rules/minimal-safe-diff.md +2 -0
- package/dist/agent-src/rules/no-roadmap-references.md +2 -0
- package/dist/agent-src/rules/notes-first-reasoning.md +2 -0
- package/dist/agent-src/rules/php-coding.md +2 -0
- package/dist/agent-src/rules/prefer-enums-over-literals.md +3 -0
- package/dist/agent-src/rules/provider-lifecycle-discipline.md +5 -0
- package/dist/agent-src/rules/roadmap-ci-steps-policy.md +2 -0
- package/dist/agent-src/rules/roadmap-progress-sync.md +2 -0
- package/dist/agent-src/rules/rule-type-governance.md +2 -0
- package/dist/agent-src/rules/scale-discipline.md +3 -0
- package/dist/agent-src/rules/secret-vcs-guard.md +2 -0
- package/dist/agent-src/rules/security-sensitive-stop.md +9 -2
- package/dist/agent-src/rules/senior-engineering-discipline.md +6 -0
- package/dist/agent-src/rules/skill-quality.md +2 -0
- package/dist/agent-src/rules/source-confidentiality.md +4 -0
- package/dist/agent-src/rules/source-discovery-gate.md +3 -0
- package/dist/agent-src/rules/source-of-truth.md +2 -0
- package/dist/agent-src/rules/strategy-safety-floor.md +2 -0
- package/dist/agent-src/rules/think-before-action.md +5 -0
- package/dist/agent-src/rules/ui-audit-gate.md +2 -0
- package/dist/agent-src/skills/design-intelligence/SKILL.md +6 -180
- package/dist/agent-src/skills/design-intelligence/references/context-and-registers.md +100 -0
- package/dist/agent-src/skills/design-intelligence/references/integration-mapping.md +91 -0
- package/dist/agent-src/skills/design-review/SKILL.md +4 -141
- package/dist/agent-src/skills/design-review/references/review-communication.md +92 -0
- package/dist/agent-src/skills/design-review/references/verification-automation.md +61 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +8 -27
- package/dist/agent-src/skills/existing-ui-audit/references/anti-slop-cross-reference.md +31 -0
- package/dist/agent-src/skills/fe-design/SKILL.md +4 -289
- package/dist/agent-src/skills/fe-design/references/design-patterns.md +227 -0
- package/dist/agent-src/skills/fe-design/references/design-read-and-memory.md +75 -0
- package/dist/agent-src/skills/roadmap-management/SKILL.md +10 -0
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +12 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +12 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/_fix_lane.ts +52 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +8 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/design.ts +6 -0
- package/dist/agent-src/templates/scripts/work_engine/intent/classify.ts +56 -4
- package/dist/cli/registry.js +2 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/chunk-AIIUU73N.js +2040 -0
- package/dist/cli-delegate/{chunk-IMSXPRVH.js → chunk-ELMFOQPW.js} +11 -0
- package/dist/cli-delegate/chunk-LF2KKJ4Q.js +183 -0
- package/dist/cli-delegate/cmd_explain.js +36 -36
- package/dist/cli-delegate/cmd_export.js +1 -1
- package/dist/cli-delegate/cmd_handoff.js +203 -2228
- package/dist/cli-delegate/cmd_prune.js +1 -1
- package/dist/cli-delegate/cmd_route_audit.js +249 -0
- package/dist/cli-delegate/cmd_route_explain.js +285 -0
- package/dist/cli-delegate/cmd_settings_migrate.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +1 -1
- package/dist/cli-delegate/cmd_uninstall.js +1 -1
- package/dist/cli-delegate/cmd_validate.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +57 -57
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.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/install/install.mjs +11 -0
- package/dist/mcp/registry-manifest.json +1 -1
- package/dist/mcp/server.json +2 -2
- package/dist/router.json +1 -1
- package/dist/server/schemas/settings.js +12 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/package.json +1 -1
- package/src/agent-src/templates/scripts/work_engine/directives/ui/_fix_lane.ts +52 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +8 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/design.ts +6 -0
- package/src/agent-src/templates/scripts/work_engine/intent/classify.ts +56 -4
- package/src/config/agent-settings.template.yml +46 -0
- package/src/config/evaluator-budgets.json +3 -3
- package/src/config/gate-coverage.yml +44 -0
- package/src/scripts/_cli/cmd_explain.ts +41 -36
- package/src/scripts/_cli/cmd_route_audit.ts +290 -0
- package/src/scripts/_cli/cmd_route_explain.ts +269 -0
- package/src/scripts/_dispatch.bash +23 -0
- package/src/scripts/_lib/git_env.ts +40 -0
- package/src/scripts/_lib/md_table.ts +27 -0
- package/src/scripts/_lib/planning_settings.ts +43 -0
- package/src/scripts/_lib/release_highlights.ts +279 -0
- package/src/scripts/_lib/router_match.ts +301 -0
- package/src/scripts/annotate_r1_outcomes.ts +258 -0
- package/src/scripts/check_completion_review.ts +1322 -0
- package/src/scripts/check_release_highlights.ts +60 -152
- package/src/scripts/compile_router.ts +20 -3
- package/src/scripts/dispatch_r2_reviewer.ts +969 -0
- package/src/scripts/eval_ui_triviality.ts +156 -0
- package/src/scripts/lint_abstraction_thresholds.ts +3 -1
- package/src/scripts/lint_plan_risk_register.ts +931 -0
- package/src/scripts/lint_trigger_collisions.ts +257 -0
- package/src/scripts/release.ts +47 -13
- package/src/scripts/router_telemetry.ts +16 -290
- package/src/scripts/schemas/rule.schema.json +10 -0
- package/src/server/schemas/settings.ts +18 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* `agent-config route:explain "<prompt>" [--files a,b]` — deterministic,
|
|
4
|
+
* offline trace of which rules the router's triggers match for one prompt.
|
|
5
|
+
*
|
|
6
|
+
* The validation surface the routing-correctness mandate asks for: matched
|
|
7
|
+
* triggers, tier, injected-vs-pointer disposition, budget consumption, and
|
|
8
|
+
* rejected candidates — all from `dist/router.json` + the ONE shared matcher
|
|
9
|
+
* (`_lib/router_match.ts`, parity-by-construction with `router_telemetry`
|
|
10
|
+
* and `explain route`).
|
|
11
|
+
*
|
|
12
|
+
* Measurement level (ADR-126): this shows TRIGGER MATCHING only. Whether the
|
|
13
|
+
* host actually invokes a matched rule is a different measurement and is NOT
|
|
14
|
+
* made here — the mandatory first output line says so on every run.
|
|
15
|
+
*
|
|
16
|
+
* Exit codes: `0` at least one non-kernel rule matched, `1` nothing matched
|
|
17
|
+
* (kernel is always active, so "no match" still leaves the kernel), `2`
|
|
18
|
+
* invocation error (missing prompt, unreadable router.json).
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import * as fs from 'node:fs';
|
|
22
|
+
import * as path from 'node:path';
|
|
23
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
match_prompt,
|
|
27
|
+
_asRuleList,
|
|
28
|
+
type Router,
|
|
29
|
+
type Rule,
|
|
30
|
+
type Trigger,
|
|
31
|
+
} from '../_lib/router_match.js';
|
|
32
|
+
import { measure, method_note } from '../_lib/token_count.js';
|
|
33
|
+
import { load_agent_settings } from '../_lib/agent_settings.js';
|
|
34
|
+
|
|
35
|
+
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
|
|
36
|
+
const ROUTER_JSON = path.join(REPO_ROOT, 'dist', 'router.json');
|
|
37
|
+
const RULES_DIST_DIR = path.join(REPO_ROOT, 'dist', 'agent-src', 'rules');
|
|
38
|
+
|
|
39
|
+
export const MEASUREMENT_HEADER =
|
|
40
|
+
'Measurement level: trigger matching only — what the host actually invokes is NOT measured here (ADR-126).';
|
|
41
|
+
|
|
42
|
+
interface RuleReport {
|
|
43
|
+
id: string;
|
|
44
|
+
tier: string;
|
|
45
|
+
matched: string[]; // "kind: value" labels
|
|
46
|
+
routes_to: string[];
|
|
47
|
+
disposition: string;
|
|
48
|
+
body_chars: number;
|
|
49
|
+
body_tokens_gpt: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface Report {
|
|
53
|
+
measurement_level: string;
|
|
54
|
+
prompt: string;
|
|
55
|
+
files: string[];
|
|
56
|
+
profile: string;
|
|
57
|
+
projection_mode: string;
|
|
58
|
+
kernel_always: string[];
|
|
59
|
+
matches: RuleReport[];
|
|
60
|
+
rejected: { reason: string; ids: string[] }[];
|
|
61
|
+
budget: { matched_chars: number; matched_tokens_gpt: number; method: string };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function _trigger_label(trigger: Trigger): string {
|
|
65
|
+
for (const kind of ['keyword', 'phrase', 'command', 'path_prefix', 'file_pattern']) {
|
|
66
|
+
if (kind in trigger) {
|
|
67
|
+
return `${kind}: ${String(trigger[kind])}`;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return `trigger: ${JSON.stringify(trigger)}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Projection mode from settings; the shipped default is eager-all. */
|
|
74
|
+
export function _projection_mode(): string {
|
|
75
|
+
try {
|
|
76
|
+
const settings = load_agent_settings({ cwd: REPO_ROOT }) as Record<string, unknown>;
|
|
77
|
+
const lp = settings['lean_projection'];
|
|
78
|
+
if (lp !== null && typeof lp === 'object' && !Array.isArray(lp)) {
|
|
79
|
+
const mode = (lp as Record<string, unknown>)['mode'];
|
|
80
|
+
if (typeof mode === 'string' && mode.trim() !== '') {
|
|
81
|
+
return mode.trim();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
// settings unreadable → shipped default
|
|
86
|
+
}
|
|
87
|
+
return 'eager-all';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function _disposition(tier: string, projection_mode: string): string {
|
|
91
|
+
if (tier === 'kernel') {
|
|
92
|
+
return 'injected (kernel, always full-bodied)';
|
|
93
|
+
}
|
|
94
|
+
// Disposition is decided at projection time, not at runtime — say so
|
|
95
|
+
// instead of inventing a runtime injection the router does not perform.
|
|
96
|
+
return projection_mode === 'eager-all'
|
|
97
|
+
? `injected at projection time (${tier}, lean_projection.mode: eager-all)`
|
|
98
|
+
: `router-resolved pointer at projection time (${tier}, lean_projection.mode: ${projection_mode})`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function _rule_body_size(rule_id: string): { chars: number; tokens: number } {
|
|
102
|
+
const p = path.join(RULES_DIST_DIR, `${rule_id}.md`);
|
|
103
|
+
try {
|
|
104
|
+
const text = fs.readFileSync(p, 'utf-8');
|
|
105
|
+
const m = measure(text);
|
|
106
|
+
return { chars: m.chars, tokens: m.tokens_gpt };
|
|
107
|
+
} catch {
|
|
108
|
+
return { chars: 0, tokens: 0 };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function build_report(
|
|
113
|
+
router: Router,
|
|
114
|
+
prompt: string,
|
|
115
|
+
files: string[],
|
|
116
|
+
profile: string,
|
|
117
|
+
): Report {
|
|
118
|
+
const projection_mode = _projection_mode();
|
|
119
|
+
const result = match_prompt(router, prompt, profile, files.length > 0 ? files : null, null);
|
|
120
|
+
|
|
121
|
+
const matchedByRule = new Map<string, { tier: string; labels: string[] }>();
|
|
122
|
+
for (const mt of result.matched_triggers) {
|
|
123
|
+
const id = String(mt.rule);
|
|
124
|
+
const entry = matchedByRule.get(id) ?? { tier: mt.tier.replace('_', '-'), labels: [] };
|
|
125
|
+
entry.labels.push(_trigger_label(mt.trigger));
|
|
126
|
+
matchedByRule.set(id, entry);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const routesById = new Map<string, string[]>();
|
|
130
|
+
for (const tierKey of ['tier_1', 'tier_2'] as const) {
|
|
131
|
+
for (const rule of _asRuleList(router[tierKey])) {
|
|
132
|
+
routesById.set(String(rule['id']), ((rule['routes_to'] as unknown[]) ?? []).map(String));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const matches: RuleReport[] = [...matchedByRule.entries()]
|
|
137
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
138
|
+
.map(([id, { tier, labels }]) => {
|
|
139
|
+
const size = _rule_body_size(id);
|
|
140
|
+
return {
|
|
141
|
+
id,
|
|
142
|
+
tier,
|
|
143
|
+
matched: labels,
|
|
144
|
+
routes_to: routesById.get(id) ?? [],
|
|
145
|
+
disposition: _disposition(tier, projection_mode),
|
|
146
|
+
body_chars: size.chars,
|
|
147
|
+
body_tokens_gpt: size.tokens,
|
|
148
|
+
};
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const rejected_no_match: string[] = [];
|
|
152
|
+
for (const tierKey of ['tier_1', 'tier_2'] as const) {
|
|
153
|
+
if (tierKey === 'tier_2' && profile !== 'full') continue;
|
|
154
|
+
for (const rule of _asRuleList(router[tierKey])) {
|
|
155
|
+
const id = String(rule['id']);
|
|
156
|
+
if (!matchedByRule.has(id)) rejected_no_match.push(id);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
rejected_no_match.sort();
|
|
160
|
+
const rejected: Report['rejected'] = [
|
|
161
|
+
{ reason: 'no trigger matched', ids: rejected_no_match },
|
|
162
|
+
];
|
|
163
|
+
if (profile !== 'full') {
|
|
164
|
+
rejected.push({
|
|
165
|
+
reason: `tier-2 excluded by profile '${profile}' (tier-2 is considered under profile 'full' only)`,
|
|
166
|
+
ids: _asRuleList(router['tier_2']).map((r: Rule) => String(r['id'])).sort(),
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const kernel_always = (Array.isArray(router['kernel']) ? router['kernel'] : []).map(String);
|
|
171
|
+
const matched_chars = matches.reduce((s, m) => s + m.body_chars, 0);
|
|
172
|
+
const matched_tokens = matches.reduce((s, m) => s + m.body_tokens_gpt, 0);
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
measurement_level: MEASUREMENT_HEADER,
|
|
176
|
+
prompt,
|
|
177
|
+
files,
|
|
178
|
+
profile,
|
|
179
|
+
projection_mode,
|
|
180
|
+
kernel_always,
|
|
181
|
+
matches,
|
|
182
|
+
rejected,
|
|
183
|
+
budget: { matched_chars, matched_tokens_gpt: matched_tokens, method: method_note() },
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function render_text(report: Report): string {
|
|
188
|
+
const out: string[] = [];
|
|
189
|
+
out.push(report.measurement_level);
|
|
190
|
+
out.push('');
|
|
191
|
+
out.push(`prompt: ${JSON.stringify(report.prompt)}`);
|
|
192
|
+
if (report.files.length > 0) out.push(`files: ${report.files.join(', ')}`);
|
|
193
|
+
out.push(`profile: ${report.profile} · projection mode: ${report.projection_mode}`);
|
|
194
|
+
out.push('');
|
|
195
|
+
out.push(`kernel (always active, ${report.kernel_always.length}): ${report.kernel_always.join(', ')}`);
|
|
196
|
+
out.push('');
|
|
197
|
+
out.push(`matched rules (${report.matches.length}):`);
|
|
198
|
+
for (const m of report.matches) {
|
|
199
|
+
out.push(` · ${m.id} [${m.tier}]`);
|
|
200
|
+
for (const label of m.matched) out.push(` matched ${label}`);
|
|
201
|
+
out.push(` disposition: ${m.disposition}`);
|
|
202
|
+
if (m.routes_to.length > 0) out.push(` routes_to: ${m.routes_to.join(', ')}`);
|
|
203
|
+
out.push(` body: ${m.body_chars} chars ≈ ${m.body_tokens_gpt} GPT tokens`);
|
|
204
|
+
}
|
|
205
|
+
if (report.matches.length === 0) {
|
|
206
|
+
out.push(' · (no trigger matched — only kernel rules active)');
|
|
207
|
+
}
|
|
208
|
+
out.push('');
|
|
209
|
+
out.push(
|
|
210
|
+
`budget consumption of matched non-kernel bodies: ${report.budget.matched_chars} chars`
|
|
211
|
+
+ ` ≈ ${report.budget.matched_tokens_gpt} GPT tokens (${report.budget.method})`,
|
|
212
|
+
);
|
|
213
|
+
for (const r of report.rejected) {
|
|
214
|
+
out.push(`rejected — ${r.reason}: ${r.ids.length} rule(s)`);
|
|
215
|
+
}
|
|
216
|
+
return out.join('\n') + '\n';
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function main(argv: string[]): number {
|
|
220
|
+
const args = [...argv];
|
|
221
|
+
const as_json = args.includes('--json');
|
|
222
|
+
let files: string[] = [];
|
|
223
|
+
let profile = 'full';
|
|
224
|
+
const positional: string[] = [];
|
|
225
|
+
for (let i = 0; i < args.length; i++) {
|
|
226
|
+
const a = args[i] as string;
|
|
227
|
+
if (a === '--json') continue;
|
|
228
|
+
if (a === '--files') {
|
|
229
|
+
files = String(args[++i] ?? '').split(',').map((s) => s.trim()).filter(Boolean);
|
|
230
|
+
} else if (a.startsWith('--files=')) {
|
|
231
|
+
files = a.slice('--files='.length).split(',').map((s) => s.trim()).filter(Boolean);
|
|
232
|
+
} else if (a === '--profile') {
|
|
233
|
+
profile = String(args[++i] ?? 'full');
|
|
234
|
+
} else if (a.startsWith('--profile=')) {
|
|
235
|
+
profile = a.slice('--profile='.length);
|
|
236
|
+
} else if (a === '-h' || a === '--help') {
|
|
237
|
+
process.stdout.write(
|
|
238
|
+
'usage: agent-config route:explain "<prompt>" [--files a,b] [--profile full|balanced] [--json]\n',
|
|
239
|
+
);
|
|
240
|
+
return 0;
|
|
241
|
+
} else {
|
|
242
|
+
positional.push(a);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const prompt = positional.join(' ').trim();
|
|
246
|
+
if (!prompt) {
|
|
247
|
+
process.stderr.write('route:explain: a prompt is required\n');
|
|
248
|
+
return 2;
|
|
249
|
+
}
|
|
250
|
+
let router: Router;
|
|
251
|
+
try {
|
|
252
|
+
router = JSON.parse(fs.readFileSync(ROUTER_JSON, 'utf-8')) as Router;
|
|
253
|
+
} catch (e) {
|
|
254
|
+
process.stderr.write(`route:explain: cannot read ${ROUTER_JSON}: ${String(e)}\n`);
|
|
255
|
+
return 2;
|
|
256
|
+
}
|
|
257
|
+
const report = build_report(router, prompt, files, profile);
|
|
258
|
+
if (as_json) {
|
|
259
|
+
process.stdout.write(JSON.stringify(report, null, 2) + '\n');
|
|
260
|
+
} else {
|
|
261
|
+
process.stdout.write(render_text(report));
|
|
262
|
+
}
|
|
263
|
+
return report.matches.length === 0 ? 1 : 0;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
267
|
+
if (process.argv[1] && (import.meta.url === pathToFileURL(process.argv[1]).href || path.resolve(process.argv[1]) === _HERE)) {
|
|
268
|
+
process.exitCode = main(process.argv.slice(2));
|
|
269
|
+
}
|
|
@@ -208,6 +208,15 @@ Tier 2 — maintenance / internal (hooks, MCP, memory, telemetry):
|
|
|
208
208
|
router + projection freshness, host bridge status.
|
|
209
209
|
Flags: --platform <p>, --workspace <path>, --json, --strict,
|
|
210
210
|
--no-freshness
|
|
211
|
+
route:explain Deterministic rule-routing trace for one prompt: matched
|
|
212
|
+
triggers, tier, injected-vs-pointer disposition, budget
|
|
213
|
+
consumption, rejected candidates. Trigger-match level only
|
|
214
|
+
(what the host invokes is not measured).
|
|
215
|
+
Flags: --files a,b, --profile full|balanced, --json
|
|
216
|
+
route:audit Replay the router matcher over the last N user prompts of
|
|
217
|
+
the chat-history log; renders matched rules per prompt.
|
|
218
|
+
Trigger-match level only; read-only, no LLM call.
|
|
219
|
+
Flags: --last N, --record (opt-in recorder), --weekly, --json
|
|
211
220
|
hooks:replay Replay a fixture through the universal dispatcher with
|
|
212
221
|
AGENT_CONFIG_REPLAY=1 (no writes under agents/runtime/state/).
|
|
213
222
|
Usage: hooks:replay --platform <name> --event <event>
|
|
@@ -1171,6 +1180,18 @@ cmd_versions() {
|
|
|
1171
1180
|
|
|
1172
1181
|
# `agent-config explain <config|rule|route>` — print the decision chain
|
|
1173
1182
|
# behind a configuration or routing outcome. Read-only diagnostic; never
|
|
1183
|
+
# Router-matcher replay over recent chat-history prompts (read-only).
|
|
1184
|
+
# See scripts/_cli/cmd_route_audit.ts.
|
|
1185
|
+
cmd_route_audit() {
|
|
1186
|
+
exec_ts "$PACKAGE_ROOT/src/scripts/_cli/cmd_route_audit.ts" "$@"
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
# Deterministic rule-routing trace (trigger-match measurement level only).
|
|
1190
|
+
# See scripts/_cli/cmd_route_explain.ts.
|
|
1191
|
+
cmd_route_explain() {
|
|
1192
|
+
exec_ts "$PACKAGE_ROOT/src/scripts/_cli/cmd_route_explain.ts" "$@"
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1174
1195
|
# edits state. See scripts/_cli/cmd_explain.ts.
|
|
1175
1196
|
cmd_explain() {
|
|
1176
1197
|
exec_ts "$PACKAGE_ROOT/src/scripts/_cli/cmd_explain.ts" "$@"
|
|
@@ -1235,6 +1256,8 @@ main() {
|
|
|
1235
1256
|
hooks:status) cmd_hooks_status "$@" ;;
|
|
1236
1257
|
hooks:doctor) cmd_hooks_doctor "$@" ;;
|
|
1237
1258
|
routing:doctor) cmd_routing_doctor "$@" ;;
|
|
1259
|
+
route:explain) cmd_route_explain "$@" ;;
|
|
1260
|
+
route:audit) cmd_route_audit "$@" ;;
|
|
1238
1261
|
hooks:replay) cmd_hooks_replay "$@" ;;
|
|
1239
1262
|
reach:doctor) cmd_reach_doctor "$@" ;;
|
|
1240
1263
|
telemetry:record) cmd_telemetry_record "$@" ;;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment for child `git` invocations that must resolve from their `cwd`.
|
|
3
|
+
*
|
|
4
|
+
* Git hooks export `GIT_DIR` (and often `GIT_INDEX_FILE`), and every child
|
|
5
|
+
* process inherits them. An inherited `GIT_DIR` **overrides** repository
|
|
6
|
+
* discovery, so `execFileSync('git', […], { cwd })` silently operates against
|
|
7
|
+
* the hook's repository instead of `cwd`. In a linked worktree the two differ,
|
|
8
|
+
* and the symptom is not an error but wrong data: `git log -- <path>` returns
|
|
9
|
+
* nothing, which the Gate R1 grandfather clause reads as "no pre-activation
|
|
10
|
+
* baseline" and reports as `missing_register` on every grandfathered roadmap.
|
|
11
|
+
*
|
|
12
|
+
* That made the pre-push enforcement layer fire spuriously while CI — which
|
|
13
|
+
* runs the same gate WITHOUT `GIT_DIR` — stayed green: the worst shape for a
|
|
14
|
+
* gate, since the failure looks like a content problem and the authoritative
|
|
15
|
+
* layer disagrees with the local one.
|
|
16
|
+
*
|
|
17
|
+
* Every gate in this family therefore runs git through this env: the variables
|
|
18
|
+
* that redirect discovery are removed, so `cwd` is the only thing that decides.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** Git env vars that override repository discovery and must not be inherited. */
|
|
22
|
+
export const GIT_DISCOVERY_VARS: readonly string[] = [
|
|
23
|
+
'GIT_DIR',
|
|
24
|
+
'GIT_WORK_TREE',
|
|
25
|
+
'GIT_INDEX_FILE',
|
|
26
|
+
'GIT_OBJECT_DIRECTORY',
|
|
27
|
+
'GIT_ALTERNATE_OBJECT_DIRECTORIES',
|
|
28
|
+
'GIT_COMMON_DIR',
|
|
29
|
+
'GIT_NAMESPACE',
|
|
30
|
+
'GIT_PREFIX',
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
/** `process.env` minus the discovery-overriding variables. */
|
|
34
|
+
export function gitEnv(base: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {
|
|
35
|
+
const env: NodeJS.ProcessEnv = { ...base };
|
|
36
|
+
for (const key of GIT_DISCOVERY_VARS) {
|
|
37
|
+
delete env[key];
|
|
38
|
+
}
|
|
39
|
+
return env;
|
|
40
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown table-row splitting, escape-aware.
|
|
3
|
+
*
|
|
4
|
+
* Both plan-governance validators parse pipe tables whose cells legitimately
|
|
5
|
+
* contain an escaped pipe: the Risk-Register contract example writes
|
|
6
|
+
* `product \| implementation`, and findings text routinely describes unions or
|
|
7
|
+
* flags (`text\|json`). Splitting on every `|` shifts every later cell by one,
|
|
8
|
+
* so `Risk type` becomes `product \` and a findings row's `Status` becomes a
|
|
9
|
+
* fragment of its own Finding text — a spurious block with no fix except
|
|
10
|
+
* rewording the prose.
|
|
11
|
+
*
|
|
12
|
+
* One definition, imported by both validators: the same defect appearing twice
|
|
13
|
+
* is what made this a shared helper rather than a local function.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** Split on unescaped `|` only, drop the outer delimiters, unescape, trim. */
|
|
17
|
+
export function splitMarkdownRow(line: string): string[] {
|
|
18
|
+
let inner = line.trim();
|
|
19
|
+
if (inner.startsWith('|')) {
|
|
20
|
+
inner = inner.slice(1);
|
|
21
|
+
}
|
|
22
|
+
// A trailing pipe is a delimiter only when it is not itself escaped.
|
|
23
|
+
if (inner.endsWith('|') && !inner.endsWith('\\|')) {
|
|
24
|
+
inner = inner.slice(0, -1);
|
|
25
|
+
}
|
|
26
|
+
return inner.split(/(?<!\\)\|/).map((c) => c.replace(/\\\|/g, '|').trim());
|
|
27
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gate-R2 settings escape hatch, shared by the validator and the dispatcher.
|
|
3
|
+
*
|
|
4
|
+
* It lives here rather than in either script because BOTH R2 enforcement layers
|
|
5
|
+
* must honour it: `check_completion_review` (the gate) and
|
|
6
|
+
* `dispatch_r2_reviewer --verify` / `--verify-current` (the manifest
|
|
7
|
+
* re-derivation CI step). A reader that only the gate consulted left the second,
|
|
8
|
+
* blocking layer firing with the gate nominally switched off. `check_*` already
|
|
9
|
+
* imports the scope hash from `dispatch_*`, so putting the predicate in either
|
|
10
|
+
* one would make the import cycle back on itself.
|
|
11
|
+
*
|
|
12
|
+
* Fail-open by design: an unreadable or unparseable settings file, or a missing
|
|
13
|
+
* key, leaves the gate ACTIVE (`=== false` is the only disabling value).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import * as fs from 'node:fs';
|
|
17
|
+
import * as path from 'node:path';
|
|
18
|
+
|
|
19
|
+
import { parse as parseYaml } from 'yaml';
|
|
20
|
+
|
|
21
|
+
function planningFlagIsFalse(settingsDir: string, key: string): boolean {
|
|
22
|
+
const settingsPath = path.join(settingsDir, '.agent-settings.yml');
|
|
23
|
+
let raw: string;
|
|
24
|
+
try {
|
|
25
|
+
raw = fs.readFileSync(settingsPath, 'utf-8');
|
|
26
|
+
} catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const parsed = parseYaml(raw) as unknown;
|
|
31
|
+
if (parsed === null || typeof parsed !== 'object') return false;
|
|
32
|
+
const planning = (parsed as Record<string, unknown>)['planning'];
|
|
33
|
+
if (planning === null || typeof planning !== 'object') return false;
|
|
34
|
+
return (planning as Record<string, unknown>)[key] === false;
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** `planning.completion_review === false` in `<dir>/.agent-settings.yml`. */
|
|
41
|
+
export function completionReviewDisabled(settingsDir: string): boolean {
|
|
42
|
+
return planningFlagIsFalse(settingsDir, 'completion_review');
|
|
43
|
+
}
|