@event4u/agent-config 8.8.0 → 8.10.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.
Files changed (215) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +139 -263
  3. package/README.md +5 -5
  4. package/dist/agent-src/commands/agent-handoff.md +39 -2
  5. package/dist/agent-src/commands/cost/report.md +13 -3
  6. package/dist/agent-src/commands/research/deep.md +8 -0
  7. package/dist/agent-src/commands/research/report.md +7 -0
  8. package/dist/agent-src/commands/roadmap/create.md +12 -0
  9. package/dist/agent-src/commands/roadmap/process-full.md +31 -5
  10. package/dist/agent-src/commands/tests/create.md +28 -6
  11. package/dist/agent-src/commands/work.md +33 -0
  12. package/dist/agent-src/commands/worktree/cleanup.md +78 -0
  13. package/dist/agent-src/commands/worktree/create.md +62 -0
  14. package/dist/agent-src/commands/worktree/status.md +63 -0
  15. package/dist/agent-src/commands/worktree/verify.md +70 -0
  16. package/dist/agent-src/commands/worktree.md +85 -0
  17. package/dist/agent-src/contexts/communication/rules-auto/think-before-action-mechanics.md +30 -1
  18. package/dist/agent-src/contexts/execution/orchestration-telemetry.md +60 -31
  19. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +37 -0
  20. package/dist/agent-src/contexts/execution/subagent-topologies.md +34 -0
  21. package/dist/agent-src/contexts/execution/verification-mechanics.md +9 -9
  22. package/dist/agent-src/rules/agent-authority.md +1 -1
  23. package/dist/agent-src/rules/communication-through-line.md +1 -1
  24. package/dist/agent-src/rules/delegation-policy.md +5 -0
  25. package/dist/agent-src/rules/design-fidelity.md +40 -0
  26. package/dist/agent-src/rules/direct-answers.md +1 -1
  27. package/dist/agent-src/rules/downstream-changes.md +1 -0
  28. package/dist/agent-src/rules/external-code-graph-interop.md +65 -0
  29. package/dist/agent-src/rules/git-history-discipline.md +18 -0
  30. package/dist/agent-src/rules/lethal-trifecta-guard.md +6 -1
  31. package/dist/agent-src/rules/minimal-safe-diff.md +58 -10
  32. package/dist/agent-src/rules/non-destructive-by-default.md +3 -0
  33. package/dist/agent-src/rules/question-not-instruction.md +76 -0
  34. package/dist/agent-src/rules/senior-engineering-discipline.md +1 -1
  35. package/dist/agent-src/rules/spreadsheet-source-quality.md +61 -0
  36. package/dist/agent-src/rules/think-before-action.md +1 -0
  37. package/dist/agent-src/rules/tool-safety.md +8 -0
  38. package/dist/agent-src/rules/untrusted-input-defense.md +87 -22
  39. package/dist/agent-src/scripts/update_roadmap_progress.ts +2 -1
  40. package/dist/agent-src/skills/accessibility-auditor/SKILL.md +19 -0
  41. package/dist/agent-src/skills/accessibility-auditor/evals/evals.json +15 -0
  42. package/dist/agent-src/skills/ai-code-blindspots/SKILL.md +10 -3
  43. package/dist/agent-src/skills/analysis-skill-router/evals/evals.json +13 -0
  44. package/dist/agent-src/skills/api-testing/SKILL.md +15 -1
  45. package/dist/agent-src/skills/character-consistency/evals/evals.json +16 -0
  46. package/dist/agent-src/skills/code-review/SKILL.md +2 -0
  47. package/dist/agent-src/skills/command-routing/evals/evals.json +14 -0
  48. package/dist/agent-src/skills/command-writing/SKILL.md +53 -40
  49. package/dist/agent-src/skills/dashboard-design/evals/evals.json +16 -0
  50. package/dist/agent-src/skills/dcf-modeling/evals/domain-truth.json +29 -0
  51. package/dist/agent-src/skills/dcf-modeling/evals/evals.json +20 -0
  52. package/dist/agent-src/skills/decision-record/evals/evals.json +20 -0
  53. package/dist/agent-src/skills/deep-reading-analyst/SKILL.md +1 -0
  54. package/dist/agent-src/skills/design-intelligence/SKILL.md +49 -12
  55. package/dist/agent-src/skills/design-intelligence/evals/evals.json +14 -0
  56. package/dist/agent-src/skills/design-review/SKILL.md +35 -15
  57. package/dist/agent-src/skills/design-system-capture/SKILL.md +84 -24
  58. package/dist/agent-src/skills/design-system-capture/evals/evals.json +15 -0
  59. package/dist/agent-src/skills/developer-like-execution/evals/evals.json +16 -0
  60. package/dist/agent-src/skills/doc-coauthoring/SKILL.md +27 -0
  61. package/dist/agent-src/skills/doc-coauthoring/evals/evals.json +16 -0
  62. package/dist/agent-src/skills/editorial-calendar/evals/domain-truth.json +16 -0
  63. package/dist/agent-src/skills/editorial-calendar/evals/evals.json +16 -0
  64. package/dist/agent-src/skills/estimate-ticket/evals/evals.json +16 -0
  65. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +30 -0
  66. package/dist/agent-src/skills/existing-ui-audit/evals/triggers.json +47 -0
  67. package/dist/agent-src/skills/fe-design/SKILL.md +36 -5
  68. package/dist/agent-src/skills/fe-design/evals/triggers.json +47 -0
  69. package/dist/agent-src/skills/forecasting/evals/domain-truth.json +29 -0
  70. package/dist/agent-src/skills/forecasting/evals/evals.json +20 -0
  71. package/dist/agent-src/skills/frontend-render-security/SKILL.md +2 -2
  72. package/dist/agent-src/skills/fundraising-narrative/evals/domain-truth.json +16 -0
  73. package/dist/agent-src/skills/fundraising-narrative/evals/evals.json +16 -0
  74. package/dist/agent-src/skills/html-deck/SKILL.md +27 -0
  75. package/dist/agent-src/skills/iconography/SKILL.md +24 -0
  76. package/dist/agent-src/skills/incident-commander/evals/domain-truth.json +16 -0
  77. package/dist/agent-src/skills/incident-commander/evals/evals.json +20 -0
  78. package/dist/agent-src/skills/launch-readiness/evals/evals.json +21 -0
  79. package/dist/agent-src/skills/logging-monitoring/evals/evals.json +16 -0
  80. package/dist/agent-src/skills/markitdown/SKILL.md +1 -0
  81. package/dist/agent-src/skills/mcp/SKILL.md +24 -0
  82. package/dist/agent-src/skills/mcp-builder/SKILL.md +9 -1
  83. package/dist/agent-src/skills/memory-consolidation/SKILL.md +55 -4
  84. package/dist/agent-src/skills/messaging-architecture/evals/domain-truth.json +16 -0
  85. package/dist/agent-src/skills/messaging-architecture/evals/evals.json +16 -0
  86. package/dist/agent-src/skills/perf-feedback-craft/evals/evals.json +16 -0
  87. package/dist/agent-src/skills/pest-testing/SKILL.md +1 -1
  88. package/dist/agent-src/skills/playwright-testing/SKILL.md +16 -0
  89. package/dist/agent-src/skills/prompt-engineering-patterns/evals/evals.json +16 -0
  90. package/dist/agent-src/skills/refine-prompt/evals/evals.json +16 -0
  91. package/dist/agent-src/skills/refine-ticket/evals/evals.json +16 -0
  92. package/dist/agent-src/skills/release-comms/evals/evals.json +16 -0
  93. package/dist/agent-src/skills/rice-prioritization/evals/evals.json +20 -0
  94. package/dist/agent-src/skills/roadmap-writing/SKILL.md +39 -5
  95. package/dist/agent-src/skills/runway-cognition/evals/domain-truth.json +29 -0
  96. package/dist/agent-src/skills/runway-cognition/evals/evals.json +20 -0
  97. package/dist/agent-src/skills/scenario-modeling/evals/domain-truth.json +29 -0
  98. package/dist/agent-src/skills/scenario-modeling/evals/evals.json +20 -0
  99. package/dist/agent-src/skills/secrets-management/SKILL.md +7 -0
  100. package/dist/agent-src/skills/skill-writing/SKILL.md +12 -3
  101. package/dist/agent-src/skills/spreadsheet-authoring/SKILL.md +98 -0
  102. package/dist/agent-src/skills/spreadsheet-authoring/evals/triggers.json +47 -0
  103. package/dist/agent-src/skills/subagent-orchestration/SKILL.md +27 -33
  104. package/dist/agent-src/skills/supply-chain-intake/SKILL.md +12 -0
  105. package/dist/agent-src/skills/systematic-debugging/evals/evals.json +20 -0
  106. package/dist/agent-src/skills/test-case-discovery/SKILL.md +118 -0
  107. package/dist/agent-src/skills/test-case-discovery/evals/triggers.json +52 -0
  108. package/dist/agent-src/skills/test-driven-development/SKILL.md +32 -6
  109. package/dist/agent-src/skills/test-driven-development/evals/evals.json +16 -0
  110. package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +34 -4
  111. package/dist/agent-src/skills/threat-modeling/evals/evals.json +21 -0
  112. package/dist/agent-src/skills/typography-system/evals/evals.json +14 -0
  113. package/dist/agent-src/skills/ui-component-architect/SKILL.md +9 -0
  114. package/dist/agent-src/skills/unit-economics-modeling/evals/domain-truth.json +40 -0
  115. package/dist/agent-src/skills/unit-economics-modeling/evals/evals.json +24 -0
  116. package/dist/agent-src/skills/verify-completion-evidence/evals/evals.json +16 -0
  117. package/dist/agent-src/skills/vision-articulation/evals/evals.json +16 -0
  118. package/dist/agent-src/skills/voice-and-tone-design/evals/evals.json +16 -0
  119. package/dist/agent-src/skills/worktree-lifecycle/SKILL.md +180 -0
  120. package/dist/agent-src/skills/worktree-lifecycle/evals/triggers.json +52 -0
  121. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  122. package/dist/agent-src/templates/roadmaps.md +36 -0
  123. package/dist/cli/registry.js +3 -0
  124. package/dist/cli/registry.js.map +1 -1
  125. package/dist/discovery/deprecation-report.md +1 -1
  126. package/dist/discovery/discovery-manifest.json +326 -67
  127. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  128. package/dist/discovery/discovery-manifest.summary.md +6 -6
  129. package/dist/discovery/orphan-report.md +1 -1
  130. package/dist/discovery/packs.json +25 -14
  131. package/dist/discovery/trust-report.md +4 -4
  132. package/dist/discovery/workspaces.json +25 -12
  133. package/dist/install/install.mjs +136 -1
  134. package/dist/mcp/registry-manifest.json +3 -3
  135. package/dist/router.json +1 -1
  136. package/dist/scripts/_lib/claude_builtin_names.js +145 -0
  137. package/dist/scripts/_lib/claude_builtin_names.js.map +1 -0
  138. package/dist/scripts/condense.js +14 -0
  139. package/dist/scripts/condense.js.map +1 -1
  140. package/docs/CLAIMS.md +36 -8
  141. package/docs/architecture.md +4 -4
  142. package/docs/archive/CHANGELOG-pre-8.9.0.md +306 -0
  143. package/docs/benchmark.md +113 -0
  144. package/docs/catalog.md +32 -8
  145. package/docs/command-flows.md +6 -1
  146. package/docs/contracts/CHANGELOG-conventions.md +6 -0
  147. package/docs/contracts/command-clusters.md +18 -0
  148. package/docs/contracts/design-artifact-lifecycle.md +93 -0
  149. package/docs/contracts/design-artifact-verification.md +153 -0
  150. package/docs/contracts/injected-block-authenticity-model.md +66 -0
  151. package/docs/contracts/mcp-tool-inventory.md +28 -28
  152. package/docs/contracts/release-pr-gating.md +26 -0
  153. package/docs/contracts/release-sizing.md +85 -0
  154. package/docs/contracts/surface-agent-contracts.md +153 -0
  155. package/docs/decisions/ADR-061-corpus-grounding-layer.md +13 -0
  156. package/docs/decisions/ADR-115-command-cluster-consolidation-phase-4.md +1 -1
  157. package/docs/decisions/ADR-116-memory-tripwire-activation-path.md +9 -0
  158. package/docs/distribution/consumer-matrix.md +96 -0
  159. package/docs/featured-skills.md +2 -2
  160. package/docs/getting-started-by-role.md +1 -1
  161. package/docs/getting-started.md +2 -2
  162. package/docs/governance-advantage.md +1 -1
  163. package/docs/guidelines/agent-infra/asking-and-brevity-examples.md +27 -0
  164. package/docs/guidelines/agent-infra/emphasis-budget.md +45 -0
  165. package/docs/guidelines/agent-infra/simplicity-and-goal-demos.md +151 -0
  166. package/docs/guidelines/agent-infra/tool-description-as-policy.md +35 -0
  167. package/docs/guidelines/design-canon.md +1 -0
  168. package/docs/proof.md +38 -23
  169. package/docs/release-runbook.md +3 -0
  170. package/docs/second-brain-scope.md +69 -26
  171. package/docs/self-review-gate.md +51 -0
  172. package/package.json +1 -1
  173. package/src/agent-src/scripts/update_roadmap_progress.ts +2 -1
  174. package/src/scripts/_dispatch.bash +27 -0
  175. package/src/scripts/_lib/claude_builtin_names.ts +148 -0
  176. package/src/scripts/_lib/file_slicer.ts +117 -0
  177. package/src/scripts/_lib/lexical_index.ts +155 -0
  178. package/src/scripts/_lib/orchestration_record.ts +183 -0
  179. package/src/scripts/_lib/orchestration_savings.ts +114 -9
  180. package/src/scripts/_lib/retrieval_sanitize.ts +71 -0
  181. package/src/scripts/_lib/security_lint.ts +1 -0
  182. package/src/scripts/_lib/stat_index.ts +98 -0
  183. package/src/scripts/benchmark.ts +122 -0
  184. package/src/scripts/build_proof.ts +30 -17
  185. package/src/scripts/check_memory.ts +2 -0
  186. package/src/scripts/check_quality_regression.ts +51 -0
  187. package/src/scripts/check_release_adjacent_health.ts +121 -0
  188. package/src/scripts/check_roadmap_trackable.ts +3 -2
  189. package/src/scripts/check_rule_invariants.ts +275 -0
  190. package/src/scripts/condense.ts +14 -0
  191. package/src/scripts/consumer_matrix.ts +353 -0
  192. package/src/scripts/discovery_graph.ts +0 -0
  193. package/src/scripts/external_sources_denylist.json +4 -1
  194. package/src/scripts/generate_index.ts +6 -2
  195. package/src/scripts/install-hooks.sh +25 -0
  196. package/src/scripts/install.ts +35 -2
  197. package/src/scripts/learning_sidecar.ts +0 -0
  198. package/src/scripts/lint_agent_skill_names.ts +29 -0
  199. package/src/scripts/lint_changelog_rollback.ts +293 -0
  200. package/src/scripts/lint_knowledge_scale.ts +4 -3
  201. package/src/scripts/lint_roadmap_complexity.ts +124 -0
  202. package/src/scripts/lint_versioned_cache.ts +195 -0
  203. package/src/scripts/mcp_server/consumer_tool_catalog.json +2 -1
  204. package/src/scripts/mcp_server/tools.ts +24 -1
  205. package/src/scripts/measure_lexical_ranking.ts +173 -0
  206. package/src/scripts/memory_lookup.ts +0 -0
  207. package/src/scripts/orchestration_record.ts +156 -0
  208. package/src/scripts/orchestration_savings_report.ts +65 -8
  209. package/src/scripts/schemas/skill.schema.json +4 -0
  210. package/src/scripts/score_domain_truth.ts +192 -0
  211. package/src/scripts/second_brain_retrieval.ts +320 -0
  212. package/src/scripts/second_brain_run.ts +341 -0
  213. package/src/scripts/self_review_gate.test.ts +65 -0
  214. package/src/scripts/self_review_gate.ts +272 -0
  215. package/src/scripts/trigger_eval_grandfather.json +223 -224
