@mikenguyen69/harness 0.1.0-beta.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 (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +43 -0
  3. package/dist/build-identity.json +1 -0
  4. package/dist/cancellation.js +138 -0
  5. package/dist/cli.js +47 -0
  6. package/dist/dispatch.js +70 -0
  7. package/dist/help.js +47 -0
  8. package/dist/internal/agent/adapter/conformance.js +53 -0
  9. package/dist/internal/agent/adapter/hook.js +113 -0
  10. package/dist/internal/agent/adapter/install.js +30 -0
  11. package/dist/internal/agent/adapter/scope.js +55 -0
  12. package/dist/internal/agent/adapter/session.js +63 -0
  13. package/dist/internal/agent/adapter/settings.js +51 -0
  14. package/dist/internal/agent/cli.js +79 -0
  15. package/dist/internal/agent/evals/index.js +111 -0
  16. package/dist/internal/agent/index.js +15 -0
  17. package/dist/internal/agent/ledger-read.js +27 -0
  18. package/dist/internal/agent/routing/cli.js +104 -0
  19. package/dist/internal/agent/routing/index.js +123 -0
  20. package/dist/internal/agent/telemetry/index.js +97 -0
  21. package/dist/internal/orchestration/activity/index.js +111 -0
  22. package/dist/internal/orchestration/analyze/index.js +237 -0
  23. package/dist/internal/orchestration/cli.js +475 -0
  24. package/dist/internal/orchestration/config/index.js +66 -0
  25. package/dist/internal/orchestration/doctor/index.js +202 -0
  26. package/dist/internal/orchestration/forge/index.js +121 -0
  27. package/dist/internal/orchestration/git/index.js +181 -0
  28. package/dist/internal/orchestration/harness/cli.js +378 -0
  29. package/dist/internal/orchestration/harness/index.js +186 -0
  30. package/dist/internal/orchestration/harness/shim.js +182 -0
  31. package/dist/internal/orchestration/index.js +29 -0
  32. package/dist/internal/orchestration/loop/index.js +1107 -0
  33. package/dist/internal/orchestration/prompts/index.js +199 -0
  34. package/dist/internal/orchestration/report/index.js +125 -0
  35. package/dist/internal/orchestration/routes/index.js +85 -0
  36. package/dist/internal/orchestration/run/index.js +221 -0
  37. package/dist/internal/orchestration/runner/index.js +90 -0
  38. package/dist/internal/orchestration/runners/claude/index.js +246 -0
  39. package/dist/internal/orchestration/runners/claude/sdk.js +198 -0
  40. package/dist/internal/orchestration/runners/claude/wiring.js +98 -0
  41. package/dist/internal/orchestration/runners/codex/exec.js +114 -0
  42. package/dist/internal/orchestration/runners/codex/index.js +72 -0
  43. package/dist/internal/orchestration/runners/conformance/index.js +169 -0
  44. package/dist/internal/orchestration/runners/cursor/exec.js +531 -0
  45. package/dist/internal/orchestration/runners/cursor/index.js +184 -0
  46. package/dist/internal/orchestration/runners/stub/index.js +168 -0
  47. package/dist/internal/orchestration/runners/telemetry.js +36 -0
  48. package/dist/internal/orchestration/runstate/index.js +213 -0
  49. package/dist/internal/orchestration/runstate/publish.js +115 -0
  50. package/dist/internal/orchestration/worktree/index.js +89 -0
  51. package/dist/internal/system/adapters/index.js +19 -0
  52. package/dist/internal/system/agentops/index.js +16 -0
  53. package/dist/internal/system/agentops/scope.js +67 -0
  54. package/dist/internal/system/assets.js +45 -0
  55. package/dist/internal/system/board/cli.js +56 -0
  56. package/dist/internal/system/board/index.js +276 -0
  57. package/dist/internal/system/board/live.js +277 -0
  58. package/dist/internal/system/calibration/cli.js +45 -0
  59. package/dist/internal/system/calibration/index.js +141 -0
  60. package/dist/internal/system/cli.js +174 -0
  61. package/dist/internal/system/corpus/replay.js +79 -0
  62. package/dist/internal/system/digest/cli.js +23 -0
  63. package/dist/internal/system/digest/index.js +145 -0
  64. package/dist/internal/system/doors/classify.js +94 -0
  65. package/dist/internal/system/doors/cli.js +184 -0
  66. package/dist/internal/system/doors/index.js +249 -0
  67. package/dist/internal/system/doors/metrics.js +47 -0
  68. package/dist/internal/system/escapes/cli.js +45 -0
  69. package/dist/internal/system/escapes/index.js +81 -0
  70. package/dist/internal/system/explore/cli.js +77 -0
  71. package/dist/internal/system/explore/index.js +91 -0
  72. package/dist/internal/system/gates/cli.js +43 -0
  73. package/dist/internal/system/gates/detectors.js +55 -0
  74. package/dist/internal/system/gates/pre.js +51 -0
  75. package/dist/internal/system/hub/cli.js +51 -0
  76. package/dist/internal/system/hub/index.js +146 -0
  77. package/dist/internal/system/init/index.js +83 -0
  78. package/dist/internal/system/ledger/cli.js +54 -0
  79. package/dist/internal/system/ledger/index.js +176 -0
  80. package/dist/internal/system/manifest/index.js +207 -0
  81. package/dist/internal/system/provenance/index.js +13 -0
  82. package/dist/internal/system/report/index.js +15 -0
  83. package/dist/internal/system/runner/index.js +74 -0
  84. package/dist/internal/system/runstatus/cli.js +86 -0
  85. package/dist/internal/system/runstatus/index.js +168 -0
  86. package/dist/internal/system/sequencer/briefing.js +75 -0
  87. package/dist/internal/system/sequencer/cli.js +170 -0
  88. package/dist/internal/system/sequencer/index.js +156 -0
  89. package/dist/internal/system/spec/cli.js +110 -0
  90. package/dist/internal/system/spec/index.js +212 -0
  91. package/dist/internal/system/spec/materialize.js +111 -0
  92. package/dist/internal/system/spec/openspec-parse.js +83 -0
  93. package/dist/internal/system/spec/project.js +208 -0
  94. package/dist/internal/system/substrate/index.js +14 -0
  95. package/dist/internal/system/system/index.js +159 -0
  96. package/dist/internal/system/units/index.js +87 -0
  97. package/dist/internal/system/verify/cli.js +103 -0
  98. package/dist/internal/system/verify/declined-reasons.js +61 -0
  99. package/dist/internal/system/verify/index.js +188 -0
  100. package/dist/internal/system/verify/signing.js +79 -0
  101. package/dist/paths.js +50 -0
  102. package/dist/routes.js +93 -0
  103. package/dist/version.js +30 -0
  104. package/dist/win32-process-group.js +360 -0
  105. package/package.json +41 -0
  106. package/profiles/python/bindings.toml +59 -0
  107. package/profiles/terraform/bindings.toml +66 -0
  108. package/profiles/typescript/bindings.toml +64 -0
  109. package/profiles/typescript/smoke/README.md +8 -0
  110. package/schema/baseline.schema.json +26 -0
  111. package/schema/decision-classes.yaml +45 -0
  112. package/schema/envelope.schema.json +186 -0
  113. package/schema/ledger-event.schema.json +235 -0
  114. package/schema/manifest.schema.json +119 -0
  115. package/schema/routes.schema.json +55 -0
  116. package/schema/run-status.schema.json +162 -0
  117. package/schema/session-event.schema.json +74 -0
  118. package/schema/spec.schema.json +114 -0
  119. package/schema/system.schema.json +142 -0
  120. package/templates/ci/verify.yml +23 -0
  121. package/templates/target-kit/README.md +78 -0
  122. package/templates/target-kit/component.toml +20 -0
  123. package/templates/target-kit/orchestration.json +9 -0
  124. package/templates/target-kit/profiles/target/bindings.toml +6 -0
  125. package/templates/target-kit/routes.toml +5 -0
  126. package/templates/target-kit/specs/first-unit.yaml +11 -0
  127. package/templates/target-kit/system.toml +7 -0
@@ -0,0 +1,111 @@
1
+ /**
2
+ * activity — the durable, append-only per-run activity log (docs/plan/
3
+ * 2026-09-02-run-activity-capture-and-synthesis.md). One JSONL file per run
4
+ * under `<repo>/.harness/runs/<run-id>/activity.jsonl`, written alongside
5
+ * (never instead of) the in-memory `RunLog` snapshot that drives the spool/
6
+ * board feed — the two are independent channels (design principle 3).
7
+ *
8
+ * `orchestrator.step`/`agent.activity` mirror `RunLog`'s `lane.step`/
9
+ * `lane.activity` events but add a durable `at` timestamp and (for agent
10
+ * activity) `kind`/`tool`/`target` — fields the snapshot never carries, since
11
+ * widening `RunEvent` would touch the golden-tested `projectRunState` reducer
12
+ * for no benefit to the live feed.
13
+ */
14
+ import { appendFileSync, mkdirSync } from "node:fs";
15
+ import { dirname } from "node:path";
16
+ /** default hard cap; `text` events are dropped past this, `tool`/`tool-result`/step/bookend events never are. */
17
+ export const ACTIVITY_LOG_MAX_LINES = 50_000;
18
+ function toIso(at) {
19
+ return new Date(at).toISOString();
20
+ }
21
+ /**
22
+ * Flush-per-line, no fd management — matches the codebase's existing
23
+ * preference for simplicity over buffering (e.g. `StubRunner`'s
24
+ * `writeFileSync`). Every `writeX` method throws on fs failure; callers (the
25
+ * loop) catch and degrade via `log.append({t:"lane.note",...})`, exactly like
26
+ * `RunLog.recordLaneActivity`'s existing catch block — the writer itself stays
27
+ * trivial and never swallows its own errors.
28
+ */
29
+ export class ActivityLogWriter {
30
+ path;
31
+ resumes;
32
+ maxLines;
33
+ linesWritten = 0;
34
+ truncated = false;
35
+ log = [];
36
+ constructor(opts) {
37
+ this.path = opts.path;
38
+ this.resumes = opts.resumes;
39
+ this.maxLines = opts.maxLines ?? ACTIVITY_LOG_MAX_LINES;
40
+ mkdirSync(dirname(this.path), { recursive: true });
41
+ }
42
+ write(ev) {
43
+ this.log.push(ev);
44
+ appendFileSync(this.path, JSON.stringify(ev) + "\n");
45
+ this.linesWritten += 1;
46
+ }
47
+ writeRunStarted(ev) {
48
+ this.write({
49
+ t: "run.started",
50
+ at: toIso(ev.at),
51
+ run_id: ev.runId,
52
+ dag_size: ev.dagSize,
53
+ max_parallel: ev.maxParallel,
54
+ ...(this.resumes ? { resumes: this.resumes } : {}),
55
+ });
56
+ }
57
+ writeRunEnded(ev) {
58
+ this.write({ t: "run.ended", at: toIso(ev.at), run_id: ev.runId, outcome: ev.outcome });
59
+ }
60
+ writeOrchestratorStep(ev) {
61
+ this.write({
62
+ t: "orchestrator.step",
63
+ at: toIso(ev.at),
64
+ run_id: ev.runId,
65
+ unit_id: ev.unitId,
66
+ step: ev.step,
67
+ attempt: ev.attempt,
68
+ session: ev.session,
69
+ });
70
+ }
71
+ writeAgentActivity(ev) {
72
+ if (this.linesWritten >= this.maxLines) {
73
+ if (!this.truncated) {
74
+ this.truncated = true;
75
+ this.write({
76
+ t: "activity.truncated",
77
+ at: toIso(ev.at),
78
+ run_id: ev.runId,
79
+ after_lines: this.maxLines,
80
+ });
81
+ }
82
+ // tool / tool-result events carry the findings-relevant signal — only
83
+ // drop `text` turns once the cap is crossed.
84
+ if (ev.kind === "text" || ev.kind === undefined)
85
+ return;
86
+ }
87
+ this.write({
88
+ t: "agent.activity",
89
+ at: toIso(ev.at),
90
+ run_id: ev.runId,
91
+ unit_id: ev.unitId,
92
+ step: ev.step,
93
+ attempt: ev.attempt,
94
+ session: ev.session,
95
+ runner: ev.runner,
96
+ model: ev.model,
97
+ turn: ev.turn,
98
+ line: ev.line,
99
+ tokens: ev.tokens,
100
+ ...(ev.kind ? { kind: ev.kind } : {}),
101
+ ...(ev.tool ? { tool: ev.tool } : {}),
102
+ ...(ev.target ? { target: ev.target } : {}),
103
+ });
104
+ }
105
+ /** in-memory mirror of every line written this process — lets the
106
+ * same-process post-run analyze step (P2) skip a re-read of the file. */
107
+ events() {
108
+ return this.log;
109
+ }
110
+ }
111
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,237 @@
1
+ const DEFAULT_HIGH_TURN_COUNT = 50;
2
+ const IMPLEMENT_STEPS = new Set(["implement"]);
3
+ const REVIEW_STEPS = new Set(["review"]);
4
+ export function parseActivityLog(text) {
5
+ const out = [];
6
+ for (const line of text.split(/\r?\n/)) {
7
+ const t = line.trim();
8
+ if (!t)
9
+ continue;
10
+ out.push(JSON.parse(t));
11
+ }
12
+ return out;
13
+ }
14
+ function isAgentActivity(e) {
15
+ return e.t === "agent.activity";
16
+ }
17
+ function isStep(e) {
18
+ return e.t === "orchestrator.step";
19
+ }
20
+ function unitMetrics(unitId, steps, activity, costByUnit) {
21
+ const allTimestamps = [...steps.map((s) => s.at), ...activity.map((a) => a.at)].sort();
22
+ const wallMs = allTimestamps.length >= 2
23
+ ? Date.parse(allTimestamps[allTimestamps.length - 1]) - Date.parse(allTimestamps[0])
24
+ : 0;
25
+ const turnsTotal = activity.filter((a) => a.kind === "tool" || a.kind === "text").length;
26
+ const toolFailures = activity.filter((a) => a.kind === "tool-result").length;
27
+ const attempts = Math.max(0, ...steps.map((s) => s.attempt), ...activity.map((a) => a.attempt));
28
+ let agentMs = 0;
29
+ const agentSpanEvents = activity.filter((a) => IMPLEMENT_STEPS.has(a.step) || REVIEW_STEPS.has(a.step));
30
+ if (agentSpanEvents.length >= 2) {
31
+ const times = agentSpanEvents.map((a) => Date.parse(a.at)).sort((x, y) => x - y);
32
+ agentMs = times[times.length - 1] - times[0];
33
+ }
34
+ const orchestratorMs = Math.max(0, wallMs - agentMs);
35
+ const out = {
36
+ unitId,
37
+ turnsTotal,
38
+ toolFailures,
39
+ attempts,
40
+ wallMs,
41
+ orchestratorMs,
42
+ agentMs,
43
+ runner: activity[0]?.runner ?? "",
44
+ model: activity[0]?.model ?? "",
45
+ };
46
+ const cost = costByUnit?.get(unitId);
47
+ if (cost !== undefined)
48
+ out.costUsd = cost;
49
+ return out;
50
+ }
51
+ export function findRepeatedRead(unitEvents) {
52
+ const byAttempt = new Map();
53
+ for (const e of unitEvents) {
54
+ const list = byAttempt.get(e.attempt) ?? [];
55
+ list.push(e);
56
+ byAttempt.set(e.attempt, list);
57
+ }
58
+ const findings = [];
59
+ for (const events of byAttempt.values()) {
60
+ const byTarget = new Map();
61
+ for (const e of events) {
62
+ if (e.kind !== "tool" || e.tool !== "read" || !e.target)
63
+ continue;
64
+ const list = byTarget.get(e.target) ?? [];
65
+ list.push(e);
66
+ byTarget.set(e.target, list);
67
+ }
68
+ for (const [target, reads] of byTarget) {
69
+ if (reads.length < 3)
70
+ continue;
71
+ findings.push({
72
+ id: "repeated-read",
73
+ severity: "warn",
74
+ unitId: reads[0].unit_id,
75
+ message: `read "${target}" ${reads.length} times in one attempt — briefing/claim output may already answer this`,
76
+ evidence: reads.map((r) => ({ line: r.line, at: r.at })),
77
+ });
78
+ }
79
+ }
80
+ return findings;
81
+ }
82
+ export function findToolFailureCluster(unitEvents) {
83
+ const findings = [];
84
+ let cluster = [];
85
+ for (const e of unitEvents) {
86
+ if (e.kind === "tool-result") {
87
+ cluster.push(e);
88
+ continue;
89
+ }
90
+ if (e.kind === "tool" || e.kind === "text") {
91
+ if (cluster.length >= 3) {
92
+ findings.push({
93
+ id: "tool-failure-cluster",
94
+ severity: "warn",
95
+ unitId: cluster[0].unit_id,
96
+ message: `${cluster.length} tool failures before the next successful action — check environment/scope setup`,
97
+ evidence: cluster.map((c) => ({ line: c.line, at: c.at })),
98
+ });
99
+ }
100
+ cluster = [];
101
+ }
102
+ }
103
+ if (cluster.length >= 3) {
104
+ findings.push({
105
+ id: "tool-failure-cluster",
106
+ severity: "warn",
107
+ unitId: cluster[0].unit_id,
108
+ message: `${cluster.length} tool failures with no successful action after them`,
109
+ evidence: cluster.map((c) => ({ line: c.line, at: c.at })),
110
+ });
111
+ }
112
+ return findings;
113
+ }
114
+ export function findHighTurnCount(unitEvents, threshold) {
115
+ const turns = unitEvents.filter((e) => e.kind === "tool" || e.kind === "text");
116
+ if (turns.length <= threshold)
117
+ return [];
118
+ return [
119
+ {
120
+ id: "high-turn-count",
121
+ severity: "action",
122
+ unitId: turns[0].unit_id,
123
+ message: `${turns.length} turns exceeds the ${threshold}-turn threshold — consider splitting the unit or narrowing scope`,
124
+ evidence: turns.slice(0, 5).map((t) => ({ line: t.line, at: t.at })),
125
+ },
126
+ ];
127
+ }
128
+ /** v0: conservative — the same evidence line repeated across the last failure
129
+ * of one attempt and the first failure of the next. Wiring real `retryFor`
130
+ * evidence (see captain-driven plan) is out of scope here. */
131
+ export function findRetryWithoutDelta(unitEvents) {
132
+ const byAttempt = new Map();
133
+ for (const e of unitEvents) {
134
+ const list = byAttempt.get(e.attempt) ?? [];
135
+ list.push(e);
136
+ byAttempt.set(e.attempt, list);
137
+ }
138
+ const attempts = [...byAttempt.keys()].sort((a, b) => a - b);
139
+ const findings = [];
140
+ for (let i = 0; i + 1 < attempts.length; i++) {
141
+ const prevFailures = (byAttempt.get(attempts[i]) ?? []).filter((e) => e.kind === "tool-result");
142
+ const nextFailures = (byAttempt.get(attempts[i + 1]) ?? []).filter((e) => e.kind === "tool-result");
143
+ const lastPrev = prevFailures.at(-1);
144
+ const firstNext = nextFailures[0];
145
+ if (lastPrev && firstNext && lastPrev.line === firstNext.line) {
146
+ findings.push({
147
+ id: "retry-without-delta",
148
+ severity: "warn",
149
+ unitId: firstNext.unit_id,
150
+ message: `attempt ${attempts[i + 1]} reproduced the same failure as attempt ${attempts[i]} — retry evidence may not be reaching the briefing`,
151
+ evidence: [
152
+ { line: lastPrev.line, at: lastPrev.at },
153
+ { line: firstNext.line, at: firstNext.at },
154
+ ],
155
+ });
156
+ }
157
+ }
158
+ return findings;
159
+ }
160
+ export function findExploreBeforeEdit(unitEvents) {
161
+ const findings = [];
162
+ let sinceEditCount = 0;
163
+ const seen = [];
164
+ const EXPLORE_THRESHOLD = 15;
165
+ for (const e of unitEvents) {
166
+ if (e.kind !== "tool")
167
+ continue;
168
+ const isEdit = e.tool === "write" || e.tool === "edit";
169
+ if (isEdit) {
170
+ if (sinceEditCount > EXPLORE_THRESHOLD) {
171
+ findings.push({
172
+ id: "explore-before-edit",
173
+ severity: "info",
174
+ unitId: e.unit_id,
175
+ message: `${sinceEditCount} tool turns before the first edit — briefing/prompt may want tuning`,
176
+ evidence: seen.slice(0, 5).map((s) => ({ line: s.line, at: s.at })),
177
+ });
178
+ }
179
+ return findings; // only the first edit per unit's events matters for this rule
180
+ }
181
+ sinceEditCount += 1;
182
+ seen.push(e);
183
+ }
184
+ return findings;
185
+ }
186
+ export function findVerifyHeavierThanImplement(unitEvents) {
187
+ const implementTurns = unitEvents.filter((e) => IMPLEMENT_STEPS.has(e.step) && (e.kind === "tool" || e.kind === "text")).length;
188
+ const reviewTurns = unitEvents.filter((e) => REVIEW_STEPS.has(e.step) && (e.kind === "tool" || e.kind === "text")).length;
189
+ if (reviewTurns <= implementTurns || reviewTurns === 0)
190
+ return [];
191
+ return [
192
+ {
193
+ id: "verify-heavier-than-implement",
194
+ severity: "info",
195
+ unitId: unitEvents[0].unit_id,
196
+ message: `review took ${reviewTurns} turns vs ${implementTurns} for implement — check verifier prompt or diff size`,
197
+ evidence: [],
198
+ },
199
+ ];
200
+ }
201
+ export function analyzeRun(events, opts) {
202
+ const now = opts.now ?? Date.now;
203
+ const runId = events.find((e) => e.t === "run.started")
204
+ ?.run_id ?? "";
205
+ const highTurnCount = opts.thresholds?.highTurnCount ?? DEFAULT_HIGH_TURN_COUNT;
206
+ const stepsByUnit = new Map();
207
+ const activityByUnit = new Map();
208
+ for (const e of events) {
209
+ if (isStep(e)) {
210
+ const list = stepsByUnit.get(e.unit_id) ?? [];
211
+ list.push(e);
212
+ stepsByUnit.set(e.unit_id, list);
213
+ }
214
+ else if (isAgentActivity(e)) {
215
+ const list = activityByUnit.get(e.unit_id) ?? [];
216
+ list.push(e);
217
+ activityByUnit.set(e.unit_id, list);
218
+ }
219
+ }
220
+ const unitIds = new Set([...stepsByUnit.keys(), ...activityByUnit.keys()]);
221
+ const units = [];
222
+ const findings = [];
223
+ for (const unitId of unitIds) {
224
+ const steps = stepsByUnit.get(unitId) ?? [];
225
+ const activity = activityByUnit.get(unitId) ?? [];
226
+ units.push(unitMetrics(unitId, steps, activity, opts.costByUnit));
227
+ findings.push(...findRepeatedRead(activity), ...findToolFailureCluster(activity), ...findHighTurnCount(activity, highTurnCount), ...findRetryWithoutDelta(activity), ...findExploreBeforeEdit(activity), ...findVerifyHeavierThanImplement(activity));
228
+ }
229
+ return {
230
+ runId,
231
+ generatedAt: new Date(now()).toISOString(),
232
+ units,
233
+ findings,
234
+ logRef: opts.logRef,
235
+ };
236
+ }
237
+ //# sourceMappingURL=index.js.map