@event4u/agent-config 8.3.0 → 8.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/.claude-plugin/marketplace.json +4 -426
  2. package/.claude-plugin/skills/install-agent-config/SKILL.md +32 -0
  3. package/CHANGELOG.md +25 -0
  4. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  5. package/dist/cli/registry.js +1 -0
  6. package/dist/cli/registry.js.map +1 -1
  7. package/dist/discovery/deprecation-report.md +1 -1
  8. package/dist/discovery/discovery-manifest.json +1 -1
  9. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  10. package/dist/discovery/discovery-manifest.summary.md +1 -1
  11. package/dist/discovery/orphan-report.md +1 -1
  12. package/dist/discovery/packs.json +1 -1
  13. package/dist/discovery/trust-report.md +1 -1
  14. package/dist/discovery/workspaces.json +1 -1
  15. package/dist/mcp/registry-manifest.json +1 -1
  16. package/docs/contracts/surface-matrix.md +103 -0
  17. package/package.json +4 -1
  18. package/src/agent-src/README.md +64 -0
  19. package/src/agent-src/scripts/archive_completed_roadmaps.ts +497 -0
  20. package/src/agent-src/scripts/scan-seeder-violations.php +145 -0
  21. package/src/agent-src/scripts/update_roadmap_progress.ts +1087 -0
  22. package/src/agent-src/templates/AGENTS.md +34 -0
  23. package/src/agent-src/templates/scripts/README.md +195 -0
  24. package/src/agent-src/templates/scripts/check_memory.ts +662 -0
  25. package/src/agent-src/templates/scripts/check_memory_proposal.ts +370 -0
  26. package/src/agent-src/templates/scripts/historical-bug-patterns.example.yml +84 -0
  27. package/src/agent-src/templates/scripts/implement_ticket/__main__.ts +27 -0
  28. package/src/agent-src/templates/scripts/memory_hash.ts +355 -0
  29. package/src/agent-src/templates/scripts/memory_lookup.ts +1089 -0
  30. package/src/agent-src/templates/scripts/memory_report.ts +868 -0
  31. package/src/agent-src/templates/scripts/memory_signal.ts +444 -0
  32. package/src/agent-src/templates/scripts/memory_status.ts +215 -0
  33. package/src/agent-src/templates/scripts/ownership-map.example.yml +87 -0
  34. package/src/agent-src/templates/scripts/pr-risk-config.example.yml +76 -0
  35. package/src/agent-src/templates/scripts/pr_review_routing.ts +544 -0
  36. package/src/agent-src/templates/scripts/pr_risk_review.ts +0 -0
  37. package/src/agent-src/templates/scripts/telemetry/aggregator.ts +0 -0
  38. package/src/agent-src/templates/scripts/telemetry/boundary.ts +164 -0
  39. package/src/agent-src/templates/scripts/telemetry/engagement.ts +479 -0
  40. package/src/agent-src/templates/scripts/telemetry/report_renderer.ts +394 -0
  41. package/src/agent-src/templates/scripts/telemetry/settings.ts +210 -0
  42. package/src/agent-src/templates/scripts/telemetry_record.ts +276 -0
  43. package/src/agent-src/templates/scripts/telemetry_report.ts +211 -0
  44. package/src/agent-src/templates/scripts/telemetry_status.ts +333 -0
  45. package/src/agent-src/templates/scripts/tier_usage_report.ts +619 -0
  46. package/src/agent-src/templates/scripts/work_engine/__main__.ts +14 -0
  47. package/src/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +1261 -0
  48. package/src/agent-src/templates/scripts/work_engine/_lib/user_global_paths.ts +329 -0
  49. package/src/agent-src/templates/scripts/work_engine/cli.ts +206 -0
  50. package/src/agent-src/templates/scripts/work_engine/cli_args.ts +249 -0
  51. package/src/agent-src/templates/scripts/work_engine/delivery_state.ts +225 -0
  52. package/src/agent-src/templates/scripts/work_engine/directives/backend/analyze.ts +125 -0
  53. package/src/agent-src/templates/scripts/work_engine/directives/backend/implement.ts +189 -0
  54. package/src/agent-src/templates/scripts/work_engine/directives/backend/index.ts +94 -0
  55. package/src/agent-src/templates/scripts/work_engine/directives/backend/memory.ts +193 -0
  56. package/src/agent-src/templates/scripts/work_engine/directives/backend/plan.ts +267 -0
  57. package/src/agent-src/templates/scripts/work_engine/directives/backend/refine.ts +518 -0
  58. package/src/agent-src/templates/scripts/work_engine/directives/backend/report.ts +379 -0
  59. package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +268 -0
  60. package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +258 -0
  61. package/src/agent-src/templates/scripts/work_engine/directives/index.ts +32 -0
  62. package/src/agent-src/templates/scripts/work_engine/directives/mixed/contract.ts +243 -0
  63. package/src/agent-src/templates/scripts/work_engine/directives/mixed/index.ts +108 -0
  64. package/src/agent-src/templates/scripts/work_engine/directives/mixed/stitch.ts +259 -0
  65. package/src/agent-src/templates/scripts/work_engine/directives/mixed/ui.ts +216 -0
  66. package/src/agent-src/templates/scripts/work_engine/directives/ui/_passthrough.ts +40 -0
  67. package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +241 -0
  68. package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +216 -0
  69. package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +506 -0
  70. package/src/agent-src/templates/scripts/work_engine/directives/ui/design.ts +325 -0
  71. package/src/agent-src/templates/scripts/work_engine/directives/ui/index.ts +102 -0
  72. package/src/agent-src/templates/scripts/work_engine/directives/ui/polish.ts +462 -0
  73. package/src/agent-src/templates/scripts/work_engine/directives/ui/review.ts +474 -0
  74. package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +352 -0
  75. package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/_skipped.ts +33 -0
  76. package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/apply.ts +213 -0
  77. package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/index.ts +111 -0
  78. package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/refine.ts +126 -0
  79. package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/report.ts +112 -0
  80. package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/test.ts +164 -0
  81. package/src/agent-src/templates/scripts/work_engine/dispatcher.ts +515 -0
  82. package/src/agent-src/templates/scripts/work_engine/emitters.ts +119 -0
  83. package/src/agent-src/templates/scripts/work_engine/errors.ts +24 -0
  84. package/src/agent-src/templates/scripts/work_engine/hook_bootstrap.ts +104 -0
  85. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/_chat_history_base.ts +189 -0
  86. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_append.ts +41 -0
  87. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_halt_append.ts +89 -0
  88. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/decision_gate.ts +193 -0
  89. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/decision_trace.ts +304 -0
  90. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/directive_set_guard.ts +110 -0
  91. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/halt_surface_audit.ts +118 -0
  92. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/index.ts +17 -0
  93. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/memory_visibility.ts +161 -0
  94. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/state_shape_validation.ts +45 -0
  95. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/trace.ts +134 -0
  96. package/src/agent-src/templates/scripts/work_engine/hooks/context.ts +94 -0
  97. package/src/agent-src/templates/scripts/work_engine/hooks/events.ts +58 -0
  98. package/src/agent-src/templates/scripts/work_engine/hooks/exceptions.ts +85 -0
  99. package/src/agent-src/templates/scripts/work_engine/hooks/index.ts +27 -0
  100. package/src/agent-src/templates/scripts/work_engine/hooks/registry.ts +66 -0
  101. package/src/agent-src/templates/scripts/work_engine/hooks/runner.ts +90 -0
  102. package/src/agent-src/templates/scripts/work_engine/hooks/settings.ts +260 -0
  103. package/src/agent-src/templates/scripts/work_engine/input_builders.ts +260 -0
  104. package/src/agent-src/templates/scripts/work_engine/intent/classify.ts +466 -0
  105. package/src/agent-src/templates/scripts/work_engine/migration/v0_to_v1.ts +531 -0
  106. package/src/agent-src/templates/scripts/work_engine/orchestration.ts +366 -0
  107. package/src/agent-src/templates/scripts/work_engine/persona_policy.ts +97 -0
  108. package/src/agent-src/templates/scripts/work_engine/resolvers/diff.ts +135 -0
  109. package/src/agent-src/templates/scripts/work_engine/resolvers/file.ts +175 -0
  110. package/src/agent-src/templates/scripts/work_engine/resolvers/prompt.ts +115 -0
  111. package/src/agent-src/templates/scripts/work_engine/scoring/confidence.ts +415 -0
  112. package/src/agent-src/templates/scripts/work_engine/scoring/decision_engine.ts +466 -0
  113. package/src/agent-src/templates/scripts/work_engine/scoring/decision_trace.ts +298 -0
  114. package/src/agent-src/templates/scripts/work_engine/scoring/memory_visibility.ts +444 -0
  115. package/src/agent-src/templates/scripts/work_engine/stack/detect.ts +252 -0
  116. package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +745 -0
  117. package/src/agent-src/templates/scripts/work_engine/state.ts +1151 -0
  118. package/src/agent-src/templates/scripts/work_engine/state_io.ts +413 -0
  119. package/src/config/surface-matrix.yml +213 -0
  120. package/src/install/atomic.ts +111 -0
  121. package/src/install/conflict.ts +226 -0
  122. package/src/install/detect.ts +280 -0
  123. package/src/install/paths.ts +90 -0
  124. package/src/install/plan.ts +205 -0
  125. package/src/install/preflight.ts +210 -0
  126. package/src/install/selectedTools.ts +52 -0
  127. package/src/install/toolDetection.ts +120 -0
  128. package/src/install/txlog.ts +161 -0
  129. package/src/install/types.ts +175 -0
  130. package/src/install/wizard-plan.ts +216 -0
  131. package/src/scripts/_cli/cmd_converge.ts +399 -0
  132. package/src/scripts/_cli/cmd_doctor.ts +94 -0
  133. package/src/scripts/_cli/cmd_upgrade.ts +40 -16
  134. package/src/scripts/_dispatch.bash +13 -0
  135. package/src/scripts/condense.ts +67 -36
  136. package/src/scripts/hook_manifest.yaml +16 -7
  137. package/src/scripts/lint_marketplace.ts +54 -56
  138. package/src/scripts/lint_surface_matrix.ts +200 -0
  139. package/src/scripts/prepack-check.mjs +108 -5
  140. package/src/scripts/surface_probe_hook.ts +231 -0
