@hmharness/agent 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21,6 +21,12 @@ export interface PipelineReport {
21
21
  finalVerdict: 'PASS' | 'FAIL' | 'none';
22
22
  stages: StageRecord[];
23
23
  repairsUsed: number;
24
+ /** set when opts.release ran on a PASS verdict (ADR-0008) */
25
+ release?: {
26
+ projectId: string;
27
+ version: string;
28
+ checkpointId: string;
29
+ };
24
30
  }
25
31
  export interface PipelineOptions {
26
32
  task: string;
@@ -53,6 +59,14 @@ export interface PipelineOptions {
53
59
  /** injectable runner (tests); default = domain-harmony runDeviceTest */
54
60
  runDeviceTestImpl?: (o: DeviceTestOptions) => Promise<DeviceTestStep[]>;
55
61
  };
62
+ /** V3 release slice (ADR-0008): when the final verdict is PASS, bind the
63
+ * work to the workspace's Project Runtime (M8): attach the run, checkpoint
64
+ * the tree, and record a release pinned to that checkpoint. FAIL/budget
65
+ * never release - no verdict, no version. */
66
+ release?: {
67
+ version: string;
68
+ notes?: string;
69
+ };
56
70
  }
57
71
  /** Pull the judge's verdict line out of the final text; absence = FAIL
58
72
  * (an judge that did not follow the contract did not render a verdict). */
package/dist/pipeline.js CHANGED
@@ -73,6 +73,7 @@ export async function runPipeline(opts) {
73
73
  let repairs = 0;
74
74
  let spent = 0;
75
75
  let status = 'completed';
76
+ let releaseTick;
76
77
  const startedAt = new Date().toISOString();
77
78
  const runStage = async (stage, attempt, directive) => {
78
79
  // maxTurns is only a soft checkpoint in the kernel loop - the HARD caps
@@ -183,7 +184,23 @@ export async function runPipeline(opts) {
183
184
  }
184
185
  judge = await runStage('judge', repairs + 1, DIRECTIVES.judge(opts.task, evidenceSummary(stages)));
185
186
  }
186
- return await finish(judge && judge.verdict !== 'n/a' ? judge.verdict : 'FAIL');
187
+ const verdict = judge && judge.verdict !== 'n/a' ? judge.verdict : 'FAIL';
188
+ // V3 release slice: only a PASS verdict earns a version (ADR-0008)
189
+ if (verdict === 'PASS' && opts.release) {
190
+ try {
191
+ const P = await import("./project.js");
192
+ const proj = await P.projectFor(opts.home, opts.ctx.cwd);
193
+ await P.attachRun(opts.home, proj, pipelineId);
194
+ const cp = await P.checkpointProject(opts.home, proj, `pipeline ${pipelineId}`);
195
+ await P.releaseProject(opts.home, proj, opts.release.version, opts.release.notes ?? `pipeline ${pipelineId} VERDICT: PASS (${stages.length} stages, ${repairs} repairs)`);
196
+ releaseTick = { projectId: proj.projectId, version: opts.release.version, checkpointId: cp.id };
197
+ }
198
+ catch (err) {
199
+ // release is bookkeeping - record the failure, never fail the pipeline
200
+ stages.push({ stage: 'judge', attempt: 0, verdict: 'n/a', text: `release binding failed: ${String(err).slice(0, 200)}`, turns: 0, toolUses: 0, reason: 'final' });
201
+ }
202
+ }
203
+ return await finish(verdict);
187
204
  }
188
205
  catch (err) {
189
206
  status = 'error';
@@ -200,6 +217,7 @@ export async function runPipeline(opts) {
200
217
  finalVerdict: verdict,
201
218
  stages,
202
219
  repairsUsed: repairs,
220
+ ...(releaseTick ? { release: releaseTick } : {}),
203
221
  };
204
222
  try {
205
223
  await writeFile(join(dir, 'pipeline.report.json'), JSON.stringify(report, null, 2) + '\n', 'utf8');
package/dist/project.js CHANGED
@@ -192,6 +192,13 @@ export async function checkpointProject(home, rec, label) {
192
192
  else {
193
193
  const rel = `artifacts/cp-${Date.now().toString(36)}`;
194
194
  const dest = join(projDir(home, rec.projectId), rel);
195
+ // the copy fallback cannot handle the workspace containing the destination
196
+ // (workspace == home in degenerate setups) - say so plainly instead of
197
+ // letting cp throw EINVAL halfway through
198
+ const norm = (p) => resolve(p).replace(/[\\/]+$/, '').toLowerCase();
199
+ if (norm(dest).startsWith(norm(rec.workspace) + '\\') || norm(dest).startsWith(norm(rec.workspace) + '/')) {
200
+ throw new Error(`checkpoint copy fallback requires the project workspace (${rec.workspace}) to be outside HMH_HOME (${home})`);
201
+ }
195
202
  await mkdir(dest, { recursive: true });
196
203
  await cp(rec.workspace, dest, { recursive: true, filter: (src) => !COPY_SKIP.has(src.split(/[\\/]/).pop() ?? '') });
197
204
  let files = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hmharness/agent",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "hmharness agent execution layer: base tools, system prompt, sub-agent spawn, and the shared task runner that frontends (cli, web) drive.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",