@event4u/agent-config 8.10.0 → 8.11.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 (192) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +148 -0
  3. package/README.md +5 -5
  4. package/dist/agent-src/commands/challenge-me/vision.md +121 -32
  5. package/dist/agent-src/commands/challenge-me/with-docs.md +21 -8
  6. package/dist/agent-src/commands/commit/in-chunks.md +1 -1
  7. package/dist/agent-src/commands/commit.md +2 -1
  8. package/dist/agent-src/commands/cost/report.md +2 -1
  9. package/dist/agent-src/commands/feature/dev.md +4 -4
  10. package/dist/agent-src/commands/humanize.md +101 -0
  11. package/dist/agent-src/commands/jira-ticket.md +2 -2
  12. package/dist/agent-src/commands/memory/load.md +15 -0
  13. package/dist/agent-src/commands/optimize/project.md +12 -0
  14. package/dist/agent-src/commands/roadmap/create.md +12 -11
  15. package/dist/agent-src/commands/tests/create.md +1 -1
  16. package/dist/agent-src/commands/worktree/cleanup.md +22 -17
  17. package/dist/agent-src/commands/worktree/create.md +3 -1
  18. package/dist/agent-src/commands/worktree/status.md +12 -0
  19. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +66 -0
  20. package/dist/agent-src/contexts/execution/orchestration-benchmark-gate.md +1 -0
  21. package/dist/agent-src/contexts/execution/orchestration-telemetry.md +47 -3
  22. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +14 -0
  23. package/dist/agent-src/contexts/execution/roadmap-writing-source-derived.md +65 -0
  24. package/dist/agent-src/contexts/execution/subagent-modes-detail.md +84 -0
  25. package/dist/agent-src/rules/autonomous-execution.md +7 -37
  26. package/dist/agent-src/rules/broken-access-control.md +7 -45
  27. package/dist/agent-src/rules/code-comment-discipline.md +148 -0
  28. package/dist/agent-src/rules/context-hygiene.md +14 -0
  29. package/dist/agent-src/rules/delegation-policy.md +10 -8
  30. package/dist/agent-src/rules/design-fidelity.md +4 -0
  31. package/dist/agent-src/rules/git-history-discipline.md +4 -79
  32. package/dist/agent-src/rules/legal-safety-floor.md +11 -55
  33. package/dist/agent-src/rules/roadmap-progress-sync.md +5 -56
  34. package/dist/agent-src/rules/think-before-action.md +10 -0
  35. package/dist/agent-src/rules/verify-before-complete.md +12 -0
  36. package/dist/agent-src/skills/agent-docs-writing/SKILL.md +13 -12
  37. package/dist/agent-src/skills/ai-council/SKILL.md +19 -9
  38. package/dist/agent-src/skills/api-design/SKILL.md +7 -5
  39. package/dist/agent-src/skills/api-testing/SKILL.md +1 -1
  40. package/dist/agent-src/skills/authz-review/SKILL.md +59 -2
  41. package/dist/agent-src/skills/blast-radius-analyzer/SKILL.md +1 -1
  42. package/dist/agent-src/skills/bug-analyzer/SKILL.md +19 -16
  43. package/dist/agent-src/skills/code-refactoring/SKILL.md +9 -9
  44. package/dist/agent-src/skills/content-funnel-design/SKILL.md +2 -0
  45. package/dist/agent-src/skills/data-flow-mapper/SKILL.md +1 -1
  46. package/dist/agent-src/skills/defense-in-depth/SKILL.md +2 -2
  47. package/dist/agent-src/skills/dependency-upgrade/SKILL.md +7 -7
  48. package/dist/agent-src/skills/design-intelligence/SKILL.md +8 -8
  49. package/dist/agent-src/skills/design-review/SKILL.md +23 -2
  50. package/dist/agent-src/skills/doc-coauthoring/SKILL.md +2 -0
  51. package/dist/agent-src/skills/editorial-calendar/SKILL.md +2 -0
  52. package/dist/agent-src/skills/fe-design/SKILL.md +7 -9
  53. package/dist/agent-src/skills/git-workflow/SKILL.md +86 -0
  54. package/dist/agent-src/skills/humanizer/SKILL.md +147 -0
  55. package/dist/agent-src/skills/humanizer/data/patterns.md +190 -0
  56. package/dist/agent-src/skills/humanizer/evals/triggers.json +17 -0
  57. package/dist/agent-src/skills/laravel/SKILL.md +14 -1
  58. package/dist/agent-src/skills/launch-readiness/SKILL.md +20 -19
  59. package/dist/agent-src/skills/legal-practice-profile/SKILL.md +126 -0
  60. package/dist/agent-src/skills/logging-monitoring/SKILL.md +1 -1
  61. package/dist/agent-src/skills/mcp/SKILL.md +1 -1
  62. package/dist/agent-src/skills/memory-consolidation/SKILL.md +14 -1
  63. package/dist/agent-src/skills/migration-architect/SKILL.md +2 -1
  64. package/dist/agent-src/skills/performance/SKILL.md +1 -1
  65. package/dist/agent-src/skills/php-debugging/SKILL.md +17 -0
  66. package/dist/agent-src/skills/readme-writing/SKILL.md +2 -0
  67. package/dist/agent-src/skills/readme-writing-package/SKILL.md +2 -0
  68. package/dist/agent-src/skills/release-comms/SKILL.md +2 -0
  69. package/dist/agent-src/skills/roadmap-writing/SKILL.md +22 -51
  70. package/dist/agent-src/skills/security-audit/SKILL.md +6 -9
  71. package/dist/agent-src/skills/sentry-integration/SKILL.md +2 -2
  72. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +8 -0
  73. package/dist/agent-src/skills/source-discovery/SKILL.md +26 -24
  74. package/dist/agent-src/skills/subagent-orchestration/SKILL.md +55 -46
  75. package/dist/agent-src/skills/subagent-orchestration/evals/evals.json +42 -0
  76. package/dist/agent-src/skills/systematic-debugging/SKILL.md +10 -0
  77. package/dist/agent-src/skills/using-git-worktrees/SKILL.md +10 -0
  78. package/dist/agent-src/skills/worktree-lifecycle/SKILL.md +27 -20
  79. package/dist/agent-src/skills/worktree-lifecycle/evals/evals.json +38 -0
  80. package/dist/agent-src/templates/agent-settings.md +3 -2
  81. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +15 -1
  82. package/dist/agent-src/templates/roadmaps.md +14 -0
  83. package/dist/cli/commands/recordTriggerEval.js +4 -14
  84. package/dist/cli/commands/recordTriggerEval.js.map +1 -1
  85. package/dist/discovery/deprecation-report.md +1 -1
  86. package/dist/discovery/discovery-manifest.json +147 -78
  87. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  88. package/dist/discovery/discovery-manifest.summary.md +6 -6
  89. package/dist/discovery/orphan-report.md +1 -1
  90. package/dist/discovery/packs.json +13 -10
  91. package/dist/discovery/trust-report.md +3 -3
  92. package/dist/discovery/workspaces.json +14 -9
  93. package/dist/install/install.mjs +12 -7
  94. package/dist/mcp/registry-manifest.json +4 -4
  95. package/dist/router.json +1 -1
  96. package/dist/scripts/_lib/claude_settings_hooks.js +8 -0
  97. package/dist/scripts/_lib/claude_settings_hooks.js.map +1 -1
  98. package/dist/scripts/_lib/trigger_eval_floors.js +24 -0
  99. package/dist/scripts/_lib/trigger_eval_floors.js.map +1 -0
  100. package/dist/scripts/condense.js +12 -2
  101. package/dist/scripts/condense.js.map +1 -1
  102. package/dist/server/schemas/settings.js +5 -2
  103. package/dist/server/schemas/settings.js.map +1 -1
  104. package/docs/CLAIMS.md +24 -3
  105. package/docs/architecture.md +3 -3
  106. package/docs/benchmark.md +51 -0
  107. package/docs/catalog.md +8 -5
  108. package/docs/command-flows.md +2 -1
  109. package/docs/contracts/ai-council-config.md +92 -0
  110. package/docs/contracts/command-clusters.md +1 -0
  111. package/docs/contracts/evidence-based-pruning.md +1 -0
  112. package/docs/contracts/hook-architecture-v1.md +25 -0
  113. package/docs/contracts/kernel-membership.md +35 -0
  114. package/docs/contracts/measurement-baseline.md +1 -0
  115. package/docs/contracts/release-sizing.md +22 -0
  116. package/docs/contracts/write-engine.md +38 -1
  117. package/docs/customization.md +12 -0
  118. package/docs/decisions/ADR-002-kernel-bucket-overrides.md +11 -0
  119. package/docs/decisions/ADR-003-flat-cluster-subs-and-colon-syntax.md +11 -0
  120. package/docs/decisions/ADR-006-skill-tools-python-pilot.md +1 -1
  121. package/docs/decisions/ADR-028-root-layout.md +1 -1
  122. package/docs/decisions/ADR-038-canonical-settings-path.md +10 -0
  123. package/docs/decisions/ADR-092-defer-command-tier-alias-removal.md +29 -5
  124. package/docs/decisions/ADR-093-ai-council-config-user-global.md +1 -1
  125. package/docs/decisions/ADR-100-global-knowledge-card-sharing.md +7 -0
  126. package/docs/decisions/ADR-103-global-knowledge-default-off-until-measured.md +2 -2
  127. package/docs/decisions/ADR-105-automatic-subagent-orchestration.md +1 -1
  128. package/docs/decisions/ADR-114-commit-policy-iron-law-override.md +11 -0
  129. package/docs/decisions/ADR-116-memory-tripwire-activation-path.md +11 -1
  130. package/docs/decisions/ADR-117-subagents-auto-default-on.md +2 -1
  131. package/docs/decisions/ADR-118-loop-engineering-boundaries.md +162 -0
  132. package/docs/decisions/ADR-119-global-knowledge-default-on.md +104 -0
  133. package/docs/decisions/ADR-200-python-to-typescript-migration.md +11 -1
  134. package/docs/decisions/ADR-rule-kernel-and-router.md +11 -0
  135. package/docs/decisions/INDEX.md +11 -9
  136. package/docs/design/cross-model-parity-eval.md +82 -0
  137. package/docs/design/length-neutral-judge-rerun.md +92 -0
  138. package/docs/featured-skills.md +2 -2
  139. package/docs/getting-started-by-role.md +1 -1
  140. package/docs/getting-started.md +2 -2
  141. package/docs/governance-advantage.md +1 -1
  142. package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +80 -5
  143. package/docs/guidelines/code-clarity.md +106 -1
  144. package/docs/guidelines/design-antipatterns.md +20 -0
  145. package/docs/guidelines/php/php-coding-patterns.md +12 -2
  146. package/docs/proof.md +20 -14
  147. package/docs/wedge/production-validator/production-validator.md +27 -0
  148. package/package.json +1 -1
  149. package/src/config/agent-settings.template.yml +29 -9
  150. package/src/scripts/_cli/cmd_doctor.ts +34 -1
  151. package/src/scripts/_lib/auto_dispatch.corpus.test.ts +87 -0
  152. package/src/scripts/_lib/claude_settings_hooks.ts +8 -0
  153. package/src/scripts/_lib/knowledge_global.ts +6 -2
  154. package/src/scripts/_lib/knowledge_global_redaction.ts +42 -4
  155. package/src/scripts/_lib/lexical_index.ts +15 -2
  156. package/src/scripts/_lib/orchestration_record.ts +14 -0
  157. package/src/scripts/_lib/trigger_eval_floors.ts +31 -0
  158. package/src/scripts/ai_council/chairman.ts +90 -0
  159. package/src/scripts/ai_council/clients.ts +18 -3
  160. package/src/scripts/ai_council/config.ts +157 -0
  161. package/src/scripts/ai_council/debate_gates.ts +89 -0
  162. package/src/scripts/ai_council/orchestrator.ts +65 -2
  163. package/src/scripts/ai_council/prompts.ts +121 -11
  164. package/src/scripts/ai_council/replay.ts +17 -0
  165. package/src/scripts/ai_council/stance_tally.ts +184 -0
  166. package/src/scripts/ai_tells_rules.ts +433 -0
  167. package/src/scripts/bench_humanizer_eval.ts +240 -0
  168. package/src/scripts/build_discovery_manifest.ts +6 -0
  169. package/src/scripts/build_proof.ts +21 -9
  170. package/src/scripts/check_claims.ts +40 -0
  171. package/src/scripts/check_rule_invariants.ts +10 -3
  172. package/src/scripts/condense.ts +16 -2
  173. package/src/scripts/consumer_matrix.ts +41 -2
  174. package/src/scripts/council_cli.ts +147 -0
  175. package/src/scripts/detect_ai_tells.ts +312 -0
  176. package/src/scripts/install.ts +12 -0
  177. package/src/scripts/knowledge_global_cli.ts +1 -1
  178. package/src/scripts/lint_framework_leakage.ts +88 -7
  179. package/src/scripts/lint_framework_leakage_allowlist.json +59 -572
  180. package/src/scripts/lint_knowledge_scale.ts +30 -0
  181. package/src/scripts/lint_originality_shingles.ts +210 -0
  182. package/src/scripts/orchestration_record.ts +1 -0
  183. package/src/scripts/schemas/agent-settings.schema.json +11 -0
  184. package/src/scripts/self_review_gate.ts +41 -15
  185. package/src/scripts/skill_eval_coverage.ts +71 -3
  186. package/src/scripts/skill_linter.ts +1 -0
  187. package/src/scripts/trigger_eval_rotation.ts +344 -0
  188. package/src/scripts/update_counts.ts +5 -17
  189. package/src/scripts/worktree_cleanup_check.ts +263 -0
  190. package/src/server/schemas/settings.ts +8 -3
  191. package/src/scripts/analysis_freshness.ts +0 -362
  192. package/src/scripts/export_replay_corpus.ts +0 -208