@@ -477,6 +477,18 @@ async function _memoryLookupHandler(
477
477
  throw new Error("'detail' must be 'index' or 'full'");
478
478
  }
479
479
 
480
+ // B1 (road-to-retrieval-substrate-hardening): optional token budget →
481
+ // compact one-line rows, hard-cut with a truncation hint. Absent → the
482
+ // envelope is unchanged (v1 default-full contract holds).
483
+ const opts: { detail: 'full' | 'index'; token_budget?: number } = { detail: detailRaw };
484
+ if (args.token_budget !== undefined) {
485
+ const tb = args.token_budget;
486
+ if (typeof tb !== 'number' || !Number.isInteger(tb) || typeof tb === 'boolean' || tb < 1) {
487
+ throw new Error("'token_budget' must be a positive integer");
488
+ }
489
+ opts.token_budget = tb;
490
+ }
491
+
480
492
  const prevCwd = process.cwd();
481
493
  let envelope: Record<string, unknown>;
482
494
  try {
@@ -485,7 +497,7 @@ async function _memoryLookupHandler(
485
497
  [...(types as string[])],
486
498
  [...(keys as string[])],
487
499
  limitRaw,
488
- { detail: detailRaw },
500
+ opts,
489
501
  );
490
502
  } finally {
491
503
  process.chdir(prevCwd);
@@ -1400,6 +1412,17 @@ export const ALLOWLIST: Record<string, BuiltinTool> = {
1400
1412
  "this first, then memory_get the ids you need. " +
1401
1413
  "'full' (default) returns complete entries.",
1402
1414
  },
1415
+ token_budget: {
1416
+ type: 'integer',
1417
+ minimum: 1,
1418
+ description:
1419
+ 'Optional token budget. When set, entries are ' +
1420
+ 'rendered as one-line compact rows (id, type, ' +
1421
+ 'confidence, `line`) and the row set is hard-cut at ' +
1422
+ 'token_budget × 4 chars; omitted hits appear as a ' +
1423
+ 'top-level `truncation` hint naming a concrete next ' +
1424
+ 'step. Absent → the envelope is unchanged.',
1425
+ },
1403
1426
  },
