halfcycle 0.3.30 → 0.3.31

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "halfcycle",
3
- "version": "0.3.30",
3
+ "version": "0.3.31",
4
4
  "description": "Halfcycle Method bundle — resolution-stub slash commands, remote method-delivery registration, and governance-hook wiring for a Halfcycle engagement repo. It ships NO worker role files: a project's roles are authored from that project's own recorded decisions at orchestration kickoff (FX-2, W3-F-27).",
5
5
  "commands": [
6
6
  {
@@ -15147,21 +15147,49 @@ function emitBlock(reason) {
15147
15147
  process.stdout.write(JSON.stringify({ decision: "block", reason }) + "\n");
15148
15148
  process.exit(0);
15149
15149
  }
15150
- function emitWarning(text) {
15151
- process.stdout.write(text + "\n");
15150
+ function nonBlockingReply(event, text) {
15151
+ if (event === "PostToolUse") {
15152
+ return JSON.stringify({
15153
+ systemMessage: text,
15154
+ hookSpecificOutput: { hookEventName: event, additionalContext: text }
15155
+ });
15156
+ }
15157
+ return JSON.stringify({ systemMessage: text });
15152
15158
  }
15153
- function emitInfraWarning(message) {
15154
- process.stdout.write(`[Halfcycle] WARNING: Guard service unavailable \u2014 ${message}. Session continues (fail-open).
15159
+ function hookReply(event) {
15160
+ const queued = [];
15161
+ return {
15162
+ notice(text) {
15163
+ queued.push(text);
15164
+ },
15165
+ block(reason) {
15166
+ const carried = queued.splice(0);
15167
+ return emitBlock(carried.length === 0 ? reason : [reason, ...carried].join("\n"));
15168
+ },
15169
+ flush() {
15170
+ if (queued.length === 0)
15171
+ return;
15172
+ const text = queued.splice(0).join("\n");
15173
+ const code = process.exitCode;
15174
+ if (code !== void 0 && code !== null && Number(code) !== 0) {
15175
+ writeSync(2, `${text}
15155
15176
  `);
15156
- }
15157
- function emitLocalFault(message) {
15158
- process.stdout.write(`[Halfcycle] WARNING: Local fault \u2014 ${message}. The guard service was not contacted. Session continues (fail-open).
15177
+ return;
15178
+ }
15179
+ process.stdout.write(`${nonBlockingReply(event, text)}
15159
15180
  `);
15181
+ }
15182
+ };
15160
15183
  }
15161
- function emitContractFault(message) {
15162
- process.stdout.write(`[Halfcycle] WARNING: Guard response could not be understood \u2014 ${message}
15163
- This is not a service outage: the guard service answered. The suspect is this repository's own installed halfcycle runner, which is out of date with what the service now sends. Update it: run "npx halfcycle" in this repository, then retry. Session continues (fail-open).
15164
- `);
15184
+ function infraWarningText(message) {
15185
+ return `[Halfcycle] WARNING: Guard service unavailable \u2014 ${message}. Session continues (fail-open).`;
15186
+ }
15187
+ function localFaultText(message) {
15188
+ return `[Halfcycle] WARNING: Local fault \u2014 ${message}. The guard service was not contacted. Session continues (fail-open).`;
15189
+ }
15190
+ function contractFaultText(message) {
15191
+ return `[Halfcycle] WARNING: Guard response could not be understood \u2014 ${message}
15192
+ This is not a service outage: the guard service answered. The suspect is this repository's own installed halfcycle runner, which is out of date with what the service now sends. Update it: run "npx halfcycle" in this repository, then retry. Session continues (fail-open).`;
15165
15193
  }
15166
15194
  var UNCONFIGURED_EXIT_TOOL_USE = 2;
15167
15195
  var UNCONFIGURED_EXIT_STOP_FAMILY = 1;
@@ -17212,6 +17240,11 @@ async function evaluateWithRefresh(opts) {
17212
17240
  // dist/hooks/post-tool-use.js
17213
17241
  var HANDLED_EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
17214
17242
  async function runPostToolUse() {
17243
+ const reply = hookReply("PostToolUse");
17244
+ await evaluateEdit(reply);
17245
+ reply.flush();
17246
+ }
17247
+ async function evaluateEdit(reply) {
17215
17248
  let input;
17216
17249
  try {
17217
17250
  const raw = await readStdin();
@@ -17228,7 +17261,7 @@ async function runPostToolUse() {
17228
17261
  return;
17229
17262
  const repoRootResult = getRepoRoot(process.cwd());
17230
17263
  if (!repoRootResult.ok) {
17231
- emitLocalFault(`Could not find git repo root: ${repoRootResult.message}`);
17264
+ reply.notice(localFaultText(`Could not find git repo root: ${repoRootResult.message}`));
17232
17265
  return;
17233
17266
  }
17234
17267
  const repoRoot = repoRootResult.value;
@@ -17280,10 +17313,10 @@ async function runPostToolUse() {
17280
17313
  return;
17281
17314
  }
17282
17315
  if (failure.kind === "contract") {
17283
- emitContractFault(failure.message);
17316
+ reply.notice(contractFaultText(failure.message));
17284
17317
  return;
17285
17318
  }
17286
- emitInfraWarning(failure.reason);
17319
+ reply.notice(infraWarningText(failure.reason));
17287
17320
  return;
17288
17321
  }
17289
17322
  const telemetryConfig = readTelemetryConfig();
@@ -17311,25 +17344,25 @@ async function runPostToolUse() {
17311
17344
  }, {
17312
17345
  warn: (msg) => process.stderr.write(msg + "\n")
17313
17346
  });
17314
- actOnEnvelope2(clientResult.envelope);
17347
+ actOnEnvelope2(clientResult.envelope, reply);
17315
17348
  }
17316
- function actOnEnvelope2(envelope) {
17349
+ function actOnEnvelope2(envelope, reply) {
17317
17350
  const notRun = notRunReport(envelope.notRun);
17318
17351
  const notRunBlock = notRun ? `
17319
17352
  ${notRun}` : "";
17320
17353
  if (envelope.guardsFired.length === 0) {
17321
17354
  if (notRun)
17322
- emitWarning(notRun);
17355
+ reply.notice(notRun);
17323
17356
  return;
17324
17357
  }
17325
17358
  if (envelope.blocking) {
17326
17359
  const guardLines = envelope.guardsFired.map((g) => ` [${g.severity.toUpperCase()}] ${g.patternRef}: ${g.explanation}`).join("\n");
17327
17360
  const reason = `Guard blocked: ${envelope.explanation}
17328
17361
  ${guardLines}${notRunBlock}`;
17329
- emitBlock(reason);
17362
+ reply.block(reason);
17330
17363
  } else {
17331
17364
  const guardLines = envelope.guardsFired.map((g) => ` [${g.severity.toUpperCase()}] ${g.patternRef}: ${g.explanation}`).join("\n");
17332
- emitWarning(`[Halfcycle] Guard warning: ${envelope.explanation}
17365
+ reply.notice(`[Halfcycle] Guard warning: ${envelope.explanation}
17333
17366
  ${guardLines}${notRunBlock}`);
17334
17367
  }
17335
17368
  }
@@ -17464,6 +17497,10 @@ function orphanSweep(repoRoot, refKnown, keepSessionId, now = Date.now()) {
17464
17497
  }
17465
17498
 
17466
17499
  // dist/hooks/session-diff.js
17500
+ var STOP_HOOK_EVENT = {
17501
+ stop: "Stop",
17502
+ "subagent-stop": "SubagentStop"
17503
+ };
17467
17504
  function resolveDiffBase2(repoRoot, sessionId) {
17468
17505
  const ref = readMarker(repoRoot, sessionId);
17469
17506
  if (ref === null)
@@ -17495,9 +17532,14 @@ function collectDiff(repoRoot, base) {
17495
17532
  };
17496
17533
  }
17497
17534
  async function runSessionDiff(input, kind) {
17535
+ const reply = hookReply(STOP_HOOK_EVENT[kind]);
17536
+ await evaluateSession(input, kind, reply);
17537
+ reply.flush();
17538
+ }
17539
+ async function evaluateSession(input, kind, reply) {
17498
17540
  const repoRootResult = getRepoRoot(process.cwd());
17499
17541
  if (!repoRootResult.ok) {
17500
- emitLocalFault(`Could not find git repo root: ${repoRootResult.message}`);
17542
+ reply.notice(localFaultText(`Could not find git repo root: ${repoRootResult.message}`));
17501
17543
  return;
17502
17544
  }
17503
17545
  const repoRoot = repoRootResult.value;
@@ -17507,7 +17549,7 @@ async function runSessionDiff(input, kind) {
17507
17549
  const diffBase = base.kind;
17508
17550
  const collected = collectDiff(repoRoot, base);
17509
17551
  if (!collected.ok) {
17510
- emitLocalFault(`Could not get session diff: ${collected.message}`);
17552
+ reply.notice(localFaultText(`Could not get session diff: ${collected.message}`));
17511
17553
  return;
17512
17554
  }
17513
17555
  const { getDiff } = collected;
@@ -17566,10 +17608,10 @@ async function runSessionDiff(input, kind) {
17566
17608
  return;
17567
17609
  }
17568
17610
  if (failure.kind === "contract") {
17569
- emitContractFault(failure.message);
17611
+ reply.notice(contractFaultText(failure.message));
17570
17612
  return;
17571
17613
  }
17572
- emitInfraWarning(failure.reason);
17614
+ reply.notice(infraWarningText(failure.reason));
17573
17615
  return;
17574
17616
  }
17575
17617
  const telemetryConfig = readTelemetryConfig();
@@ -17595,28 +17637,28 @@ async function runSessionDiff(input, kind) {
17595
17637
  warn: (msg) => process.stderr.write(msg + "\n")
17596
17638
  });
17597
17639
  writeDedupe(repoRoot, sessionId, { base: baseKey, digest });
17598
- actOnEnvelope3(clientResult.envelope, kind, input.stop_hook_active === true);
17640
+ actOnEnvelope3(clientResult.envelope, kind, input.stop_hook_active === true, reply);
17599
17641
  }
17600
- function actOnEnvelope3(envelope, kind, neverBlock) {
17642
+ function actOnEnvelope3(envelope, kind, neverBlock, reply) {
17601
17643
  const notRun = notRunReport(envelope.notRun);
17602
17644
  const notRunBlock = notRun ? `
17603
17645
  ${notRun}` : "";
17604
17646
  if (envelope.guardsFired.length === 0) {
17605
17647
  if (notRun)
17606
- emitWarning(notRun);
17648
+ reply.notice(notRun);
17607
17649
  return;
17608
17650
  }
17609
17651
  const where = kind === "stop" ? "session stop" : "session stop";
17610
17652
  const guardLines = envelope.guardsFired.map((g) => ` [${g.severity.toUpperCase()}] ${g.patternRef}: ${g.explanation}`).join("\n");
17611
17653
  if (envelope.blocking && !neverBlock) {
17612
- emitBlock(`Guard blocked at ${where}: ${envelope.explanation}
17654
+ reply.block(`Guard blocked at ${where}: ${envelope.explanation}
17613
17655
  ${guardLines}${notRunBlock}`);
17614
17656
  } else if (envelope.blocking && neverBlock) {
17615
- emitWarning(`[Halfcycle] Guard would block at ${where} but the stop was already continued (stop_hook_active) \u2014 surfacing as a warning:
17657
+ reply.notice(`[Halfcycle] Guard would block at ${where} but the stop was already continued (stop_hook_active) \u2014 surfacing as a warning:
17616
17658
  ${envelope.explanation}
17617
17659
  ${guardLines}${notRunBlock}`);
17618
17660
  } else {
17619
- emitWarning(`[Halfcycle] Guard warning at ${where}: ${envelope.explanation}
17661
+ reply.notice(`[Halfcycle] Guard warning at ${where}: ${envelope.explanation}
17620
17662
  ${guardLines}${notRunBlock}`);
17621
17663
  }
17622
17664
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "halfcycle",
3
- "version": "0.3.30",
3
+ "version": "0.3.31",
4
4
  "description": "Your coding agents are the engineers. Halfcycle is the rest of the team: product owner, architect, planner and independent reviewer for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",