@tangle-network/agent-app 0.60.215 → 0.60.216

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 (34) hide show
  1. package/dist/chunk-CD2LQXY6.js +306 -0
  2. package/dist/chunk-CD2LQXY6.js.map +1 -0
  3. package/dist/{chunk-KDPTXXOQ.js → chunk-J3N6RNMH.js} +456 -75
  4. package/dist/chunk-J3N6RNMH.js.map +1 -0
  5. package/dist/chunk-LRYY3OX7.js +105 -0
  6. package/dist/chunk-LRYY3OX7.js.map +1 -0
  7. package/dist/{chunk-LLO436QO.js → chunk-QM7RTTDY.js} +27 -86
  8. package/dist/chunk-QM7RTTDY.js.map +1 -0
  9. package/dist/{chunk-ZRLKB4KB.js → chunk-YWLZOPDS.js} +10 -11
  10. package/dist/{chunk-ZRLKB4KB.js.map → chunk-YWLZOPDS.js.map} +1 -1
  11. package/dist/launch-invariants/catalog.d.ts +16 -0
  12. package/dist/launch-invariants/cli.js +5 -2
  13. package/dist/launch-invariants/cli.js.map +1 -1
  14. package/dist/launch-invariants/index.d.ts +1 -1
  15. package/dist/launch-invariants/index.js +13 -5
  16. package/dist/launch-invariants/index.js.map +1 -1
  17. package/dist/launch-invariants/limit-alarms.d.ts +19 -1
  18. package/dist/launch-invariants/testing.d.ts +0 -3
  19. package/dist/launch-invariants/testing.js +5 -2
  20. package/dist/signoff/app-defaults.d.ts +65 -0
  21. package/dist/signoff/cli.js +71 -4
  22. package/dist/signoff/cli.js.map +1 -1
  23. package/dist/signoff/defaults-cli.d.ts +1 -0
  24. package/dist/signoff/index.js +2 -1
  25. package/dist/signoff/types.d.ts +6 -0
  26. package/dist/signoff/worker-defaults.d.ts +8 -0
  27. package/dist/turn-health/index.js +12 -291
  28. package/dist/turn-health/index.js.map +1 -1
  29. package/dist/turn-trace/index.js +20 -3
  30. package/dist/turn-trace/index.js.map +1 -1
  31. package/dist/turn-trace/otel.d.ts +11 -4
  32. package/package.json +1 -1
  33. package/dist/chunk-KDPTXXOQ.js.map +0 -1
  34. package/dist/chunk-LLO436QO.js.map +0 -1
@@ -0,0 +1,65 @@
1
+ /**
2
+ * The defaults an agent app inherits from agent-app, and whether this app has them.
3
+ *
4
+ * Each proven improvement ships as a default inside agent-app, so an app gets it by construction. This report reads
5
+ * an app's checkout and says which defaults it has, which it turned off, and which it replaced with a local copy.
6
+ * It is a report: it fails only on an explicit opt-out that gives no reason, so a gap stays visible without
7
+ * blocking a merge, and an exemption is a line in the app's `signoff.config.mjs` with the reason beside it.
8
+ *
9
+ * The rows are the outcomes Drew named on 2026-10-19: the turn trace on every turn with `first_text_ms`, the shared
10
+ * prewarm route, Worker placement beside the D1 primary, limit alarms wired in the server entry, and the Worker
11
+ * upload size against Cloudflare's limit. A row is read from the source, the Wrangler config and the lockfile; it
12
+ * never runs the app.
13
+ */
14
+ /** The name the built-in step reports under; a repo cannot declare a step with it. */
15
+ export declare const APP_DEFAULTS_STEP = "agent-app defaults";
16
+ /** Defaults an app may turn off with a reason. */
17
+ export declare const EXEMPTIBLE_DEFAULTS: readonly ['turn-trace', 'first-text', 'prewarm', 'placement', 'limit-alarms'];
18
+ type ExemptibleDefault = (typeof EXEMPTIBLE_DEFAULTS)[number];
19
+ /** An app's declared opt-outs: the default and why. An empty reason is a configuration error. */
20
+ export type AppDefaultsExemptions = Readonly<Partial<Record<ExemptibleDefault, string>>>;
21
+ type DefaultStatus = 'ok' | 'gap' | 'unknown' | 'exempt' | 'opted-out' | 'na';
22
+ interface DefaultRow {
23
+ readonly id: 'pin' | 'turn-trace' | 'first-text' | 'prewarm' | 'placement' | 'tool-count' | 'limit-alarms' | 'worker-bundle';
24
+ readonly label: string;
25
+ readonly status: DefaultStatus;
26
+ readonly detail: string;
27
+ }
28
+ interface AppDefaultsReport {
29
+ /** The package name. */
30
+ readonly app: string;
31
+ /** The package directory relative to the repo root. */
32
+ readonly dir: string;
33
+ /** The `@tangle-network/agent-app` version the package pins, or null when it is not a plain version. */
34
+ readonly pin: string | null;
35
+ readonly rows: readonly DefaultRow[];
36
+ /** Opt-outs that give no reason. These alone fail the step. */
37
+ readonly findings: readonly string[];
38
+ }
39
+ export interface AppDefaultsResult {
40
+ readonly apps: readonly AppDefaultsReport[];
41
+ readonly findings: readonly string[];
42
+ }
43
+ export interface AppDefaultsOptions {
44
+ /** Declared opt-outs per package name. Applied to every app when keyed by `*`. */
45
+ readonly exemptions?: AppDefaultsExemptions;
46
+ /** Measure the Worker upload with the app's own Wrangler (default true). Needs a built app. */
47
+ readonly measureBundle?: boolean;
48
+ /** The last turn trace Intelligence accepted per Intelligence project; see {@link readLastTurnExport}. */
49
+ readonly exports?: ReadonlyMap<string, LastTurnExport | null>;
50
+ }
51
+ /** The newest `agent_app.turn` root span Intelligence holds for a project. */
52
+ interface LastTurnExport {
53
+ readonly at: number;
54
+ readonly traceId: string;
55
+ readonly firstTextMs?: number;
56
+ }
57
+ /** Report each agent app in the checkout against the defaults. Reads only tracked files; measures the Worker upload with the app's own Wrangler. */
58
+ export declare function checkAppDefaults(repoRoot: string, options?: AppDefaultsOptions): AppDefaultsResult;
59
+ export declare function formatAppDefaults(result: AppDefaultsResult): string;
60
+ /**
61
+ * The newest turn Intelligence holds for each project the apps' sinks name, read with one platform key. A project the
62
+ * key's tenant does not own, or one the ingest has never accepted, reads as null; a failed request leaves it out.
63
+ */
64
+ export declare function collectLastExports(repoRoot: string, apiKey: string): Promise<Map<string, LastTurnExport | null>>;
65
+ export {};
@@ -1,19 +1,84 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ checkAppDefaults,
4
+ collectLastExports,
5
+ formatAppDefaults,
3
6
  formatSignoffLine,
