ai-foreman 1.7.20 → 1.7.21

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 (189) hide show
  1. package/README.md +12 -2
  2. package/dist/activity.d.ts +3 -0
  3. package/dist/activity.js +18 -6
  4. package/dist/adapters/claude.d.ts +16 -0
  5. package/dist/adapters/claude.js +28 -3
  6. package/dist/adapters/codex.d.ts +20 -0
  7. package/dist/adapters/codex.js +99 -5
  8. package/dist/adapters/recovering.d.ts +9 -0
  9. package/dist/adapters/recovering.js +10 -0
  10. package/dist/adapters/types.d.ts +15 -0
  11. package/dist/agentRun.d.ts +7 -0
  12. package/dist/agentRun.js +24 -13
  13. package/dist/branch/currentGuard.d.ts +1 -0
  14. package/dist/branch/currentGuard.js +1 -0
  15. package/dist/branch/finalization.js +6 -1
  16. package/dist/branch/git.d.ts +1 -0
  17. package/dist/branch/git.js +15 -1
  18. package/dist/branch/runner.d.ts +2 -0
  19. package/dist/branch/runner.js +90 -4
  20. package/dist/buildAdmission.js +2 -0
  21. package/dist/buildAssignment.d.ts +33 -0
  22. package/dist/buildAssignment.js +215 -0
  23. package/dist/buildInterventions.d.ts +90 -0
  24. package/dist/buildInterventions.js +334 -0
  25. package/dist/buildOwnershipReconciliation.d.ts +19 -0
  26. package/dist/buildOwnershipReconciliation.js +41 -0
  27. package/dist/buildOwnershipRepair.d.ts +33 -0
  28. package/dist/buildOwnershipRepair.js +111 -0
  29. package/dist/buildRuns.d.ts +6 -0
  30. package/dist/buildRuns.js +23 -17
  31. package/dist/buildSyntheticWork.d.ts +2 -0
  32. package/dist/buildSyntheticWork.js +30 -0
  33. package/dist/buildWorkAdmission.d.ts +17 -0
  34. package/dist/buildWorkAdmission.js +57 -0
  35. package/dist/buildWorkAuthorization.d.ts +2 -0
  36. package/dist/buildWorkAuthorization.js +23 -0
  37. package/dist/buildWorkContext.d.ts +3 -0
  38. package/dist/buildWorkContext.js +6 -0
  39. package/dist/buildWorkMigration.d.ts +9 -0
  40. package/dist/buildWorkMigration.js +206 -0
  41. package/dist/buildWorkReconciliation.d.ts +12 -0
  42. package/dist/buildWorkReconciliation.js +70 -0
  43. package/dist/builderGuidanceFollowup.d.ts +15 -0
  44. package/dist/builderGuidanceFollowup.js +34 -0
  45. package/dist/cli/doctor.js +3 -2
  46. package/dist/cli/graph.d.ts +2 -0
  47. package/dist/cli/graph.js +75 -0
  48. package/dist/cli/manager.d.ts +8 -0
  49. package/dist/cli/manager.js +234 -12
  50. package/dist/cli/recovery.js +3 -2
  51. package/dist/cli/runtimeAuthPrompt.js +28 -8
  52. package/dist/cli/start.js +152 -48
  53. package/dist/cli/status.js +20 -5
  54. package/dist/cli/tickets.js +9 -7
  55. package/dist/continuity.d.ts +11 -0
  56. package/dist/continuity.js +22 -6
  57. package/dist/foreman.d.ts +10 -2
  58. package/dist/foreman.js +330 -39
  59. package/dist/graph/boundary.d.ts +15 -0
  60. package/dist/graph/boundary.js +50 -0
  61. package/dist/graph/bridge.d.ts +9 -0
  62. package/dist/graph/bridge.js +146 -0
  63. package/dist/graph/bridge.py +205 -0
  64. package/dist/graph/config.d.ts +11 -0
  65. package/dist/graph/config.js +40 -0
  66. package/dist/graph/corpus.d.ts +16 -0
  67. package/dist/graph/corpus.js +188 -0
  68. package/dist/graph/derived.d.ts +17 -0
  69. package/dist/graph/derived.js +73 -0
  70. package/dist/graph/host.d.ts +4 -0
  71. package/dist/graph/host.js +160 -0
  72. package/dist/graph/install.d.ts +3 -0
  73. package/dist/graph/install.js +50 -0
  74. package/dist/graph/lifecycle.d.ts +11 -0
  75. package/dist/graph/lifecycle.js +124 -0
  76. package/dist/graph/maintenance.d.ts +43 -0
  77. package/dist/graph/maintenance.js +301 -0
  78. package/dist/graph/manager.d.ts +9 -0
  79. package/dist/graph/manager.js +92 -0
  80. package/dist/graph/preparation.d.ts +41 -0
  81. package/dist/graph/preparation.js +43 -0
  82. package/dist/graph/read.d.ts +27 -0
  83. package/dist/graph/read.js +152 -0
  84. package/dist/graph/semantic.d.ts +17 -0
  85. package/dist/graph/semantic.js +27 -0
  86. package/dist/graph/session.d.ts +8 -0
  87. package/dist/graph/session.js +92 -0
  88. package/dist/graph/sources.d.ts +4 -0
  89. package/dist/graph/sources.js +11 -0
  90. package/dist/graph/storage.d.ts +38 -0
  91. package/dist/graph/storage.js +159 -0
  92. package/dist/graph/turn.d.ts +17 -0
  93. package/dist/graph/turn.js +360 -0
  94. package/dist/graph/util.d.ts +5 -0
  95. package/dist/graph/util.js +51 -0
  96. package/dist/handoffs.d.ts +1 -0
  97. package/dist/handoffs.js +5 -1
  98. package/dist/humanDecision.d.ts +21 -2
  99. package/dist/humanDecision.js +169 -11
  100. package/dist/index.js +2 -0
  101. package/dist/installOwnership.d.ts +46 -0
  102. package/dist/installOwnership.js +440 -0
  103. package/dist/linuxProbeInventory.d.ts +12 -0
  104. package/dist/linuxProbeInventory.js +48 -0
  105. package/dist/log.d.ts +1 -1
  106. package/dist/log.js +7 -1
  107. package/dist/managerEvidence.d.ts +25 -0
  108. package/dist/managerEvidence.js +326 -0
  109. package/dist/managerEvidenceArtifacts.d.ts +38 -0
  110. package/dist/managerEvidenceArtifacts.js +118 -0
  111. package/dist/managerPacket.d.ts +2 -0
  112. package/dist/managerPacket.js +23 -0
  113. package/dist/markers.d.ts +1 -1
  114. package/dist/markers.js +2 -2
  115. package/dist/observability.js +1 -1
  116. package/dist/processIdentity.js +7 -0
  117. package/dist/providerPhase.d.ts +13 -0
  118. package/dist/providerPhase.js +26 -0
  119. package/dist/qaAuthorizedDisposition.d.ts +24 -0
  120. package/dist/qaAuthorizedDisposition.js +26 -0
  121. package/dist/qaBuildGate.d.ts +4 -0
  122. package/dist/qaBuildGate.js +216 -0
  123. package/dist/qaBuilderCoverage.d.ts +5 -0
  124. package/dist/qaBuilderCoverage.js +78 -0
  125. package/dist/qaContractCoverage.d.ts +10 -0
  126. package/dist/qaContractCoverage.js +55 -0
  127. package/dist/qaContractDelivery.d.ts +23 -0
  128. package/dist/qaContractDelivery.js +145 -0
  129. package/dist/qaContractFreshness.d.ts +15 -0
  130. package/dist/qaContractFreshness.js +36 -0
  131. package/dist/qaDeliveryJournal.d.ts +10 -0
  132. package/dist/qaDeliveryJournal.js +16 -1
  133. package/dist/qaEffectiveConfig.d.ts +20 -0
  134. package/dist/qaEffectiveConfig.js +40 -0
  135. package/dist/qaEvidenceReader.d.ts +14 -0
  136. package/dist/qaEvidenceReader.js +176 -0
  137. package/dist/qaFailureDelivery.d.ts +2 -0
  138. package/dist/qaFailureDelivery.js +107 -10
  139. package/dist/qaHandbackMigration.d.ts +1 -1
  140. package/dist/qaHandbackMigration.js +3 -3
  141. package/dist/qaPreparation.d.ts +26 -0
  142. package/dist/qaPreparation.js +150 -0
  143. package/dist/qaPreparationInputs.d.ts +8 -0
  144. package/dist/qaPreparationInputs.js +47 -0
  145. package/dist/qaPreparationMetrics.d.ts +145 -0
  146. package/dist/qaPreparationMetrics.js +120 -0
  147. package/dist/qaPreparationPolicy.d.ts +5 -0
  148. package/dist/qaPreparationPolicy.js +51 -0
  149. package/dist/qaPreparationStore.d.ts +157 -0
  150. package/dist/qaPreparationStore.js +412 -0
  151. package/dist/qaProtocolV2.d.ts +28 -0
  152. package/dist/qaProtocolV2.js +12 -0
  153. package/dist/qaReview.d.ts +6 -0
  154. package/dist/qaReview.js +234 -24
  155. package/dist/qaTimeline.d.ts +18 -0
  156. package/dist/qaTimeline.js +123 -0
  157. package/dist/qaVerificationContract.d.ts +17 -0
  158. package/dist/qaVerificationContract.js +236 -0
  159. package/dist/readinessCleanup.js +12 -5
  160. package/dist/recoveryGuidance.d.ts +9 -0
  161. package/dist/recoveryGuidance.js +13 -0
  162. package/dist/runtimeAuth.d.ts +1 -0
  163. package/dist/runtimeAuth.js +5 -1
  164. package/dist/runtimeReadiness.d.ts +1 -0
  165. package/dist/runtimeReadiness.js +13 -1
  166. package/dist/sessionIdentity.d.ts +2 -0
  167. package/dist/sessionIdentity.js +14 -0
  168. package/dist/sessionLifecycle.js +3 -3
  169. package/dist/sources/sourceRegistry.js +7 -0
  170. package/dist/stateTransfer.js +50 -1
  171. package/dist/statusReporter.d.ts +1 -0
  172. package/dist/statusReporter.js +1 -0
  173. package/dist/supervisedStart.js +18 -8
  174. package/dist/terminalStyle.d.ts +21 -0
  175. package/dist/terminalStyle.js +50 -0
  176. package/dist/ticketPopulation.d.ts +1 -0
  177. package/dist/ticketPopulation.js +5 -1
  178. package/dist/tickets/commands.js +2 -2
  179. package/dist/tickets/queue.js +1 -1
  180. package/dist/tickets/ticketSchema.d.ts +198 -0
  181. package/dist/tickets/ticketSchema.js +2 -0
  182. package/dist/workflowDb.d.ts +51 -5
  183. package/dist/workflowDb.js +338 -7
  184. package/dist/workflowReader.d.ts +109 -2
  185. package/dist/workflowReader.js +48 -4
  186. package/package.json +84 -7
  187. package/scripts/copy-graph-assets.mjs +3 -0
  188. package/scripts/graph-benchmark.mjs +20 -0
  189. package/scripts/graph-evaluation.mjs +85 -0