1404
1427
  required: ['types'],
1405
1428
  additionalProperties: false,
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Ranking-lift measurement for the hand-rolled lexical index
4
+ * (road-to-retrieval-substrate-hardening B2, deterministic + free).
5
+ *
6
+ * The gate on shipping the index (roadmap Phase 2): it must measurably rank
7
+ * BETTER than the current `_score` bucket scorer, else honest-null. This
8
+ * script runs both scorers over the SAME retrieval-precision corpus + store
9
+ * (internal/bench/second-brain/retrieval-store/) and reports, per task and in
10
+ * aggregate:
11
+ *
12
+ * - mean top tie-set size — how many entries share the TOP score. `_score`
13
+ * gives 0.8 to any keyword match, so the needed entry ties with its
14
+ * confusers (the "recalls but does not rank" finding, mean 3.3). A good
15
+ * ranker drives this toward 1.
16
+ * - precision@1 / @k — was the NEEDED entry actually first / in the top-k.
17
+ *
18
+ * No model, no spend: pure retrieval scoring. Exit 0 ok / 1 error / 2 usage.
19
+ *
20
+ * Usage: measure_lexical_ranking.ts [--write] [--format text|json]
21
+ * --write persist the report to internal/bench/reports/lexical-ranking.json
22
+ */
23
+ import * as fs from 'node:fs';
24
+ import * as path from 'node:path';
25
+ import { fileURLToPath } from 'node:url';
26
+
27
+ import * as YAML from 'yaml';
28
+
29
+ import { LexicalIndex } from './_lib/lexical_index.js';
30
+ import { _score, _setIntakeRoot, _setKnowledgeRoot, _setMemoryRoot, retrieve } from './memory_lookup.js';
31
+
32
+ const _HERE = fileURLToPath(import.meta.url);
33
+ export const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
34
+ export const STORE_DIR = path.join(REPO_ROOT, 'internal', 'bench', 'second-brain', 'retrieval-store');
35
+ export const CORPUS = path.join(STORE_DIR, 'retrieval-corpus.yml');
36
+ export const REPORT = path.join(REPO_ROOT, 'internal', 'bench', 'reports', 'lexical-ranking.json');
37
+
38
+ interface Task {
39
+ id: string;
40
+ query: string[];
41
+ needed: string;
42
+ }
43
+
44
+ interface TaskResult {
45
+ id: string;
46
+ needed: string;
47
+ baseline: { rank: number; tieSet: number };
48
+ index: { rank: number; tieSet: number };
49
+ }
50
+
51
+ interface RankingSummary {
52
+ tasks: number;
53
+ k: number;
54
+ baseline_mean_tie_set: number;
55
+ index_mean_tie_set: number;
56
+ baseline_precision_at_1: number;
57
+ index_precision_at_1: number;
58
+ baseline_precision_at_k: number;
59
+ index_precision_at_k: number;
60
+ }
61
+
62
+ /** Text an entry contributes to the index — the fields `_score` also reads. */
63
+ function entryText(entry: Record<string, unknown>): string {
64
+ const parts: string[] = [];
65
+ for (const f of ['path', 'key', 'symptom', 'feature', 'rule', 'body']) {
66
+ const v = entry[f];
67
+ if (typeof v === 'string') parts.push(v);
68
+ else if (Array.isArray(v)) for (const x of v) parts.push(String(x));
69
+ }
70
+ return parts.join(' | ');
71
+ }
72
+
73
+ /** Top tie-set size + rank of the needed id in a sorted (id, score) list. */
74
+ function diagnose(ranked: Array<{ id: string; score: number }>, needed: string): { rank: number; tieSet: number } {
75
+ if (ranked.length === 0) return { rank: -1, tieSet: 0 };
76
+ const top = ranked[0]?.score ?? 0;
77
+ const tieSet = ranked.filter((r) => r.score === top).length;
78
+ const rank = ranked.findIndex((r) => r.id === needed);
79
+ return { rank, tieSet };
80
+ }
81
+
82
+ export function measure(): { tasks: TaskResult[]; summary: RankingSummary } {
83
+ _setMemoryRoot(STORE_DIR);
84
+ _setKnowledgeRoot(path.join(STORE_DIR, 'knowledge-none'));
85
+ _setIntakeRoot(path.join(STORE_DIR, 'intake-none'));
86
+
87
+ const doc = YAML.parse(fs.readFileSync(CORPUS, 'utf-8')) as { type: string; k: number; tasks: Task[] };
88
+ const type = doc.type;
89
+ const k = doc.k;
90
+ const results: TaskResult[] = [];
91
+
92
+ for (const task of doc.tasks) {
93
+ // Shared universe: every store entry `_score` considers relevant.
94
+ const hits = retrieve([type], task.query, 100);
95
+
96
+ // Baseline — rank by the current bucket scorer, ties broken by the
97
+ // order `retrieve` already returns (store order), exactly as today.
98
+ const baselineRanked = hits.map((h) => ({ id: h.id, score: h.score }));
99
+ const baseline = diagnose(baselineRanked, task.needed);
100
+
101
+ // Index — BM25 over the same entries' text.
102
+ const index = new LexicalIndex(hits.map((h) => ({ id: h.id, text: entryText(h.entry) })));
103
+ const indexRanked = index.rank(task.query);
104
+ const idx = diagnose(indexRanked, task.needed);
105
+
106
+ results.push({ id: task.id, needed: task.needed, baseline, index: idx });
107
+ void _score; // referenced for provenance — retrieve() applies it internally
108
+ }
109
+
110
+ const n = results.length;
111
+ const mean = (sel: (r: TaskResult) => number): number =>
112
+ n === 0 ? 0 : Math.round((results.reduce((a, r) => a + sel(r), 0) / n) * 1000) / 1000;
113
+ const precAt = (sel: (r: TaskResult) => number, kk: number): number =>
114
+ n === 0 ? 0 : Math.round((results.filter((r) => sel(r) >= 0 && sel(r) < kk).length / n) * 1000) / 1000;
115
+
116
+ const summary = {
117
+ tasks: n,
118
+ k,
119
+ baseline_mean_tie_set: mean((r) => r.baseline.tieSet),
120
+ index_mean_tie_set: mean((r) => r.index.tieSet),
121
+ baseline_precision_at_1: precAt((r) => r.baseline.rank, 1),
122
+ index_precision_at_1: precAt((r) => r.index.rank, 1),
123
+ baseline_precision_at_k: precAt((r) => r.baseline.rank, k),
124
+ index_precision_at_k: precAt((r) => r.index.rank, k),
125
+ };
126
+ return { tasks: results, summary };
127
+ }
128
+
129
+ export function main(argv: string[]): number {
130
+ let write = false;
131
+ let format: 'text' | 'json' = 'text';
132
+ for (let i = 0; i < argv.length; i++) {
133
+ const a = argv[i] as string;
134
+ if (a === '--write') write = true;
135
+ else if (a === '--format') format = (argv[++i] as 'text' | 'json') ?? 'text';
136
+ else if (a === '-h' || a === '--help') {
137
+ process.stdout.write('usage: measure_lexical_ranking.ts [--write] [--format text|json]\n');
138
+ return 0;
139
+ } else {
140
+ process.stderr.write(`measure_lexical_ranking: unknown argument ${a}\n`);
141
+ return 2;
142
+ }
143
+ }
144
+
145
+ let out: { tasks: TaskResult[]; summary: RankingSummary };
146
+ try {
147
+ out = measure();
148
+ } catch (exc) {
149
+ process.stderr.write(`measure_lexical_ranking: error: ${String(exc)}\n`);
150
+ return 1;
151
+ }
152
+
153
+ if (write) {
154
+ fs.mkdirSync(path.dirname(REPORT), { recursive: true });
155
+ fs.writeFileSync(REPORT, JSON.stringify({ schema_version: 1, ...out }, null, 2) + '\n');
156
+ }
157
+ if (format === 'json') {
158
+ process.stdout.write(JSON.stringify(out, null, 2) + '\n');
159
+ return 0;
160
+ }
161
+ const s = out.summary;
162
+ process.stdout.write(
163
+ `lexical-ranking (${s.tasks} tasks, k=${s.k}):\n` +
164
+ ` mean top tie-set baseline=${s.baseline_mean_tie_set} index=${s.index_mean_tie_set}\n` +
165
+ ` precision@1 baseline=${s.baseline_precision_at_1} index=${s.index_precision_at_1}\n` +
166
+ ` precision@${s.k} baseline=${s.baseline_precision_at_k} index=${s.index_precision_at_k}\n`,
167
+ );
168
+ return 0;
169
+ }
170
+
171
+ if (fileURLToPath(import.meta.url) === path.resolve(process.argv[1] ?? '')) {
172
+ process.exit(main(process.argv.slice(2)));
173
+ }
Binary file
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Orchestration telemetry recorder — CLI.
3
+ *
4
+ * Appends ONE validated audit-log-v1 line (with an `orchestration` object) to
5
+ * the monthly audit log, from the counts the orchestrating agent has after an
6
+ * auto-dispatch. One command instead of hand-authored JSON — the reliable,
7
+ * lock-respecting (2026-06-30 no-hook decision) capture path.
8
+ *
9
+ * Usage:
10
+ * ./scripts-run src/scripts/orchestration_record \
11
+ * --spawn-count 1 --token-delta -72000 --provenance measured \
12
+ * --tier-chosen lite --tier-source inferred --task-class read-only-fanout \
13
+ * [--tiers sonnet,opus] [--wall-clock-ms 18500] [--dispatch-outcome DONE] \
14
+ * [--first-pass-success true|false] [--escalated true|false] \
15
+ * [--dir <audit-dir>] [--dry-run]
16
+ *
17
+ * Read by `src/scripts/orchestration_savings_report.ts`.
18
+ */
19
+
20
+ import * as crypto from 'node:crypto';
21
+ import * as fs from 'node:fs';
22
+ import * as path from 'node:path';
23
+ import { pathToFileURL } from 'node:url';
24
+
25
+ import {
26
+ buildOrchestrationLine,
27
+ type Provenance,
28
+ type TierChosen,
29
+ type TierSource,
30
+ type DispatchOutcome,
31
+ type VerifyMode,
32
+ type Band,
33
+ type LinePhase,
34
+ type LineOutcome,
35
+ type RecordInput,
36
+ } from './_lib/orchestration_record.js';
37
+
38
+ const DEFAULT_DIR = 'agents/runtime/state/audit';
39
+
40
+ type Flags = Record<string, string | boolean>;
41
+
42
+ function parseFlags(argv: string[]): Flags {
43
+ const flags: Flags = {};
44
+ for (let i = 0; i < argv.length; i++) {
45
+ const a = argv[i];
46
+ if (a === undefined || !a.startsWith('--')) continue;
47
+ const eq = a.indexOf('=');
48
+ if (eq !== -1) {
49
+ flags[a.slice(2, eq)] = a.slice(eq + 1);
50
+ continue;
51
+ }
52
+ const key = a.slice(2);
53
+ const next = argv[i + 1];
54
+ if (next !== undefined && !next.startsWith('--')) {
55
+ flags[key] = next;
56
+ i++;
57
+ } else {
58
+ flags[key] = true; // boolean flag (e.g. --dry-run)
59
+ }
60
+ }
61
+ return flags;
62
+ }
63
+
64
+ function str(flags: Flags, key: string): string | undefined {
65
+ const v = flags[key];
66
+ return typeof v === 'string' ? v : undefined;
67
+ }
68
+
69
+ /**
70
+ * Parse an explicit boolean flag value. `--key true|false` → boolean; a bare
71
+ * `--key` counts as `true`; any other value is passed through so the lib's
72
+ * non-boolean validation rejects it with a clear error.
73
+ */
74
+ function bool(flags: Flags, key: string): boolean | undefined {
75
+ const v = flags[key];
76
+ if (v === undefined) return undefined;
77
+ if (v === true || v === 'true') return true;
78
+ if (v === 'false') return false;
79
+ return v as unknown as boolean; // invalid string → caught by the lib's typeof validation
80
+ }
81
+
82
+ function int(flags: Flags, key: string): number | undefined {
83
+ const v = str(flags, key);
84
+ if (v === undefined) return undefined;
85
+ const n = Number(v);
86
+ return Number.isFinite(n) ? n : NaN; // NaN → caught by the lib's isInt validation
87
+ }
88
+
89
+ /** Deterministic content-hash id (contract allows "ULID or content hash"). */
90
+ function contentHashId(input: RecordInput): string {
91
+ const canonical = JSON.stringify({
92
+ ts: input.ts,
93
+ spawn_count: input.spawn_count,
94
+ token_delta: input.token_delta,
95
+ tier_chosen: input.tier_chosen ?? null,
96
+ task_class: input.task_class ?? null,
97
+ });
98
+ return crypto.createHash('sha256').update(canonical).digest('hex').slice(0, 26).toUpperCase();
99
+ }
100
+
101
+ export function main(argv: string[] = process.argv.slice(2)): number {
102
+ const flags = parseFlags(argv);
103
+ const dir = str(flags, 'dir') ?? DEFAULT_DIR;
104
+ const dryRun = flags['dry-run'] === true;
105
+ const ts = new Date().toISOString();
106
+
107
+ const partial: Omit<RecordInput, 'id'> = {
108
+ spawn_count: int(flags, 'spawn-count') ?? NaN,
109
+ token_delta: int(flags, 'token-delta') ?? NaN,
110
+ token_delta_provenance: str(flags, 'provenance') as Provenance | undefined,
111
+ tiers: str(flags, 'tiers')?.split(',').map((s) => s.trim()).filter(Boolean),
112
+ tier_chosen: str(flags, 'tier-chosen') as TierChosen | undefined,
113
+ tier_source: str(flags, 'tier-source') as TierSource | undefined,
114
+ task_class: str(flags, 'task-class'),
115
+ task_size_estimate: int(flags, 'task-size-estimate'),
116
+ wall_clock_ms: int(flags, 'wall-clock-ms'),
117
+ dispatch_tokens: int(flags, 'dispatch-tokens'),
118
+ session_tier: str(flags, 'session-tier'),
119
+ dispatch_outcome: str(flags, 'dispatch-outcome') as DispatchOutcome | undefined,
120
+ verify_mode: str(flags, 'verify-mode') as VerifyMode | undefined,
121
+ first_pass_success: bool(flags, 'first-pass-success'),
122
+ escalated: bool(flags, 'escalated'),
123
+ phase: str(flags, 'phase') as LinePhase | undefined,
124
+ outcome: str(flags, 'outcome') as LineOutcome | undefined,
125
+ confidence_band: str(flags, 'confidence-band') as Band | undefined,
126
+ risk_class: str(flags, 'risk-class') as Band | undefined,
127
+ persona: str(flags, 'persona'),
128
+ work_id: str(flags, 'work-id'),
129
+ ts,
130
+ };
131
+ const input: RecordInput = { ...partial, id: contentHashId({ ...partial, id: '' }) };
132
+
133
+ const { line, errors } = buildOrchestrationLine(input);
134
+ if (errors.length || line === null) {
135
+ process.stderr.write('orchestration_record: refused — invalid input:\n');
136
+ for (const e of errors) process.stderr.write(` - ${e}\n`);
137
+ return 1;
138
+ }
139
+
140
+ const serialized = JSON.stringify(line);
141
+ if (dryRun) {
142
+ process.stdout.write(serialized + '\n');
143
+ return 0;
144
+ }
145
+
146
+ fs.mkdirSync(dir, { recursive: true });
147
+ const file = path.join(dir, `${ts.slice(0, 7)}.jsonl`);
148
+ fs.appendFileSync(file, serialized + '\n', 'utf8');
149
+ process.stdout.write(`orchestration telemetry recorded → ${file} (id ${line.id})\n`);
150
+ return 0;
151
+ }
152
+
153
+ if (process.argv[1] !== undefined) {
154
+ const invokedUrl = pathToFileURL(path.resolve(process.argv[1])).href;
155
+ if (import.meta.url === invokedUrl) process.exit(main());
156
+ }
@@ -5,11 +5,18 @@
5
5
  * and prints an aggregate token-savings report. Read-only; no network, no