4
7
  formatSignoffReport,
8
+ loadSignoffConfig,
5
9
  runShellAlignmentCli,
6
10
  runSignoff,
7
11
  runSurfaceLadderCli,
8
12
  runWorkspaceSurfaceCli
9
- } from "../chunk-KDPTXXOQ.js";
13
+ } from "../chunk-J3N6RNMH.js";
14
+ import "../chunk-LRYY3OX7.js";
10
15
  import {
11
16
  invokedAsScript
12
17
  } from "../chunk-C3CAYGGQ.js";
13
18
 
14
19
  // src/signoff/cli.ts
20
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
21
+ import { dirname as dirname2, resolve as resolve2 } from "path";
22
+
23
+ // src/signoff/defaults-cli.ts
15
24
  import { mkdirSync, writeFileSync } from "fs";
16
25
  import { dirname, resolve } from "path";
26
+ function defaultsUsage() {
27
+ return [
28
+ "Usage: agent-app-signoff defaults [repoDir] [options]",
29
+ "",
30
+ " --probe ask Intelligence for each app's last exported turn (needs TANGLE_INTELLIGENCE_API_KEY)",
31
+ " --no-bundle skip the Worker upload measure (it runs the app's own wrangler, so build first)",
32
+ " --json <path> write the rows"
33
+ ].join("\n");
34
+ }
35
+ async function runDefaultsCli(argv) {
36
+ let repoDir = process.cwd();
37
+ let probe = false;
38
+ let measureBundle = true;
39
+ let jsonPath;
40
+ for (let index = 0; index < argv.length; index += 1) {
41
+ const flag = argv[index];
42
+ if (flag === "--probe") probe = true;
43
+ else if (flag === "--no-bundle") measureBundle = false;
44
+ else if (flag === "--json") {
45
+ jsonPath = argv[index + 1];
46
+ index += 1;
47
+ if (jsonPath === void 0) return usageError("--json needs a path");
48
+ } else if (flag === "-h" || flag === "--help") {
49
+ process.stdout.write(`${defaultsUsage()}
50
+ `);
51
+ return 0;
52
+ } else if (flag.startsWith("-")) return usageError(`unknown option: ${flag}`);
53
+ else repoDir = resolve(flag);
54
+ }
55
+ let exemptions;
56
+ try {
57
+ exemptions = (await loadSignoffConfig({ repoRoot: repoDir })).config.agentApp?.exemptions;
58
+ } catch {
59
+ }
60
+ const apiKey = process.env.TANGLE_INTELLIGENCE_API_KEY?.trim();
61
+ if (probe && !apiKey) return usageError("--probe needs TANGLE_INTELLIGENCE_API_KEY");
62
+ const exports = probe && apiKey ? await collectLastExports(repoDir, apiKey) : void 0;
63
+ const result = checkAppDefaults(repoDir, { exemptions, measureBundle, exports });
64
+ if (jsonPath !== void 0) {
65
+ const abs = resolve(jsonPath);
66
+ mkdirSync(dirname(abs), { recursive: true });
67
+ writeFileSync(abs, `${JSON.stringify(result, null, 2)}
68
+ `);
69
+ }
70
+ process.stdout.write(formatAppDefaults(result));
71
+ return result.findings.length === 0 ? 0 : 1;
72
+ }
73
+ function usageError(message) {
74
+ process.stderr.write(`agent-app-signoff defaults: ${message}
75
+
76
+ ${defaultsUsage()}
77
+ `);
78
+ return 2;
79
+ }
80
+
81
+ // src/signoff/cli.ts
17
82
  function usage() {
18
83
  return [
19
84
  "Usage: agent-app-signoff [repoDir] [options]",
@@ -30,6 +95,7 @@ function usage() {
30
95
  " --quiet verdict only; no per-step progress",
31
96
  "",
32
97
  "Subcommands:",
98
+ " defaults report which agent-app defaults each app has (--help for options)",
33
99
  " shell-alignment measure header dividers against the rail in a browser (--help for options)",
34
100
  " surface-ladder measure card and field steps, field borders and card copy in a browser (--help for options)",
35
101
  " workspace-surface every page sits on one shared panel under an inset shell header (--help for options)"
@@ -136,15 +202,16 @@ function progressWriter() {
136
202
  };
137
203
  }
138
204
  async function runSignoffCli(argv) {
205
+ if (argv[0] === "defaults") return runDefaultsCli(argv.slice(1));
139
206
  if (argv[0] === "shell-alignment") return runShellAlignmentCli(argv.slice(1));
140
207
  if (argv[0] === "surface-ladder") return runSurfaceLadderCli(argv.slice(1));
141
208
  if (argv[0] === "workspace-surface") return runWorkspaceSurfaceCli(argv.slice(1));
142
209
  const args = parseArgs(argv);
143
210
  const report = await runSignoff({ ...args, onEvent: args.quiet ? void 0 : progressWriter() });
144
211
  if (args.jsonPath !== void 0) {
145
- const abs = resolve(args.jsonPath);
146
- mkdirSync(dirname(abs), { recursive: true });
147
- writeFileSync(abs, `${JSON.stringify(report, null, 2)}
212
+ const abs = resolve2(args.jsonPath);
213
+ mkdirSync2(dirname2(abs), { recursive: true });
214
+ writeFileSync2(abs, `${JSON.stringify(report, null, 2)}
148
215
  `);
149
216
  }
150
217
  process.stdout.write(`${args.quiet ? formatSignoffLine(report) : formatSignoffReport(report)}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/signoff/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `agent-app-signoff` — run the local sign-off gate and print the proof.\n *\n * Exits 0 only when every declared step passed in a pristine tree. Exit 1 is a\n * real failure; exit 2 is a usage or config error, so a script can tell \"your\n * code is broken\" from \"your gate is misconfigured\".\n *\n * agent-app-signoff # this repo, working tree, default config\n * agent-app-signoff --source head # exactly the commit that would merge\n * agent-app-signoff --seed 12345 # reproduce a previous run's orders\n * agent-app-signoff --keep-going # run every step, full picture\n * agent-app-signoff --json proof.json # machine-readable record\n * agent-app-signoff shell-alignment --base-url <url> --route <path> # header dividers meet the rail\n * agent-app-signoff surface-ladder --base-url <url> --route <path> # cards and fields step off their parent\n * agent-app-signoff workspace-surface --base-url <url> --route <path> # every page on the shared panel\n */\nimport { mkdirSync, writeFileSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport { invokedAsScript } from './invoked-as-script'\nimport { formatSignoffLine, formatSignoffReport } from './report'\nimport { runSignoff, type RunSignoffOptions } from './run'\nimport { runShellAlignmentCli } from './shell-alignment-cli'\nimport { runSurfaceLadderCli } from './surface-ladder-cli'\nimport { runWorkspaceSurfaceCli } from './workspace-surface-cli'\nimport type { SignoffEvent, SignoffSource } from './types'\n\ninterface ParsedArgs extends RunSignoffOptions {\n readonly jsonPath?: string\n readonly quiet?: boolean\n}\n\nfunction usage(): string {\n return [\n 'Usage: agent-app-signoff [repoDir] [options]',\n '',\n ' --source head|working-tree bytes to verify (default working-tree)',\n ' --config <path> config module (default signoff.config.mjs)',\n ' --seed <n> base seed; reproduces a previous run',\n ' --shuffle-runs <n> override every shuffled step\\'s run count',\n ' --max-parallel <n> cap concurrent steps',\n ' --cache-dir <path> clean trees + pristine stores live here',\n ' --keep-going run every step even after one fails',\n ' --keep-workspace leave the clean tree on disk to inspect',\n ' --json <path> write the machine-readable report',\n ' --quiet verdict only; no per-step progress',\n '',\n 'Subcommands:',\n ' shell-alignment measure header dividers against the rail in a browser (--help for options)',\n ' surface-ladder measure card and field steps, field borders and card copy in a browser (--help for options)',\n ' workspace-surface every page sits on one shared panel under an inset shell header (--help for options)',\n ].join('\\n')\n}\n\nfunction fail(message: string): never {\n process.stderr.write(`agent-app-signoff: ${message}\\n\\n${usage()}\\n`)\n process.exit(2)\n}\n\nfunction positiveInt(raw: string, flag: string): number {\n const value = Number.parseInt(raw, 10)\n if (!Number.isFinite(value) || value < 0) fail(`${flag} needs a non-negative integer, got \"${raw}\"`)\n return value\n}\n\nexport function parseArgs(argv: readonly string[]): ParsedArgs {\n const parsed: {\n repoDir?: string\n configPath?: string\n source?: SignoffSource\n seed?: number\n shuffleRuns?: number\n maxParallel?: number\n cacheDir?: string\n keepGoing?: boolean\n keepWorkspace?: boolean\n jsonPath?: string\n quiet?: boolean\n } = {}\n\n for (let index = 0; index < argv.length; index += 1) {\n const flag = argv[index] as string\n const take = (): string => {\n const value = argv[index + 1]\n if (value === undefined) fail(`${flag} needs a value`)\n index += 1\n return value\n }\n switch (flag) {\n case '--source': {\n const value = take()\n if (value !== 'head' && value !== 'working-tree') fail(`--source must be head or working-tree, got \"${value}\"`)\n parsed.source = value\n break\n }\n case '--config': parsed.configPath = take(); break\n case '--seed': parsed.seed = positiveInt(take(), '--seed'); break\n case '--shuffle-runs': parsed.shuffleRuns = positiveInt(take(), '--shuffle-runs'); break\n case '--max-parallel': parsed.maxParallel = positiveInt(take(), '--max-parallel'); break\n case '--cache-dir': parsed.cacheDir = take(); break\n case '--keep-going': parsed.keepGoing = true; break\n case '--keep-workspace': parsed.keepWorkspace = true; break\n case '--json': parsed.jsonPath = take(); break\n case '--quiet': parsed.quiet = true; break\n case '-h':\n case '--help':\n process.stdout.write(`${usage()}\\n`)\n process.exit(0)\n break\n default:\n if (flag.startsWith('-')) fail(`unknown option: ${flag}`)\n if (parsed.repoDir !== undefined) fail(`unexpected second directory: ${flag}`)\n parsed.repoDir = flag\n }\n }\n return parsed\n}\n\nfunction progressWriter(): (event: SignoffEvent) => void {\n return (event) => {\n switch (event.kind) {\n case 'tree':\n process.stderr.write(`· clean tree at ${event.path} (${event.head.slice(0, 12)}${event.dirty ? ' + working tree' : ''})\\n`)\n break\n case 'store':\n process.stderr.write(`· store ${event.cacheHit ? 'warm' : 'COLD'} ${event.cacheKey.slice(0, 16)}\\n`)\n break\n case 'install-start':\n process.stderr.write(`· ${event.command}\\n`)\n break\n case 'install-end':\n process.stderr.write(`· install exit ${event.exitCode} in ${event.durationMs}ms\\n`)\n break\n case 'step-start':\n process.stderr.write(`· start ${event.name}${event.seed === null ? '' : ` (seed ${event.seed})`}\\n`)\n break\n case 'step-end':\n process.stderr.write(`· ${event.status === 'passed' ? 'ok' : event.status} ${event.name} ${event.durationMs}ms\\n`)\n break\n }\n }\n}\n\nexport async function runSignoffCli(argv: readonly string[]): Promise<number> {\n if (argv[0] === 'shell-alignment') return runShellAlignmentCli(argv.slice(1))\n if (argv[0] === 'surface-ladder') return runSurfaceLadderCli(argv.slice(1))\n if (argv[0] === 'workspace-surface') return runWorkspaceSurfaceCli(argv.slice(1))\n const args = parseArgs(argv)\n const report = await runSignoff({ ...args, onEvent: args.quiet ? undefined : progressWriter() })\n\n if (args.jsonPath !== undefined) {\n const abs = resolve(args.jsonPath)\n mkdirSync(dirname(abs), { recursive: true })\n writeFileSync(abs, `${JSON.stringify(report, null, 2)}\\n`)\n }\n\n // `--quiet` is documented as \"verdict only\", and CLAUDE.md names the one-line\n // `signoff PASS <sha> …` summary as an accepted PR proof. Printing the full\n // report here contradicted both, and left the only producer of that summary\n // with no caller.\n process.stdout.write(`${args.quiet ? formatSignoffLine(report) : formatSignoffReport(report)}\\n`)\n return report.ok ? 0 : 1\n}\n\n/* c8 ignore start — process wiring, exercised by the bin itself */\nif (invokedAsScript(import.meta.url, process.argv[1])) {\n runSignoffCli(process.argv.slice(2))\n .then((code) => process.exit(code))\n .catch((err: unknown) => {\n process.stderr.write(`${err instanceof Error ? err.message : String(err)}\\n`)\n process.exit(2)\n })\n}\n/* c8 ignore stop */\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/signoff/cli.ts","../../src/signoff/defaults-cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `agent-app-signoff` — run the local sign-off gate and print the proof.\n *\n * Exits 0 only when every declared step passed in a pristine tree. Exit 1 is a\n * real failure; exit 2 is a usage or config error, so a script can tell \"your\n * code is broken\" from \"your gate is misconfigured\".\n *\n * agent-app-signoff # this repo, working tree, default config\n * agent-app-signoff --source head # exactly the commit that would merge\n * agent-app-signoff --seed 12345 # reproduce a previous run's orders\n * agent-app-signoff --keep-going # run every step, full picture\n * agent-app-signoff --json proof.json # machine-readable record\n * agent-app-signoff defaults [repoDir] # which agent-app defaults each app has\n * agent-app-signoff shell-alignment --base-url <url> --route <path> # header dividers meet the rail\n * agent-app-signoff surface-ladder --base-url <url> --route <path> # cards and fields step off their parent\n * agent-app-signoff workspace-surface --base-url <url> --route <path> # every page on the shared panel\n */\nimport { mkdirSync, writeFileSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport { runDefaultsCli } from './defaults-cli'\nimport { invokedAsScript } from './invoked-as-script'\nimport { formatSignoffLine, formatSignoffReport } from './report'\nimport { runSignoff, type RunSignoffOptions } from './run'\nimport { runShellAlignmentCli } from './shell-alignment-cli'\nimport { runSurfaceLadderCli } from './surface-ladder-cli'\nimport { runWorkspaceSurfaceCli } from './workspace-surface-cli'\nimport type { SignoffEvent, SignoffSource } from './types'\n\ninterface ParsedArgs extends RunSignoffOptions {\n readonly jsonPath?: string\n readonly quiet?: boolean\n}\n\nfunction usage(): string {\n return [\n 'Usage: agent-app-signoff [repoDir] [options]',\n '',\n ' --source head|working-tree bytes to verify (default working-tree)',\n ' --config <path> config module (default signoff.config.mjs)',\n ' --seed <n> base seed; reproduces a previous run',\n ' --shuffle-runs <n> override every shuffled step\\'s run count',\n ' --max-parallel <n> cap concurrent steps',\n ' --cache-dir <path> clean trees + pristine stores live here',\n ' --keep-going run every step even after one fails',\n ' --keep-workspace leave the clean tree on disk to inspect',\n ' --json <path> write the machine-readable report',\n ' --quiet verdict only; no per-step progress',\n '',\n 'Subcommands:',\n ' defaults report which agent-app defaults each app has (--help for options)',\n ' shell-alignment measure header dividers against the rail in a browser (--help for options)',\n ' surface-ladder measure card and field steps, field borders and card copy in a browser (--help for options)',\n ' workspace-surface every page sits on one shared panel under an inset shell header (--help for options)',\n ].join('\\n')\n}\n\nfunction fail(message: string): never {\n process.stderr.write(`agent-app-signoff: ${message}\\n\\n${usage()}\\n`)\n process.exit(2)\n}\n\nfunction positiveInt(raw: string, flag: string): number {\n const value = Number.parseInt(raw, 10)\n if (!Number.isFinite(value) || value < 0) fail(`${flag} needs a non-negative integer, got \"${raw}\"`)\n return value\n}\n\nexport function parseArgs(argv: readonly string[]): ParsedArgs {\n const parsed: {\n repoDir?: string\n configPath?: string\n source?: SignoffSource\n seed?: number\n shuffleRuns?: number\n maxParallel?: number\n cacheDir?: string\n keepGoing?: boolean\n keepWorkspace?: boolean\n jsonPath?: string\n quiet?: boolean\n } = {}\n\n for (let index = 0; index < argv.length; index += 1) {\n const flag = argv[index] as string\n const take = (): string => {\n const value = argv[index + 1]\n if (value === undefined) fail(`${flag} needs a value`)\n index += 1\n return value\n }\n switch (flag) {\n case '--source': {\n const value = take()\n if (value !== 'head' && value !== 'working-tree') fail(`--source must be head or working-tree, got \"${value}\"`)\n parsed.source = value\n break\n }\n case '--config': parsed.configPath = take(); break\n case '--seed': parsed.seed = positiveInt(take(), '--seed'); break\n case '--shuffle-runs': parsed.shuffleRuns = positiveInt(take(), '--shuffle-runs'); break\n case '--max-parallel': parsed.maxParallel = positiveInt(take(), '--max-parallel'); break\n case '--cache-dir': parsed.cacheDir = take(); break\n case '--keep-going': parsed.keepGoing = true; break\n case '--keep-workspace': parsed.keepWorkspace = true; break\n case '--json': parsed.jsonPath = take(); break\n case '--quiet': parsed.quiet = true; break\n case '-h':\n case '--help':\n process.stdout.write(`${usage()}\\n`)\n process.exit(0)\n break\n default:\n if (flag.startsWith('-')) fail(`unknown option: ${flag}`)\n if (parsed.repoDir !== undefined) fail(`unexpected second directory: ${flag}`)\n parsed.repoDir = flag\n }\n }\n return parsed\n}\n\nfunction progressWriter(): (event: SignoffEvent) => void {\n return (event) => {\n switch (event.kind) {\n case 'tree':\n process.stderr.write(`· clean tree at ${event.path} (${event.head.slice(0, 12)}${event.dirty ? ' + working tree' : ''})\\n`)\n break\n case 'store':\n process.stderr.write(`· store ${event.cacheHit ? 'warm' : 'COLD'} ${event.cacheKey.slice(0, 16)}\\n`)\n break\n case 'install-start':\n process.stderr.write(`· ${event.command}\\n`)\n break\n case 'install-end':\n process.stderr.write(`· install exit ${event.exitCode} in ${event.durationMs}ms\\n`)\n break\n case 'step-start':\n process.stderr.write(`· start ${event.name}${event.seed === null ? '' : ` (seed ${event.seed})`}\\n`)\n break\n case 'step-end':\n process.stderr.write(`· ${event.status === 'passed' ? 'ok' : event.status} ${event.name} ${event.durationMs}ms\\n`)\n break\n }\n }\n}\n\nexport async function runSignoffCli(argv: readonly string[]): Promise<number> {\n if (argv[0] === 'defaults') return runDefaultsCli(argv.slice(1))\n if (argv[0] === 'shell-alignment') return runShellAlignmentCli(argv.slice(1))\n if (argv[0] === 'surface-ladder') return runSurfaceLadderCli(argv.slice(1))\n if (argv[0] === 'workspace-surface') return runWorkspaceSurfaceCli(argv.slice(1))\n const args = parseArgs(argv)\n const report = await runSignoff({ ...args, onEvent: args.quiet ? undefined : progressWriter() })\n\n if (args.jsonPath !== undefined) {\n const abs = resolve(args.jsonPath)\n mkdirSync(dirname(abs), { recursive: true })\n writeFileSync(abs, `${JSON.stringify(report, null, 2)}\\n`)\n }\n\n // `--quiet` is documented as \"verdict only\", and CLAUDE.md names the one-line\n // `signoff PASS <sha> …` summary as an accepted PR proof. Printing the full\n // report here contradicted both, and left the only producer of that summary\n // with no caller.\n process.stdout.write(`${args.quiet ? formatSignoffLine(report) : formatSignoffReport(report)}\\n`)\n return report.ok ? 0 : 1\n}\n\n/* c8 ignore start — process wiring, exercised by the bin itself */\nif (invokedAsScript(import.meta.url, process.argv[1])) {\n runSignoffCli(process.argv.slice(2))\n .then((code) => process.exit(code))\n .catch((err: unknown) => {\n process.stderr.write(`${err instanceof Error ? err.message : String(err)}\\n`)\n process.exit(2)\n })\n}\n/* c8 ignore stop */\n","/**\n * `agent-app-signoff defaults` — report which agent-app defaults each app in a checkout has.\n *\n * agent-app-signoff defaults [repoDir] # the matrix for the apps in repoDir\n * agent-app-signoff defaults --probe # also ask Intelligence for each app's last exported turn\n * agent-app-signoff defaults --json matrix.json # the same rows, machine-readable\n *\n * Exit 0 unless an app turns a default off without a reason (1), or the usage is wrong (2). `--probe` reads the\n * Intelligence key from `TANGLE_INTELLIGENCE_API_KEY`; it reads the traces of the tenant that owns the apps' export keys.\n */\nimport { mkdirSync, writeFileSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport { checkAppDefaults, collectLastExports, formatAppDefaults } from './app-defaults'\nimport { loadSignoffConfig } from './config'\n\nfunction defaultsUsage(): string {\n return [\n 'Usage: agent-app-signoff defaults [repoDir] [options]',\n '',\n ' --probe ask Intelligence for each app\\'s last exported turn (needs TANGLE_INTELLIGENCE_API_KEY)',\n ' --no-bundle skip the Worker upload measure (it runs the app\\'s own wrangler, so build first)',\n ' --json <path> write the rows',\n ].join('\\n')\n}\n\nexport async function runDefaultsCli(argv: readonly string[]): Promise<number> {\n let repoDir = process.cwd()\n let probe = false\n let measureBundle = true\n let jsonPath: string | undefined\n for (let index = 0; index < argv.length; index += 1) {\n const flag = argv[index] as string\n if (flag === '--probe') probe = true\n else if (flag === '--no-bundle') measureBundle = false\n else if (flag === '--json') {\n jsonPath = argv[index + 1]\n index += 1\n if (jsonPath === undefined) return usageError('--json needs a path')\n } else if (flag === '-h' || flag === '--help') {\n process.stdout.write(`${defaultsUsage()}\\n`)\n return 0\n } else if (flag.startsWith('-')) return usageError(`unknown option: ${flag}`)\n else repoDir = resolve(flag)\n }\n let exemptions\n try {\n exemptions = (await loadSignoffConfig({ repoRoot: repoDir })).config.agentApp?.exemptions\n } catch {\n // A checkout with no signoff config, or one that does not load here, declares no exemptions.\n }\n const apiKey = process.env.TANGLE_INTELLIGENCE_API_KEY?.trim()\n if (probe && !apiKey) return usageError('--probe needs TANGLE_INTELLIGENCE_API_KEY')\n const exports = probe && apiKey ? await collectLastExports(repoDir, apiKey) : undefined\n const result = checkAppDefaults(repoDir, { exemptions, measureBundle, exports })\n if (jsonPath !== undefined) {\n const abs = resolve(jsonPath)\n mkdirSync(dirname(abs), { recursive: true })\n writeFileSync(abs, `${JSON.stringify(result, null, 2)}\\n`)\n }\n process.stdout.write(formatAppDefaults(result))\n return result.findings.length === 0 ? 0 : 1\n}\n\nfunction usageError(message: string): number {\n process.stderr.write(`agent-app-signoff defaults: ${message}\\n\\n${defaultsUsage()}\\n`)\n return 2\n}\n"],"mappings":"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","names":["mkdirSync","writeFileSync","dirname","resolve","resolve","mkdirSync","dirname","writeFileSync"]}
@@ -0,0 +1 @@
1
+ export declare function runDefaultsCli(argv: readonly string[]): Promise<number>;
@@ -41,7 +41,8 @@ import {
41
41
  runWorkspaceSurfaceCli,
42
42
  summarizeSurfacePage,
43
43
  surfaceDeltaL
44
- } from "../chunk-KDPTXXOQ.js";
44
+ } from "../chunk-J3N6RNMH.js";
45
+ import "../chunk-LRYY3OX7.js";
45
46
  export {
46
47
  HEADER_TOP_TOLERANCE_PX,
47
48
  NETWORK_OVER_BUDGET_EXIT,
@@ -10,6 +10,7 @@
10
10
  * install filter, step list, language (one has a Python step) and repo-specific
11
11
  * gates.
12
12
  */
13
+ import type { AppDefaultsExemptions } from './app-defaults';
13
14
  /** Which bytes get verified. */
14
15
  export type SignoffSource =
15
16
  /** Exactly the commit that would merge. Uncommitted work is NOT included. */
@@ -99,6 +100,11 @@ export interface SignoffConfig {
99
100
  * every tracked `wrangler.{toml,json,jsonc}`. Each is checked against the
100
101
  * agent-app Worker defaults (`worker-defaults.ts`). */
101
102
  readonly workerConfigs?: readonly string[];
103
+ /** What this app declares about the agent-app defaults (`app-defaults.ts`). */
104
+ readonly agentApp?: {
105
+ /** Defaults the app turns off, each with the reason. An empty reason is a configuration error. */
106
+ readonly exemptions?: AppDefaultsExemptions;
107
+ };
102
108
  }
103
109
  /** Where a config came from. Printed in the proof — a run against a derived
104
110
  * default is a weaker claim than one against a declared step list. */
@@ -41,6 +41,14 @@ export interface WorkerDefaultsResult {
41
41
  }[];
42
42
  readonly findings: readonly WorkerDefaultsFinding[];
43
43
  }
44
+ /** Every file the repo tracks, repo-relative. Generated output is gitignored, so this is the complete source list without a hand-kept exclude list. */
45
+ export declare function trackedFiles(repoRoot: string): string[];
46
+ /**
47
+ * Wrangler configs the repo tracks. Generated output (`.wrangler/`, build
48
+ * directories) is gitignored, so `git ls-files` is the complete source list
49
+ * without a hand-kept exclude list.
50
+ */
51
+ export declare function trackedWorkerConfigs(repoRoot: string): string[];
44
52
  /**
45
53
  * Check each Worker config, and every environment it deploys, against the
46
54
  * defaults. `configs` are repo-relative paths; omit them to check every tracked
@@ -3,297 +3,18 @@ import {
3
3
  sizedBatches
4
4
  } from "../chunk-5UQJ7F3P.js";
5
5
  import {
6
- postSlackAlert
7
- } from "../chunk-F2VX6TTJ.js";
8
-
9
- // src/turn-health/classify.ts
10
- var ARTIFACT_PART_KINDS = /* @__PURE__ */ new Set(["file", "image", "work-product", "plan", "interaction"]);
11
- var KNOWN_NON_OUTPUT_PART_KINDS = /* @__PURE__ */ new Set([
12
- "reasoning",
13
- "step-start",
14
- "step-finish",
15
- "source",
16
- "source-url",
17
- "data"
18
- ]);
19
- var SAMPLE_CHARS = 120;
20
- function asRecord(value) {
21
- return typeof value === "object" && value !== null ? value : null;
22
- }
23
- function nonEmptyString(value) {
24
- return typeof value === "string" && value.trim().length > 0 ? value : null;
25
- }
26
- function isUnparseableJson(value) {
27
- const trimmed = value.trim();
28
- if (trimmed.length === 0) return false;
29
- try {
30
- JSON.parse(trimmed);
31
- return false;
32
- } catch {
33
- return true;
34
- }
35
- }
36
- var SETTLED_TOOL_STATUSES = /* @__PURE__ */ new Set(["completed", "complete", "success", "done"]);
37
- var FAILED_TOOL_STATUSES = /* @__PURE__ */ new Set(["error", "failed"]);
38
- function classifyTurnOutcome(input) {
39
- const reasons = [];
40
- if (input.failed) {
41
- reasons.push({
42
- kind: "turn_failed",
43
- reason: nonEmptyString(input.failureReason) ?? "unspecified"
44
- });
45
- }
46
- const parts = Array.isArray(input.parts) ? input.parts : [];
47
- let hasVisibleText = nonEmptyString(input.finalText) !== null;
48
- let artifactCount = 0;
49
- let toolCalls = 0;
50
- const opaqueTypes = [];
51
- let interpretedParts = 0;
52
- for (const raw of parts) {
53
- const part = asRecord(raw);
54
- if (!part) continue;
55
- const type = typeof part.type === "string" ? part.type : "";
56
- if (type === "text") {
57
- interpretedParts += 1;
58
- if (nonEmptyString(part.text) !== null) hasVisibleText = true;
59
- continue;
60
- }
61
- if (ARTIFACT_PART_KINDS.has(type)) {
62
- interpretedParts += 1;
63
- artifactCount += 1;
64
- continue;
65
- }
66
- if (KNOWN_NON_OUTPUT_PART_KINDS.has(type)) {
67
- interpretedParts += 1;
68
- continue;
69
- }
70
- if (type !== "tool") {
71
- opaqueTypes.push(type || "(missing type)");
72
- continue;
73
- }
74
- interpretedParts += 1;
75
- toolCalls += 1;
76
- const tool = nonEmptyString(part.tool) ?? "unknown";
77
- const state = asRecord(part.state);
78
- const status = typeof state?.status === "string" ? state.status : "unknown";
79
- const toolInput = state?.input;
80
- const inputRecord = asRecord(toolInput);
81
- const rejection = nonEmptyString(inputRecord?.error) ?? nonEmptyString(state?.error);
82
- if (rejection && SETTLED_TOOL_STATUSES.has(status)) {
83
- reasons.push({
84
- kind: "tool_call_rejected",
85
- // The payload names the tool the model MEANT to call; the part's own
86
- // `tool` is the harness's placeholder for a rejected call.
87
- tool: nonEmptyString(inputRecord?.tool) ?? tool,
88
- error: rejection.slice(0, 200)
89
- });
90
- continue;
91
- }
92
- if (typeof toolInput === "string" && isUnparseableJson(toolInput)) {
93
- reasons.push({
94
- kind: "malformed_tool_call",
95
- tool,
96
- inputLength: toolInput.length,
97
- sample: toolInput.slice(0, SAMPLE_CHARS)
98
- });
99
- continue;
100
- }
101
- if (!SETTLED_TOOL_STATUSES.has(status) && !FAILED_TOOL_STATUSES.has(status)) {
102
- reasons.push({ kind: "tool_call_no_effect", tool, status });
103
- }
104
- }
105
- const partsReadable = opaqueTypes.length === 0;
106
- const uniqueOpaque = [...new Set(opaqueTypes)];
107
- const unreadable = opaqueTypes.length > 0 && interpretedParts === 0 && !hasVisibleText;
108
- if (unreadable) {
109
- reasons.push({ kind: "unreadable_turn", partTypes: uniqueOpaque });
110
- return {
111
- healthy: false,
112
- severity: "warning",
113
- reasons,
114
- toolCalls,
115
- unreadable: true,
116
- partsReadable: false,
117
- opaquePartTypes: uniqueOpaque,
118
- // Structurally 0 here — `unreadable` is only reached when
119
- // `interpretedParts === 0`. Carried rather than hardcoded so the field
120
- // has one source on every return path.
121
- interpretedParts
122
- };
123
- }
124
- if (input.gated) {
125
- reasons.push({ kind: "answered_without_model" });
126
- } else if (!input.failed && !hasVisibleText && artifactCount === 0) {
127
- reasons.push({
128
- kind: "empty_completion",
129
- outputTokens: input.outputTokens ?? null,
130
- partCount: parts.length,
131
- ...input.durationMs !== void 0 ? { durationMs: input.durationMs } : {}
132
- });
133
- }
134
- return {
135
- healthy: reasons.length === 0,
136
- severity: severityOf(reasons),
137
- reasons,
138
- toolCalls,
139
- unreadable: false,
140
- partsReadable,
141
- opaquePartTypes: uniqueOpaque,
142
- interpretedParts
143
- };
144
- }
145
- function severityOf(reasons) {
146
- if (reasons.length === 0) return null;
147
- const critical = reasons.some(
148
- (r) => r.kind === "empty_completion" || r.kind === "turn_failed" || r.kind === "tool_call_rejected"
149
- );
150
- return critical ? "critical" : "warning";
151
- }
152
- function describeReason(reason) {
153
- switch (reason.kind) {
154
- case "empty_completion":
155
- return `completed with NO output (${reason.partCount} parts, outputTokens=${reason.outputTokens ?? "unknown"})`;
156
- case "malformed_tool_call":
157
- return `tool \`${reason.tool}\` arguments did not parse (${reason.inputLength} chars): ${reason.sample}`;
158
- case "tool_call_no_effect":
159
- return `tool \`${reason.tool}\` left no effect (status=${reason.status})`;
160
- case "turn_failed":
161
- return `turn failed: ${reason.reason}`;
162
- case "tool_call_rejected":
163
- return `tool \`${reason.tool}\` was REJECTED but settled as completed: ${reason.error}`;
164
- case "answered_without_model":
165
- return "answered by a pre-producer gate \u2014 the model never ran";
166
- case "unreadable_turn":
167
- return `turn could not be read: every part had an uninterpretable type (${reason.partTypes.join(
168
- ", "
169
- )})`;
170
- }
171
- }
172
-
173
- // src/turn-health/sink.ts
174
- function turnAlert(input) {
175
- const kinds = [...new Set(input.reasons.map((r) => r.kind))].sort();
176
- return {
177
- product: input.product,
178
- severity: input.severity,
179
- // Keyed by product + reason kinds ONLY. A blank-completion storm across
180
- // 200 turns is one incident, not 200 pages.
181
- key: `turn:${input.product}:${kinds.join("+")}`,
182
- title: `${input.product}: turn completed but delivered nothing (${kinds.join(", ")})`,
183
- details: input.reasons.map(describeReason),
184
- data: {
185
- kinds,
186
- ...input.threadId ? { threadId: input.threadId } : {},
187
- ...input.turnId ? { turnId: input.turnId } : {},
188
- ...input.model ? { model: input.model } : {}
189
- },
190
- at: input.at ?? Date.now()
191
- };
192
- }
193
- function createWebhookAlertSink(options) {
194
- const fetchImpl = options.fetchImpl ?? globalThis.fetch;
195
- return {
196
- async deliver(alert) {
197
- const icon = alert.severity === "critical" ? ":rotating_light:" : ":warning:";
198
- const lines = [
199
- `${icon} *${alert.title}*`,
200
- ...alert.details.map((d) => `\u2022 ${d}`),
201
- `_${new Date(alert.at).toISOString()}_`
202
- ];
203
- const response = await fetchImpl(options.webhookUrl, {
204
- method: "POST",
205
- headers: { "Content-Type": "application/json" },
206
- body: JSON.stringify({ text: lines.join("\n") })
207
- });
208
- if (!response.ok) {
209
- throw new Error(`alert webhook responded ${response.status}`);
210
- }
211
- }
212
- };
213
- }
214
- function createSlackBotAlertSink(options) {
215
- const fetchImpl = options.fetchImpl ?? globalThis.fetch;
216
- return {
217
- async deliver(alert) {
218
- const icon = alert.severity === "critical" ? ":rotating_light:" : ":warning:";
219
- const lines = [
220
- `${icon} *${alert.title}*`,
221
- ...alert.details.map((d) => `\u2022 ${d}`),
222
- `_${new Date(alert.at).toISOString()}_`
223
- ];
224
- const outcome = await postSlackAlert({
225
- token: options.botToken,
226
- channel: options.channel,
227
- text: lines.join("\n"),
228
- attempts: 1,
229
- // Preserve the public minimal-fetch seam without owning a second Slack
230
- // request builder or response parser. Real callers use native fetch.
231
- fetchImpl: options.fetchImpl ? (async (url, init) => {
232
- const response = await fetchImpl(String(url), {
233
- method: init?.method ?? "POST",
234
- headers: Object.fromEntries(new Headers(init?.headers)),
235
- body: String(init?.body ?? "")
236
- });
237
- return new Response(await response.text?.() ?? "", { status: response.status });
238
- }) : void 0
239
- });
240
- if (!outcome.delivered) throw new Error(`Slack alert failed (${outcome.reason}): ${outcome.detail}`);
241
- }
242
- };
243
- }
244
- function createConsoleAlertSink(log = console.error) {
245
- return {
246
- async deliver(alert) {
247
- log(
248
- `[turn-health] ${alert.severity.toUpperCase()} ${alert.title} :: ${alert.details.join(" | ")}`
249
- );
250
- }
251
- };
252
- }
253
- function createMultiAlertSink(sinks) {
254
- return {
255
- async deliver(alert) {
256
- const settled = await Promise.allSettled(sinks.map((s) => s.deliver(alert)));
257
- const failures = settled.filter((r) => r.status === "rejected");
258
- if (failures.length === sinks.length && sinks.length > 0) {
259
- throw new Error("every alert sink failed");
260
- }
261
- }
262
- };
263
- }
264
- function createMemoryThrottleStore() {
265
- const seen = /* @__PURE__ */ new Map();
266
- return {
267
- async lastSentAt(key) {
268
- return seen.get(key) ?? null;
269
- },
270
- async markSent(key, at) {
271
- seen.set(key, at);
272
- }
273
- };
274
- }
275
- function createThrottledAlertSink(inner, options) {
276
- const store = options.store ?? createMemoryThrottleStore();
277
- return {
278
- async deliver(alert) {
279
- const last = await store.lastSentAt(alert.key);
280
- if (last !== null && alert.at - last < options.windowMs) return;
281
- await inner.deliver(alert);
282
- await store.markSent(alert.key, alert.at);
283
- }
284
- };
285
- }
286
- function createGuardedAlertSink(inner, onError = (e) => console.error("[turn-health] alert delivery failed", e)) {
287
- return {
288
- async deliver(alert) {
289
- try {
290
- await inner.deliver(alert);
291
- } catch (error) {
292
- onError(error);
293
- }
294
- }
295
- };
296
- }
6
+ classifyTurnOutcome,
7
+ createConsoleAlertSink,
8
+ createGuardedAlertSink,
9
+ createMemoryThrottleStore,
10
+ createMultiAlertSink,
11
+ createSlackBotAlertSink,
12
+ createThrottledAlertSink,
13
+ createWebhookAlertSink,
14
+ describeReason,
15
+ turnAlert
16
+ } from "../chunk-CD2LQXY6.js";
17
+ import "../chunk-F2VX6TTJ.js";
297
18
 
298
19
  // src/turn-health/lifecycle.ts
299
20
  function errorText(error) {