@event4u/agent-config 8.11.0 → 8.13.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/.claude-plugin/marketplace.json +1 -1
- package/AGENTS.md +1 -0
- package/CHANGELOG.md +64 -266
- package/README.md +2 -2
- package/dist/agent-src/commands/pr/create.md +26 -0
- package/dist/agent-src/commands/team/adversarial.md +96 -0
- package/dist/agent-src/commands/team/delegate.md +109 -0
- package/dist/agent-src/commands/team/review.md +84 -0
- package/dist/agent-src/commands/team/status.md +92 -0
- package/dist/agent-src/commands/team.md +147 -0
- package/dist/agent-src/contexts/execution/roadmap-ci-steps-mechanics.md +117 -0
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +106 -94
- package/dist/agent-src/rules/architecture.md +6 -51
- package/dist/agent-src/rules/code-comment-discipline.md +10 -94
- package/dist/agent-src/rules/decision-revisit-gate.md +9 -77
- package/dist/agent-src/rules/improve-before-implement.md +8 -65
- package/dist/agent-src/rules/no-roadmap-references.md +13 -77
- package/dist/agent-src/rules/persona-governance.md +5 -47
- package/dist/agent-src/rules/provider-lifecycle-discipline.md +6 -32
- package/dist/agent-src/rules/roadmap-ci-steps-policy.md +21 -104
- package/dist/agent-src/rules/untrusted-input-defense.md +3 -75
- package/dist/agent-src/skills/agent-docs-writing/SKILL.md +83 -0
- package/dist/agent-src/skills/decision-review/SKILL.md +86 -0
- package/dist/agent-src/skills/module-detect-on-the-fly/SKILL.md +58 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/contexts/knowledge-card.md +12 -0
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +159 -21
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +11 -6
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +10 -5
- package/dist/install/install.mjs +38 -0
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/router.json +1 -1
- package/dist/server/schemas/settings.js +7 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/docs/CLAIMS.md +31 -3
- package/docs/architecture.md +2 -2
- package/docs/archive/CHANGELOG-pre-8.12.0.md +304 -0
- package/docs/benchmark.md +39 -0
- package/docs/catalog.md +10 -4
- package/docs/command-flows.md +6 -1
- package/docs/contracts/ai-council-config.md +9 -4
- package/docs/contracts/ai-team-config.md +191 -0
- package/docs/contracts/command-clusters.md +1 -0
- package/docs/contracts/persona-schema.md +49 -0
- package/docs/contracts/provider-lifecycle.md +29 -4
- package/docs/decisions/ADR-120-council-chairman-mode.md +70 -0
- package/docs/decisions/ADR-121-knowledge-sensitivity-classes.md +90 -0
- package/docs/decisions/INDEX.md +2 -0
- package/docs/design/council-vs-solo-baseline.md +92 -0
- package/docs/design/utilization-window-criteria.md +51 -0
- package/docs/featured-skills.md +1 -1
- package/docs/getting-started-by-role.md +3 -1
- package/docs/getting-started.md +31 -1
- package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +68 -0
- package/docs/guidelines/agent-infra/rule-body-migration-inventory.md +241 -0
- package/docs/guidelines/agent-infra/untrusted-input-spotlighting.md +80 -3
- package/docs/guidelines/code-clarity.md +86 -1
- package/docs/maintainers/branch-protection.md +105 -0
- package/docs/maintainers/system-map.md +85 -0
- package/docs/proof.md +15 -4
- package/package.json +1 -1
- package/src/config/agent-settings.template.yml +49 -0
- package/src/scripts/_cli/cmd_doctor.ts +291 -1
- package/src/scripts/_lib/claude_plugin.ts +39 -4
- package/src/scripts/_lib/knowledge_global.ts +44 -0
- package/src/scripts/_lib/knowledge_global_promote.ts +150 -0
- package/src/scripts/ai_council/orchestrator.ts +188 -1
- package/src/scripts/ai_team/config.ts +162 -0
- package/src/scripts/bench_persona_placebo.ts +308 -0
- package/src/scripts/bench_quality_rerun.ts +357 -0
- package/src/scripts/build_proof.ts +9 -0
- package/src/scripts/check_artefact_count_messaging.ts +56 -0
- package/src/scripts/check_knowledge_cards.ts +33 -4
- package/src/scripts/check_portability.ts +1 -0
- package/src/scripts/complexity_report.ts +776 -0
- package/src/scripts/consumer_matrix.ts +251 -12
- package/src/scripts/council_cli.ts +56 -1
- package/src/scripts/explain_run.ts +593 -0
- package/src/scripts/install.ts +39 -0
- package/src/scripts/knowledge_global_cli.ts +234 -45
- package/src/scripts/rule_backlinks.ts +350 -0
- package/src/server/schemas/settings.ts +17 -0
|
@@ -4,15 +4,25 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Exercises the PUBLISHED TARBALL the way a consumer does: pack → fresh
|
|
6
6
|
* global install (isolated prefix) → `init` into a fresh project → doctor →
|
|
7
|
-
* conformance → MCP stdio handshake → hook health →
|
|
8
|
-
* uninstall. Optional upgrade leg: install the last
|
|
9
|
-
* registry, then upgrade to the packed tarball and
|
|
7
|
+
* conformance → MCP stdio handshake → hook health → hook lifecycle →
|
|
8
|
+
* projection presence → uninstall. Optional upgrade leg: install the last
|
|
9
|
+
* published minor from the registry, then upgrade to the packed tarball and
|
|
10
|
+
* assert doctor stays green.
|
|
10
11
|
*
|
|
11
12
|
* Why this exists: the release-PR gate skips source-level matrices (soundly —
|
|
12
13
|
* a version-bump diff cannot regress source behaviour), but nothing between
|
|
13
14
|
* merge and tag exercised the tarball shape. Two published minors shipped
|
|
14
15
|
* without `src/install/` while published code imported from it; `tsx` was
|
|
15
16
|
* missing from the published package; each manifested only at publish time.
|
|
17
|
+
* A third bug (missing `dist/install/rule_scope.js`) shipped past every leg
|
|
18
|
+
* because none of them fired a REAL installed hook command — `conformance`'s
|
|
19
|
+
* dispatcher smoke calls `dispatch_hook.ts` directly via `tsx`, bypassing the
|
|
20
|
+
* `agent-config` binary (and its ESM import graph) entirely. The `hooks`
|
|
21
|
+
* (static doctor) and `hook-lifecycle` (this file's live leg, below) legs
|
|
22
|
+
* close that gap: the latter runs the EXACT shell command Claude installed in
|
|
23
|
+
* `~/.claude/settings.json`, so a crash anywhere in the binary's load path —
|
|
24
|
+
* including a missing compiled sibling module — fails the leg with the
|
|
25
|
+
* culprit's stderr, not silently at publish time.
|
|
16
26
|
* Every leg here asserts exit code + an expected artifact, no LLM calls.
|
|
17
27
|
*
|
|
18
28
|
* Usage:
|
|
@@ -30,7 +40,9 @@ import { spawnSync, spawn } from 'node:child_process';
|
|
|
30
40
|
import * as fs from 'node:fs';
|
|
31
41
|
import * as os from 'node:os';
|
|
32
42
|
import * as path from 'node:path';
|
|
33
|
-
import { fileURLToPath } from 'node:url';
|
|
43
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
44
|
+
|
|
45
|
+
import { MANAGED_SIGNATURE } from './_lib/claude_settings_hooks.js';
|
|
34
46
|
|
|
35
47
|
const _HERE = fileURLToPath(import.meta.url);
|
|
36
48
|
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
@@ -63,7 +75,7 @@ function log(msg: string): void {
|
|
|
63
75
|
function run(
|
|
64
76
|
cmd: string,
|
|
65
77
|
args: string[],
|
|
66
|
-
opts: { cwd?: string; env?: NodeJS.ProcessEnv; timeoutMs?: number } = {},
|
|
78
|
+
opts: { cwd?: string; env?: NodeJS.ProcessEnv; timeoutMs?: number; input?: string } = {},
|
|
67
79
|
): { status: number; stdout: string; stderr: string } {
|
|
68
80
|
const r = spawnSync(cmd, args, {
|
|
69
81
|
cwd: opts.cwd ?? REPO_ROOT,
|
|
@@ -71,6 +83,7 @@ function run(
|
|
|
71
83
|
encoding: 'utf8',
|
|
72
84
|
timeout: opts.timeoutMs ?? 600_000,
|
|
73
85
|
maxBuffer: 32 * 1024 * 1024,
|
|
86
|
+
input: opts.input,
|
|
74
87
|
});
|
|
75
88
|
return { status: r.status ?? 1, stdout: r.stdout ?? '', stderr: r.stderr ?? '' };
|
|
76
89
|
}
|
|
@@ -216,6 +229,210 @@ function legHooks(ctx: Ctx): string {
|
|
|
216
229
|
return 'hooks:doctor green';
|
|
217
230
|
}
|
|
218
231
|
|
|
232
|
+
// ── hook-lifecycle leg ─────────────────────────────────────────────
|
|
233
|
+
//
|
|
234
|
+
// `hooks:doctor` (above) is a read-only static report — it never runs
|
|
235
|
+
// anything. `conformance`'s dispatcher smoke calls `dispatch_hook.ts`
|
|
236
|
+
// directly via `tsx`, bypassing the installed `agent-config` binary (and
|
|
237
|
+
// its ESM import graph) entirely. Neither exercises what a real Claude
|
|
238
|
+
// Code SessionStart/PreToolUse/PostToolUse/Stop event actually invokes:
|
|
239
|
+
// the exact shell command the installer registered in
|
|
240
|
+
// `~/.claude/settings.json`. This leg does — plus a deterministic
|
|
241
|
+
// completeness check on the compiled entrypoint's module graph, so a
|
|
242
|
+
// missing sibling `.js` (the historical `dist/install/rule_scope.js` bug)
|
|
243
|
+
// fails with the exact culprit instead of a live crash.
|
|
244
|
+
|
|
245
|
+
interface HookCommandEntry {
|
|
246
|
+
/** Native platform event name, e.g. `SessionStart`, `PreToolUse`. */
|
|
247
|
+
nativeEvent: string;
|
|
248
|
+
/** The exact managed shell command string installed for this event. */
|
|
249
|
+
command: string;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Read every agent-config-managed hook command out of an installed Claude
|
|
254
|
+
* settings file, keyed by native event. Mirrors the shape
|
|
255
|
+
* `ensure_managed_hooks` (src/scripts/_lib/claude_settings_hooks.ts) writes;
|
|
256
|
+
* a group is "managed" iff one of its commands contains `MANAGED_SIGNATURE`
|
|
257
|
+
* — the same identity check the installer itself uses, so this can never
|
|
258
|
+
* drift from what actually got registered.
|
|
259
|
+
*/
|
|
260
|
+
export function readInstalledClaudeHookCommands(settingsPath: string): HookCommandEntry[] {
|
|
261
|
+
if (!fs.existsSync(settingsPath)) return [];
|
|
262
|
+
const raw = JSON.parse(fs.readFileSync(settingsPath, 'utf8')) as {
|
|
263
|
+
hooks?: Record<string, Array<{ hooks?: Array<{ command?: unknown }> }>>;
|
|
264
|
+
};
|
|
265
|
+
const hooks = raw.hooks ?? {};
|
|
266
|
+
const out: HookCommandEntry[] = [];
|
|
267
|
+
for (const [nativeEvent, groups] of Object.entries(hooks)) {
|
|
268
|
+
if (!Array.isArray(groups)) continue;
|
|
269
|
+
for (const group of groups) {
|
|
270
|
+
for (const h of group.hooks ?? []) {
|
|
271
|
+
if (typeof h.command === 'string' && h.command.includes(MANAGED_SIGNATURE)) {
|
|
272
|
+
out.push({ nativeEvent, command: h.command });
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return out;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** A representative Claude Code event envelope, shaped per native event. */
|
|
281
|
+
export function buildHookEnvelope(nativeEvent: string, projectDir: string): string {
|
|
282
|
+
const base = {
|
|
283
|
+
session_id: 'consumer-matrix-fixture',
|
|
284
|
+
transcript_path: '',
|
|
285
|
+
cwd: projectDir,
|
|
286
|
+
hook_event_name: nativeEvent,
|
|
287
|
+
};
|
|
288
|
+
switch (nativeEvent) {
|
|
289
|
+
case 'PreToolUse':
|
|
290
|
+
return JSON.stringify({
|
|
291
|
+
...base,
|
|
292
|
+
tool_name: 'Read',
|
|
293
|
+
tool_input: { file_path: path.join(projectDir, 'README.md') },
|
|
294
|
+
});
|
|
295
|
+
case 'PostToolUse':
|
|
296
|
+
return JSON.stringify({
|
|
297
|
+
...base,
|
|
298
|
+
tool_name: 'Read',
|
|
299
|
+
tool_input: { file_path: path.join(projectDir, 'README.md') },
|
|
300
|
+
tool_response: { success: true },
|
|
301
|
+
});
|
|
302
|
+
case 'Stop':
|
|
303
|
+
return JSON.stringify({ ...base, stop_hook_active: false });
|
|
304
|
+
case 'UserPromptSubmit':
|
|
305
|
+
return JSON.stringify({ ...base, prompt: 'consumer-matrix smoke prompt' });
|
|
306
|
+
case 'SessionEnd':
|
|
307
|
+
return JSON.stringify({ ...base, reason: 'exit' });
|
|
308
|
+
case 'SessionStart':
|
|
309
|
+
default:
|
|
310
|
+
return JSON.stringify({ ...base, source: 'startup' });
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Run one installed hook command exactly as the platform would — a shell
|
|
316
|
+
* invocation with the event envelope piped to stdin — and report whether it
|
|
317
|
+
* exited clean.
|
|
318
|
+
*/
|
|
319
|
+
export function invokeHookCommand(
|
|
320
|
+
entry: HookCommandEntry,
|
|
321
|
+
opts: { cwd: string; env: NodeJS.ProcessEnv; projectDir: string; timeoutMs?: number },
|
|
322
|
+
): { ok: boolean; status: number; stderrTail: string } {
|
|
323
|
+
const payload = buildHookEnvelope(entry.nativeEvent, opts.projectDir);
|
|
324
|
+
const r = run('bash', ['-c', entry.command], {
|
|
325
|
+
cwd: opts.cwd,
|
|
326
|
+
env: opts.env,
|
|
327
|
+
timeoutMs: opts.timeoutMs ?? 30_000,
|
|
328
|
+
input: payload,
|
|
329
|
+
});
|
|
330
|
+
const stderrTail = r.stderr.trim().split('\n').slice(-5).join(' | ');
|
|
331
|
+
return { ok: r.status === 0, status: r.status, stderrTail };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
interface MissingModule {
|
|
335
|
+
from: string;
|
|
336
|
+
spec: string;
|
|
337
|
+
resolved: string;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Extract relative (`./`, `../`) ESM specifiers from a compiled `.js` file —
|
|
342
|
+
* static `from "..."`, bare `import "...";`, and dynamic `import("...")`.
|
|
343
|
+
* Regex, not a full parser: good enough for tsc's fixed output shape, and
|
|
344
|
+
* deliberately ignores bare package specifiers (`from 'commander'`) so the
|
|
345
|
+
* walk never leaves the package's own `dist/` tree.
|
|
346
|
+
*/
|
|
347
|
+
export function parseRelativeSpecifiers(source: string): string[] {
|
|
348
|
+
const out = new Set<string>();
|
|
349
|
+
const patterns = [
|
|
350
|
+
/\bfrom\s*["']([^"']+)["']/g,
|
|
351
|
+
/^\s*import\s*["']([^"']+)["']\s*;/gm,
|
|
352
|
+
/\bimport\(\s*["']([^"']+)["']\s*\)/g,
|
|
353
|
+
];
|
|
354
|
+
for (const re of patterns) {
|
|
355
|
+
let m: RegExpExecArray | null;
|
|
356
|
+
while ((m = re.exec(source)) !== null) {
|
|
357
|
+
const spec = m[1];
|
|
358
|
+
if (spec && (spec.startsWith('./') || spec.startsWith('../'))) out.add(spec);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return [...out];
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Walk the compiled ESM module graph reachable from `entryFile`, following
|
|
366
|
+
* every relative import/export/dynamic-import specifier, and report any
|
|
367
|
+
* resolved target that does not exist on disk — the deterministic twin of
|
|
368
|
+
* "does this crash at require time", without spawning a process.
|
|
369
|
+
*/
|
|
370
|
+
export function walkEsmModuleGraph(entryFile: string): { visited: string[]; missing: MissingModule[] } {
|
|
371
|
+
const visited = new Set<string>();
|
|
372
|
+
const missing: MissingModule[] = [];
|
|
373
|
+
const stack: string[] = [entryFile];
|
|
374
|
+
while (stack.length > 0) {
|
|
375
|
+
const file = stack.pop() as string;
|
|
376
|
+
if (visited.has(file)) continue;
|
|
377
|
+
visited.add(file);
|
|
378
|
+
let source: string;
|
|
379
|
+
try {
|
|
380
|
+
source = fs.readFileSync(file, 'utf8');
|
|
381
|
+
} catch {
|
|
382
|
+
missing.push({ from: '(entry)', spec: file, resolved: file });
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
for (const spec of parseRelativeSpecifiers(source)) {
|
|
386
|
+
const resolved = path.resolve(path.dirname(file), spec);
|
|
387
|
+
if (!fs.existsSync(resolved)) {
|
|
388
|
+
missing.push({ from: file, spec, resolved });
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
if (!visited.has(resolved)) stack.push(resolved);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return { visited: [...visited], missing };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/** Dist-manifest completeness gate — every module the entrypoint can reach exists. */
|
|
398
|
+
export function checkDistManifestCompleteness(entryFile: string): { ok: boolean; message: string } {
|
|
399
|
+
const { visited, missing } = walkEsmModuleGraph(entryFile);
|
|
400
|
+
if (missing.length > 0) {
|
|
401
|
+
const detail = missing
|
|
402
|
+
.slice(0, 5)
|
|
403
|
+
.map((m) => `${path.relative(path.dirname(entryFile), m.from)} → missing ${m.spec}`)
|
|
404
|
+
.join('; ');
|
|
405
|
+
return { ok: false, message: `dist-manifest incomplete: ${missing.length} missing module(s): ${detail}` };
|
|
406
|
+
}
|
|
407
|
+
return {
|
|
408
|
+
ok: true,
|
|
409
|
+
message: `dist-manifest complete — ${visited.length} module(s) reachable from ${path.basename(entryFile)}`,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function legHookLifecycle(ctx: Ctx): string {
|
|
414
|
+
const settingsPath = path.join(ctx.tmpRoot, 'home', '.claude', 'settings.json');
|
|
415
|
+
const entries = readInstalledClaudeHookCommands(settingsPath);
|
|
416
|
+
if (entries.length === 0) {
|
|
417
|
+
throw new Error(`no managed Claude hook commands found in ${settingsPath} (did init register hooks?)`);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const entryScript = fs.realpathSync(ctx.bin);
|
|
421
|
+
const manifestCheck = checkDistManifestCompleteness(entryScript);
|
|
422
|
+
if (!manifestCheck.ok) throw new Error(manifestCheck.message);
|
|
423
|
+
|
|
424
|
+
const env = { ...binEnv(ctx), CLAUDE_PROJECT_DIR: ctx.projectDir };
|
|
425
|
+
const fired: string[] = [];
|
|
426
|
+
for (const entry of entries) {
|
|
427
|
+
const result = invokeHookCommand(entry, { cwd: ctx.projectDir, env, projectDir: ctx.projectDir });
|
|
428
|
+
if (!result.ok) {
|
|
429
|
+
throw new Error(`hook '${entry.nativeEvent}' exited ${result.status}: ${result.stderrTail}`);
|
|
430
|
+
}
|
|
431
|
+
fired.push(`${entry.nativeEvent}=${result.status}`);
|
|
432
|
+
}
|
|
433
|
+
return `${manifestCheck.message}; fired ${fired.length} live event(s): ${fired.join(', ')}`;
|
|
434
|
+
}
|
|
435
|
+
|
|
219
436
|
function legProjections(ctx: Ctx): string {
|
|
220
437
|
const home = path.join(ctx.tmpRoot, 'home');
|
|
221
438
|
const missing = ['.cursor/rules', '.codeium/windsurf', '.claude/skills']
|
|
@@ -278,6 +495,7 @@ const LEGS: Array<{ name: string; fn: (ctx: Ctx) => string | Promise<string> }>
|
|
|
278
495
|
{ name: 'conformance', fn: legConformance },
|
|
279
496
|
{ name: 'mcp-handshake', fn: legMcpHandshake },
|
|
280
497
|
{ name: 'hooks', fn: legHooks },
|
|
498
|
+
{ name: 'hook-lifecycle', fn: legHookLifecycle },
|
|
281
499
|
{ name: 'projections', fn: legProjections },
|
|
282
500
|
{ name: 'uninstall', fn: legUninstall },
|
|
283
501
|
{ name: 'upgrade', fn: legUpgrade },
|
|
@@ -383,10 +601,31 @@ async function main(): Promise<number> {
|
|
|
383
601
|
return failed ? 1 : 0;
|
|
384
602
|
}
|
|
385
603
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
)
|
|
604
|
+
// Entry guard (mirrors src/scripts/hooks/dispatch_hook.ts): a test file
|
|
605
|
+
// imports the pure helpers above (readInstalledClaudeHookCommands,
|
|
606
|
+
// checkDistManifestCompleteness, invokeHookCommand, …) without wanting a
|
|
607
|
+
// full `npm pack` + global-install run to fire as a side effect of the
|
|
608
|
+
// import. `main()` only runs when this file is the process's actual entry
|
|
609
|
+
// point (`npx tsx src/scripts/consumer_matrix.ts …`).
|
|
610
|
+
function _isCliEntry(): boolean {
|
|
611
|
+
if (process.argv[1] === undefined) return false;
|
|
612
|
+
const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
|
|
613
|
+
if (import.meta.url === argvUrl) return true;
|
|
614
|
+
try {
|
|
615
|
+
const here = fs.realpathSync(fileURLToPath(import.meta.url));
|
|
616
|
+
const argv = fs.realpathSync(path.resolve(process.argv[1]));
|
|
617
|
+
return here === argv;
|
|
618
|
+
} catch {
|
|
619
|
+
return false;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
if (_isCliEntry()) {
|
|
624
|
+
main().then(
|
|
625
|
+
(code) => process.exit(code),
|
|
626
|
+
(err) => {
|
|
627
|
+
log(`❌ consumer-matrix crashed: ${(err as Error).stack ?? err}`);
|
|
628
|
+
process.exit(2);
|
|
629
|
+
},
|
|
630
|
+
);
|
|
631
|
+
}
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
bundle_roadmap,
|
|
26
26
|
} from './ai_council/bundler.js';
|
|
27
27
|
import { select_chairman } from './ai_council/chairman.js';
|
|
28
|
+
import { jaccardSimilarity } from './_lib/text_similarity.js';
|
|
28
29
|
import type { ChairmanCandidate } from './ai_council/chairman.js';
|
|
29
30
|
import { synthesis_template } from './ai_council/prompts.js';
|
|
30
31
|
import type {
|
|
@@ -1906,6 +1907,7 @@ function cmd_run(
|
|
|
1906
1907
|
_isDict(ai_cfg) &&
|
|
1907
1908
|
_isDict(ai_cfg['stance_tally']) &&
|
|
1908
1909
|
(ai_cfg['stance_tally'] as Dict)['enabled'] === true;
|
|
1910
|
+
const stance_repairs: CouncilResponse[] = [];
|
|
1909
1911
|
const responses = consult(members, question, budget, {
|
|
1910
1912
|
table,
|
|
1911
1913
|
project,
|
|
@@ -1913,6 +1915,14 @@ function cmd_run(
|
|
|
1913
1915
|
rounds,
|
|
1914
1916
|
advisor_plans,
|
|
1915
1917
|
stance_tally: stance_tally_on,
|
|
1918
|
+
// Interactive one-line confirm (cmd_run has no --auto-continue); the
|
|
1919
|
+
// repaired-call cost is collected so cost_usd_actual stays honest.
|
|
1920
|
+
on_stance_repair: stance_tally_on
|
|
1921
|
+
? (member: string): boolean => _make_repair_confirm(false)(member, 'missing stance line')
|
|
1922
|
+
: null,
|
|
1923
|
+
on_stance_repair_result: (r: CouncilResponse): void => {
|
|
1924
|
+
stance_repairs.push(r);
|
|
1925
|
+
},
|
|
1916
1926
|
});
|
|
1917
1927
|
const persona_labels = build_persona_labels(advisor_plans, billable);
|
|
1918
1928
|
const peer_review = _maybe_run_peer_review(
|
|
@@ -1941,6 +1951,7 @@ function cmd_run(
|
|
|
1941
1951
|
if (chairman !== null && chairman.response !== null) {
|
|
1942
1952
|
all_responses.push(chairman.response);
|
|
1943
1953
|
}
|
|
1954
|
+
all_responses.push(...stance_repairs);
|
|
1944
1955
|
for (const r of all_responses) {
|
|
1945
1956
|
if (r.error) {
|
|
1946
1957
|
continue;
|
|
@@ -2068,6 +2079,20 @@ function _listEq(a: string[], b: string[]): boolean {
|
|
|
2068
2079
|
return a.every((v, i) => v === b[i]);
|
|
2069
2080
|
}
|
|
2070
2081
|
|
|
2082
|
+
|
|
2083
|
+
/** Phase 3 repair transport (council 2026-07-12): auto-fire under
|
|
2084
|
+
* --auto-continue; one-line confirm otherwise (blank/EOF stdin = N). */
|
|
2085
|
+
function _make_repair_confirm(auto_continue: boolean): (member: string, reason: string) => boolean {
|
|
2086
|
+
if (auto_continue) {
|
|
2087
|
+
return () => true;
|
|
2088
|
+
}
|
|
2089
|
+
return (member: string, reason: string): boolean => {
|
|
2090
|
+
_stdout(`\ndebate:repair ${member} — ${reason} — send one bounded repair re-prompt? [y/N]: `);
|
|
2091
|
+
const answer = _readStdinLine();
|
|
2092
|
+
return answer === 'y' || answer === 'yes';
|
|
2093
|
+
};
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2071
2096
|
function _make_debate_continue_prompt(opts: {
|
|
2072
2097
|
auto_continue: boolean;
|
|
2073
2098
|
stream?: ((s: string) => void) | null;
|
|
@@ -2263,6 +2288,31 @@ function cmd_debate(
|
|
|
2263
2288
|
auto_continue: Boolean(_getattr(args, 'auto_continue', false)),
|
|
2264
2289
|
});
|
|
2265
2290
|
|
|
2291
|
+
// Phase 3: restate pass — CLI flag OR config key; default off.
|
|
2292
|
+
const restate_on =
|
|
2293
|
+
Boolean(_getattr(args, 'restate', false)) ||
|
|
2294
|
+
(_isDict(ai_cfg) && _isDict(ai_cfg['restate']) && (ai_cfg['restate'] as Dict)['enabled'] === true);
|
|
2295
|
+
const restate_responses: CouncilResponse[] = [];
|
|
2296
|
+
const ask_for_divergence = (args.original_ask as string) || '';
|
|
2297
|
+
const on_restate = (rs: CouncilResponse[]): void => {
|
|
2298
|
+
restate_responses.push(...rs);
|
|
2299
|
+
const blocks = rs
|
|
2300
|
+
.filter((r) => !r.error && r.text.trim() !== '')
|
|
2301
|
+
.map((r) => `### ${r.provider} - ${r.model}\n\n${r.text.trim()}`);
|
|
2302
|
+
if (blocks.length > 0) {
|
|
2303
|
+
_stdout(`\n## Restatements (pre-round-1)\n\n${blocks.join('\n\n')}\n`);
|
|
2304
|
+
}
|
|
2305
|
+
// Divergence flag BEFORE further spend: a restatement far from the
|
|
2306
|
+
// stated ask is surfaced to the user (Jaccard over token sets).
|
|
2307
|
+
if (ask_for_divergence.trim() !== '') {
|
|
2308
|
+
for (const r of rs) {
|
|
2309
|
+
if (!r.error && r.text.trim() !== '' && jaccardSimilarity(ask_for_divergence, r.text) < 0.1) {
|
|
2310
|
+
_stderr(`⚠️ restate divergence: ${r.provider} restated the ask with little overlap — verify framing before continuing.\n`);
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
};
|
|
2315
|
+
|
|
2266
2316
|
// Phase 3: debate gates (anti-conformity directive on round 2+). Read
|
|
2267
2317
|
// defensively from the raw ai_council block — a malformed/absent
|
|
2268
2318
|
// `debate_gates` reads as off, keeping the debate prompt byte-identical.
|
|
@@ -2282,6 +2332,9 @@ function cmd_debate(
|
|
|
2282
2332
|
on_round_complete: _on_round_complete,
|
|
2283
2333
|
on_continue,
|
|
2284
2334
|
debate_gates: debate_gates_on,
|
|
2335
|
+
on_repair: debate_gates_on ? _make_repair_confirm(Boolean(_getattr(args, 'auto_continue', false))) : null,
|
|
2336
|
+
restate: restate_on,
|
|
2337
|
+
on_restate: restate_on ? on_restate : null,
|
|
2285
2338
|
advisor_plans,
|
|
2286
2339
|
seed_round_1: seed,
|
|
2287
2340
|
});
|
|
@@ -2299,7 +2352,7 @@ function cmd_debate(
|
|
|
2299
2352
|
}
|
|
2300
2353
|
|
|
2301
2354
|
let actual_total = 0.0;
|
|
2302
|
-
for (const rnd of all_rounds) {
|
|
2355
|
+
for (const rnd of [...all_rounds, restate_responses]) {
|
|
2303
2356
|
for (const r of rnd) {
|
|
2304
2357
|
if (r.error) {
|
|
2305
2358
|
continue;
|
|
@@ -2546,6 +2599,7 @@ interface Args {
|
|
|
2546
2599
|
single: boolean;
|
|
2547
2600
|
prose_synthesis: boolean | null;
|
|
2548
2601
|
auto_continue: boolean;
|
|
2602
|
+
restate?: boolean;
|
|
2549
2603
|
continue_as_debate: string | null;
|
|
2550
2604
|
// render / replay
|
|
2551
2605
|
responses?: string | null;
|
|
@@ -2675,6 +2729,7 @@ function _specsFor(cmd: string): { positionals: string[]; opts: OptSpec[]; requi
|
|
|
2675
2729
|
{ flag: '--confirm', takesValue: false, apply: (o) => (o.confirm = true) },
|
|
2676
2730
|
{ flag: '--rounds', takesValue: true, isInt: true, apply: (o, v) => (o.rounds = v === null ? null : parseInt(v, 10)) },
|
|
2677
2731
|
{ flag: '--auto-continue', takesValue: false, apply: (o) => (o.auto_continue = true) },
|
|
2732
|
+
{ flag: '--restate', takesValue: false, apply: (o) => (o.restate = true) },
|
|
2678
2733
|
{ flag: '--continue-as-debate', takesValue: true, apply: (o, v) => (o.continue_as_debate = v) },
|
|
2679
2734
|
{ flag: '--invocation', takesValue: true, choices: ['agent', 'user_explicit'], apply: (o, v) => (o.invocation = v as string) },
|
|
2680
2735
|
{ flag: '--proceed-anyway', takesValue: false, apply: (o) => (o.proceed_anyway = true) },
|