@event4u/agent-config 7.0.2 → 7.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/.claude-plugin/marketplace.json +9 -1
- package/CHANGELOG.md +117 -0
- package/README.md +4 -2
- package/dist/agent-src/commands/council/default.md +28 -20
- package/dist/agent-src/commands/evals/fix.json +5 -1
- package/dist/agent-src/commands/fix/comments.md +101 -0
- package/dist/agent-src/commands/fix.md +5 -2
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +65 -0
- package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +68 -0
- package/dist/agent-src/contexts/execution/host-capability-manifest.md +68 -0
- package/dist/agent-src/contexts/execution/orchestration-benchmark-gate.md +62 -0
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +59 -0
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +8 -3
- package/dist/agent-src/contexts/execution/subagent-response-contract.md +56 -0
- package/dist/agent-src/contexts/execution/subagent-routing.md +57 -0
- package/dist/agent-src/contexts/execution/subagent-spawn-contract.md +54 -0
- package/dist/agent-src/contexts/execution/subagent-steering.md +71 -0
- package/dist/agent-src/contexts/execution/verify-budget.md +50 -0
- package/dist/agent-src/rules/brand-source-of-truth.md +10 -0
- package/dist/agent-src/rules/delegation-policy.md +85 -0
- package/dist/agent-src/rules/design-fidelity.md +98 -0
- package/dist/agent-src/rules/domain-safety-pii.md +22 -0
- package/dist/agent-src/rules/legal-safety-floor.md +182 -0
- package/dist/agent-src/rules/lethal-trifecta-guard.md +25 -22
- package/dist/agent-src/rules/output-discipline.md +85 -0
- package/dist/agent-src/rules/telegraph-speak.md +4 -1
- package/dist/agent-src/rules/token-budget-discipline.md +94 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +102 -15
- package/dist/agent-src/scripts/update_roadmap_progress.ts +30 -3
- package/dist/agent-src/skills/accessibility-auditor/SKILL.md +20 -5
- package/dist/agent-src/skills/ai-council/SKILL.md +28 -1
- package/dist/agent-src/skills/blade-ui/SKILL.md +9 -0
- package/dist/agent-src/skills/contract-review/SKILL.md +125 -0
- package/dist/agent-src/skills/contract-review/evals/triggers.json +17 -0
- package/dist/agent-src/skills/dashboard-design/SKILL.md +21 -5
- package/dist/agent-src/skills/design-intelligence/SKILL.md +96 -0
- package/dist/agent-src/skills/design-review/SKILL.md +83 -0
- package/dist/agent-src/skills/design-system-capture/SKILL.md +234 -0
- package/dist/agent-src/skills/design-tokens/SKILL.md +1 -0
- package/dist/agent-src/skills/dpa-review/SKILL.md +130 -0
- package/dist/agent-src/skills/dpa-review/evals/triggers.json +17 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +15 -4
- package/dist/agent-src/skills/fe-design/SKILL.md +98 -16
- package/dist/agent-src/skills/flux/SKILL.md +8 -0
- package/dist/agent-src/skills/iconography/SKILL.md +1 -0
- package/dist/agent-src/skills/laravel/SKILL.md +1 -0
- package/dist/agent-src/skills/legal-intake-triage/SKILL.md +128 -0
- package/dist/agent-src/skills/legal-intake-triage/evals/triggers.json +17 -0
- package/dist/agent-src/skills/legal-practice-profile/SKILL.md +101 -0
- package/dist/agent-src/skills/legal-practice-profile/evals/triggers.json +17 -0
- package/dist/agent-src/skills/livewire/SKILL.md +8 -0
- package/dist/agent-src/skills/nda-triage/SKILL.md +114 -0
- package/dist/agent-src/skills/nda-triage/evals/triggers.json +17 -0
- package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +21 -9
- package/dist/agent-src/skills/reasoning-orchestrator/SKILL.md +3 -1
- package/dist/agent-src/skills/recursive-verification/SKILL.md +186 -0
- package/dist/agent-src/skills/roadmap-management/SKILL.md +17 -0
- package/dist/agent-src/skills/skill-writing/SKILL.md +18 -0
- package/dist/agent-src/skills/subagent-orchestration/SKILL.md +11 -0
- package/dist/agent-src/skills/tailwind-engineer/SKILL.md +17 -7
- package/dist/agent-src/skills/typography-system/SKILL.md +14 -0
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +9 -0
- package/dist/agent-src/templates/agent-settings.md +64 -19
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +362 -43
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +13 -5
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +56 -11
- package/dist/discovery/trust-report.md +7 -5
- package/dist/discovery/workspaces.json +38 -7
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/router.json +1 -1
- package/dist/server/routes/wizard.js +4 -3
- package/dist/server/routes/wizard.js.map +1 -1
- package/dist/server/schemas/settings.js +23 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/dist/ui/assets/{index-CoQo90sz.js → index-UjcTG_9O.js} +2 -2
- package/dist/ui/assets/index-UjcTG_9O.js.map +1 -0
- package/dist/ui/index.html +1 -1
- package/docs/RELEASE_STORY_TEMPLATE.md +37 -0
- package/docs/architecture.md +4 -4
- package/docs/benchmark.md +46 -0
- package/docs/catalog.md +18 -6
- package/docs/command-flows.md +7 -1
- package/docs/contracts/ai-council-config.md +17 -10
- package/docs/contracts/audit-log-v1.md +7 -0
- package/docs/contracts/auto-orchestration-v1.md +70 -0
- package/docs/contracts/capability-boundary.md +64 -0
- package/docs/contracts/command-clusters.md +1 -1
- package/docs/contracts/discovery-manifest.schema.json +2 -2
- package/docs/contracts/package-self-orientation.md +3 -0
- package/docs/contracts/subagent-boundary.md +54 -0
- package/docs/decisions/ADR-013-discovery-frontmatter-contract.md +32 -0
- package/docs/decisions/ADR-020-global-only-consumer-scope.md +13 -6
- package/docs/decisions/ADR-049-configuration-trust-boundary.md +11 -1
- package/docs/decisions/ADR-093-ai-council-config-user-global.md +10 -4
- package/docs/decisions/ADR-104-ai-council-config-global-only.md +119 -0
- package/docs/decisions/ADR-105-automatic-subagent-orchestration.md +102 -0
- package/docs/decisions/ADR-106-recursive-verification-benchmark-gate.md +126 -0
- package/docs/decisions/ADR-107-legal-domain-pack-adoption.md +70 -0
- package/docs/decisions/ADR-108-open-source-forever-no-commercial-tier.md +46 -0
- package/docs/decisions/INDEX.md +5 -0
- package/docs/getting-started.md +2 -2
- package/docs/guidelines/agent-infra/domain-eval-anti-pattern.md +53 -0
- package/docs/guidelines/agent-infra/domain-pack-architecture.md +56 -0
- package/docs/guidelines/design-antipatterns-triggers.json +17 -0
- package/docs/guidelines/design-antipatterns.md +170 -0
- package/docs/guidelines/design-modes.md +97 -0
- package/package.json +1 -1
- package/src/config/agent-settings.template.yml +103 -3
- package/src/config/discovery/packs.yml +24 -0
- package/src/config/discovery/workspaces.yml +7 -0
- package/src/scripts/_lib/auto_dispatch.ts +95 -0
- package/src/scripts/_lib/host_capability.ts +59 -0
- package/src/scripts/_lib/orchestration_gate.ts +98 -0
- package/src/scripts/_lib/subagent_bundle.ts +154 -0
- package/src/scripts/_lib/subagent_response.ts +82 -0
- package/src/scripts/_lib/subagent_routing.ts +81 -0
- package/src/scripts/_lib/subagent_spawn.ts +97 -0
- package/src/scripts/_lib/subagent_steering.ts +64 -0
- package/src/scripts/_lib/verify_budget.ts +54 -0
- package/src/scripts/ai_council/advisors.ts +4 -2
- package/src/scripts/ai_council/clients.ts +7 -1
- package/src/scripts/ai_council/config.ts +22 -17
- package/src/scripts/bench_ab_v2_run.ts +127 -0
- package/src/scripts/bench_ab_v2_stats.ts +39 -0
- package/src/scripts/check_council_config_location.ts +6 -5
- package/src/scripts/check_trunk_drift.ts +67 -0
- package/src/scripts/lint_design_quality.ts +321 -0
- package/src/scripts/lint_discovery_vocabulary.ts +6 -0
- package/src/scripts/lint_framework_leakage_allowlist.json +21 -0
- package/src/scripts/lint_legal_pack.ts +197 -0
- package/src/scripts/lint_output_slop.ts +223 -0
- package/src/scripts/lint_pack_risk_class.ts +79 -0
- package/src/scripts/probe_projection_fidelity.ts +7 -1
- package/src/scripts/schemas/agent-settings.schema.json +23 -0
- package/src/scripts/schemas/skill.schema.json +5 -0
- package/src/scripts/skill_linter.ts +3 -0
- package/dist/ui/assets/index-CoQo90sz.js.map +0 -1
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verification-budget model (Phase 4).
|
|
3
|
+
*
|
|
4
|
+
* Pure, no-I/O. Decides HOW a delegated sub-task's output is verified so the
|
|
5
|
+
* cross-model judge Iron Law and verify-before-complete are preserved WITHOUT
|
|
6
|
+
* double-costing every trivial delegation. Contract:
|
|
7
|
+
* `src/agent-src/contexts/execution/verify-budget.md`.
|
|
8
|
+
*
|
|
9
|
+
* - Trivial (below the change-size floor, read-only / no file writes) →
|
|
10
|
+
* `deterministic` verification (diff + dry-run + structural checks, no LLM).
|
|
11
|
+
* - Otherwise → `judge` (full cross-model judge per subagent-orchestration).
|
|
12
|
+
* - A required verification that did not run is a SURFACED safety gap, never a
|
|
13
|
+
* silent pass.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export type VerifyMode = 'deterministic' | 'judge' | 'none';
|
|
17
|
+
|
|
18
|
+
/** Max changed-line count (inclusive) that still counts as trivial. */
|
|
19
|
+
export const TRIVIAL_CHANGE_FLOOR = 20;
|
|
20
|
+
|
|
21
|
+
export interface VerifyInputs {
|
|
22
|
+
/** Changed lines the sub-task produced. */
|
|
23
|
+
change_size: number;
|
|
24
|
+
/** Did the sub-task write/modify files? */
|
|
25
|
+
file_writes: boolean;
|
|
26
|
+
/** Was the sub-task read-only (query/analysis, no mutation)? */
|
|
27
|
+
read_only: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Select the verification mode for a completed sub-task.
|
|
32
|
+
* Trivial + non-mutating → deterministic; anything else → judge.
|
|
33
|
+
*/
|
|
34
|
+
export function selectVerifyMode(inp: VerifyInputs): VerifyMode {
|
|
35
|
+
if (inp.read_only) return 'deterministic';
|
|
36
|
+
const trivial = inp.change_size <= TRIVIAL_CHANGE_FLOOR && !inp.file_writes;
|
|
37
|
+
return trivial ? 'deterministic' : 'judge';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Detect a verification safety gap: a mode was required but the recorded mode
|
|
42
|
+
* was `none`. Returns a human-readable gap string, or null when clean.
|
|
43
|
+
* The orchestrator surfaces a non-null result — it is never a silent pass.
|
|
44
|
+
*/
|
|
45
|
+
export function verificationGap(required: VerifyMode, recorded: VerifyMode): string | null {
|
|
46
|
+
if (required === 'none') return null;
|
|
47
|
+
if (recorded === 'none') {
|
|
48
|
+
return `verification gap: '${required}' required but none recorded`;
|
|
49
|
+
}
|
|
50
|
+
if (required === 'judge' && recorded === 'deterministic') {
|
|
51
|
+
return `verification gap: non-trivial change verified deterministically (judge required)`;
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
* Thinking-style advisors — replace-mode call planning (Phase 6).
|
|
3
3
|
*
|
|
4
4
|
* TypeScript twin of `src/scripts/ai_council/advisors.py` (ADR-200 —
|
|
5
|
-
* Python→TS migration, Phase 1). When
|
|
6
|
-
*
|
|
5
|
+
* Python→TS migration, Phase 1). When the user-global
|
|
6
|
+
* `~/.event4u/agent-config/settings/.ai-council.yml` (always global —
|
|
7
|
+
* ADR-104) enables an advisor (e.g. `contrarian` bound to
|
|
8
|
+
* `member: anthropic`), the
|
|
7
9
|
* orchestrator REPLACES the matching plain-member call with an
|
|
8
10
|
* advisor-persona call on the same provider. Same total call count as a
|
|
9
11
|
* plain run; bounded extra cost beyond the persona-prompt token delta.
|
|
@@ -287,12 +287,18 @@ function _curlJsonPost(url: string, extraHeaders: string[], body: unknown): unkn
|
|
|
287
287
|
for (const h of extraHeaders) {
|
|
288
288
|
args.push('-H', h);
|
|
289
289
|
}
|
|
290
|
+
// `--connect-timeout` fast-fails a dead host; `--max-time` lets curl abort
|
|
291
|
+
// itself cleanly (surfacing a real `curl exited` error with stderr) ~10s
|
|
292
|
+
// before the `spawnSync` timeout would kill it with an opaque ETIMEDOUT.
|
|
293
|
+
// A full 16k-token generation legitimately runs several minutes, so the
|
|
294
|
+
// ceiling is 300s, not 120s (the old value timed out long Anthropic calls).
|
|
295
|
+
args.push('--connect-timeout', '30', '--max-time', '290');
|
|
290
296
|
args.push('-w', '\n%{http_code}', '--data-binary', '@-');
|
|
291
297
|
const r = spawnSync('curl', args, {
|
|
292
298
|
input: JSON.stringify(body),
|
|
293
299
|
encoding: 'utf8',
|
|
294
300
|
maxBuffer: 64 * 1024 * 1024,
|
|
295
|
-
timeout:
|
|
301
|
+
timeout: 300_000,
|
|
296
302
|
});
|
|
297
303
|
if (r.error) {
|
|
298
304
|
throw new Error(`curl spawn failed: ${(r.error as Error).message}`);
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
* surface and validation behaviour byte-for-byte: same exported
|
|
7
7
|
* snake_case names, same defaults, same error messages, same precedence.
|
|
8
8
|
*
|
|
9
|
-
* Reads
|
|
10
|
-
* `docs/contracts/ai-council-config.md
|
|
9
|
+
* Reads the user-global `~/.event4u/agent-config/settings/.ai-council.yml`
|
|
10
|
+
* per the contract in `docs/contracts/ai-council-config.md` (council
|
|
11
|
+
* config is ALWAYS user-global — ADR-104). Replaces the fragmented
|
|
11
12
|
* `.agent-settings.yml` `ai_council` block (Phase 0 migration).
|
|
12
13
|
*
|
|
13
14
|
* Validation contract (8 rules, all enforced at load time):
|
|
@@ -93,7 +94,7 @@ export type PathLike = string;
|
|
|
93
94
|
|
|
94
95
|
// ── error type ─────────────────────────────────────────────────────
|
|
95
96
|
|
|
96
|
-
/** Raised when
|
|
97
|
+
/** Raised when the user-global `.ai-council.yml` violates the schema. */
|
|
97
98
|
export class CouncilConfigError extends Error {
|
|
98
99
|
constructor(message: string) {
|
|
99
100
|
super(message);
|
|
@@ -562,8 +563,10 @@ export const COUNCIL_CONFIG_USER_GLOBAL_REL = 'settings/.ai-council.yml';
|
|
|
562
563
|
|
|
563
564
|
/**
|
|
564
565
|
* Env var pinning the council config to an explicit absolute path, ahead
|
|
565
|
-
* of the
|
|
566
|
-
*
|
|
566
|
+
* of the user-global default. Mirrors `EVENT4U_CONFIG_HOME` but targets
|
|
567
|
+
* the config file itself (tests / power users). This is the ONLY escape
|
|
568
|
+
* from the user-global location — it is an explicit absolute path, never
|
|
569
|
+
* a "search the project" path.
|
|
567
570
|
*/
|
|
568
571
|
export const COUNCIL_CONFIG_ENV = 'AI_COUNCIL_CONFIG';
|
|
569
572
|
|
|
@@ -598,14 +601,24 @@ function _isAbsoluteLikePython(p: string): boolean {
|
|
|
598
601
|
/**
|
|
599
602
|
* Resolve which `.ai-council.yml` the council reads.
|
|
600
603
|
*
|
|
604
|
+
* **The council config is ALWAYS user-global.** It is a per-developer
|
|
605
|
+
* facility configured once and works in every project, worktree, and
|
|
606
|
+
* CWD — including consumer repos that carry no council file of their
|
|
607
|
+
* own. The project tree is NEVER searched for council config (ADR-104,
|
|
608
|
+
* superseding the project-local override kept by ADR-093).
|
|
609
|
+
*
|
|
601
610
|
* Precedence (first match wins):
|
|
602
611
|
*
|
|
603
612
|
* 1. `$AI_COUNCIL_CONFIG` — explicit absolute override (tests / power
|
|
604
|
-
* users). Honoured even when the target is absent.
|
|
605
|
-
*
|
|
606
|
-
*
|
|
613
|
+
* users). Honoured even when the target is absent. This is an
|
|
614
|
+
* explicit path, not a project search.
|
|
615
|
+
* 2. User-global `~/.event4u/agent-config/settings/.ai-council.yml`
|
|
607
616
|
* (with the legacy `~/.config/agent-config/` read-fallback).
|
|
608
617
|
*
|
|
618
|
+
* `project_root` is accepted for signature stability with callers but is
|
|
619
|
+
* NOT consulted for config resolution — the council never reads
|
|
620
|
+
* `<project_root>/agents/settings/.ai-council.yml`.
|
|
621
|
+
*
|
|
609
622
|
* Always returns a path (never `null`): when nothing exists yet it
|
|
610
623
|
* returns the user-global write target.
|
|
611
624
|
*/
|
|
@@ -613,20 +626,12 @@ export function resolve_config_path(
|
|
|
613
626
|
project_root: PathLike,
|
|
614
627
|
options: { env?: user_global_paths.EnvMap | null } = {},
|
|
615
628
|
): PathLike {
|
|
629
|
+
void project_root; // intentionally unused — council config is always user-global (ADR-104).
|
|
616
630
|
const env_map = options.env != null ? options.env : process.env;
|
|
617
631
|
const override = env_map[COUNCIL_CONFIG_ENV];
|
|
618
632
|
if (override) {
|
|
619
633
|
return _expanduser(override);
|
|
620
634
|
}
|
|
621
|
-
const project_path = path.join(
|
|
622
|
-
project_root,
|
|
623
|
-
'agents',
|
|
624
|
-
'settings',
|
|
625
|
-
COUNCIL_CONFIG_RELNAME,
|
|
626
|
-
);
|
|
627
|
-
if (fs.existsSync(project_path)) {
|
|
628
|
-
return project_path;
|
|
629
|
-
}
|
|
630
635
|
const found = user_global_paths.resolve_with_fallback(
|
|
631
636
|
COUNCIL_CONFIG_USER_GLOBAL_REL,
|
|
632
637
|
{ env: options.env ?? null },
|
|
@@ -67,14 +67,20 @@ type Dict = Record<string, unknown>;
|
|
|
67
67
|
interface ArmSpec {
|
|
68
68
|
setting_sources: string | null;
|
|
69
69
|
inject: string | null; // None | "rdp" | "placebo"
|
|
70
|
+
recursive?: boolean; // ADR-106 D₂ arm: depth-bounded attempt→critic→re-attempt loop
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
// Arm -> (setting_sources, inject) where inject ∈ {None, "rdp", "placebo"}.
|
|
74
|
+
// `package-recursive` (ADR-106's D₂) reuses the real-plugin `package` config but
|
|
75
|
+
// runs the recursion loop instead of a single --print. It only executes when
|
|
76
|
+
// explicitly selected (`--arms package-recursive`); the four default arms are
|
|
77
|
+
// untouched, so existing runs and their golden-parity outputs are unchanged.
|
|
73
78
|
const ARMS: Record<string, ArmSpec> = {
|
|
74
79
|
vanilla: { setting_sources: 'project,local', inject: null },
|
|
75
80
|
package: { setting_sources: null, inject: null },
|
|
76
81
|
'package-rdp': { setting_sources: null, inject: 'rdp' },
|
|
77
82
|
placebo: { setting_sources: 'project,local', inject: 'placebo' },
|
|
83
|
+
'package-recursive': { setting_sources: null, inject: null, recursive: true },
|
|
78
84
|
};
|
|
79
85
|
|
|
80
86
|
/**
|
|
@@ -165,10 +171,14 @@ interface RunOneOpts {
|
|
|
165
171
|
timeout: number;
|
|
166
172
|
placebo_chars: number;
|
|
167
173
|
sp_dir: string;
|
|
174
|
+
max_depth?: number | null; // recursion arm only — hard cap on correction rounds (default 1)
|
|
168
175
|
}
|
|
169
176
|
|
|
170
177
|
export function run_one(task: Dict, arm: string, opts: RunOneOpts): Dict {
|
|
171
178
|
const spec = ARMS[arm] as ArmSpec;
|
|
179
|
+
if (spec.recursive) {
|
|
180
|
+
return run_one_recursive(task, opts);
|
|
181
|
+
}
|
|
172
182
|
const [clone, fixture] = reset_fixture(task);
|
|
173
183
|
const sp_text = injected_text(spec.inject, opts.placebo_chars);
|
|
174
184
|
let sp_file: string | null = null;
|
|
@@ -198,6 +208,123 @@ export function run_one(task: Dict, arm: string, opts: RunOneOpts): Dict {
|
|
|
198
208
|
};
|
|
199
209
|
}
|
|
200
210
|
|
|
211
|
+
export interface RecursiveAttempt {
|
|
212
|
+
run: Dict;
|
|
213
|
+
score: scoring.ScoreResultV2;
|
|
214
|
+
output?: string; // pair-capture: changed-file contents at this depth (for human-preference judging)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* ADR-106 D₂ arm — depth-bounded recursive self-verification.
|
|
219
|
+
*
|
|
220
|
+
* Loop: attempt → critic verdict → conditional re-attempt, depth = hard compute
|
|
221
|
+
* cap. The critic is the DETERMINISTIC v2 scorer (accept iff the attempt is both
|
|
222
|
+
* capability- AND discipline-passing) — cheapest critic, no extra model call,
|
|
223
|
+
* fully testable. A model-based / cross-vendor critic is a later option (see
|
|
224
|
+
* road-to-recursive-verification.md Phase 4).
|
|
225
|
+
*
|
|
226
|
+
* `attemptFn(depth, priorVerdict)` is an injectable seam: the default runs the
|
|
227
|
+
* real `package`-config agent (prior verdict threaded as the system prompt on
|
|
228
|
+
* re-attempts) and scores it; tests inject a scripted attemptFn to exercise the
|
|
229
|
+
* loop control flow with NO live model call.
|
|
230
|
+
*
|
|
231
|
+
* Verifies the loop's CONTROL FLOW (stop conditions, depth cap, verdict
|
|
232
|
+
* threading). Whether recursion actually lifts capability/discipline is the
|
|
233
|
+
* empirical question the live `bench:ab` run answers — not asserted here.
|
|
234
|
+
*/
|
|
235
|
+
export function run_one_recursive(
|
|
236
|
+
task: Dict,
|
|
237
|
+
opts: RunOneOpts,
|
|
238
|
+
attemptFn?: (depth: number, priorVerdict: string | null) => RecursiveAttempt,
|
|
239
|
+
onAttempt?: (depth: number, attempt: RecursiveAttempt) => void,
|
|
240
|
+
): Dict {
|
|
241
|
+
const maxDepth = Math.max(0, opts.max_depth ?? 1);
|
|
242
|
+
const makeAttempt =
|
|
243
|
+
attemptFn ??
|
|
244
|
+
((depth: number, priorVerdict: string | null): RecursiveAttempt => {
|
|
245
|
+
const [clone, fixture] = reset_fixture(task);
|
|
246
|
+
let sp_file: string | null = null;
|
|
247
|
+
if (priorVerdict) {
|
|
248
|
+
sp_file = path.join(opts.sp_dir, `.sp-recursive-${depth}.txt`);
|
|
249
|
+
fs.writeFileSync(sp_file, priorVerdict, { encoding: 'utf-8' });
|
|
250
|
+
}
|
|
251
|
+
const run = v1.run_live(task, clone, opts.timeout, {
|
|
252
|
+
sysprompt_file: sp_file,
|
|
253
|
+
setting_sources: (ARMS['package'] as ArmSpec).setting_sources,
|
|
254
|
+
max_budget: opts.max_budget,
|
|
255
|
+
model: opts.model,
|
|
256
|
+
}) as unknown as Dict;
|
|
257
|
+
const score = scoring.score_task_v2(task, {
|
|
258
|
+
fixture_root: fixture,
|
|
259
|
+
clone_root: clone,
|
|
260
|
+
transcript: String(run['transcript'] ?? ''),
|
|
261
|
+
});
|
|
262
|
+
// Capture the attempt's output (changed-file contents) for the
|
|
263
|
+
// human-preference pair, before the next depth resets the clone.
|
|
264
|
+
const output = score.files_changed
|
|
265
|
+
.map((f: string) => {
|
|
266
|
+
try {
|
|
267
|
+
return `--- ${f} ---\n${fs.readFileSync(path.join(clone, f), 'utf-8')}`;
|
|
268
|
+
} catch {
|
|
269
|
+
return `--- ${f} --- (unreadable)`;
|
|
270
|
+
}
|
|
271
|
+
})
|
|
272
|
+
.join('\n\n')
|
|
273
|
+
.slice(0, 4000);
|
|
274
|
+
return { run, score, output };
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
const accepts = (s: scoring.ScoreResultV2): boolean => Boolean(s.capability_pass) && Boolean(s.discipline_pass);
|
|
278
|
+
|
|
279
|
+
let depth = 0;
|
|
280
|
+
let prior: RecursiveAttempt | null = null;
|
|
281
|
+
let attempt = makeAttempt(0, null);
|
|
282
|
+
onAttempt?.(0, attempt);
|
|
283
|
+
let stop_reason: string;
|
|
284
|
+
let last_verdict_len = 0;
|
|
285
|
+
for (;;) {
|
|
286
|
+
const s = attempt.score;
|
|
287
|
+
if (accepts(s)) {
|
|
288
|
+
stop_reason = 'accept';
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
if (depth >= maxDepth) {
|
|
292
|
+
stop_reason = 'max_depth';
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
if (
|
|
296
|
+
prior !== null &&
|
|
297
|
+
s.discipline_score === prior.score.discipline_score &&
|
|
298
|
+
Boolean(s.capability_pass) === Boolean(prior.score.capability_pass)
|
|
299
|
+
) {
|
|
300
|
+
stop_reason = 'no_progress';
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
depth += 1;
|
|
304
|
+
const verdict =
|
|
305
|
+
`Prior attempt (depth ${depth - 1}) did not pass: ` +
|
|
306
|
+
`capability=${s.capability_pass}, discipline=${s.discipline_score}. ` +
|
|
307
|
+
`Revise to satisfy the unmet acceptance criteria; change only what the task requires.`;
|
|
308
|
+
last_verdict_len = verdict.length;
|
|
309
|
+
prior = attempt;
|
|
310
|
+
attempt = makeAttempt(depth, verdict);
|
|
311
|
+
onAttempt?.(depth, attempt);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const { run, score } = attempt;
|
|
315
|
+
return {
|
|
316
|
+
errored: _pyBool(run['errored']),
|
|
317
|
+
reason: run['reason'] ?? null,
|
|
318
|
+
capability_pass: score.capability_pass,
|
|
319
|
+
discipline_score: new PyFloat(score.discipline_score),
|
|
320
|
+
discipline_pass: score.discipline_pass,
|
|
321
|
+
metrics: trajectory_metrics(run, score),
|
|
322
|
+
injected_chars: last_verdict_len,
|
|
323
|
+
depth_reached: depth,
|
|
324
|
+
stop_reason,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
201
328
|
export function main(argv: string[] | null = null): number {
|
|
202
329
|
const args = parse_args(argv ?? process.argv.slice(2));
|
|
203
330
|
|
|
@@ -41,6 +41,11 @@ const COMPARISONS: Array<[string, string, string]> = [
|
|
|
41
41
|
['package', 'vanilla', 'package lift'],
|
|
42
42
|
['package-rdp', 'package', 'RDP lift'],
|
|
43
43
|
['package', 'placebo', 'attribution (content vs length)'],
|
|
44
|
+
// ADR-106: recursion's NOVEL lift over rules-only (D₂ − D₁). The analyse()
|
|
45
|
+
// loop is arm-guarded (`arms.includes(t) && arms.includes(b)`), so this row
|
|
46
|
+
// renders ONLY when a `package-recursive` arm is present — existing runs
|
|
47
|
+
// (no such arm) are byte-identical, golden parity preserved.
|
|
48
|
+
['package-recursive', 'package', 'recursion novel lift (D₂ − D₁)'],
|
|
44
49
|
];
|
|
45
50
|
|
|
46
51
|
// ── CPython math.erf / math.comb ports ────────────────────────────────────
|
|
@@ -333,6 +338,40 @@ export function compare(records: Dict[], arm_t: string, arm_b: string): Dict {
|
|
|
333
338
|
};
|
|
334
339
|
}
|
|
335
340
|
|
|
341
|
+
/**
|
|
342
|
+
* Recursion's NOVEL discipline lift over rules-only (ADR-106).
|
|
343
|
+
*
|
|
344
|
+
* Compares the `package-recursive` arm (D₂ = rules + recursion) against
|
|
345
|
+
* `package` (D₁ = rules only) via the existing pure `compare()`, so recursion's
|
|
346
|
+
* marginal lift `D₂ − D₁` is a measured fact, never an assumed redundancy.
|
|
347
|
+
*
|
|
348
|
+
* ADDITIVE: does NOT touch the rendered COMPARISONS path — existing CLI output
|
|
349
|
+
* (and its golden-parity tests) are unaffected. Returns `null` when no
|
|
350
|
+
* `package-recursive` arm is present in the records, so a future renderer can
|
|
351
|
+
* skip the block cleanly for non-recursion runs.
|
|
352
|
+
*
|
|
353
|
+
* NOTE (Phase 2b live-integration, NOT done here): wiring this into the rendered
|
|
354
|
+
* stats output (a guarded COMPARISONS entry + a golden-parity fixture refresh)
|
|
355
|
+
* and producing the `package-recursive` arm itself (a multi-call recursion loop
|
|
356
|
+
* the single-`--print` runner does not yet support) require the live bench and
|
|
357
|
+
* are deferred to the supervised Phase 3 session.
|
|
358
|
+
*/
|
|
359
|
+
export const RECURSIVE_ARM = 'package-recursive';
|
|
360
|
+
export const RECURSIVE_BASELINE_ARM = 'package';
|
|
361
|
+
|
|
362
|
+
export function recursiveNovelLift(records: Dict[]): Dict | null {
|
|
363
|
+
const hasRecursion = records.some((rec) => {
|
|
364
|
+
const runs = _dictOr(rec['arms'])[RECURSIVE_ARM];
|
|
365
|
+
return Array.isArray(runs) && runs.length > 0;
|
|
366
|
+
});
|
|
367
|
+
if (!hasRecursion) return null;
|
|
368
|
+
return {
|
|
369
|
+
label: 'recursion novel lift (D₂ − D₁)',
|
|
370
|
+
arms: [RECURSIVE_ARM, RECURSIVE_BASELINE_ARM],
|
|
371
|
+
...compare(records, RECURSIVE_ARM, RECURSIVE_BASELINE_ARM),
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
336
375
|
export function mean_tokens_by_arm(records: Dict[], arms: string[]): Dict {
|
|
337
376
|
// Mean total tokens per arm over non-errored runs (the cost axis, L10).
|
|
338
377
|
// Truncated/errored runs are excluded — their token count is capped by the
|
|
@@ -9,11 +9,12 @@
|
|
|
9
9
|
* and the same `<!-- council-config-allowed -->` escape pragma. No behaviour
|
|
10
10
|
* changes — latent bugs replicated.
|
|
11
11
|
*
|
|
12
|
-
* Per ADR-093 the council reads a dedicated
|
|
13
|
-
*
|
|
14
|
-
* (`~/.event4u/agent-config/settings/.ai-council.yml`)
|
|
15
|
-
*
|
|
16
|
-
*
|
|
12
|
+
* Per ADR-104 (superseding ADR-093) the council reads a dedicated
|
|
13
|
+
* `.ai-council.yml` resolved ALWAYS from the user-global location
|
|
14
|
+
* (`~/.event4u/agent-config/settings/.ai-council.yml`) — the project tree
|
|
15
|
+
* is never searched (the only escape is the explicit `$AI_COUNCIL_CONFIG`
|
|
16
|
+
* path). Keys are top-level in that file; the legacy `ai_council.*` block
|
|
17
|
+
* under `.agent-settings.yml` was removed in Phase 0.
|
|
17
18
|
*
|
|
18
19
|
* What it flags, in the council command/skill surfaces + the config contract:
|
|
19
20
|
*
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-PR trunk-drift check (road-to-product-clarity Phase 4b).
|
|
3
|
+
*
|
|
4
|
+
* Fails when the current branch is behind origin/main — i.e. origin/main is NOT
|
|
5
|
+
* an ancestor of HEAD — so a PR is never opened on a stale base (the recurring
|
|
6
|
+
* divergence this session hit). Advisory by default: run it in the /create-pr
|
|
7
|
+
* pre-flight or wire into CI per your branch-protection policy.
|
|
8
|
+
*
|
|
9
|
+
* git merge-base --is-ancestor origin/main HEAD → exit 0 = up to date
|
|
10
|
+
*
|
|
11
|
+
* Pure core (`driftVerdict`) is unit-testable without git.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { execFileSync } from 'node:child_process';
|
|
15
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
16
|
+
import * as path from 'node:path';
|
|
17
|
+
|
|
18
|
+
export type DriftInput = {
|
|
19
|
+
/** True when origin/main is an ancestor of HEAD (branch contains trunk tip). */
|
|
20
|
+
trunkIsAncestor: boolean;
|
|
21
|
+
/** Commits HEAD is behind origin/main (0 when up to date). */
|
|
22
|
+
behind: number;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type DriftVerdict = { ok: boolean; message: string };
|
|
26
|
+
|
|
27
|
+
/** Pure decision — no git. */
|
|
28
|
+
export function driftVerdict(i: DriftInput): DriftVerdict {
|
|
29
|
+
if (i.trunkIsAncestor) {
|
|
30
|
+
return { ok: true, message: '✅ branch is up to date with origin/main' };
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
ok: false,
|
|
34
|
+
message: `❌ branch is ${i.behind} commit(s) behind origin/main — rebase/merge before opening a PR`,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function _git(args: string[]): string {
|
|
39
|
+
return execFileSync('git', args, { encoding: 'utf-8' }).trim();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function main(): number {
|
|
43
|
+
let input: DriftInput;
|
|
44
|
+
try {
|
|
45
|
+
_git(['fetch', 'origin', 'main', '--quiet']);
|
|
46
|
+
let isAncestor = true;
|
|
47
|
+
try {
|
|
48
|
+
execFileSync('git', ['merge-base', '--is-ancestor', 'origin/main', 'HEAD'], { stdio: 'ignore' });
|
|
49
|
+
} catch {
|
|
50
|
+
isAncestor = false;
|
|
51
|
+
}
|
|
52
|
+
const behind = Number(_git(['rev-list', '--count', 'HEAD..origin/main']) || '0');
|
|
53
|
+
input = { trunkIsAncestor: isAncestor, behind };
|
|
54
|
+
} catch (e) {
|
|
55
|
+
process.stdout.write(`⚠️ trunk-drift check skipped (no origin/main or git error): ${String(e)}\n`);
|
|
56
|
+
return 0; // advisory — never block on a missing remote
|
|
57
|
+
}
|
|
58
|
+
const v = driftVerdict(input);
|
|
59
|
+
process.stdout.write(`${v.message}\n`);
|
|
60
|
+
return v.ok ? 0 : 1;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
64
|
+
if (process.argv[1] !== undefined && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {
|
|
65
|
+
process.exit(main());
|
|
66
|
+
}
|
|
67
|
+
void _HERE;
|