6
6
  * mutation. Handles a missing/empty audit dir gracefully.
7
7
  *
8
+ * QUALITY × COST PAIRING (council verdict): the savings figures never render
9
+ * without the quality columns (first_pass_success_rate / escalation_rate).
10
+ * When ≥ 20 lines in the window carry the quality fields the real rates
11
+ * render; below 20 the columns render as "n/a (n=<count>)" beside savings.
12
+ *
8
13
  * Usage:
9
- * ./scripts-run src/scripts/orchestration_savings_report [--dir <path>] [--format text|json]
14
+ * ./scripts-run src/scripts/orchestration_savings_report \
15
+ * [--dir <path>] [--format text|json] [--weights lite=1,medium=5,high=15]
10
16
  *
11
17
  * Default dir: agents/runtime/state/audit (the audit-log-v1 path where the
12
- * orchestration layer writes telemetry lines). See
18
+ * orchestration layer writes telemetry lines). `--weights` tunes the
19
+ * provider-neutral tier weights behind the MODELED cost-%. See
13
20
  * `src/agent-src/contexts/execution/orchestration-telemetry.md`.
14
21
  */
15
22
 
@@ -17,17 +24,30 @@ import * as fs from 'node:fs';
17
24
  import * as path from 'node:path';
18
25
  import { pathToFileURL } from 'node:url';