@@ -1,3 +1,4 @@
1
+ import { type GraphRecordKind } from "./graph/storage.js";
1
2
  import { type ReadinessProcess } from "./readinessCleanup.js";
2
3
  import type { PendingHumanDecision, RecoveryAttemptReceipt, ResolvedAutonomyPolicy, SupervisorState, WorkflowIssue } from "rafi-spec";
3
4
  import { type OperationRecord, type RoleMutationLease, type ProjectLease, type WorkflowRunSnapshot } from "./workflowDb.js";
@@ -8,12 +9,118 @@ export declare class WorkflowReader {
8
9
  readonly path: string;
9
10
  private readonly db?;
10
11
  constructor(projectDir: string, path?: string);
12
+ qaPreparation(runId: string): {
13
+ mode: string;
14
+ works: Array<{
15
+ workId: string;
16
+ state: string;
17
+ digest?: string;
18
+ depth?: number;
19
+ detail?: string;
20
+ }>;
21
+ };
22
+ qaPreparationMetrics(runId: string): {
23
+ withheldEvents?: number | undefined;
24
+ limitation?: string | undefined;
25
+ approved: number;
26
+ started: number;
27
+ comparableStarted: number;
28
+ excludedNonEquivalentReviews: number;
29
+ approvedNeverStarted: number;
30
+ preparationFailures: number;
31
+ firstReviews: number;
32
+ firstReviewPasses: number;
33
+ firstReviewPassRate: number | null;
34
+ endToEndFirstAttempt: number;
35
+ endToEndRate: number | null;
36
+ omissionWorks: number;
37
+ omissionRounds: number;
38
+ omissionIncidence: number | null;
39
+ omissionRoundsPerStarted: number | null;
40
+ causes: Record<import("./qaPreparationMetrics.js").FindingCause, number>;
41
+ statuses: Record<string, number>;
42
+ quality: {
43
+ day7: {
44
+ observed: number;
45
+ reopened: number;
46
+ escaped: number;
47
+ unknown: number;
48
+ };
49
+ day30: {
50
+ observed: number;
51
+ reopened: number;
52
+ escaped: number;
53
+ unknown: number;
54
+ };
55
+ };
56
+ phaseCosts: Record<string, {
57
+ knownUsd: number;
58
+ knownSamples: number;
59
+ unknownSamples: number;
60
+ durationMs: number;
61
+ }>;
62
+ outsideContractEvents: number;
63
+ formatRepairs: number;
64
+ providerRetries: number;
65
+ byMode: {
66
+ [k: string]: {
67
+ works: number;
68
+ started: number;
69
+ };
70
+ };
71
+ samples: {
72
+ eventId: string;
73
+ workKey: string;
74
+ mode: "legacy" | "shadow" | "enforce";
75
+ level: number | undefined;
76
+ cohort: string;
77
+ at: string;
78
+ costUsd: number | null;
79
+ durationMs: number | null;
80
+ evidenceRefs: string[];
81
+ contractDigest?: string | undefined;
82
+ reviewDigest?: string | undefined;
83
+ assessor?: string | undefined;
84
+ checkUsefulness?: {
85
+ checkId: string;
86
+ result: string;
87
+ duplicate?: boolean;
88
+ irrelevant?: boolean;
89
+ observation: string;
90
+ }[] | undefined;
91
+ excessiveInvestigation?: string;
92
+ coverageAdequate?: boolean | undefined;
93
+ }[];
94
+ cohortSizes: {
95
+ [k: string]: number;
96
+ };
97
+ };
98
+ qaPreparationEvents(runId: string): Array<{
99
+ eventId: string;
100
+ workId: string;
101
+ kind: string;
102
+ value: unknown;
103
+ }>;
104
+ graphHostProjectIdentity(): string | undefined;
105
+ graphEvidenceAllowed(digest: string): boolean;
11
106
  close(): void;
107
+ graphRecord<T>(kind: GraphRecordKind, id: string): {
108
+ revision: number;
109
+ value: T;
110
+ } | undefined;
111
+ graphReceiptSummaries(runId: string): unknown[];
112
+ graphRecentJobs(): Array<{
113
+ id: string;
114
+ state: string;
115
+ reason?: string;
116
+ generationId?: string;
117
+ }>;
118
+ graphGenerationForCorpus<T>(corpusDigest: string, policyDigest: string): T | undefined;
12
119
  readinessProcesses(): ReadinessProcess[];
13
120
  available(): boolean;
14
121
  getRun(runId: string): WorkflowRunSnapshot | undefined;
15
122
  activeRuns(): WorkflowRunSnapshot[];
16
- buildRuns(): WorkflowRunSnapshot[];
123
+ buildRuns(strict?: boolean): WorkflowRunSnapshot[];
17
124
  events(runId: string): Array<{
18
125
  sequence: number;
19
126
  type: string;
@@ -22,7 +129,7 @@ export declare class WorkflowReader {
22
129
  at: string;
23
130
  }>;
24
131
  issues(runId: string): WorkflowIssue[];
25
- operations(runId: string): OperationRecord[];
132
+ operations(runId: string, strict?: boolean): OperationRecord[];
26
133
  currentLease(): ProjectLease | undefined;
27
134
  pendingQaTicketIds(runId: string): string[];
28
135
  preparationSuccessor(runId: string): string;
@@ -1,3 +1,7 @@
1
+ import { bytesDigest } from "./graph/util.js";
2
+ import { measureQaPreparation } from "./qaPreparationMetrics.js";
3
+ import { graphDerivedAllowed } from "./graph/derived.js";
4
+ import { readGraphRecord } from "./graph/storage.js";
1
5
  import { checkBuildOwnershipSchema, canonicalProject } from "./buildAdmission.js";
2
6
  import { existsSync } from "node:fs";
3
7
  import { join, resolve } from "node:path";
@@ -23,7 +27,43 @@ export class WorkflowReader {
23
27
  throw error;
24
28
  }
25
29
  }
30
+ qaPreparation(runId) {
31
+ if (!this.db || !this.db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='qa_preparation_policy'").get())
32
+ return { mode: "legacy", works: [] };
33
+ const policy = this.db.prepare("SELECT record_json FROM qa_preparation_policy WHERE run_id=?").get(runId);
34
+ const rows = this.db.prepare("SELECT h.work_id,h.state,h.digest,h.detail,c.record_json FROM qa_contract_heads h LEFT JOIN qa_verification_contracts c ON c.digest=h.digest WHERE h.run_id=?").all(runId);
35
+ return { mode: policy ? JSON.parse(policy.record_json).mode : "legacy", works: rows.map(row => ({ workId: row.work_id, state: row.state, digest: row.digest, detail: row.detail && !this.graphEvidenceAllowed(bytesDigest(row.detail)) ? "Graph-derived preparation detail withheld; source-based recovery required" : row.detail, depth: row.record_json && this.graphEvidenceAllowed(bytesDigest(row.record_json)) ? JSON.parse(row.record_json).depthDecision?.level : undefined })) };
36
+ }
37
+ qaPreparationMetrics(runId) {
38
+ const events = this.qaPreparationEvents(runId), withheld = events.filter(event => event.kind === "evidence-unavailable").length;
39
+ return { ...measureQaPreparation(events.filter(event => event.kind === "metric").map(event => event.value)), ...(withheld ? { withheldEvents: withheld, limitation: "Metrics are incomplete: graph-derived events are unavailable" } : {}) };
40
+ }
41
+ qaPreparationEvents(runId) {
42
+ if (!this.db || !this.db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='qa_preparation_events'").get())
43
+ return [];
44
+ return this.db.prepare("SELECT event_id,work_id,kind,record_json FROM qa_preparation_events WHERE run_id=? ORDER BY rowid").all(runId).map(row => ({ eventId: row.event_id, workId: row.work_id, kind: this.graphEvidenceAllowed(bytesDigest(row.record_json)) ? row.kind : "evidence-unavailable", value: this.graphEvidenceAllowed(bytesDigest(row.record_json)) ? JSON.parse(row.record_json) : { unavailable: "graph-access-revoked", originalDigest: bytesDigest(row.record_json) } }));
45
+ }
46
+ graphHostProjectIdentity() { if (!this.db || !this.db.prepare("SELECT 1 FROM sqlite_master WHERE name='build_project_identity'").get())
47
+ return; return this.db.prepare("SELECT project_id FROM build_project_identity WHERE singleton=1").get()?.project_id; }
48
+ graphEvidenceAllowed(digest) { return this.db ? graphDerivedAllowed(this.db, this.projectDir, digest) : true; }
26
49
  close() { this.db?.close(); }
50
+ graphRecord(kind, id) { return readGraphRecord(this.db, kind, id); }
51
+ graphReceiptSummaries(runId) {
52
+ if (!this.db || !this.db.prepare("SELECT 1 FROM sqlite_master WHERE name='graph_records'").get())
53
+ return [];
54
+ return this.db.prepare("SELECT id AS receiptId,json_extract(value,'$.purpose') AS purpose,json_extract(value,'$.decision') AS decision,json_extract(value,'$.deliveredAt') AS deliveredAt,json_extract(value,'$.generationIds') AS generationIds FROM graph_records WHERE kind='receipt' AND json_extract(value,'$.runId')=? ORDER BY rowid DESC LIMIT 20").all(runId);
55
+ }
56
+ graphRecentJobs() {
57
+ if (!this.db || !this.db.prepare("SELECT 1 FROM sqlite_master WHERE name='graph_records'").get())
58
+ return [];
59
+ return this.db.prepare("SELECT id,json_extract(value,'$.state') AS state,json_extract(value,'$.reason') AS reason,json_extract(value,'$.generationId') AS generationId FROM graph_records WHERE kind='job' ORDER BY rowid DESC LIMIT 15").all();
60
+ }
61
+ graphGenerationForCorpus(corpusDigest, policyDigest) {
62
+ if (!this.db || !this.db.prepare("SELECT 1 FROM sqlite_master WHERE name='graph_records'").get())
63
+ return;
64
+ const row = this.db.prepare("SELECT value FROM graph_records WHERE kind='generation' AND json_extract(value,'$.corpusDigest')=? AND json_extract(value,'$.policyDigest')=? ORDER BY json_extract(value,'$.createdAt') DESC LIMIT 1").get(corpusDigest, policyDigest);
65
+ return row ? JSON.parse(row.value) : undefined;
66
+ }
27
67
  readinessProcesses() {
28
68
  if (!this.db)
29
69
  return [];
@@ -53,13 +93,15 @@ export class WorkflowReader {
53
93
  return [];
54
94
  }
55
95
  }
56
- buildRuns() {
96
+ buildRuns(strict = false) {
57
97
  if (!this.db)
58
98
  return [];
59
99
  try {
60
100
  return this.db.prepare("SELECT * FROM workflow_runs WHERE kind='build' ORDER BY updated_at DESC").all().map(toRun);
61
101
  }
62
- catch {
102
+ catch (error) {
103
+ if (strict)
104
+ throw error;
63
105
  return [];
64
106
  }
65
107
  }
@@ -83,13 +125,15 @@ export class WorkflowReader {
83
125
  return [];
84
126
  }
85
127
  }
86
- operations(runId) {
128
+ operations(runId, strict = false) {
87
129
  if (!this.db)
88
130
  return [];
89
131
  try {
90
132
  return this.db.prepare("SELECT * FROM operation_journal WHERE run_id=? ORDER BY created_at,idempotency_key").all(runId).map(row => ({ idempotencyKey: String(row.idempotency_key), runId: String(row.run_id), kind: String(row.kind), status: String(row.status), intent: JSON.parse(String(row.intent_json)), ...(row.result_json ? { result: JSON.parse(String(row.result_json)) } : {}), ...(row.external_id ? { externalId: String(row.external_id) } : {}), ...(row.error ? { error: String(row.error) } : {}), createdAt: String(row.created_at), updatedAt: String(row.updated_at) }));
91
133
  }
92
- catch {
134
+ catch (error) {
135
+ if (strict)
136
+ throw error;
93
137
  return [];
94
138
  }
95
139
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-foreman",
3
- "version": "1.7.20",
3
+ "version": "1.7.21",
4
4
  "description": "An orchestrator that keeps Codex CLI / Claude Code builders moving through their step list unattended. Part of Rafi.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -17,10 +17,38 @@
17
17
  "ai-foreman": "dist/index.js"
18
18
  },
19
19
  "exports": {
20
+ "./build-assignment.js": {
21
+ "types": "./dist/buildAssignment.d.ts",
22
+ "default": "./dist/buildAssignment.js"
23
+ },
24
+ "./build-ownership-reconciliation.js": {
25
+ "types": "./dist/buildOwnershipReconciliation.d.ts",
26
+ "default": "./dist/buildOwnershipReconciliation.js"
27
+ },
28
+ "./manager-evidence.js": {
29
+ "types": "./dist/managerEvidence.d.ts",
30
+ "default": "./dist/managerEvidence.js"
31
+ },
32
+ "./qa-evidence-reader.js": {
33
+ "types": "./dist/qaEvidenceReader.d.ts",
34
+ "default": "./dist/qaEvidenceReader.js"
35
+ },
36
+ "./manager-evidence-artifacts.js": {
37
+ "types": "./dist/managerEvidenceArtifacts.d.ts",
38
+ "default": "./dist/managerEvidenceArtifacts.js"
39
+ },
40
+ "./qa-timeline.js": {
41
+ "types": "./dist/qaTimeline.d.ts",
42
+ "default": "./dist/qaTimeline.js"
43
+ },
20
44
  "./activity.js": {
21
45
  "types": "./dist/activity.d.ts",
22
46
  "default": "./dist/activity.js"
23
47
  },
48
+ "./terminal-style.js": {
49
+ "types": "./dist/terminalStyle.d.ts",
50
+ "default": "./dist/terminalStyle.js"
51
+ },
24
52
  "./agent-run.js": {
25
53
  "types": "./dist/agentRun.d.ts",
26
54
  "default": "./dist/agentRun.js"
@@ -188,21 +216,69 @@
188
216
  "./state-transfer.js": {
189
217
  "types": "./dist/stateTransfer.d.ts",
190
218
  "default": "./dist/stateTransfer.js"
191
- }
219
+ },
220
+ "./human-decision.js": {
221
+ "types": "./dist/humanDecision.d.ts",
222
+ "default": "./dist/humanDecision.js"
223
+ },
224
+ "./build-approval.js": {
225
+ "types": "./dist/buildApproval.d.ts",
226
+ "default": "./dist/buildApproval.js"
227
+ },
228
+ "./build-work-admission.js": {
229
+ "types": "./dist/buildWorkAdmission.d.ts",
230
+ "default": "./dist/buildWorkAdmission.js"
231
+ },
232
+ "./build-work-migration.js": {
233
+ "types": "./dist/buildWorkMigration.d.ts",
234
+ "default": "./dist/buildWorkMigration.js"
235
+ },
236
+ "./build-interventions.js": {
237
+ "types": "./dist/buildInterventions.d.ts",
238
+ "default": "./dist/buildInterventions.js"
239
+ },
240
+ "./build-ownership-repair.js": {
241
+ "types": "./dist/buildOwnershipRepair.d.ts",
242
+ "default": "./dist/buildOwnershipRepair.js"
243
+ },
244
+ "./graph.js": {
245
+ "types": "./dist/graph/turn.d.ts",
246
+ "default": "./dist/graph/turn.js"
247
+ },
248
+ "./graph-maintenance.js": {
249
+ "types": "./dist/graph/maintenance.d.ts",
250
+ "default": "./dist/graph/maintenance.js"
251
+ },
252
+ "./cli/graph.js": {
253
+ "types": "./dist/cli/graph.d.ts",
254
+ "default": "./dist/cli/graph.js"
255
+ },
256
+ "./qa-preparation-policy.js": {
257
+ "types": "./dist/qaPreparationPolicy.d.ts",
258
+ "default": "./dist/qaPreparationPolicy.js"
259
+ },
260
+ "./qa-effective-config.js": {
261
+ "types": "./dist/qaEffectiveConfig.d.ts",
262
+ "default": "./dist/qaEffectiveConfig.js"
263
+ },
264
+ "./install-ownership.js": "./dist/installOwnership.js"
192
265
  },
193
266
  "files": [
194
267
  "dist",
195
268
  "README.md",
196
269
  "foreman.yaml",
197
- "LICENSE"
270
+ "LICENSE",
271
+ "scripts/copy-graph-assets.mjs",
272
+ "scripts/graph-evaluation.mjs",
273
+ "scripts/graph-benchmark.mjs"
198
274
  ],
199
275
  "scripts": {
200
- "build": "tsc -p tsconfig.build.json",
276
+ "build": "tsc -p tsconfig.build.json && node scripts/copy-graph-assets.mjs",
201
277
  "dev": "tsx src/index.ts",
202
278
  "test": "tsx --test --test-concurrency=1 test/*.test.ts test/*.test.mjs",
203
279
  "typecheck": "tsc --noEmit",
204
280
  "test:live-providers": "RAFI_LIVE_PROVIDER_SESSIONS=1 tsx --test test/providerSmoke.test.ts",
205
- "prepublishOnly": "tsc -p ../spec/tsconfig.build.json && tsc -p tsconfig.build.json && pnpm --filter ai-foreman test && tsc --noEmit"
281
+ "prepublishOnly": "tsc -p ../spec/tsconfig.build.json && pnpm build && pnpm --filter ai-foreman test && tsc --noEmit"
206
282
  },
207
283
  "keywords": [
208
284
  "claude",
@@ -222,16 +298,17 @@
222
298
  "@anthropic-ai/claude-agent-sdk": "^0.3.148"
223
299
  },
224
300
  "dependencies": {
225
- "special-agents": "0.8.12",
226
- "rafi-spec": "0.8.13",
227
301
  "@clack/prompts": "^1.4.0",
228
302
  "ajv": "^8.20.0",
229
303
  "ajv-formats": "^3.0.1",
230
304
  "better-sqlite3": "^12.10.0",
231
305
  "commander": "^12.1.0",
306
+ "ignore": "7.0.5",
232
307
  "luxon": "^3.7.2",
233
308
  "parse5": "7.2.1",
234
309
  "pdfjs-dist": "4.10.38",
310
+ "rafi-spec": "0.8.14",
311
+ "special-agents": "0.8.13",
235
312
  "yaml": "^2.5.1"
236
313
  },
237
314
  "devDependencies": {
@@ -0,0 +1,3 @@
1
+ import { copyFileSync, mkdirSync } from "node:fs";
2
+ mkdirSync(new URL("../dist/graph/", import.meta.url), { recursive: true });
3
+ copyFileSync(new URL("../src/graph/bridge.py", import.meta.url), new URL("../dist/graph/bridge.py", import.meta.url));
@@ -0,0 +1,20 @@
1
+ /** Offline deterministic bridge measurement. No agents, network or corpus scan. */
2
+ import { cpus, platform, release } from "node:os";
3
+ import { performance } from "node:perf_hooks";
4
+ import { resolveGraphPython, runBridge } from "../dist/graph/bridge.js";
5
+ const python=await resolveGraphPython();
6
+ const resourceControls=(await runBridge(python,{action:"probe"})).resourceControls;
7
+ const samples=[];
8
+ for(const count of [10000,100000]){
9
+ const graph={directed:true,multigraph:false,graph:{schema_version:1},nodes:Array.from({length:count},(_,i)=>({id:`n${i}`,label:`component${i}`,source_file:"fixture.py"})),links:Array.from({length:count-1},(_,i)=>({source:`n${i}`,target:`n${i+1}`,relation:"calls",confidence:"EXTRACTED",source_file:"fixture.py"}))};
10
+ const durations=[];let returned=0;
11
+ for(let sample=0;sample<5;sample++){
12
+ const start=performance.now();
13
+ const result=await runBridge(python,{action:"read",graph,operation:{operation:"neighbors",seeds:["n5000"],direction:"outgoing",depth:2}},{timeoutMs:15000});
14
+ durations.push(performance.now()-start);returned=result.nodes.length;
15
+ if(!result.nodes.some(n=>n.id==="n5002")||result.nodes.some(n=>n.id==="n4999"))throw new Error("Directed neighborhood correctness failed");
16
+ }
17
+ durations.sort((a,b)=>a-b);
18
+ samples.push({nodes:count,edges:count-1,samples:durations.length,medianMs:durations[2],largestObservedMs:durations[4],returnedNodes:returned,mode:"cold one-shot subprocess",warmLatency:"not measured",semanticCost:"not applicable"});
19
+ }
20
+ console.log(JSON.stringify({schema:1,measuredAt:new Date().toISOString(),resourceControls,environment:{platform:platform(),release:release(),cpu:cpus()[0]?.model,node:process.version},samples,limits:"Five samples establish a local fixture observation, not a production percentile or agent-quality evaluation."},null,2));
@@ -0,0 +1,85 @@
1
+ /** Offline evaluation manifest and matched-result validator. Never invokes a provider. */
2
+ import { createHash } from 'node:crypto';
3
+ import { readFileSync, mkdirSync, writeFileSync } from 'node:fs';
4
+ import { resolve, join } from 'node:path';
5
+ import { pathToFileURL } from 'node:url';
6
+ const hash = value => createHash('sha256').update(JSON.stringify(value)).digest('hex');
7
+ const categories = ['missed-caller', 'undeclared-dependency', 'cross-layer-regression', 'existing-invariant', 'captured-requirement'];
8
+ export function evaluationManifest() {
9
+ const cases = categories.flatMap((category, index) => Array.from({ length: 4 }, (_, variant) => {
10
+ const name = `${category}-${variant + 1}`;
11
+ const files = {
12
+ 'requirements.md': `# Approved requirement\nReject negative values; preserve zero; return the normalized integer to every caller.\n`,
13
+ 'core.py': 'def normalize(value):\n if value < 0:\n raise ValueError("negative")\n return int(value)\n',
14
+ 'api.py': 'from core import normalize\ndef response(value):\n return {"value": normalize(value)}\n',
15
+ 'worker.py': 'from core import normalize\ndef process(value):\n return normalize(value)\n',
16
+ 'test_contract.py': 'from api import response\nfrom worker import process\ndef test_zero():\n assert response(0) == {"value": 0}\n assert process(0) == 0\n',
17
+ 'plugin.py': 'def dynamic(module, value):\n return getattr(module, "normalize")(value)\n',
18
+ };
19
+ const tasks = {
20
+ 'missed-caller': 'Assess changing normalize to accept a required precision argument. Identify every affected caller, including uncertain dynamic dispatch.',
21
+ 'undeclared-dependency': 'Plan a worker normalization change. Identify dependencies that must be reviewed before implementation.',
22
+ 'cross-layer-regression': 'Change integer conversion behavior while keeping API and worker contracts consistent. Identify regression tests.',
23
+ 'existing-invariant': 'Optimize normalization while preserving rejection of negative values and acceptance of zero.',
24
+ 'captured-requirement': 'Trace the approved negative-value and zero requirements to implementation and tests; identify missing test coverage.',
25
+ };
26
+ // Variants alter a source fact, not only the case identifier.
27
+ files['variant.py'] = `from ${variant % 2 ? 'api import response' : 'worker import process'}\ndef scheduled_${variant}(value):\n return ${variant % 2 ? 'response' : 'process'}(value)\n`;
28
+ return { id: name, category, files, baselineDigest: hash(files), task: tasks[category], relevantPaths: Object.keys(files),
29
+ sourceFacts: ['core.py rejects negative values', 'test_contract.py tests zero but omits negative-value coverage', 'plugin.py dispatch is dynamic and must be inspected in source'],
30
+ absentOrAmbiguousEdges: ['dynamic getattr dispatch is not assumed resolved', 'requirements-to-test semantic edges are navigation hints, not proof'],
31
+ mandatoryQa: ['inspect all affected callers', 'verify negative and zero behavior', 'run source tests', 'inspect dynamic dispatch'],
32
+ order: (index + variant) % 2 ? ['off', 'on'] : ['on', 'off'] };
33
+ }));
34
+ return { version: 1, kind: 'rafi_graph_evaluation', cases, negativeControl: { task: 'Correct a comment typo in core.py', expectedMaintenanceRuns: 0 },
35
+ controls: ['separate sessions and workspaces for each arm', 'identical approved scope, QA, depth, model, provider and baseline per pair', 'record cold and warm costs separately', 'retain blocked, cancelled, degraded and retried results', 'source inspection fallback is not graph retrieval'],
36
+ qualityTarget: { repeatedExplorationOrOmissionReduction: 0.2, coverageMustNotDecline: true },
37
+ limitation: 'Synthetic engineering fixtures validate machinery. Twenty matched fixture pairs do not establish representative production usefulness.' };
38
+ }
39
+ const metrics = ['usefulFindings', 'missedPaths', 'falseSuggestions', 'omissionRemediations', 'escapedDefects', 'repeatedExploration', 'preparationMs', 'buildMs', 'reviewMs', 'graphMs', 'extractionCostUsd', 'providerCostUsd', 'inputTokens', 'outputTokens'];
40
+ export function reportResults(manifest, rows) {
41
+ if (!Array.isArray(rows)) throw new Error('Results must be an array');
42
+ const keys = new Set(), sessions = new Set();
43
+ for (const row of rows) {
44
+ const fixture = manifest.cases.find(c => c.id === row.caseId);
45
+ if (!fixture || !['on', 'off'].includes(row.arm) || !['cold', 'warm'].includes(row.cache)) throw new Error('Unknown case, arm or cache classification');
46
+ const key = `${row.caseId}:${row.arm}:${row.cache}`;
47
+ if (keys.has(key)) throw new Error(`Duplicate outcome ${key}`);
48
+ keys.add(key);
49
+ if (!row.sessionId || sessions.has(row.sessionId)) throw new Error('Each outcome needs an isolated session');
50
+ sessions.add(row.sessionId);
51
+ if (row.baselineDigest !== fixture.baselineDigest || row.qaDigest !== hash(fixture.mandatoryQa)) throw new Error('Baseline or mandatory QA differs from manifest');
52
+ if (!['completed', 'blocked', 'cancelled', 'degraded', 'failed'].includes(row.status)) throw new Error('Missing outcome classification');
53
+ for (const field of ['provider', 'model', 'depth', 'executionMode', 'semanticMode', 'difficulty']) if (typeof row[field] !== 'string' || !row[field]) throw new Error(`Missing ${field}`);
54
+ if (!Number.isInteger(row.retries) || row.retries < 0) throw new Error('Retain retry accounting');
55
+ if (typeof row.sourceVerified !== 'boolean' || typeof row.coverageVerified !== 'boolean') throw new Error('Explicit source and QA coverage review required');
56
+ for (const metric of metrics) if (row[metric] !== null && !(Number.isFinite(row[metric]) && row[metric] >= 0)) throw new Error(`${metric} must be a nonnegative number or null (unmeasured)`);
57
+ }
58
+ const pairs = [], unmatched = [];
59
+ for (const fixture of manifest.cases) for (const cache of ['cold', 'warm']) {
60
+ const arms = rows.filter(r => r.caseId === fixture.id && r.cache === cache);
61
+ if (arms.length !== 2) { unmatched.push({ caseId: fixture.id, cache, recordedArms: arms.map(r => r.arm) }); continue; }
62
+ const on = arms.find(r => r.arm === 'on'), off = arms.find(r => r.arm === 'off');
63
+ for (const field of ['provider', 'model', 'depth', 'executionMode', 'semanticMode', 'difficulty']) if (on[field] !== off[field]) throw new Error(`Unmatched ${field} in ${fixture.id}`);
64
+ pairs.push({ caseId: fixture.id, cache, statuses: { on: on.status, off: off.status }, coverageVerified: on.coverageVerified && off.coverageVerified,
65
+ differencesOnMinusOff: Object.fromEntries(metrics.map(m => [m, on[m] === null || off[m] === null ? null : on[m] - off[m]])) });
66
+ }
67
+ return { version: 1, recordedOutcomes: rows.length, matchedPairs: pairs.length, pairs, unmatched, outcomes: rows,
68
+ qualityConclusion: 'No automatic benefit claim. Review source-backed findings and equal QA coverage, include all statuses and unknown costs, and disclose sample uncertainty.',
69
+ liveEvaluation: rows.length ? 'User-supplied observations; harness does not execute or certify providers' : 'Deferred by user; no billed runs performed' };
70
+ }
71
+ if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
72
+ const [command, destination] = process.argv.slice(2);
73
+ const manifest = evaluationManifest();
74
+ if (command === 'prepare' && destination) {
75
+ mkdirSync(destination, { recursive: true });
76
+ for (const fixture of manifest.cases) {
77
+ const root = join(destination, fixture.id); mkdirSync(root, { recursive: true });
78
+ for (const [path, content] of Object.entries(fixture.files)) writeFileSync(join(root, path), content, { flag: 'wx' });
79
+ }
80
+ writeFileSync(join(destination, 'manifest.json'), JSON.stringify(manifest, null, 2), { flag: 'wx' });
81
+ console.log(`Prepared ${manifest.cases.length} offline cases in ${destination}. No providers invoked.`);
82
+ } else if (command === 'report') {
83
+ console.log(JSON.stringify(reportResults(manifest, destination ? JSON.parse(readFileSync(destination, 'utf8')) : []), null, 2));
84
+ } else throw new Error('Usage: node scripts/graph-evaluation.mjs prepare <new-directory> | report [results.json]');
85
+ }