@@ -0,0 +1,276 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `./agent-config telemetry:record` — append one engagement event.
4
+ *
5
+ * TypeScript twin of `telemetry_record.py` (ADR-200). Byte-for-byte parity on
6
+ * stdout / stderr / exit code and on the written JSONL line.
7
+ *
8
+ * Default-off: when `enabled: false` (default) the script exits 0 silently and
9
+ * performs zero file IO.
10
+ *
11
+ * Exit codes:
12
+ * 0 success or disabled (silent)
13
+ * 1 schema-validation failure
14
+ * 2 IO / settings parse error
15
+ */
16
+ import * as fs from 'node:fs';
17
+ import process from 'node:process';
18
+ import { fileURLToPath, pathToFileURL } from 'node:url';
19
+ import * as path from 'node:path';
20
+
21
+ import { record_event } from './telemetry/boundary.js';
22
+ import {
23
+ ALLOWED_OUTCOMES,
24
+ EngagementEvent,
25
+ EngagementSchemaError,
26
+ now_utc_iso,
27
+ } from './telemetry/engagement.js';
28
+ import { type TelemetrySettings, read_settings } from './telemetry/settings.js';
29
+
30
+ /** Mirror Python `raise SystemExit(msg)` — prints msg to stderr, exit 1. */
31
+ class SystemExitError extends Error {
32
+ constructor(public payload: string) {
33
+ super(payload);
34
+ }
35
+ }
36
+
37
+ /** Turn `["skills:a", "skills:b", "rules:c"]` into a kind→ids dict. */
38
+ function _parse_kv_list(values: string[]): Record<string, string[]> {
39
+ const out: Record<string, string[]> = {};
40
+ for (const raw of values) {
41
+ if (!raw.includes(':')) {
42
+ throw new SystemExitError(
43
+ `❌ --consulted/--applied must be 'kind:id', got ${_reprStr(raw)}`,
44
+ );
45
+ }
46
+ const idx = raw.indexOf(':');
47
+ const kind = raw.slice(0, idx).trim();
48
+ const art_id = raw.slice(idx + 1).trim();
49
+ if (!kind || !art_id) {
50
+ throw new SystemExitError(`❌ empty kind or id in ${_reprStr(raw)}`);
51
+ }
52
+ (out[kind] ??= []).push(art_id);
53
+ }
54
+ return out;
55
+ }
56
+
57
+ function _reprStr(s: string): string {
58
+ return `'${s}'`;
59
+ }
60
+
61
+ interface RecordArgs {
62
+ task_id: string;
63
+ boundary: string;
64
+ consulted: string[] | null;
65
+ applied: string[] | null;
66
+ outcome: string[] | null;
67
+ ts: string;
68
+ payload_file: string | null;
69
+ stdin: boolean;
70
+ settings: string;
71
+ force: boolean;
72
+ }
73
+
74
+ function _build_event_from_args(args: RecordArgs): EngagementEvent {
75
+ return new EngagementEvent({
76
+ ts: args.ts || now_utc_iso(),
77
+ task_id: args.task_id,
78
+ boundary_kind: args.boundary,
79
+ consulted: _parse_kv_list(args.consulted ?? []),
80
+ applied: _parse_kv_list(args.applied ?? []),
81
+ outcomes: args.outcome && args.outcome.length > 0 ? [...args.outcome] : null,
82
+ });
83
+ }
84
+
85
+ function _build_event_from_payload(raw: string): EngagementEvent {
86
+ let data: unknown;
87
+ try {
88
+ data = JSON.parse(raw);
89
+ } catch (exc) {
90
+ throw new SystemExitError(
91
+ `❌ payload is not valid JSON: ${exc instanceof Error ? exc.message : String(exc)}`,
92
+ );
93
+ }
94
+ if (typeof data !== 'object' || data === null || Array.isArray(data)) {
95
+ throw new SystemExitError('❌ payload must be a JSON object');
96
+ }
97
+ const d = data as Record<string, unknown>;
98
+ return new EngagementEvent({
99
+ ts: (d['ts'] as string) || now_utc_iso(),
100
+ task_id: (d['task_id'] as string) ?? '',
101
+ boundary_kind: (d['boundary_kind'] as string) ?? '',
102
+ consulted: _orEmpty(d['consulted']),
103
+ applied: _orEmpty(d['applied']),
104
+ outcomes: (d['outcomes'] as string[] | null | undefined) ?? null,
105
+ tokens_estimate: (d['tokens_estimate'] as Record<string, number> | null | undefined) ?? null,
106
+ });
107
+ }
108
+
109
+ function _orEmpty(v: unknown): Record<string, string[]> {
110
+ if (v && typeof v === 'object' && !Array.isArray(v)) {
111
+ return v as Record<string, string[]>;
112
+ }
113
+ return {};
114
+ }
115
+
116
+ class ArgError extends Error {}
117
+
118
+ function _parseArgs(argv: string[]): RecordArgs {
119
+ const a: RecordArgs = {
120
+ task_id: '',
121
+ boundary: 'task',
122
+ consulted: null,
123
+ applied: null,
124
+ outcome: null,
125
+ ts: '',
126
+ payload_file: null,
127
+ stdin: false,
128
+ settings: '.agent-settings.yml',
129
+ force: false,
130
+ };
131
+ const boundaryChoices = ['task', 'phase-step', 'tool-call'];
132
+ for (let i = 0; i < argv.length; i += 1) {
133
+ let tok = argv[i] as string;
134
+ let inlineVal: string | null = null;
135
+ const eq = tok.indexOf('=');
136
+ if (tok.startsWith('--') && eq !== -1) {
137
+ inlineVal = tok.slice(eq + 1);
138
+ tok = tok.slice(0, eq);
139
+ }
140
+ const next = (): string => {
141
+ if (inlineVal !== null) {
142
+ return inlineVal;
143
+ }
144
+ i += 1;
145
+ return argv[i] as string;
146
+ };
147
+ if (tok === '--task-id') a.task_id = next();
148
+ else if (tok === '--boundary') {
149
+ const v = next();
150
+ if (!boundaryChoices.includes(v)) {
151
+ throw new ArgError(
152
+ `argument --boundary: invalid choice: ${_reprStr(v)} `
153
+ + `(choose from ${boundaryChoices.map(_reprStr).join(', ')})`,
154
+ );
155
+ }
156
+ a.boundary = v;
157
+ } else if (tok === '--consulted') (a.consulted ??= []).push(next());
158
+ else if (tok === '--applied') (a.applied ??= []).push(next());
159
+ else if (tok === '--outcome') {
160
+ const v = next();
161
+ if (!(ALLOWED_OUTCOMES as readonly string[]).includes(v)) {
162
+ throw new ArgError(
163
+ `argument --outcome: invalid choice: ${_reprStr(v)} `
164
+ + `(choose from ${ALLOWED_OUTCOMES.map(_reprStr).join(', ')})`,
165
+ );
166
+ }
167
+ (a.outcome ??= []).push(v);
168
+ } else if (tok === '--ts') a.ts = next();
169
+ else if (tok === '--payload-file') a.payload_file = next();
170
+ else if (tok === '--stdin') a.stdin = true;
171
+ else if (tok === '--settings') a.settings = next();
172
+ else if (tok === '--force') a.force = true;
173
+ else throw new ArgError(`unrecognized arguments: ${argv[i]}`);
174
+ }
175
+ return a;
176
+ }
177
+
178
+ export function main(argv: string[] = process.argv.slice(2)): number {
179
+ let args: RecordArgs;
180
+ try {
181
+ args = _parseArgs(argv);
182
+ } catch (exc) {
183
+ if (exc instanceof ArgError) {
184
+ // argparse prints usage + error to stderr, exits 2.
185
+ process.stderr.write(`error: ${exc.message}\n`);
186
+ return 2;
187
+ }
188
+ throw exc;
189
+ }
190
+
191
+ let settings: TelemetrySettings;
192
+ try {
193
+ settings = read_settings(args.settings);
194
+ } catch (exc) {
195
+ process.stderr.write(`❌ cannot read settings: ${_osErr(exc)}\n`);
196
+ return 2;
197
+ }
198
+
199
+ if (!settings.enabled && !args.force) {
200
+ // Default-off: silent success, zero work attributable to telemetry.
201
+ return 0;
202
+ }
203
+
204
+ let event: EngagementEvent;
205
+ try {
206
+ if (args.payload_file) {
207
+ let raw: string;
208
+ try {
209
+ raw = fs.readFileSync(args.payload_file, 'utf-8');
210
+ } catch (exc) {
211
+ process.stderr.write(`❌ cannot read --payload-file: ${_osErr(exc)}\n`);
212
+ return 2;
213
+ }
214
+ event = _build_event_from_payload(raw);
215
+ } else if (args.stdin) {
216
+ event = _build_event_from_payload(fs.readFileSync(0, 'utf-8'));
217
+ } else {
218
+ if (!args.task_id) {
219
+ process.stderr.write('❌ --task-id required (or pass --payload-file/--stdin)\n');
220
+ return 1;
221
+ }
222
+ event = _build_event_from_args(args);
223
+ }
224
+ } catch (exc) {
225
+ if (exc instanceof SystemExitError) {
226
+ process.stderr.write(`${exc.payload}\n`);
227
+ return 1;
228
+ }
229
+ throw exc;
230
+ }
231
+
232
+ try {
233
+ record_event(settings.log_path, event);
234
+ } catch (exc) {
235
+ if (exc instanceof EngagementSchemaError) {
236
+ process.stderr.write(`❌ schema validation failed: ${exc.message}\n`);
237
+ return 1;
238
+ }
239
+ process.stderr.write(`❌ cannot write engagement log: ${_osErr(exc)}\n`);
240
+ return 2;
241
+ }
242
+
243
+ return 0;
244
+ }
245
+
246
+ function _osErr(exc: unknown): string {
247
+ return exc instanceof Error ? exc.message : String(exc);
248
+ }
249
+
250
+ function _isCliEntry(): boolean {
251
+ if (process.argv[1] === undefined) {
252
+ return false;
253
+ }
254
+ const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
255
+ if (import.meta.url === argvUrl) {
256
+ return true;
257
+ }
258
+ // A symlinked invocation (e.g. via an installed `.augment/` projection,
259
+ // or macOS /var → /private/var temp dirs) makes the raw URLs differ:
260
+ // import.meta.url is the resolved real path while argv[1] keeps the
261
+ // symlink path. Compare realpaths so the entry guard still fires
262
+ // (without this the CLI silently no-ops when run through a symlink).
263
+ try {
264
+ const here = fs.realpathSync(fileURLToPath(import.meta.url));
265
+ const argv = fs.realpathSync(path.resolve(process.argv[1]));
266
+ return here === argv;
267
+ } catch {
268
+ return false;
269
+ }
270
+ }
271
+
272
+ const _invokedDirectly =
273
+ _isCliEntry();
274
+ if (_invokedDirectly) {
275
+ process.exitCode = main();
276
+ }
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `./agent-config telemetry:report` — aggregate the engagement log.
4
+ *
5
+ * TypeScript twin of `telemetry_report.py` (ADR-200). Byte-for-byte parity on
6
+ * stdout / stderr / exit code for both markdown and JSON formats.
7
+ *
8
+ * Exit codes:
9
+ * 0 success (empty log → empty-but-valid report)
10
+ * 2 IO / settings parse error, unparseable --since, or redaction-validator
11
+ * failure on a row sourced from the log
12
+ */
13
+ import process from 'node:process';
14
+ import * as fs from 'node:fs';
15
+ import { fileURLToPath, pathToFileURL } from 'node:url';
16
+ import * as path from 'node:path';
17
+
18
+ import { aggregate } from './telemetry/aggregator.js';
19
+ import { EngagementSchemaError } from './telemetry/engagement.js';
20
+ import { render_json, render_markdown } from './telemetry/report_renderer.js';
21
+ import { read_settings } from './telemetry/settings.js';
22
+
23
+ const _DURATION_RE = /^\s*(\d+)\s*([dhm])\s*$/u;
24
+
25
+ /**
26
+ * Parse `30d` / `7d` / `24h` / `60m` into a UTC cutoff (epoch ms).
27
+ * Returns `[cutoff_or_null, human_label]`. `null` / `"all"` → no lower bound.
28
+ * Throws on malformed input so the CLI surfaces a clean error and exits 2.
29
+ */
30
+ function _parse_since(value: string | null): [number | null, string | null] {
31
+ if (value === null || value.trim().toLowerCase() === 'all') {
32
+ return [null, null];
33
+ }
34
+ const match = _DURATION_RE.exec(value);
35
+ if (!match) {
36
+ throw new ValueError(`--since must be <int>{d,h,m} or 'all', got ${_reprStr(value)}`);
37
+ }
38
+ const qty = Number(match[1]);
39
+ const unit = match[2] as string;
40
+ const ms: Record<string, number> = {
41
+ d: qty * 86400000,
42
+ h: qty * 3600000,
43
+ m: qty * 60000,
44
+ };
45
+ const cutoff = Date.now() - (ms[unit] as number);
46
+ const label = `last ${qty}${unit}`;
47
+ return [cutoff, label];
48
+ }
49
+
50
+ class ValueError extends Error {}
51
+
52
+ function _reprStr(s: string): string {
53
+ return `'${s}'`;
54
+ }
55
+
56
+ interface ReportArgs {
57
+ since: string;
58
+ top: number;
59
+ format: string;
60
+ log_path: string | null;
61
+ settings: string;
62
+ }
63
+
64
+ class ArgError extends Error {}
65
+
66
+ function _parseArgs(argv: string[]): ReportArgs {
67
+ const a: ReportArgs = {
68
+ since: '30d',
69
+ top: 20,
70
+ format: 'markdown',
71
+ log_path: null,
72
+ settings: '.agent-settings.yml',
73
+ };
74
+ const formatChoices = ['markdown', 'json'];
75
+ for (let i = 0; i < argv.length; i += 1) {
76
+ let tok = argv[i] as string;
77
+ let inlineVal: string | null = null;
78
+ const eq = tok.indexOf('=');
79
+ if (tok.startsWith('--') && eq !== -1) {
80
+ inlineVal = tok.slice(eq + 1);
81
+ tok = tok.slice(0, eq);
82
+ }
83
+ const next = (): string => {
84
+ if (inlineVal !== null) {
85
+ return inlineVal;
86
+ }
87
+ i += 1;
88
+ return argv[i] as string;
89
+ };
90
+ if (tok === '--since') a.since = next();
91
+ else if (tok === '--top') {
92
+ const v = next();
93
+ if (!/^[+-]?\d+$/u.test(v.trim())) {
94
+ throw new ArgError(`argument --top: invalid int value: ${_reprStr(v)}`);
95
+ }
96
+ a.top = parseInt(v, 10);
97
+ } else if (tok === '--format') {
98
+ const v = next();
99
+ if (!formatChoices.includes(v)) {
100
+ throw new ArgError(
101
+ `argument --format: invalid choice: ${_reprStr(v)} `
102
+ + `(choose from ${formatChoices.map(_reprStr).join(', ')})`,
103
+ );
104
+ }
105
+ a.format = v;
106
+ } else if (tok === '--log-path') a.log_path = next();
107
+ else if (tok === '--settings') a.settings = next();
108
+ else throw new ArgError(`unrecognized arguments: ${argv[i]}`);
109
+ }
110
+ return a;
111
+ }
112
+
113
+ export function main(argv: string[] = process.argv.slice(2)): number {
114
+ let args: ReportArgs;
115
+ try {
116
+ args = _parseArgs(argv);
117
+ } catch (exc) {
118
+ if (exc instanceof ArgError) {
119
+ process.stderr.write(`error: ${exc.message}\n`);
120
+ return 2;
121
+ }
122
+ throw exc;
123
+ }
124
+
125
+ let cutoff: number | null;
126
+ let since_label: string | null;
127
+ try {
128
+ [cutoff, since_label] = _parse_since(args.since);
129
+ } catch (exc) {
130
+ if (exc instanceof ValueError) {
131
+ process.stderr.write(`❌ ${exc.message}\n`);
132
+ return 2;
133
+ }
134
+ throw exc;
135
+ }
136
+
137
+ let log_path: string;
138
+ if (args.log_path !== null) {
139
+ log_path = args.log_path;
140
+ } else {
141
+ try {
142
+ const settings = read_settings(args.settings);
143
+ log_path = settings.log_path;
144
+ } catch (exc) {
145
+ process.stderr.write(`❌ cannot read settings: ${_osErr(exc)}\n`);
146
+ return 2;
147
+ }
148
+ }
149
+
150
+ let result;
151
+ try {
152
+ result = aggregate(log_path, { since: cutoff });
153
+ } catch (exc) {
154
+ process.stderr.write(`❌ cannot read log ${log_path}: ${_osErr(exc)}\n`);
155
+ return 2;
156
+ }
157
+
158
+ const top = args.top <= 0 ? null : args.top;
159
+ let rendered: string;
160
+ try {
161
+ if (args.format === 'json') {
162
+ rendered = render_json(result, { top, since_label });
163
+ } else {
164
+ rendered = render_markdown(result, { top, since_label });
165
+ }
166
+ } catch (exc) {
167
+ if (exc instanceof EngagementSchemaError) {
168
+ process.stderr.write(`❌ redaction validator refused report: ${exc.message}\n`);
169
+ return 2;
170
+ }
171
+ throw exc;
172
+ }
173
+ process.stdout.write(rendered);
174
+
175
+ if (result.skipped_lines) {
176
+ process.stderr.write(`⚠️ skipped ${result.skipped_lines} malformed line(s)\n`);
177
+ }
178
+ return 0;
179
+ }
180
+
181
+ function _osErr(exc: unknown): string {
182
+ return exc instanceof Error ? exc.message : String(exc);
183
+ }
184
+
185
+ function _isCliEntry(): boolean {
186
+ if (process.argv[1] === undefined) {
187
+ return false;
188
+ }
189
+ const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
190
+ if (import.meta.url === argvUrl) {
191
+ return true;
192
+ }
193
+ // A symlinked invocation (e.g. via an installed `.augment/` projection,
194
+ // or macOS /var → /private/var temp dirs) makes the raw URLs differ:
195
+ // import.meta.url is the resolved real path while argv[1] keeps the
196
+ // symlink path. Compare realpaths so the entry guard still fires
197
+ // (without this the CLI silently no-ops when run through a symlink).
198
+ try {
199
+ const here = fs.realpathSync(fileURLToPath(import.meta.url));
200
+ const argv = fs.realpathSync(path.resolve(process.argv[1]));
201
+ return here === argv;
202
+ } catch {
203
+ return false;
204
+ }
205
+ }
206
+
207
+ const _invokedDirectly =
208
+ _isCliEntry();
209
+ if (_invokedDirectly) {
210
+ process.exitCode = main();
211
+ }