19
26
 
20
- import { aggregateOrchestrationSavings, type AuditLine, type SavingsReport } from './_lib/orchestration_savings.js';
27
+ import { aggregateOrchestrationSavings, DEFAULT_TIER_WEIGHTS, type AuditLine, type SavingsReport, type TierWeights } from './_lib/orchestration_savings.js';
21
28
 
22
29
  const DEFAULT_DIR = 'agents/runtime/state/audit';
23
30
 
24
31
  interface Options {
25
32
  dir: string;
26
33
  format: 'text' | 'json';
34
+ weights: TierWeights;
35
+ }
36
+
37
+ /** Parse `lite=1,medium=5,high=15` → weight map. Returns undefined if nothing valid parsed. */
38
+ function parseWeights(spec: string | undefined): TierWeights | undefined {
39
+ if (!spec) return undefined;
40
+ const w: TierWeights = {};
41
+ for (const pair of spec.split(',')) {
42
+ const [k, v] = pair.split('=');
43
+ const n = Number(v);
44
+ if (k && Number.isFinite(n)) w[k.trim()] = n;
45
+ }
46
+ return Object.keys(w).length ? w : undefined;
27
47
  }
28
48
 
29
49
  function parseArgs(argv: string[]): Options {
30
- const opts: Options = { dir: DEFAULT_DIR, format: 'text' };
50
+ const opts: Options = { dir: DEFAULT_DIR, format: 'text', weights: DEFAULT_TIER_WEIGHTS };
31
51
  for (let i = 0; i < argv.length; i++) {
32
52
  const a = argv[i];
33
53
  if (a === undefined) continue;
@@ -35,6 +55,8 @@ function parseArgs(argv: string[]): Options {
35
55
  else if (a.startsWith('--dir=')) opts.dir = a.slice('--dir='.length);
36
56
  else if (a === '--format') opts.format = (argv[++i] as Options['format']) ?? opts.format;
37
57
  else if (a.startsWith('--format=')) opts.format = a.slice('--format='.length) as Options['format'];
58
+ else if (a === '--weights') opts.weights = parseWeights(argv[++i]) ?? opts.weights;
59
+ else if (a.startsWith('--weights=')) opts.weights = parseWeights(a.slice('--weights='.length)) ?? opts.weights;
38
60
  }
39
61
  if (opts.format !== 'text' && opts.format !== 'json') opts.format = 'text';
40
62
  return opts;
@@ -60,13 +82,41 @@ export function readAuditLines(dir: string): AuditLine[] {
60
82
  return lines;
61
83
  }
62
84
 
63
- function renderText(r: SavingsReport, dir: string): string {
85
+ /** Dispatches carrying the quality fields at/above this render the real rates; below, "n/a (n=<count>)". */
86
+ export const QUALITY_GATE_MIN_LINES = 20;
87
+
88
+ function pct(v: number): string {
89
+ return `${(v * 100).toFixed(0)}%`;
90
+ }
91
+
92
+ /**
93
+ * Render the paired cost × quality block. Council verdict: savings NEVER render
94
+ * without the quality columns — at n ≥ QUALITY_GATE_MIN_LINES the real rates
95
+ * render; below, the quality columns render as "n/a (n=<count>)" alongside the
96
+ * savings figures. There is no savings-only render path.
97
+ */
98
+ function renderPairedCostQuality(r: SavingsReport): string[] {
99
+ const q = r.quality;
100
+ const gated = q.quality_lines >= QUALITY_GATE_MIN_LINES;
101
+ const fps = gated && q.first_pass_success_rate !== null
102
+ ? `${pct(q.first_pass_success_rate)} (n=${q.first_pass_lines})`
103
+ : `n/a (n=${q.first_pass_lines})`;
104
+ const esc = gated && q.escalation_rate !== null
105
+ ? `${pct(q.escalation_rate)} (n=${q.escalated_lines})`
106
+ : `n/a (n=${q.escalated_lines})`;
107
+ return [
108
+ ' cost × quality (paired — never savings alone):',
109
+ ` net token_delta: ${r.net_token_delta} (negative = net saved) | first_pass_success_rate: ${fps}`,
110
+ ` tokens saved: ${r.tokens_saved} · tokens added: ${r.tokens_added} | escalation_rate: ${esc}`,
111
+ ];
112
+ }
113
+
114
+ export function renderText(r: SavingsReport, dir: string): string {
64
115
  const out: string[] = [];
65
116
  out.push('Orchestration savings report');
66
117
  out.push(` source: ${dir}`);
67
118
  out.push(` dispatches: ${r.dispatches} (total spawns: ${r.total_spawns})`);
68
- out.push(` net token_delta: ${r.net_token_delta} (negative = net saved)`);
69
- out.push(` tokens saved: ${r.tokens_saved} tokens added: ${r.tokens_added}`);
119
+ out.push(...renderPairedCostQuality(r));
70
120
  out.push(` provenance: measured ${r.by_provenance.measured.dispatches} (Δ ${r.by_provenance.measured.net_token_delta}) · estimated ${r.by_provenance.estimated.dispatches} (Δ ${r.by_provenance.estimated.net_token_delta})`);
71
121
  out.push(` measured share: ${(r.measured_share * 100).toFixed(0)}%`);
72
122
  if (Object.keys(r.by_tier).length) {
@@ -77,6 +127,13 @@ function renderText(r: SavingsReport, dir: string): string {
77
127
  out.push(' by task_class (Δ tokens):');
78
128
  for (const [cls, d] of Object.entries(r.by_task_class).sort()) out.push(` ${cls}: ${d}`);
79
129
  }
130
+ const mc = r.modeled_cost;
131
+ if (mc.cost_reduction_pct !== null) {
132
+ out.push(` MODELED cost reduction: ${(mc.cost_reduction_pct * 100).toFixed(0)}% (over ${mc.covered_dispatches}/${r.dispatches} dispatch(es) with tier data)`);
133
+ out.push(` weights ${JSON.stringify(mc.weights)} · baseline ${mc.baseline_cost_units} → delegated ${mc.delegated_cost_units} cost-units · MODEL, not measured $`);
134
+ } else {
135
+ out.push(' MODELED cost reduction: n/a (needs dispatch_tokens + session_tier + tier_chosen on a dispatch)');
136
+ }
80
137
  out.push(' notes:');
81
138
  for (const n of r.notes) out.push(` - ${n}`);
82
139
  return out.join('\n');
@@ -85,7 +142,7 @@ function renderText(r: SavingsReport, dir: string): string {
85
142
  export function main(argv: string[] = process.argv.slice(2)): number {
86
143
  const opts = parseArgs(argv);
87
144
  const lines = readAuditLines(opts.dir);
88
- const report = aggregateOrchestrationSavings(lines);
145
+ const report = aggregateOrchestrationSavings(lines, opts.weights);
89
146
  if (opts.format === 'json') {
90
147
  process.stdout.write(JSON.stringify({ ...report, source: opts.dir }, null, 2) + '\n');
91
148
  } else {
@@ -13,6 +13,10 @@
13
13
  "pattern": "^[a-z][a-z0-9-]*$",
14
14
  "description": "Lowercase, hyphenated slug; must match the parent directory name."
15
15
  },
16
+ "user-invocable": {
17
+ "type": "boolean",
18
+ "description": "Claude Code Agent-Skills extension: false = the skill claims no /name slash command (only the model can load it). REQUIRED (false) when name equals a Claude Code built-in command or bundled skill (see scripts/_lib/claude_builtin_names.ts) so the suite never shadows the host's own /name. Other tools ignore the key."
19
+ },
16
20
  "description": {
17
21
  "type": "string",
18
22
  "minLength": 1,