@@ -1,362 +0,0 @@
1
- #!/usr/bin/env tsx
2
- /**
3
- * Project-analysis freshness loop — a cheap *heuristic* staleness signal.
4
- *
5
- * Ported from the retired Python `src/scripts/analysis_freshness.py` (ADR-200,
6
- * Python→TypeScript migration). The CLI contract is pinned — the
7
- * required mutually-exclusive group (`--stamp FILE | --stamp-all | --check FILE
8
- * | --check-all`), the argparse usage banner + error text (missing required
9
- * group → exit 2, conflicting args → exit 2, missing value → exit 2,
10
- * `-h`/`--help` → exit 0), the git probe (`git -C REPO_ROOT … check=False`,
11
- * stdout stripped), the freshness-header regex + path-token regex, the
12
- * staleness bands, `sorted(ANALYSIS_DIR.glob("*.md"))`, and byte-identical
13
- * stdout. snake_case kept.
14
- *
15
- * `project-analyzer` output under ``agents/evidence/analysis/*.md`` is rich but
16
- * has no signal for *"is this still current?"*. This script adds a one-line
17
- * freshness header and a probe that tells the agent whether a high-tier
18
- * re-analysis is likely worth it. File-first, no runtime.
19
- *
20
- * Header shape (top of each analysis file):
21
- *
22
- * <!-- analyzed: 2026-06-15 | commit: 57588489 | files: 4 -->
23
- *
24
- * Usage:
25
- * python3 src/scripts/analysis_freshness.py --stamp agents/evidence/analysis/foo.md
26
- * python3 src/scripts/analysis_freshness.py --stamp-all
27
- * python3 src/scripts/analysis_freshness.py --check agents/evidence/analysis/foo.md
28
- * python3 src/scripts/analysis_freshness.py --check-all
29
- */
30
-
31
- import { spawnSync } from 'node:child_process';
32
- import * as fs from 'node:fs';
33
- import * as path from 'node:path';
34
- import { fileURLToPath, pathToFileURL } from 'node:url';
35
-
36
- const _HERE = path.resolve(fileURLToPath(import.meta.url));
37
- // _HERE = Path(__file__).resolve().parent ; REPO_ROOT = _HERE.parent.parent
38
- const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
39
- // Mutable binding so tests can sandbox the scan target (mirrors the pytest
40
- // monkeypatch.setattr seam used by sibling lint twins).
41
- let ANALYSIS_DIR = path.join(REPO_ROOT, 'agents', 'evidence', 'analysis');
42
-
43
- function _setAnalysisDirForTest(p: string): void {
44
- ANALYSIS_DIR = p;
45
- }
46
-
47
- // _HEADER_RE = re.compile(
48
- // r"<!--\s*analyzed:\s*(?P<date>[\d-]+)\s*\|\s*commit:\s*(?P<commit>[0-9a-f]+)"
49
- // r"\s*\|\s*files:\s*(?P<files>\d+)\s*-->")
50
- const _HEADER_RE =
51
- /<!--[\s]*analyzed:[\s]*(?<date>[\d-]+)[\s]*\|[\s]*commit:[\s]*(?<commit>[0-9a-f]+)[\s]*\|[\s]*files:[\s]*(?<files>\d+)[\s]*-->/;
52
- // _PATH_RE = re.compile(r"`(?P<p>(?:src|docs|agents|scripts|tests)/[\w./-]+)`")
53
- const _PATH_RE = /`(?<p>(?:src|docs|agents|scripts|tests)\/[\w./-]+)`/g;
54
-
55
- // Heuristic staleness bands by count of changed files over the analyzed paths.
56
- const _AGING = 1;
57
- const _STALE = 8;
58
-
59
- /**
60
- * subprocess.run(["git", "-C", REPO_ROOT, *args], capture_output=True,
61
- * text=True, check=False).stdout.strip()
62
- *
63
- * Python `str.strip()` removes leading + trailing whitespace; `text=True`
64
- * decodes stdout as UTF-8. On a non-zero exit, stdout is still returned
65
- * (check=False), and a failure to spawn yields "" (mirrors empty stdout).
66
- */
67
- function _git(...args: string[]): string {
68
- const res = spawnSync('git', ['-C', REPO_ROOT, ...args], { encoding: 'utf8' });
69
- const out = res.stdout ?? '';
70
- return _pyStrip(out);
71
- }
72
-
73
- /** Python str.strip() — strips the ASCII + Unicode whitespace set Python uses. */
74
- function _pyStrip(s: string): string {
75
- // Python str.strip() default whitespace: spaces, \t \n \r \v \f and a set
76
- // of Unicode whitespace. For git output the ASCII set is sufficient; mirror
77
- // \s plus the explicit C0 set Python treats as whitespace.
78
- return s.replace(/^[\s… ]+/, '').replace(/[\s… ]+$/, '');
79
- }
80
-
81
- function _head_short(): string {
82
- return _git('rev-parse', '--short', 'HEAD') || 'unknown';
83
- }
84
-
85
- /** Best-effort: repo paths the doc cites (deduped, existing only). */
86
- function _referenced_paths(text: string): string[] {
87
- const seen: string[] = [];
88
- _PATH_RE.lastIndex = 0;
89
- let m: RegExpExecArray | null;
90
- while ((m = _PATH_RE.exec(text)) !== null) {
91
- const p = m.groups?.p as string;
92
- if (!seen.includes(p) && _exists(path.join(REPO_ROOT, p))) {
93
- seen.push(p);
94
- }
95
- }
96
- return seen;
97
- }
98
-
99
- function _exists(p: string): boolean {
100
- try {
101
- fs.statSync(p);
102
- return true;
103
- } catch {
104
- return false;
105
- }
106
- }
107
-
108
- function cmd_stamp(p: string): number {
109
- const text = fs.readFileSync(p, 'utf-8');
110
- const paths = _referenced_paths(text);
111
- // Backfill the analyzed date from the file's own last commit (honest for
112
- // retro-stamps); go-forward stamps land on today via a fresh re-stamp.
113
- const date = _git('log', '-1', '--format=%cs', '--', p) || _git('log', '-1', '--format=%cs');
114
- const header = `<!-- analyzed: ${date} | commit: ${_head_short()} | files: ${paths.length} -->`;
115
- const body = _HEADER_RE.test(text)
116
- ? _lstripNewlines(_subFirst(_HEADER_RE, text, ''))
117
- : text;
118
- fs.writeFileSync(p, header + '\n' + body, 'utf-8');
119
- process.stdout.write(` stamped ${_relToRoot(p)} → ${header}\n`);
120
- return 0;
121
- }
122
-
123
- /** re.sub(pattern, "", text, count=1) — replace first match only. */
124
- function _subFirst(re: RegExp, text: string, repl: string): string {
125
- const m = re.exec(text);
126
- if (!m) {
127
- return text;
128
- }
129
- return text.slice(0, m.index) + repl + text.slice(m.index + m[0].length);
130
- }
131
-
132
- /** str.lstrip("\n") — strip leading newlines only. */
133
- function _lstripNewlines(s: string): string {
134
- return s.replace(/^\n+/, '');
135
- }
136
-
137
- function _relToRoot(p: string): string {
138
- return path.relative(REPO_ROOT, p).split(path.sep).join('/');
139
- }
140
-
141
- function cmd_check(p: string): number {
142
- const text = fs.readFileSync(p, 'utf-8');
143
- const m = _HEADER_RE.exec(text);
144
- if (!m) {
145
- process.stdout.write(` ⚠️ ${_relToRoot(p)} — no freshness header (run --stamp)\n`);
146
- return 0;
147
- }
148
- const commit = m.groups?.commit as string;
149
- const paths = _referenced_paths(text);
150
- const scope = paths.length ? paths : ['.'];
151
- let changed = _git('diff', '--name-only', `${commit}..HEAD`, '--', ...scope).split('\n');
152
- changed = changed.filter((c) => _pyStrip(c) !== '');
153
- const n = changed.length;
154
- const band =
155
- n === 0 ? 'fresh' : n < _STALE ? 'aging' : 'STALE — re-analysis likely worth it';
156
- let verdict: string;
157
- if (n >= _AGING) {
158
- verdict = n >= _STALE ? `⚠️ ${band}` : `· ${band}`;
159
- } else {
160
- verdict = '✅ fresh';
161
- }
162
- process.stdout.write(
163
- ` ${verdict} — ${_relToRoot(p)}: ${n} changed file(s) ` +
164
- `over ${scope.length} analyzed path(s) since ${commit}\n`,
165
- );
166
- return 0;
167
- }
168
-
169
- /** sorted(ANALYSIS_DIR.glob("*.md")). */
170
- function _iter_analysis(): string[] {
171
- let entries: fs.Dirent[];
172
- try {
173
- entries = fs.readdirSync(ANALYSIS_DIR, { withFileTypes: true });
174
- } catch {
175
- return [];
176
- }
177
- const out: string[] = [];
178
- for (const ent of entries) {
179
- // glob("*.md") matches files + dirs ending in .md, non-recursively.
180
- if (ent.name.endsWith('.md')) {
181
- out.push(path.join(ANALYSIS_DIR, ent.name));
182
- }
183
- }
184
- out.sort();
185
- return out;
186
- }
187
-
188
- // ---------------------------------------------------------------------------
189
- // argparse-faithful CLI
190
- // ---------------------------------------------------------------------------
191
-
192
- const _PROG = 'analysis_freshness.py';
193
- const _USAGE =
194
- `usage: ${_PROG} [-h]\n` +
195
- ` (--stamp FILE | --stamp-all | --check FILE | --check-all)\n`;
196
-
197
- interface Args {
198
- stamp: string | null;
199
- stamp_all: boolean;
200
- check: string | null;
201
- check_all: boolean;
202
- }
203
-
204
- class ArgError {
205
- constructor(public readonly code: number) {}
206
- }
207
-
208
- function _argError(msg: string): never {
209
- process.stderr.write(_USAGE + `${_PROG}: error: ${msg}\n`);
210
- throw new ArgError(2);
211
- }
212
-
213
- function _parseArgs(argv: readonly string[]): Args {
214
- // Mirror argparse's parse_args precedence for this parser:
215
- // 1. -h/--help → print + exit 0 (fires immediately on encounter).
216
- // 2. missing value (--stamp / --check with no following token) → exit 2.
217
- // 3. mutex conflict (two group members) → exit 2 (during parse).
218
- // 4. required-group-not-satisfied → exit 2 (end of parse_known_args).
219
- // 5. unrecognized extras → exit 2 (after parse_known_args returns,
220
- // only when 4 passed). Extras are collected, then reported together.
221
- let stamp: string | null = null;
222
- let stamp_all = false;
223
- let check: string | null = null;
224
- let check_all = false;
225
- let firstSeen: string | null = null;
226
- const extras: string[] = [];
227
-
228
- const conflict = (current: string): void => {
229
- if (firstSeen !== null && firstSeen !== current) {
230
- _argError(`argument ${current}: not allowed with argument ${firstSeen}`);
231
- }
232
- if (firstSeen === null) {
233
- firstSeen = current;
234
- }
235
- };
236
-
237
- let i = 0;
238
- while (i < argv.length) {
239
- const a = argv[i] as string;
240
- if (a === '-h' || a === '--help') {
241
- // argparse prints full help (usage + description + options) and
242
- // exits 0. Tests do NOT byte-compare help prose; print the usage
243
- // banner (deterministic head) and exit 0.
244
- process.stdout.write(_USAGE);
245
- throw new ArgError(0);
246
- }
247
- if (a === '--stamp') {
248
- const v = argv[i + 1];
249
- if (v === undefined) {
250
- _argError('argument --stamp: expected one argument');
251
- }
252
- conflict('--stamp');
253
- stamp = v as string;
254
- i += 2;
255
- continue;
256
- }
257
- if (a === '--check') {
258
- const v = argv[i + 1];
259
- if (v === undefined) {
260
- _argError('argument --check: expected one argument');
261
- }
262
- conflict('--check');
263
- check = v as string;
264
- i += 2;
265
- continue;
266
- }
267
- if (a === '--stamp-all') {
268
- conflict('--stamp-all');
269
- stamp_all = true;
270
- i += 1;
271
- continue;
272
- }
273
- if (a === '--check-all') {
274
- conflict('--check-all');
275
- check_all = true;
276
- i += 1;
277
- continue;
278
- }
279
- // Unknown token: collect, defer error to after the required-group check.
280
- extras.push(a);
281
- i += 1;
282
- }
283
-
284
- // 4. Required mutex group must be satisfied (fires before the extras check).
285
- if (firstSeen === null) {
286
- _argError('one of the arguments --stamp --stamp-all --check --check-all is required');
287
- }
288
-
289
- // 5. Now report any unrecognized extras (group was satisfied).
290
- if (extras.length) {
291
- _argError(`unrecognized arguments: ${extras.join(' ')}`);
292
- }
293
-
294
- return { stamp, stamp_all, check, check_all };
295
- }
296
-
297
- function main(argv?: readonly string[]): number {
298
- let args: Args;
299
- try {
300
- args = _parseArgs(argv ?? process.argv.slice(2));
301
- } catch (exc) {
302
- if (exc instanceof ArgError) {
303
- return exc.code;
304
- }
305
- throw exc;
306
- }
307
-
308
- if (args.stamp !== null) {
309
- return cmd_stamp(args.stamp);
310
- }
311
- if (args.check !== null) {
312
- return cmd_check(args.check);
313
- }
314
- if (args.stamp_all) {
315
- for (const p of _iter_analysis()) {
316
- cmd_stamp(p);
317
- }
318
- return 0;
319
- }
320
- if (args.check_all) {
321
- for (const p of _iter_analysis()) {
322
- cmd_check(p);
323
- }
324
- return 0;
325
- }
326
- return 0;
327
- }
328
-
329
- function _isCliEntry(): boolean {
330
- if (process.argv[1] === undefined) {
331
- return false;
332
- }
333
- const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
334
- if (import.meta.url === argvUrl) {
335
- return true;
336
- }
337
- // A symlinked invocation (e.g. via an installed `.augment/` projection,
338
- // or macOS /var → /private/var temp dirs) makes the raw URLs differ:
339
- // import.meta.url is the resolved real path while argv[1] keeps the
340
- // symlink path. Compare realpaths so the entry guard still fires
341
- // (without this the CLI silently no-ops when run through a symlink).
342
- try {
343
- const here = fs.realpathSync(fileURLToPath(import.meta.url));
344
- const argv = fs.realpathSync(path.resolve(process.argv[1]));
345
- return here === argv;
346
- } catch {
347
- return false;
348
- }
349
- }
350
-
351
- if (_isCliEntry() || process.argv[1] === _HERE) {
352
- process.exitCode = main();
353
- }
354
-
355
- export {
356
- REPO_ROOT,
357
- ANALYSIS_DIR,
358
- _setAnalysisDirForTest,
359
- cmd_stamp,
360
- cmd_check,
361
- main,
362
- };
@@ -1,208 +0,0 @@
1
- #!/usr/bin/env tsx
2
- /**
3
- * Field-corpus exporter for router-telemetry replays
4
- * (road-to-token-proof-and-story Phase 2).
5
- *
6
- * Reads one or more `.agent-chat-history` JSONL logs (schema: one JSON object
7
- * per line; user turns are `{"t": "user", "text": "..."}`), extracts real
8
- * prompts, applies a built-in redaction pass, and emits a
9
- * `router_telemetry`-compatible corpus (`prompts:` entries with
10
- * `{id, text, command}`), so the three/four replay arms run on
11
- * consumer-shaped traffic instead of synthetic tasks.
12
- *
13
- * PRIVACY GATE (operator, non-optional): the built-in redaction is a
14
- * mechanical first pass — emails, home paths, key-shaped tokens, long hex,
15
- * URLs with credentials. The exported file MUST be human-reviewed under the
16
- * low-impact-corpus privacy floor BEFORE it is stored, committed, or fed to
17
- * any report that leaves the machine. The tool prints this reminder and
18
- * defaults to a `.local.yaml` output name so an unreviewed export cannot be
19
- * committed silently (gitignored pattern).
20
- *
21
- * Usage:
22
- * ./scripts-run src/scripts/export_replay_corpus \
23
- * --history agents/runtime/.agent-chat-history \
24
- * [--history <other-repo>/agents/runtime/.agent-chat-history ...] \
25
- * [--out internal/bench/corpora/field-prompts.local.yaml] \
26
- * [--limit 200] [--min-chars 20]
27
- *
28
- * Exit codes: 0 written · 1 usage/file error.
29
- */
30
- import * as fs from 'node:fs';
31
- import * as path from 'node:path';
32
- import process from 'node:process';
33
- import { fileURLToPath, pathToFileURL } from 'node:url';
34
-
35
- const _HERE = fileURLToPath(import.meta.url);
36
- const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
37
- const DEFAULT_OUT = path.join(REPO_ROOT, 'internal/bench/corpora/field-prompts.local.yaml');
38
-
39
- export interface FieldPrompt {
40
- id: string;
41
- text: string;
42
- command: string | null;
43
- }
44
-
45
- const REDACTIONS: Array<[RegExp, string]> = [
46
- // emails
47
- [/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g, '<EMAIL>'],
48
- // API-key-shaped tokens (sk-…, ghp_…, xox…, AKIA…, long base64-ish runs)
49
- [/\b(?:sk-[A-Za-z0-9_-]{10,}|ghp_[A-Za-z0-9]{20,}|gho_[A-Za-z0-9]{20,}|xox[a-z]-[A-Za-z0-9-]{10,}|AKIA[A-Z0-9]{12,})\b/g, '<KEY>'],
50
- // credentials embedded in URLs
51
- [/(https?:\/\/)[^\s/@]+:[^\s/@]+@/g, '$1<CREDENTIALS>@'],
52
- // absolute home paths (macOS/Linux)
53
- [/(?:\/Users|\/home)\/[A-Za-z0-9._-]+/g, '<HOME>'],
54
- // long hex blobs (hashes, tokens)
55
- [/\b[a-f0-9]{32,}\b/gi, '<HEX>'],
56
- // IPv4
57
- [/\b(?:\d{1,3}\.){3}\d{1,3}\b/g, '<IP>'],
58
- ];
59
-
60
- /** Mechanical first-pass redaction — NOT a substitute for the operator review. */
61
- export function redact(text: string): string {
62
- let out = text;
63
- for (const [re, sub] of REDACTIONS) {
64
- out = out.replace(re, sub);
65
- }
66
- return out;
67
- }
68
-
69
- /** Extract user prompts from one .agent-chat-history JSONL file. */
70
- export function extract_prompts(jsonlText: string, minChars: number): string[] {
71
- const out: string[] = [];
72
- for (const line of jsonlText.split('\n')) {
73
- const trimmed = line.trim();
74
- if (!trimmed) continue;
75
- let obj: Record<string, unknown>;
76
- try {
77
- obj = JSON.parse(trimmed) as Record<string, unknown>;
78
- } catch {
79
- continue; // tolerate partial/corrupt lines — logs are append-only
80
- }
81
- if (obj['t'] !== 'user' || typeof obj['text'] !== 'string') continue;
82
- const text = (obj['text'] as string).trim();
83
- if (text.length < minChars) continue;
84
- out.push(text);
85
- }
86
- return out;
87
- }
88
-
89
- /** First `/token` of a prompt that looks like a slash command. */
90
- export function detect_command(text: string): string | null {
91
- const m = /^\/([a-z0-9:_-]+)/i.exec(text.trim());
92
- return m ? `/${(m[1] as string).toLowerCase()}` : null;
93
- }
94
-
95
- /** Build the corpus entries: redacted, de-duplicated, capped. */
96
- export function build_corpus(rawPrompts: string[], limit: number): FieldPrompt[] {
97
- const seen = new Set<string>();
98
- const out: FieldPrompt[] = [];
99
- for (const raw of rawPrompts) {
100
- const text = redact(raw);
101
- const key = text.slice(0, 200);
102
- if (seen.has(key)) continue;
103
- seen.add(key);
104
- out.push({
105
- id: `field-${String(out.length + 1).padStart(3, '0')}`,
106
- text,
107
- command: detect_command(text),
108
- });
109
- if (out.length >= limit) break;
110
- }
111
- return out;
112
- }
113
-
114
- function _yamlQuote(s: string): string {
115
- return JSON.stringify(s); // JSON string is valid YAML double-quoted scalar
116
- }
117
-
118
- export function to_yaml(prompts: FieldPrompt[]): string {
119
- const lines: string[] = [
120
- '# Field replay corpus — exported by export_replay_corpus.',
121
- '# PRIVACY: operator review REQUIRED before this file is stored/committed',
122
- '# (low-impact-corpus privacy floor). Redaction here is a mechanical first',
123
- '# pass only. Replay caveat: intent triggers are informational-only in',
124
- '# router_telemetry — replay UNDERCOUNTS intent-triggered rule loads.',
125
- 'prompts:',
126
- ];
127
- for (const p of prompts) {
128
- lines.push(` - id: ${p.id}`);
129
- lines.push(` text: ${_yamlQuote(p.text)}`);
130
- if (p.command) lines.push(` command: ${_yamlQuote(p.command)}`);
131
- }
132
- return lines.join('\n') + '\n';
133
- }
134
-
135
- export function main(argv: string[]): number {
136
- const histories: string[] = [];
137
- let out = DEFAULT_OUT;
138
- let limit = 200;
139
- let minChars = 20;
140
- for (let i = 0; i < argv.length; i += 1) {
141
- const a = argv[i] as string;
142
- if (a === '--history') histories.push(String(argv[(i += 1)] ?? ''));
143
- else if (a === '--out') out = path.resolve(String(argv[(i += 1)] ?? ''));
144
- else if (a === '--limit') limit = Number(argv[(i += 1)]);
145
- else if (a === '--min-chars') minChars = Number(argv[(i += 1)]);
146
- else if (a === '-h' || a === '--help') {
147
- process.stdout.write(
148
- 'usage: export_replay_corpus --history <jsonl> [--history ...] [--out FILE] [--limit N] [--min-chars N]\n',
149
- );
150
- return 0;
151
- } else {
152
- process.stderr.write(`error: unknown argument: ${a}\n`);
153
- return 1;
154
- }
155
- }
156
- if (histories.length === 0 || !Number.isFinite(limit) || limit <= 0) {
157
- process.stderr.write('error: at least one --history <path> is required\n');
158
- return 1;
159
- }
160
- const raw: string[] = [];
161
- for (const h of histories) {
162
- let text: string;
163
- try {
164
- text = fs.readFileSync(h, 'utf-8');
165
- } catch (e) {
166
- process.stderr.write(`error: cannot read ${h}: ${(e as Error).message}\n`);
167
- return 1;
168
- }
169
- raw.push(...extract_prompts(text, minChars));
170
- }
171
- const corpus = build_corpus(raw, limit);
172
- fs.mkdirSync(path.dirname(out), { recursive: true });
173
- fs.writeFileSync(out, to_yaml(corpus), 'utf-8');
174
- process.stdout.write(
175
- `wrote ${corpus.length} prompt(s) → ${path.relative(REPO_ROOT, out)}\n` +
176
- '⚠️ PRIVACY GATE: operator review required before this corpus is stored,\n' +
177
- ' committed, or used in any report (low-impact-corpus privacy floor).\n',
178
- );
179
- return 0;
180
- }
181
-
182
- function _isCliEntry(): boolean {
183
- if (process.argv[1] === undefined) {
184
- return false;
185
- }
186
- const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
187
- if (import.meta.url === argvUrl) {
188
- return true;
189
- }
190
- // A symlinked invocation (e.g. via an installed `.augment/` projection,
191
- // or macOS /var → /private/var temp dirs) makes the raw URLs differ:
192
- // import.meta.url is the resolved real path while argv[1] keeps the
193
- // symlink path. Compare realpaths so the entry guard still fires
194
- // (without this the CLI silently no-ops when run through a symlink).
195
- try {
196
- const here = fs.realpathSync(fileURLToPath(import.meta.url));
197
- const argv = fs.realpathSync(path.resolve(process.argv[1]));
198
- return here === argv;
199
- } catch {
200
- return false;
201
- }
202
- }
203
-
204
- const _IS_MAIN =
205
- _isCliEntry();
206
- if (_IS_MAIN) {
207
- process.exit(main(process.argv.slice(2)));
208
- }