halfcycle 0.4.16 → 0.4.18

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.4.16",
3
+ "version": "0.4.18",
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
  {
@@ -15715,12 +15715,25 @@ var seatCoverageSchema = external_exports.object({
15715
15715
  message: "conformant cannot exceed enrolled",
15716
15716
  path: ["conformant"]
15717
15717
  });
15718
+ var LEFT_FOR_OWNER_TEXT_MAX = 300;
15719
+ var LEFT_FOR_OWNER_ENTRIES_MAX = 500;
15720
+ var leftForOwnerDecisionSchema = external_exports.object({
15721
+ item: plainText(LEFT_FOR_OWNER_TEXT_MAX),
15722
+ question: plainText(LEFT_FOR_OWNER_TEXT_MAX)
15723
+ }).strict();
15724
+ var leftForOwnerSchema = external_exports.object({
15725
+ tasks: external_exports.array(plainText(LEFT_FOR_OWNER_TEXT_MAX)).max(LEFT_FOR_OWNER_ENTRIES_MAX),
15726
+ waiting: external_exports.array(leftForOwnerDecisionSchema).max(LEFT_FOR_OWNER_ENTRIES_MAX)
15727
+ }).strict().refine((v) => v.tasks.length + v.waiting.length > 0, {
15728
+ message: "is sent only when some work was left for the owner"
15729
+ });
15718
15730
  var observedCloseSchema = external_exports.object({
15719
15731
  sequence: external_exports.number().int().min(1),
15720
15732
  closedAt: utcIso8601,
15721
15733
  closedBy: closedBySchema,
15722
15734
  crossed: crossedSchema,
15723
- seatCoverage: seatCoverageSchema.nullable()
15735
+ seatCoverage: seatCoverageSchema.nullable(),
15736
+ leftForOwner: leftForOwnerSchema.optional()
15724
15737
  }).strict();
15725
15738
  var couldNotRunCheckSchema = external_exports.object({
15726
15739
  check: plainText(),
@@ -18282,7 +18295,8 @@ var chatReviewAcceptRequestSchema = external_exports.object({
18282
18295
  act: external_exports.literal("accept"),
18283
18296
  reference: phaseIdentitySchema,
18284
18297
  reviewVersion: external_exports.number().int().min(0),
18285
- parts: external_exports.array(systemPartIdSchema).max(REVIEW_PARTS_MAX)
18298
+ parts: external_exports.array(systemPartIdSchema).max(REVIEW_PARTS_MAX),
18299
+ waitingClaims: external_exports.literal(true).optional()
18286
18300
  }).strict();
18287
18301
  function notedRequest(act) {
18288
18302
  return external_exports.object({
@@ -18297,12 +18311,20 @@ var chatReviewQuestionRequestSchema = notedRequest("question");
18297
18311
  var chatReviewSendBackRequestSchema = notedRequest("send-back");
18298
18312
  var chatReviewCarryRequestSchema = notedRequest("carry");
18299
18313
  var chatReviewDropRequestSchema = notedRequest("drop");
18314
+ var chatReviewRejectRequestSchema = external_exports.object({
18315
+ act: external_exports.literal("reject"),
18316
+ reference: phaseIdentitySchema,
18317
+ reviewVersion: external_exports.number().int().min(0),
18318
+ task: buildTaskIdSchema,
18319
+ note: reviewNoteWireSchema
18320
+ }).strict();
18300
18321
  var chatReviewRequestSchema = external_exports.discriminatedUnion("act", [
18301
18322
  chatReviewAcceptRequestSchema,
18302
18323
  chatReviewQuestionRequestSchema,
18303
18324
  chatReviewSendBackRequestSchema,
18304
18325
  chatReviewCarryRequestSchema,
18305
- chatReviewDropRequestSchema
18326
+ chatReviewDropRequestSchema,
18327
+ chatReviewRejectRequestSchema
18306
18328
  ]);
18307
18329
  function bareOutcome2(outcome) {
18308
18330
  return external_exports.object({ outcome: external_exports.literal(outcome) }).strict();
@@ -18315,7 +18337,8 @@ var chatReviewResultSchema = external_exports.discriminatedUnion("outcome", [
18315
18337
  ...reviewDecisionResultSchema.options.filter(notTheConsoleNotDecidable),
18316
18338
  bareOutcome2("show-first"),
18317
18339
  bareOutcome2("not-offered"),
18318
- chatAmbiguousSchema
18340
+ chatAmbiguousSchema,
18341
+ bareOutcome2("nothing-waiting")
18319
18342
  ]);
18320
18343
  var reviewAnswerRequestSchema = external_exports.object({
18321
18344
  part: systemPartIdSchema,
@@ -18411,7 +18434,7 @@ var ownerWaitDeclarationSchema = external_exports.object({
18411
18434
  path: ["fixedItem"]
18412
18435
  });
18413
18436
  var ownerWaitDeclarationsSchema = external_exports.object({ declarations: external_exports.array(ownerWaitDeclarationSchema) }).strict();
18414
- var OWNER_WAIT_TOO_MANY = `This project already has ${String(OWNER_WAIT_ADDITIONS_MAX)} things added to what waits for the owner. Ask the owner to take one off in the console first.`;
18437
+ var OWNER_WAIT_TOO_MANY = `This project already has ${String(OWNER_WAIT_ADDITIONS_MAX)} things added to what waits for the owner. Ask the owner to take one off on their settings page in the console first; it cannot be done by typing in chat.`;
18415
18438
 
18416
18439
  // ../events/dist/pacing.js
18417
18440
  var CLAUDE_PLANS = Object.freeze([
@@ -23344,7 +23367,7 @@ function distinctBullets(explanations) {
23344
23367
  return bullets([...new Set(explanations)]);
23345
23368
  }
23346
23369
  function foundLine(count) {
23347
- return count === 1 ? "Halfcycle found 1 small thing to fix in this turn's work. The agent gets the details with your next message and will fix it; there is nothing for you to do." : `Halfcycle found ${count} small things to fix in this turn's work. The agent gets the details with your next message and will fix them; there is nothing for you to do.`;
23370
+ return count === 1 ? `Halfcycle found 1 small thing to fix in this turn's work. The agent gets the details with your next message and will fix it; there is nothing for you to do. Say "fix it" to have it fixed.` : `Halfcycle found ${count} small things to fix in this turn's work. The agent gets the details with your next message and will fix them; there is nothing for you to do. Say "fix them" to have them fixed.`;
23348
23371
  }
23349
23372
  function fixedLine(count, all) {
23350
23373
  if (all)
@@ -23354,7 +23377,7 @@ function fixedLine(count, all) {
23354
23377
  function stillOpenMessage(explanations) {
23355
23378
  const n = explanations.length;
23356
23379
  const head = n === 1 ? "Halfcycle: 1 thing it found is still not fixed after the agent's last turn." : `Halfcycle: ${n} things it found are still not fixed after the agent's last turn.`;
23357
- const tail = n === 1 ? "Ask the agent to fix it, or tell it to leave it as it is if that is what you want." : "Ask the agent to fix them, or tell it to leave them as they are if that is what you want.";
23380
+ const tail = n === 1 ? 'Ask the agent to fix it, or tell it to leave it as it is if that is what you want. Say "fix it" to have it fixed.' : 'Ask the agent to fix them, or tell it to leave them as they are if that is what you want. Say "fix them" to have them fixed.';
23358
23381
  return `${head}
23359
23382
  ${bullets(explanations)}
23360
23383
  ${tail}`;
@@ -30416,6 +30439,9 @@ function reviewPhraseShape(prompt) {
30416
30439
  const line2 = prompt.trim();
30417
30440
  if (LINE_BREAK.test(line2))
30418
30441
  return null;
30442
+ const reject = REJECT_VERB.exec(line2);
30443
+ if (reject !== null)
30444
+ return rejectShape(line2.slice(reject[0].length));
30419
30445
  for (const [prefix, act] of VERBS) {
30420
30446
  const verb = prefix.exec(line2);
30421
30447
  if (verb === null)
@@ -30433,6 +30459,21 @@ function reviewPhraseShape(prompt) {
30433
30459
  }
30434
30460
  return null;
30435
30461
  }
30462
+ var REJECT_VERB = /^reject /i;
30463
+ function rejectShape(rest) {
30464
+ const colon = rest.indexOf(":");
30465
+ if (colon === -1)
30466
+ return null;
30467
+ const head = rest.slice(0, colon);
30468
+ const slash = head.indexOf("/");
30469
+ if (slash === -1)
30470
+ return null;
30471
+ const reference = head.slice(0, slash);
30472
+ const task = head.slice(slash + 1);
30473
+ if (!isValidPhaseIdentity(reference) || /\s/.test(task) || !buildTaskIdSchema.safeParse(task).success)
30474
+ return null;
30475
+ return { kind: "reject", reference, task, words: rest.slice(colon + 1).trim() };
30476
+ }
30436
30477
  function partShape(act, head, words) {
30437
30478
  const slash = head.indexOf("/");
30438
30479
  if (slash === -1)
@@ -30445,10 +30486,15 @@ function partShape(act, head, words) {
30445
30486
  }
30446
30487
  function matchReviewPhraseShape(shape, reviews) {
30447
30488
  const resolved = resolvePhaseReference(shape.reference, reviews);
30448
- const act = shape.kind === "phase" ? "accept" : shape.act;
30489
+ const act = shape.kind === "phase" ? "accept" : shape.kind === "reject" ? "reject" : shape.act;
30449
30490
  if (resolved.kind === "ambiguous")
30450
30491
  return { kind: "ambiguous", act, entries: resolved.entries };
30451
30492
  const entry = resolved.kind === "found" ? resolved.entry : null;
30493
+ if (shape.kind === "reject") {
30494
+ if (entry === null)
30495
+ return { kind: "show-first", act: "reject" };
30496
+ return { kind: "reject", entry, reference: shape.reference, task: shape.task, words: shape.words };
30497
+ }
30452
30498
  if (shape.kind === "phase") {
30453
30499
  if (entry === null)
30454
30500
  return { kind: "nothing" };
@@ -30637,6 +30683,8 @@ function reviewWrongActLine(name, built) {
30637
30683
  }
30638
30684
  function wordsActNoun(act) {
30639
30685
  switch (act) {
30686
+ case "reject":
30687
+ return "a rejection";
30640
30688
  case "question":
30641
30689
  return "a question";
30642
30690
  case "send-back":
@@ -30673,7 +30721,10 @@ function reviewPartNotListedLine(part, phase) {
30673
30721
  return line(`${NOTHING_RECORDED}: "${part}" is not a part of the review of ${namedPhase(phase)} as it was listed here.`);
30674
30722
  }
30675
30723
  function reviewNothingToAcceptLine(phase) {
30676
- return line(`${NOTHING_RECORDED}: no built part of ${namedPhase(phase)} waits for your decision.`);
30724
+ return line(`${NOTHING_RECORDED}: no built part and no check nobody ran of ${namedPhase(phase)} waits for your decision.`);
30725
+ }
30726
+ function reviewNoClaimWaitingLine(task, phase) {
30727
+ return line(`${NOTHING_RECORDED}: no check nobody ran on task "${task}" of ${namedPhase(phase)} waits for your answer. ${LIST_AGAIN} and copy the task from its reject line.`);
30677
30728
  }
30678
30729
  function reviewListedTooLongAgoLine(phase) {
30679
30730
  return line(`${NOTHING_RECORDED}: ${namedPhase(phase)} was listed an hour or more ago. ${LIST_AGAIN}, then type the line again within the hour.`);
@@ -30749,9 +30800,12 @@ function reviewRecordedLine(decided, phase, waiting) {
30749
30800
  }
30750
30801
  function reviewBulkRecordedLine(accepted, phase, waiting) {
30751
30802
  const named = namedPhase(phase);
30752
- const first = accepted === 1 ? `Recorded in your session: 1 part of ${named} accepted.` : `Recorded in your session: ${accepted} parts of ${named} accepted.`;
30803
+ const first = accepted === 0 ? `Recorded in your session: any check nobody ran of ${named} that waited is accepted.` : accepted === 1 ? `Recorded in your session: 1 part of ${named} accepted, with any check nobody ran that waited.` : `Recorded in your session: ${accepted} parts of ${named} accepted, with any check nobody ran that waited.`;
30753
30804
  return line(`${first} ${stillWaiting(waiting)}`);
30754
30805
  }
30806
+ function reviewClaimsRejectedLine(task, phase, waiting) {
30807
+ return line(`Recorded in your session: the checks nobody ran on task "${task}" are rejected, with your words. ${stillWaitingOf(phase, waiting)}`);
30808
+ }
30755
30809
  function reviewClosedLine(phase) {
30756
30810
  return `Recorded in your session. That was the last decision: phase ${namedPhase(phase)} is closed. Run its close step now, then tell the person what it says.`;
30757
30811
  }
@@ -30778,12 +30832,16 @@ function reviewLineForAnswer(context, answer) {
30778
30832
  switch (answer.outcome) {
30779
30833
  case "recorded": {
30780
30834
  if (answer.closed) {
30781
- return answer.decided.length === 0 ? reviewNoPartsClosedLine(phase) : reviewClosedLine(phase);
30835
+ return context.listedParts === 0 ? reviewNoPartsClosedLine(phase) : reviewClosedLine(phase);
30782
30836
  }
30837
+ if (context.task !== null)
30838
+ return reviewClaimsRejectedLine(context.task, phase, answer.waiting);
30783
30839
  if (context.bulk)
30784
30840
  return reviewBulkRecordedLine(answer.decided.length, phase, answer.waiting);
30785
30841
  return reviewRecordedLine(answer.decided[0], phase, answer.waiting);
30786
30842
  }
30843
+ case "nothing-waiting":
30844
+ return context.task !== null ? reviewNoClaimWaitingLine(context.task, phase) : reviewNothingToAcceptLine(phase);
30787
30845
  case "review-changed":
30788
30846
  return reviewChangedLine(phase);
30789
30847
  case "no-review":
@@ -30964,14 +31022,22 @@ async function checkAndSendReview(match, engagementId, credential, env, opts) {
30964
31022
  return reviewListedTooLongAgoLine(entry.phase);
30965
31023
  let candidate;
30966
31024
  if (match.kind === "accept") {
30967
- if (entry.parts.length > 0 && match.parts.length === 0)
30968
- return reviewNothingToAcceptLine(entry.phase);
30969
31025
  candidate = {
30970
31026
  act: "accept",
30971
31027
  reference: match.reference,
30972
31028
  reviewVersion: entry.reviewVersion,
30973
- parts: match.parts.map((p) => p.part)
31029
+ parts: match.parts.map((p) => p.part),
31030
+ waitingClaims: true
30974
31031
  };
31032
+ } else if (match.kind === "reject") {
31033
+ const words = match.words;
31034
+ if (words === "")
31035
+ return reviewNoWordsLine("reject");
31036
+ if (!reviewNoteWireSchema.safeParse(words).success)
31037
+ return reviewWordsTooLongLine();
31038
+ if (looksLikeSecret(words))
31039
+ return reviewWordsLookSecretLine();
31040
+ candidate = { act: "reject", reference: match.reference, reviewVersion: entry.reviewVersion, task: match.task, note: words };
30975
31041
  } else {
30976
31042
  const { act, part } = match;
30977
31043
  if (!reviewActsFor(part.built).includes(act))
@@ -31024,7 +31090,14 @@ async function postReview(bulk, entry, request, engagementId, credential, env, o
31024
31090
  return reviewCannotReachLine();
31025
31091
  moveListedReviewForward(engagementId, opts.home, entry, answer.reviewVersion, answer.decided);
31026
31092
  }
31027
- return reviewLineForAnswer({ act: request.act, bulk, phase: entry.phase, number: entry.number }, answer);
31093
+ return reviewLineForAnswer({
31094
+ act: request.act,
31095
+ bulk,
31096
+ phase: entry.phase,
31097
+ number: entry.number,
31098
+ listedParts: entry.parts.length,
31099
+ task: request.act === "reject" ? request.task : null
31100
+ }, answer);
31028
31101
  } catch {
31029
31102
  return reviewCannotReachLine();
31030
31103
  } finally {
@@ -1 +1 @@
1
- {"version":3,"file":"render-v2.d.ts","sourceRoot":"","sources":["../../src/build-record/render-v2.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAmBL,KAAK,WAAW,EAQjB,MAAM,mBAAmB,CAAC;AA8J3B;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAepD;AAKD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CASpD;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOvD;AA2MD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAO/D;AAwVD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAgFvE"}
1
+ {"version":3,"file":"render-v2.d.ts","sourceRoot":"","sources":["../../src/build-record/render-v2.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAmBL,KAAK,WAAW,EAQjB,MAAM,mBAAmB,CAAC;AA8J3B;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAepD;AAKD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CASpD;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOvD;AA2PD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAO/D;AA2WD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAgFvE"}
package/dist/cli.js CHANGED
@@ -1446,12 +1446,25 @@ var seatCoverageSchema = z6.object({
1446
1446
  message: "conformant cannot exceed enrolled",
1447
1447
  path: ["conformant"]
1448
1448
  });
1449
+ var LEFT_FOR_OWNER_TEXT_MAX = 300;
1450
+ var LEFT_FOR_OWNER_ENTRIES_MAX = 500;
1451
+ var leftForOwnerDecisionSchema = z6.object({
1452
+ item: plainText(LEFT_FOR_OWNER_TEXT_MAX),
1453
+ question: plainText(LEFT_FOR_OWNER_TEXT_MAX)
1454
+ }).strict();
1455
+ var leftForOwnerSchema = z6.object({
1456
+ tasks: z6.array(plainText(LEFT_FOR_OWNER_TEXT_MAX)).max(LEFT_FOR_OWNER_ENTRIES_MAX),
1457
+ waiting: z6.array(leftForOwnerDecisionSchema).max(LEFT_FOR_OWNER_ENTRIES_MAX)
1458
+ }).strict().refine((v) => v.tasks.length + v.waiting.length > 0, {
1459
+ message: "is sent only when some work was left for the owner"
1460
+ });
1449
1461
  var observedCloseSchema = z6.object({
1450
1462
  sequence: z6.number().int().min(1),
1451
1463
  closedAt: utcIso8601,
1452
1464
  closedBy: closedBySchema,
1453
1465
  crossed: crossedSchema,
1454
- seatCoverage: seatCoverageSchema.nullable()
1466
+ seatCoverage: seatCoverageSchema.nullable(),
1467
+ leftForOwner: leftForOwnerSchema.optional()
1455
1468
  }).strict();
1456
1469
  var couldNotRunCheckSchema = z6.object({
1457
1470
  check: plainText(),
@@ -4838,7 +4851,8 @@ var chatReviewAcceptRequestSchema = z30.object({
4838
4851
  act: z30.literal("accept"),
4839
4852
  reference: phaseIdentitySchema,
4840
4853
  reviewVersion: z30.number().int().min(0),
4841
- parts: z30.array(systemPartIdSchema).max(REVIEW_PARTS_MAX)
4854
+ parts: z30.array(systemPartIdSchema).max(REVIEW_PARTS_MAX),
4855
+ waitingClaims: z30.literal(true).optional()
4842
4856
  }).strict();
4843
4857
  function notedRequest(act) {
4844
4858
  return z30.object({
@@ -4853,12 +4867,20 @@ var chatReviewQuestionRequestSchema = notedRequest("question");
4853
4867
  var chatReviewSendBackRequestSchema = notedRequest("send-back");
4854
4868
  var chatReviewCarryRequestSchema = notedRequest("carry");
4855
4869
  var chatReviewDropRequestSchema = notedRequest("drop");
4870
+ var chatReviewRejectRequestSchema = z30.object({
4871
+ act: z30.literal("reject"),
4872
+ reference: phaseIdentitySchema,
4873
+ reviewVersion: z30.number().int().min(0),
4874
+ task: buildTaskIdSchema,
4875
+ note: reviewNoteWireSchema
4876
+ }).strict();
4856
4877
  var chatReviewRequestSchema = z30.discriminatedUnion("act", [
4857
4878
  chatReviewAcceptRequestSchema,
4858
4879
  chatReviewQuestionRequestSchema,
4859
4880
  chatReviewSendBackRequestSchema,
4860
4881
  chatReviewCarryRequestSchema,
4861
- chatReviewDropRequestSchema
4882
+ chatReviewDropRequestSchema,
4883
+ chatReviewRejectRequestSchema
4862
4884
  ]);
4863
4885
  function bareOutcome2(outcome) {
4864
4886
  return z30.object({ outcome: z30.literal(outcome) }).strict();
@@ -4871,7 +4893,8 @@ var chatReviewResultSchema = z30.discriminatedUnion("outcome", [
4871
4893
  ...reviewDecisionResultSchema.options.filter(notTheConsoleNotDecidable),
4872
4894
  bareOutcome2("show-first"),
4873
4895
  bareOutcome2("not-offered"),
4874
- chatAmbiguousSchema
4896
+ chatAmbiguousSchema,
4897
+ bareOutcome2("nothing-waiting")
4875
4898
  ]);
4876
4899
  var reviewAnswerRequestSchema = z30.object({
4877
4900
  part: systemPartIdSchema,
@@ -4979,11 +5002,15 @@ function ownerWaitItemPhraseOf(item) {
4979
5002
  const bare = cut.endsWith(".") ? cut.slice(0, -1) : cut;
4980
5003
  return bare.charAt(0).toLowerCase() + bare.slice(1);
4981
5004
  }
4982
- var OWNER_WAIT_TOO_MANY = `This project already has ${String(OWNER_WAIT_ADDITIONS_MAX)} things added to what waits for the owner. Ask the owner to take one off in the console first.`;
5005
+ var OWNER_WAIT_TOO_MANY = `This project already has ${String(OWNER_WAIT_ADDITIONS_MAX)} things added to what waits for the owner. Ask the owner to take one off on their settings page in the console first; it cannot be done by typing in chat.`;
5006
+ function ownerWaitClosedItemOf(item) {
5007
+ if (!("fixed" in item))
5008
+ return `"${item.addedWords}" (added by an agent)`;
5009
+ const phrase = ownerWaitItemPhraseOf(item.fixed);
5010
+ return phrase.charAt(0).toUpperCase() + phrase.slice(1);
5011
+ }
4983
5012
  function ownerWaitClosedLineOf(item, question2) {
4984
- const phrase = "fixed" in item ? ownerWaitItemPhraseOf(item.fixed) : "";
4985
- const which = "fixed" in item ? phrase.charAt(0).toUpperCase() + phrase.slice(1) : `"${item.addedWords}" (added by an agent)`;
4986
- return `Waited for you: ${which} \u2014 the agent asked: "${question2}"`;
5013
+ return `Waited for you: ${ownerWaitClosedItemOf(item)} \u2014 the agent asked: "${question2}"`;
4987
5014
  }
4988
5015
  var OWNER_WAIT_DECLARATIONS_READ_FAILED = "Halfcycle could not read the decisions that waited for you.";
4989
5016
 
@@ -5092,7 +5119,7 @@ function claudePlanFromAnswer(answer) {
5092
5119
  return CLAUDE_PLANS[Number(trimmed) - 1]?.id ?? null;
5093
5120
  }
5094
5121
  function claudePlanRememberedText(plan, settingsUrl) {
5095
- return `Using the plan you gave before: ${claudePlanOf(plan).words}. You can change it at ${settingsUrl}.`;
5122
+ return `Using the plan you gave before: ${claudePlanOf(plan).words}. You can change it on your settings page, not by typing in chat: ${settingsUrl}`;
5096
5123
  }
5097
5124
  function paceP1Of(n, minutes) {
5098
5125
  const first = `Halfcycle suggests running ${paceAgentsOf(n)} at once.`;
@@ -5107,7 +5134,7 @@ function paceP3Of(window) {
5107
5134
  }
5108
5135
  function paceP4Of(n, settingsUrl) {
5109
5136
  const first = `Halfcycle does not know which Claude plan you use, so it starts with ${String(n)}.`;
5110
- return settingsUrl === null ? first : `${first} You can say which at ${settingsUrl}.`;
5137
+ return settingsUrl === null ? first : `${first} You can say which on your settings page, not by typing in chat: ${settingsUrl}`;
5111
5138
  }
5112
5139
  var PACE_P5 = "This build may use all of your allowance. It will not slow down to leave room for you.";
5113
5140
  function paceP6Of(k) {
@@ -5115,7 +5142,7 @@ function paceP6Of(k) {
5115
5142
  }
5116
5143
  function paceP7Of(settingsUrl) {
5117
5144
  const first = "Say a different number to change it.";
5118
- return settingsUrl === null ? first : `${first} To let builds use all of your allowance, change it at ${settingsUrl}.`;
5145
+ return settingsUrl === null ? first : `${first} To let builds use all of your allowance, change it on your settings page, not by typing in chat: ${settingsUrl}`;
5119
5146
  }
5120
5147
  function paceStartLinesOf(s) {
5121
5148
  const lines = [paceP1Of(s.number, s.minutes)];
@@ -5330,8 +5357,9 @@ var savedSchema = z33.object({
5330
5357
  function autonomyA13(accepted, carried) {
5331
5358
  return `Your autonomy setting accepted the ${String(accepted)} built parts and carried ${String(carried)} unbuilt parts forward. The phase is closed.`;
5332
5359
  }
5333
- function autonomyA14(count, parts) {
5334
- return `The phase waits for you: ${String(count)} tests the agents said pass were not run by Halfcycle, so ${namesOf(parts)} wait for you too. Only you can accept or reject those tests and decide those parts. See the console's review.`;
5360
+ function autonomyA14(count, parts, acceptLine) {
5361
+ const typed = acceptLine === null ? 'say "review what was built" and type the lines it gives.' : `say "review what was built", then type ${acceptLine} to accept them with those parts, or type the reject line it gives for a task's tests.`;
5362
+ return `The phase waits for you: ${String(count)} tests the agents said pass were not run by Halfcycle, so ${namesOf(parts)} wait for you too. Only you can accept or reject those tests. In your own Claude Code session, ` + typed;
5335
5363
  }
5336
5364
  function autonomyA15(count) {
5337
5365
  return `The phase waits for you: ${String(count)} parts were built without an approved scope, and only you can decide them.`;
@@ -7395,12 +7423,13 @@ ${GENERATED_USER_PROMPT_SCOPE_HEADER} the Halfcycle installer, now also for the
7395
7423
  # own on or off, by typing one line as the whole prompt in their own Claude
7396
7424
  # Code session: "approve <phase>", "send back <phase>: <note>", "accept
7397
7425
  # <phase>/<part>", "question <phase>/<part>: <words>", "carry <phase>/<part>:
7398
- # <words>", "drop <phase>/<part>: <words>", "autonomy on" or "autonomy off".
7426
+ # <words>", "drop <phase>/<part>: <words>", "reject <phase>/<task>: <words>",
7427
+ # "autonomy on" or "autonomy off".
7399
7428
  #
7400
7429
  # AN ORDINARY PROMPT STARTS NOTHING. This script reads the hook input once and
7401
7430
  # looks only at the start of the prompt's value. Unless the prompt begins, after
7402
7431
  # any whitespace (typed, or escaped in the input), with "approve ", "send back ",
7403
- # "accept ", "question ", "carry ", "drop " or "autonomy ", in any case, it exits
7432
+ # "accept ", "question ", "carry ", "drop ", "reject " or "autonomy ", in any case, it exits
7404
7433
  # 0 with no output, and Node never starts. A quote inside any other field arrives
7405
7434
  # escaped, so no other field (the folder the session is in, for one) can read as
7406
7435
  # the start of the prompt.
@@ -7424,7 +7453,7 @@ IFS= read -r -d '' HC_INPUT || true
7424
7453
  # \\u escape, or as the UTF-8 bytes of a Unicode space. Compared byte for byte.
7425
7454
  HC_LEAD='[[:space:]]|\\\\[nrtf]|\\\\u(000[9a-d]|0020|00a0|1680|200[0-9a]|202[89f]|205f|3000|feff)'
7426
7455
  HC_LEAD="$HC_LEAD|"$'\\xc2\\xa0|\\xe1\\x9a\\x80|\\xe2\\x80[\\x80-\\x8a\\xa8\\xa9\\xaf]|\\xe2\\x81\\x9f|\\xe3\\x80\\x80|\\xef\\xbb\\xbf'
7427
- if ! printf '%s' "$HC_INPUT" | LC_ALL=C grep -Eiq "\\"prompt\\"[[:space:]]*:[[:space:]]*\\"($HC_LEAD)*(approve|send back|accept|question|carry|drop|autonomy) "; then
7456
+ if ! printf '%s' "$HC_INPUT" | LC_ALL=C grep -Eiq "\\"prompt\\"[[:space:]]*:[[:space:]]*\\"($HC_LEAD)*(approve|send back|accept|question|carry|drop|reject|autonomy) "; then
7428
7457
  exit 0
7429
7458
  fi
7430
7459
 
@@ -10457,6 +10486,27 @@ function renderCouldNotRun(entries, p) {
10457
10486
  function headlineText(observed, attested, checks) {
10458
10487
  const met = attested.decision === "done-when-met";
10459
10488
  const problemsReported = attested.walk !== null && attested.walk.verdict === "defects";
10489
+ const left = observed.leftForOwner;
10490
+ if (left !== void 0 && observed.crossed.kind === "none") {
10491
+ const saw = `Halfcycle saw work that waits for you: ${leftForYouSummary(left)}, each listed in this record.`;
10492
+ if (!met) {
10493
+ return [LEFT_FOR_YOU_TITLE, `${saw} The person closing the phase chose to override rather than state its finish conditions were met.`];
10494
+ }
10495
+ return problemsReported ? [LEFT_FOR_YOU_TITLE, `${saw} The person closing the phase reported problems found in its acceptance walk.`] : [LEFT_FOR_YOU_TITLE, `${saw} The person closing the phase stated its finish conditions were met, but this work is still yours to do.`];
10496
+ }
10497
+ const [title, sentence] = headlineOverCrossed(observed, met, problemsReported, checks);
10498
+ return left === void 0 ? [title, sentence] : [title, `${sentence} Halfcycle also saw work that waits for you: ${leftForYouSummary(left)}, each listed in this record.`];
10499
+ }
10500
+ var LEFT_FOR_YOU_TITLE = "Closed with work left for you.";
10501
+ function leftForYouSummary(left) {
10502
+ const parts = [];
10503
+ if (left.tasks.length > 0)
10504
+ parts.push(`${counted(left.tasks.length, "task", "tasks")} held for you`);
10505
+ if (left.waiting.length > 0)
10506
+ parts.push(`${counted(left.waiting.length, "decision", "decisions")} waiting for your answer`);
10507
+ return parts.join(" and ");
10508
+ }
10509
+ function headlineOverCrossed(observed, met, problemsReported, checks) {
10460
10510
  switch (observed.crossed.kind) {
10461
10511
  case "none":
10462
10512
  if (!met) {
@@ -10524,6 +10574,18 @@ function outstanding(crossed, p) {
10524
10574
  ];
10525
10575
  }
10526
10576
  }
10577
+ function leftForYouLines(left, p) {
10578
+ if (left === void 0)
10579
+ return [];
10580
+ const lines = [`- Work left for you when the phase closed, which only you can do: ${leftForYouSummary(left)}.`];
10581
+ for (const title of left.tasks)
10582
+ lines.push(` - Task held for you: ${p.text(title)}`);
10583
+ for (const decision of left.waiting) {
10584
+ lines.push(` - Decision waiting for your answer, as the agent asked it: ${p.text(decision.question)}`);
10585
+ lines.push(` - On the list of what always waits for you as: ${p.text(decision.item)}`);
10586
+ }
10587
+ return lines;
10588
+ }
10527
10589
  function shownBy(steps) {
10528
10590
  const named = steps.filter((s) => s.name !== void 0).length;
10529
10591
  if (named === 0)
@@ -10596,6 +10658,7 @@ function renderClose(heading, observed, attested, checks, p, closingRound = fals
10596
10658
  `- Closed at: ${closedAtText(observed.closedAt, p)}`,
10597
10659
  `- Signed in as: ${signedInAs(observed.closedBy, p)}`,
10598
10660
  ...outstanding(observed.crossed, p),
10661
+ ...leftForYouLines(observed.leftForOwner, p),
10599
10662
  machines(observed.seatCoverage, checks)
10600
10663
  ].join("\n"),
10601
10664
  STATED_LABEL,
@@ -12875,23 +12938,36 @@ function typedLine(act, reference, part) {
12875
12938
  return `drop ${reference}/${part}: <why>`;
12876
12939
  }
12877
12940
  }
12878
- function rv7Lines(view) {
12941
+ function rv7aLine(reference) {
12942
+ return `The line accept ${reference} also accepts every check nobody ran that is listed below. Reject a task's checks first if you do not accept them.`;
12943
+ }
12944
+ function tasksWithWaitingClaims(claims) {
12945
+ return [...new Set(claims.filter((c) => c.answer === null).map((c) => c.task))];
12946
+ }
12947
+ function rv7Lines(view, claims) {
12879
12948
  const reference = typedPhaseReference(view.phase, view.number);
12880
- if (view.parts.length === 0)
12881
- return [`This phase has no parts to review. To close it, type: accept ${reference}`];
12949
+ const claimTasks = tasksWithWaitingClaims(claims);
12882
12950
  const decidable = view.parts.filter((p) => p.decidable);
12883
- if (decidable.length === 0)
12884
- return ["No part waits for your decision now."];
12951
+ if (claimTasks.length === 0) {
12952
+ if (view.parts.length === 0)
12953
+ return [`This phase has no parts to review. To close it, type: accept ${reference}`];
12954
+ if (decidable.length === 0)
12955
+ return ["No part waits for your decision now."];
12956
+ }
12885
12957
  const lines = ["To decide, type one of these lines in your own Claude Code session:"];
12886
12958
  for (const part of decidable) {
12887
12959
  for (const act of reviewActsFor(part.built))
12888
12960
  lines.push(` ${typedLine(act, reference, part.part)}`);
12889
12961
  }
12890
- if (decidable.some((p) => p.built))
12962
+ for (const task of claimTasks)
12963
+ lines.push(` reject ${reference}/${task}: <why>`);
12964
+ if (decidable.some((p) => p.built) || claimTasks.length > 0)
12891
12965
  lines.push(` accept ${reference}`);
12966
+ if (claimTasks.length > 0)
12967
+ lines.push(rv7aLine(reference));
12892
12968
  return lines;
12893
12969
  }
12894
- function reviewListingLines(view) {
12970
+ function reviewListingLines(view, claims = []) {
12895
12971
  const lines = [rv1Line(view)];
12896
12972
  if (!view.counted)
12897
12973
  lines.push(RV1A_LINE);
@@ -12899,7 +12975,7 @@ function reviewListingLines(view) {
12899
12975
  lines.push(...observedPhaseLines(view.observed));
12900
12976
  for (const part of view.parts)
12901
12977
  lines.push(...partLines(part, view));
12902
- lines.push(...rv7Lines(view));
12978
+ lines.push(...rv7Lines(view, claims));
12903
12979
  return lines;
12904
12980
  }
12905
12981
  var CALLS_HEADING2 = "Calls made without asking you";
@@ -12924,20 +13000,17 @@ var CHECKS_SUBHEADING = "An agent says these pass and Halfcycle never ran them.
12924
13000
  function uncheckedClaimLine(test) {
12925
13001
  return `Unchecked: ${test.claimedBy} says "${test.label}" passes. Halfcycle never ran it.`;
12926
13002
  }
12927
- function answerClaimsInSystemViewLine(engagementId, phase) {
12928
- const address = systemViewUrl(engagementId, phase);
12929
- return address === void 0 ? "Accept or reject each one in the System view." : `Accept or reject each one in the System view: ${address}`;
12930
- }
12931
- function checksSectionLines(work, engagementId) {
13003
+ var ANSWER_CLAIMS_BY_TYPING_LINE = "To answer them, type the reject or accept line listed above in your own Claude Code session.";
13004
+ function checksSectionLines(work) {
12932
13005
  const unchecked = work.tasks.flatMap((task) => task.tests.filter((t) => t.state === "unchecked"));
12933
13006
  if (unchecked.length === 0)
12934
13007
  return [];
12935
- return [CHECKS_HEADING, CHECKS_SUBHEADING, ...unchecked.map(uncheckedClaimLine), answerClaimsInSystemViewLine(engagementId, work.phase)];
13008
+ return [CHECKS_HEADING, CHECKS_SUBHEADING, ...unchecked.map(uncheckedClaimLine), ANSWER_CLAIMS_BY_TYPING_LINE];
12936
13009
  }
12937
- function workSectionLines(work, engagementId) {
13010
+ function workSectionLines(work, _engagementId) {
12938
13011
  if (work === null)
12939
13012
  return [];
12940
- return [...callsSectionLines(work.calls), ...checksSectionLines(work, engagementId)];
13013
+ return [...callsSectionLines(work.calls), ...checksSectionLines(work)];
12941
13014
  }
12942
13015
  function rv8Line(view) {
12943
13016
  if (view.state === "closed") {
@@ -13079,7 +13152,8 @@ async function runReviewShow(repoRoot, phase, deps) {
13079
13152
  if (view.state !== "open")
13080
13153
  return refused(rv8Line(view));
13081
13154
  const work = await deps.readWork(credential, phase);
13082
- const stdout = `${[...reviewListingLines(view), ...workSectionLines(work.kind === "work" ? work.work : null, credential.engagementId)].join("\n")}
13155
+ const phaseWork = work.kind === "work" ? work.work : null;
13156
+ const stdout = `${[...reviewListingLines(view, phaseWork?.uncheckedClaims ?? []), ...workSectionLines(phaseWork, credential.engagementId)].join("\n")}
13083
13157
  `;
13084
13158
  const written = deps.recordShown(credential.engagementId, shownReviewEntry(view, deps.now().toISOString()));
13085
13159
  if (!written.ok)
@@ -13339,14 +13413,25 @@ function reviewOpenedLine(phase) {
13339
13413
  return `[halfcycle] The review of phase "${phase}" is open. The project's owner reviews each part: accept it, ask a question or send it back. Each part that was not built is carried to the next phase or dropped, with a reason. The phase closes when every part is decided.`;
13340
13414
  }
13341
13415
  var COULD_NOT_COUNT_LINE = "[halfcycle] Halfcycle could not count what this phase changed, so the owner judges each part on what the agents reported alone.";
13342
- function typedReviewHintLine(offered) {
13343
- return offered ? '[halfcycle] The owner reviews each part in their own Claude Code session: they say "review what was built" to see the list, then type one decision per line.' : `[halfcycle] ${TYPED_REVIEW_OFF_SENTENCE}`;
13416
+ function typedAcceptLine(phase) {
13417
+ return phase.includes(":") ? null : `accept ${phase}`;
13418
+ }
13419
+ function typedReviewHintLine(offered, phase) {
13420
+ if (!offered)
13421
+ return `[halfcycle] ${TYPED_REVIEW_OFF_SENTENCE}`;
13422
+ const accept = typedAcceptLine(phase);
13423
+ const lines = accept === null ? "then type one decision per line, as the whole message." : `then type ${accept} as the whole message to accept every built part, or one line per part, such as ${accept}/<part>; the list gives each part's lines.`;
13424
+ return `[halfcycle] The owner reviews each part in their own Claude Code session: they say "review what was built" to see the list, ${lines}`;
13344
13425
  }
13345
13426
  function systemViewReviewLine(offered, engagementId, phase) {
13346
13427
  const address = systemViewUrl(engagementId, phase);
13347
13428
  if (address === void 0)
13348
13429
  return void 0;
13349
- return offered ? `[halfcycle] Or the owner reviews it in the System view: ${address}` : `[halfcycle] The owner reviews it in the System view: ${address}`;
13430
+ if (!offered)
13431
+ return `[halfcycle] The owner reviews it in the System view: ${address}`;
13432
+ const accept = typedAcceptLine(phase);
13433
+ const instead = accept === null ? "typing in their own Claude Code session" : `typing ${accept} in their own Claude Code session`;
13434
+ return `[halfcycle] Or, instead of ${instead}, the owner reviews it in the System view: ${address}`;
13350
13435
  }
13351
13436
  function tasksOpenLine(phase, open, more) {
13352
13437
  const total = open.length + more;
@@ -13399,7 +13484,7 @@ function closeStepReport(phase, attempt, engagementId) {
13399
13484
  stdout.push(reviewOpenedLine(phase));
13400
13485
  if (!answer.counted)
13401
13486
  stdout.push(COULD_NOT_COUNT_LINE);
13402
- stdout.push(typedReviewHintLine(answer.typedReviewOffered));
13487
+ stdout.push(typedReviewHintLine(answer.typedReviewOffered, phase));
13403
13488
  {
13404
13489
  const viewLine = systemViewReviewLine(answer.typedReviewOffered, engagementId, phase);
13405
13490
  if (viewLine !== void 0)
@@ -13436,7 +13521,7 @@ function closeStepReport(phase, attempt, engagementId) {
13436
13521
  exitCode = 0;
13437
13522
  break;
13438
13523
  case "autonomy-waits":
13439
- stdout.push(answer.reason === "unchecked-claims" ? autonomyA14(answer.count, answer.parts.map((p) => p.name)) : autonomyA15(answer.count));
13524
+ stdout.push(answer.reason === "unchecked-claims" ? autonomyA14(answer.count, answer.parts.map((p) => p.name), typedAcceptLine(phase)) : autonomyA15(answer.count));
13440
13525
  exitCode = 0;
13441
13526
  break;
13442
13527
  default: