halfcycle 0.4.9 → 0.4.11

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.
package/dist/cli.js CHANGED
@@ -420,18 +420,6 @@ function hasDuplicates(items) {
420
420
  }
421
421
 
422
422
  // ../core/dist/phase-reference.js
423
- var TYPED_NUMBER = /^(?:0|[1-9][0-9]*)$/;
424
- function resolvePhaseReference(typed, phases) {
425
- if (typeof typed !== "string" || typed === "")
426
- return null;
427
- const byIdentity = phases.filter((entry) => entry.phase === typed);
428
- if (byIdentity.length > 0)
429
- return byIdentity.length === 1 ? byIdentity[0] : null;
430
- if (!TYPED_NUMBER.test(typed))
431
- return null;
432
- const byNumber = phases.filter((entry) => Number.isInteger(entry.number) && String(entry.number) === typed);
433
- return byNumber.length === 1 ? byNumber[0] : null;
434
- }
435
423
  function typedPhaseReference(phase, number) {
436
424
  return phase.includes(":") && number !== null ? String(number) : phase;
437
425
  }
@@ -1162,10 +1150,10 @@ var openPhaseResponseSchema = z6.object({
1162
1150
 
1163
1151
  // ../events/dist/crew.js
1164
1152
  import { z as z7 } from "zod";
1165
- var crewGroupSchema = z7.enum(["builder", "reviewer", "always-on"]);
1153
+ var crewGroupSchema = z7.enum(["analyst-architect", "builder", "reviewer", "lead"]);
1166
1154
  var crewTierSchema = z7.enum(["deep", "standard", "fast"]);
1167
1155
  var CREW_DISCIPLINE_MAX = 64;
1168
- var crewFaceHeadSchema = z7.enum(["sq", "sqr", "rnd", "dome", "hex"]);
1156
+ var crewFaceHeadSchema = z7.enum(["sq", "sqr", "rnd", "dome", "hex", "gable"]);
1169
1157
  var crewFaceAntennaSchema = z7.enum(["none", "stalk", "twin", "dish"]);
1170
1158
  var crewFaceEyesSchema = z7.enum(["dots", "bars", "visor", "cyclops"]);
1171
1159
  var crewFaceMouthSchema = z7.enum(["line", "grid", "dots", "wave"]);
@@ -1182,12 +1170,25 @@ var crewFaceSchema = z7.object({
1182
1170
  // Whether this member is drawn with ears.
1183
1171
  ears: z7.boolean()
1184
1172
  }).strict();
1173
+ var crewRoleSchema = z7.enum([
1174
+ "business-analyst",
1175
+ "architect",
1176
+ "spec-writer",
1177
+ "builder",
1178
+ "context-docs",
1179
+ "spec-reviewer",
1180
+ "code-test-reviewer",
1181
+ "delivery-lead",
1182
+ "quality-lead"
1183
+ ]);
1185
1184
  var crewMemberSchema = z7.object({
1186
- // The agent's name. The same twenty-one on every project, so a name is a
1185
+ // The agent's name. The same names on every project, so a name is a
1187
1186
  // vocabulary a returning client already knows.
1188
1187
  callsign: z7.string().min(1).max(32),
1189
- // Which of the three headings this member reads under.
1188
+ // Which of the four teams this member belongs to.
1190
1189
  group: crewGroupSchema,
1190
+ // The part this member plays, which decides the stages it leads and helps with.
1191
+ role: crewRoleSchema,
1191
1192
  // The kind of work this member takes. A task names a discipline, and only
1192
1193
  // agents holding it can be dispatched to it.
1193
1194
  discipline: z7.string().min(1).max(CREW_DISCIPLINE_MAX),
@@ -1199,10 +1200,44 @@ var crewMemberSchema = z7.object({
1199
1200
  face: crewFaceSchema
1200
1201
  }).strict();
1201
1202
  var CREW_ROSTER = [
1203
+ // ── analysts and architects ─────────────────────────────────────────────────
1204
+ {
1205
+ callsign: "SOFIA",
1206
+ group: "analyst-architect",
1207
+ role: "business-analyst",
1208
+ discipline: "business analyst",
1209
+ tier: "deep",
1210
+ face: { head: "gable", antenna: "stalk", eyes: "dots", mouth: "wave", ears: false }
1211
+ },
1212
+ {
1213
+ callsign: "BEA",
1214
+ group: "analyst-architect",
1215
+ role: "business-analyst",
1216
+ discipline: "business analyst",
1217
+ tier: "standard",
1218
+ face: { head: "gable", antenna: "none", eyes: "visor", mouth: "line", ears: false }
1219
+ },
1220
+ {
1221
+ callsign: "WREN",
1222
+ group: "analyst-architect",
1223
+ role: "architect",
1224
+ discipline: "solution architect",
1225
+ tier: "deep",
1226
+ face: { head: "gable", antenna: "dish", eyes: "bars", mouth: "line", ears: true }
1227
+ },
1228
+ {
1229
+ callsign: "PEI",
1230
+ group: "analyst-architect",
1231
+ role: "architect",
1232
+ discipline: "technical architect",
1233
+ tier: "standard",
1234
+ face: { head: "gable", antenna: "twin", eyes: "cyclops", mouth: "dots", ears: false }
1235
+ },
1202
1236
  // ── builders: code ──────────────────────────────────────────────────────────
1203
1237
  {
1204
1238
  callsign: "ADA",
1205
1239
  group: "builder",
1240
+ role: "builder",
1206
1241
  discipline: "implementation",
1207
1242
  tier: "deep",
1208
1243
  face: { head: "sq", antenna: "stalk", eyes: "dots", mouth: "line", ears: false }
@@ -1210,6 +1245,7 @@ var CREW_ROSTER = [
1210
1245
  {
1211
1246
  callsign: "OTTO",
1212
1247
  group: "builder",
1248
+ role: "builder",
1213
1249
  discipline: "implementation",
1214
1250
  tier: "deep",
1215
1251
  face: { head: "sq", antenna: "twin", eyes: "bars", mouth: "grid", ears: true }
@@ -1217,6 +1253,7 @@ var CREW_ROSTER = [
1217
1253
  {
1218
1254
  callsign: "MILO",
1219
1255
  group: "builder",
1256
+ role: "builder",
1220
1257
  discipline: "implementation",
1221
1258
  tier: "standard",
1222
1259
  face: { head: "sqr", antenna: "stalk", eyes: "dots", mouth: "dots", ears: false }
@@ -1224,42 +1261,24 @@ var CREW_ROSTER = [
1224
1261
  {
1225
1262
  callsign: "NOVA",
1226
1263
  group: "builder",
1264
+ role: "builder",
1227
1265
  discipline: "implementation",
1228
1266
  tier: "standard",
1229
1267
  face: { head: "sq", antenna: "dish", eyes: "cyclops", mouth: "line", ears: false }
1230
1268
  },
1231
- {
1232
- callsign: "KAI",
1233
- group: "builder",
1234
- discipline: "implementation",
1235
- tier: "standard",
1236
- face: { head: "sqr", antenna: "none", eyes: "bars", mouth: "line", ears: true }
1237
- },
1238
1269
  {
1239
1270
  callsign: "HUGO",
1240
1271
  group: "builder",
1272
+ role: "builder",
1241
1273
  discipline: "infrastructure",
1242
1274
  tier: "standard",
1243
1275
  face: { head: "sq", antenna: "twin", eyes: "dots", mouth: "grid", ears: true }
1244
1276
  },
1245
- {
1246
- callsign: "WREN",
1247
- group: "builder",
1248
- discipline: "refactor",
1249
- tier: "fast",
1250
- face: { head: "sqr", antenna: "stalk", eyes: "bars", mouth: "wave", ears: false }
1251
- },
1252
- {
1253
- callsign: "PIP",
1254
- group: "builder",
1255
- discipline: "refactor",
1256
- tier: "fast",
1257
- face: { head: "sqr", antenna: "none", eyes: "dots", mouth: "dots", ears: false }
1258
- },
1259
1277
  // ── builders: specs and docs ────────────────────────────────────────────────
1260
1278
  {
1261
1279
  callsign: "IRIS",
1262
1280
  group: "builder",
1281
+ role: "spec-writer",
1263
1282
  discipline: "feature spec",
1264
1283
  tier: "deep",
1265
1284
  face: { head: "dome", antenna: "stalk", eyes: "visor", mouth: "line", ears: false }
@@ -1267,6 +1286,7 @@ var CREW_ROSTER = [
1267
1286
  {
1268
1287
  callsign: "JUNO",
1269
1288
  group: "builder",
1289
+ role: "spec-writer",
1270
1290
  discipline: "feature spec",
1271
1291
  tier: "standard",
1272
1292
  face: { head: "dome", antenna: "none", eyes: "dots", mouth: "wave", ears: false }
@@ -1274,6 +1294,7 @@ var CREW_ROSTER = [
1274
1294
  {
1275
1295
  callsign: "LEX",
1276
1296
  group: "builder",
1297
+ role: "context-docs",
1277
1298
  discipline: "context & docs",
1278
1299
  tier: "fast",
1279
1300
  face: { head: "dome", antenna: "twin", eyes: "bars", mouth: "dots", ears: false }
@@ -1282,6 +1303,7 @@ var CREW_ROSTER = [
1282
1303
  {
1283
1304
  callsign: "VERA",
1284
1305
  group: "reviewer",
1306
+ role: "spec-reviewer",
1285
1307
  discipline: "spec consistency",
1286
1308
  tier: "deep",
1287
1309
  face: { head: "rnd", antenna: "stalk", eyes: "visor", mouth: "line", ears: false }
@@ -1289,6 +1311,7 @@ var CREW_ROSTER = [
1289
1311
  {
1290
1312
  callsign: "ODIN",
1291
1313
  group: "reviewer",
1314
+ role: "spec-reviewer",
1292
1315
  discipline: "cross-spec audit",
1293
1316
  tier: "deep",
1294
1317
  face: { head: "rnd", antenna: "dish", eyes: "cyclops", mouth: "line", ears: false }
@@ -1296,6 +1319,7 @@ var CREW_ROSTER = [
1296
1319
  {
1297
1320
  callsign: "CASS",
1298
1321
  group: "reviewer",
1322
+ role: "code-test-reviewer",
1299
1323
  discipline: "code review",
1300
1324
  tier: "deep",
1301
1325
  face: { head: "rnd", antenna: "none", eyes: "visor", mouth: "grid", ears: true }
@@ -1303,13 +1327,23 @@ var CREW_ROSTER = [
1303
1327
  {
1304
1328
  callsign: "THEO",
1305
1329
  group: "reviewer",
1330
+ role: "code-test-reviewer",
1306
1331
  discipline: "code review",
1307
1332
  tier: "standard",
1308
1333
  face: { head: "rnd", antenna: "twin", eyes: "bars", mouth: "line", ears: false }
1309
1334
  },
1335
+ {
1336
+ callsign: "KAI",
1337
+ group: "reviewer",
1338
+ role: "code-test-reviewer",
1339
+ discipline: "code review",
1340
+ tier: "standard",
1341
+ face: { head: "rnd", antenna: "none", eyes: "bars", mouth: "line", ears: true }
1342
+ },
1310
1343
  {
1311
1344
  callsign: "ECHO",
1312
1345
  group: "reviewer",
1346
+ role: "code-test-reviewer",
1313
1347
  discipline: "test quality",
1314
1348
  tier: "standard",
1315
1349
  face: { head: "rnd", antenna: "stalk", eyes: "dots", mouth: "wave", ears: false }
@@ -1317,6 +1351,7 @@ var CREW_ROSTER = [
1317
1351
  {
1318
1352
  callsign: "MAVIS",
1319
1353
  group: "reviewer",
1354
+ role: "code-test-reviewer",
1320
1355
  discipline: "suites & coverage",
1321
1356
  tier: "standard",
1322
1357
  face: { head: "rnd", antenna: "none", eyes: "visor", mouth: "dots", ears: false }
@@ -1324,6 +1359,7 @@ var CREW_ROSTER = [
1324
1359
  {
1325
1360
  callsign: "ZARA",
1326
1361
  group: "reviewer",
1362
+ role: "code-test-reviewer",
1327
1363
  discipline: "smoke & walk aid",
1328
1364
  tier: "standard",
1329
1365
  face: { head: "rnd", antenna: "dish", eyes: "bars", mouth: "wave", ears: false }
@@ -1331,39 +1367,31 @@ var CREW_ROSTER = [
1331
1367
  {
1332
1368
  callsign: "NORA",
1333
1369
  group: "reviewer",
1370
+ role: "code-test-reviewer",
1334
1371
  discipline: "evidence & provenance",
1335
1372
  tier: "fast",
1336
1373
  face: { head: "rnd", antenna: "twin", eyes: "dots", mouth: "line", ears: false }
1337
1374
  },
1338
- // ── always on: continuous, takes no task ────────────────────────────────────
1375
+ // ── leads ───────────────────────────────────────────────────────────────────
1339
1376
  {
1340
1377
  callsign: "ARGUS",
1341
- group: "always-on",
1342
- discipline: "guard \xB7 every diff",
1378
+ group: "lead",
1379
+ role: "quality-lead",
1380
+ discipline: "quality lead \xB7 every diff",
1343
1381
  tier: "standard",
1344
1382
  face: { head: "hex", antenna: "dish", eyes: "visor", mouth: "grid", ears: true }
1345
1383
  },
1346
1384
  {
1347
1385
  callsign: "ATLAS",
1348
- group: "always-on",
1349
- discipline: "coordinator",
1386
+ group: "lead",
1387
+ role: "delivery-lead",
1388
+ discipline: "delivery lead",
1350
1389
  tier: "deep",
1351
1390
  face: { head: "hex", antenna: "twin", eyes: "cyclops", mouth: "line", ears: true }
1352
1391
  }
1353
1392
  ];
1354
1393
  var CREW_ROSTER_SIZE = CREW_ROSTER.length;
1355
1394
  var CREW_CALLSIGNS = CREW_ROSTER.map((member) => member.callsign);
1356
- var crewRoleSchema = z7.enum([
1357
- "business-analyst",
1358
- "architect",
1359
- "spec-writer",
1360
- "builder",
1361
- "context-docs",
1362
- "spec-reviewer",
1363
- "code-test-reviewer",
1364
- "delivery-lead",
1365
- "quality-lead"
1366
- ]);
1367
1395
  var crewLayerSchema = methodLayerSchema.extract(["l0", "l1", "l2", "l3", "l4", "l5"]);
1368
1396
  var crewRoleLayersSchema = z7.object({
1369
1397
  leads: z7.array(crewLayerSchema),
@@ -1722,7 +1750,8 @@ var systemDrawDrawnSchema = /* @__PURE__ */ z8.object({
1722
1750
  mermaid: z8.string().min(1).refine((v) => utf8ByteLength2(v) <= SYSTEM_DRAW_MERMAID_MAX_BYTES, {
1723
1751
  message: "the diagram text is larger than 256 KB"
1724
1752
  }),
1725
- pictures: systemPicturesSchema
1753
+ pictures: systemPicturesSchema,
1754
+ closedAt: utcIso8601.optional()
1726
1755
  }).strict();
1727
1756
  var systemDrawRefusedSchema = z8.object({
1728
1757
  outcome: z8.literal("refused"),
@@ -1967,14 +1996,13 @@ var SCOPE_STATE_NAMES = [
1967
1996
  "waiting",
1968
1997
  "sent-back",
1969
1998
  "approved",
1970
- "partly-stale",
1971
- "withdrawn"
1999
+ "partly-stale"
1972
2000
  ];
1973
2001
  var scopeStateNameSchema = z10.enum(SCOPE_STATE_NAMES);
1974
2002
  var plannedScopeStateSchema = scopeStateNameSchema.exclude(["not-planned"]);
1975
2003
  var SCOPE_PART_STATUSES = ["approved", "waiting"];
1976
2004
  var scopePartStatusSchema = z10.enum(SCOPE_PART_STATUSES);
1977
- var SCOPE_ACTS = ["approve", "send-back", "withdraw"];
2005
+ var SCOPE_ACTS = ["approve", "send-back"];
1978
2006
  var scopeActSchema = z10.enum(SCOPE_ACTS);
1979
2007
  var SCOPE_GIVEN_IN = ["console", "chat"];
1980
2008
  var scopeGivenInSchema = z10.enum(SCOPE_GIVEN_IN);
@@ -2045,14 +2073,9 @@ var scopeSendBackRequestSchema = z10.object({
2045
2073
  parts: z10.array(scopeDecisionPartSchema).max(PHASE_PLAN_CHANGES_MAX),
2046
2074
  note: scopeNoteWireSchema.optional()
2047
2075
  }).strict();
2048
- var scopeWithdrawRequestSchema = z10.object({
2049
- act: z10.literal("withdraw"),
2050
- planVersion: planVersionSchema
2051
- }).strict();
2052
2076
  var scopeDecisionRequestSchema = z10.discriminatedUnion("act", [
2053
2077
  scopeApproveRequestSchema,
2054
- scopeSendBackRequestSchema,
2055
- scopeWithdrawRequestSchema
2078
+ scopeSendBackRequestSchema
2056
2079
  ]);
2057
2080
  var scopeDecisionRecordedSchema = z10.object({
2058
2081
  outcome: z10.literal("recorded"),
@@ -2085,7 +2108,6 @@ var scopeDecisionResultSchema = z10.discriminatedUnion("outcome", [
2085
2108
  reasonOutcome("not-owner"),
2086
2109
  reasonOutcome("no-owner"),
2087
2110
  reasonOutcome("plan-changed"),
2088
- reasonOutcome("nothing-to-withdraw"),
2089
2111
  reasonOutcome("build-started")
2090
2112
  ]);
2091
2113
  var scopeReadPartSchema = z10.object({
@@ -2387,6 +2409,15 @@ var observedChangesSchema = z13.object({
2387
2409
  ctx.addIssue({ code: "custom", path: ["descriptionVersion"], message: "must name the version the counts used" });
2388
2410
  }
2389
2411
  });
2412
+ function partChangedUnderPaths(observed, partId, description) {
2413
+ if (observed === null || observed.counting.state !== "counted")
2414
+ return "unknown";
2415
+ const part = description.parts.find((candidate) => candidate.id === partId);
2416
+ if (part === void 0 || part.outside)
2417
+ return "unknown";
2418
+ const entry = observed.parts.find((candidate) => candidate.part === partId);
2419
+ return entry !== void 0 && entry.files !== null && filesChanged(entry.files) >= 1 ? "changed" : "unchanged";
2420
+ }
2390
2421
  var partOutsideThePlanSchema = z13.object({
2391
2422
  part: systemPartIdSchema,
2392
2423
  name: systemPartSchema.shape.name,
@@ -2404,14 +2435,78 @@ function agentSaid(task) {
2404
2435
  return `The agent on "${title}" said: "${whyOutsidePlan}"`;
2405
2436
  return `${agent} said, on "${title}": "${whyOutsidePlan}"`;
2406
2437
  }
2438
+ var NOTHING_COUNTED_DESPITE_FINISHED_LINE = "Halfcycle only counts committed changes. Work not yet committed, saved in a folder this project ignores, or committed outside this part's own folders, shows as nothing here even when every task that builds this part has finished.";
2439
+ function hasWildcard(segment) {
2440
+ return segment.includes("*");
2441
+ }
2442
+ function leadingLiteralPrefix(segments) {
2443
+ const firstWildcard = segments.findIndex(hasWildcard);
2444
+ return segments.slice(0, firstWildcard === -1 ? segments.length : firstWildcard).join("/");
2445
+ }
2446
+ function isWholeWildcard(segment) {
2447
+ return segment === "*" || segment === "**";
2448
+ }
2449
+ function ownsEverythingUnderPrefix(segments) {
2450
+ const firstWildcard = segments.findIndex(hasWildcard);
2451
+ if (firstWildcard === -1)
2452
+ return false;
2453
+ const tail = segments.slice(firstWildcard);
2454
+ if (!tail.every(isWholeWildcard))
2455
+ return false;
2456
+ return tail.filter((segment) => segment === "*").length <= 1;
2457
+ }
2458
+ var EXTENSION_GLOB_SEGMENT = /^\*\.([^*]+)$/;
2459
+ var EXTENSIONLESS_FILE_NAMES = /* @__PURE__ */ new Set([
2460
+ "Makefile",
2461
+ "Dockerfile",
2462
+ "Gemfile",
2463
+ "Rakefile",
2464
+ "Procfile",
2465
+ "Vagrantfile",
2466
+ "LICENSE",
2467
+ "CHANGELOG",
2468
+ "README",
2469
+ "NOTICE",
2470
+ "Jenkinsfile",
2471
+ "Justfile",
2472
+ "CODEOWNERS"
2473
+ ]);
2474
+ var DOT_PREFIXED_FOLDER_NAMES = /* @__PURE__ */ new Set([".github", ".vscode", ".circleci", ".husky", ".changeset", ".devcontainer", ".idea", ".claude", ".config"]);
2475
+ function isBareFileName(name) {
2476
+ if (EXTENSIONLESS_FILE_NAMES.has(name))
2477
+ return true;
2478
+ if (DOT_PREFIXED_FOLDER_NAMES.has(name))
2479
+ return false;
2480
+ return name.includes(".");
2481
+ }
2407
2482
  function whereOf(owns) {
2408
2483
  if (owns.length !== 1)
2409
- return "its paths";
2484
+ return "under its paths";
2410
2485
  const pattern = owns[0];
2411
- if (pattern.endsWith("/**"))
2412
- return pattern.slice(0, -2);
2413
- const last = pattern.slice(pattern.lastIndexOf("/") + 1);
2414
- return last.includes(".") ? pattern : `${pattern}/`;
2486
+ const segments = pattern.split("/");
2487
+ const lastSegment = segments[segments.length - 1];
2488
+ const extensionGlob = EXTENSION_GLOB_SEGMENT.exec(lastSegment);
2489
+ if (extensionGlob !== null) {
2490
+ const ext = extensionGlob[1];
2491
+ const before = segments.slice(0, -1);
2492
+ if (before.length === 0 || before.some(hasWildcard)) {
2493
+ const prefix2 = leadingLiteralPrefix(before);
2494
+ return prefix2 === "" ? `in its .${ext} files` : `in .${ext} files under ${prefix2}`;
2495
+ }
2496
+ return `in the .${ext} files directly in ${before.join("/")}`;
2497
+ }
2498
+ if (!hasWildcard(pattern)) {
2499
+ return isBareFileName(lastSegment) ? `in ${pattern}` : `under ${pattern}/`;
2500
+ }
2501
+ if (lastSegment === "*") {
2502
+ const folder = segments.slice(0, -1).join("/");
2503
+ if (folder !== "" && !hasWildcard(folder))
2504
+ return `in the ${folder} folder`;
2505
+ }
2506
+ const prefix = leadingLiteralPrefix(segments);
2507
+ if (ownsEverythingUnderPrefix(segments))
2508
+ return prefix === "" ? "anywhere in the project" : `under ${prefix}/`;
2509
+ return prefix === "" ? "in some paths in the project" : `in some paths under ${prefix}/`;
2415
2510
  }
2416
2511
  function howMany(n, one, many) {
2417
2512
  return `${n} ${n === 1 ? one : many}`;
@@ -2440,7 +2535,7 @@ function observedLines(observed, part, description) {
2440
2535
  const t = files === null ? 0 : files.testFilesAdded;
2441
2536
  const where = whereOf(held.owns);
2442
2537
  return [
2443
- n === 0 ? `Nothing changed under ${where}` : `${howMany(n, "file", "files")} changed under ${where}`,
2538
+ n === 0 ? `Nothing changed ${where}` : `${howMany(n, "file", "files")} changed ${where}`,
2444
2539
  t === 0 ? "No test files added" : `${howMany(t, "test file", "test files")} added`,
2445
2540
  checks
2446
2541
  ];
@@ -2772,11 +2867,16 @@ var phaseReviewViewSchema = z14.discriminatedUnion("state", [
2772
2867
 
2773
2868
  // ../events/dist/build-record-v3.js
2774
2869
  var BUILD_RECORD_V3_FORMAT = "halfcycle-build-record/v3";
2870
+ var recordRoundStatedSchema = z15.object({
2871
+ actor: attestedCloseSchema.shape.actor,
2872
+ statements: attestedStatementsSchema
2873
+ }).strict();
2775
2874
  var recordReviewRoundSchema = z15.object({
2776
2875
  round: z15.number().int().min(1),
2777
2876
  openedAt: utcIso8601,
2778
2877
  verdict: walkVerdictSchema,
2779
- findings: z15.number().int().min(0)
2878
+ findings: z15.number().int().min(0),
2879
+ stated: recordRoundStatedSchema.optional()
2780
2880
  }).strict();
2781
2881
  var recordReviewApprovalSchema = z15.discriminatedUnion("kind", [
2782
2882
  z15.object({
@@ -2838,6 +2938,9 @@ var recordReviewSchema = z15.object({
2838
2938
  if (r.round !== i + 1) {
2839
2939
  ctx.addIssue({ code: "custom", path: ["rounds", i, "round"], message: "rounds must be numbered 1, 2, 3 \u2026 in order with no gap" });
2840
2940
  }
2941
+ if (i === v.rounds.length - 1 && r.stated !== void 0) {
2942
+ ctx.addIssue({ code: "custom", path: ["rounds", i, "stated"], message: "the last round's words are the close's own, so the round does not repeat them" });
2943
+ }
2841
2944
  });
2842
2945
  const seen = /* @__PURE__ */ new Set();
2843
2946
  v.parts.forEach((p, i) => {
@@ -3021,6 +3124,21 @@ function loopbackVerificationUrl(verificationUrl, target) {
3021
3124
  return url.toString();
3022
3125
  }
3023
3126
 
3127
+ // ../events/dist/project-page.js
3128
+ function projectPageUrl(engagementId) {
3129
+ return isEngagementId(engagementId) ? `${DASHBOARD_URL}/${encodeURIComponent(engagementId)}` : void 0;
3130
+ }
3131
+ function systemViewUrl(engagementId, phase) {
3132
+ const page = projectPageUrl(engagementId);
3133
+ if (page === void 0)
3134
+ return void 0;
3135
+ return `${page}/console/system-view?${new URLSearchParams({ phase }).toString()}`;
3136
+ }
3137
+ function requestAccessSentence(engagementId) {
3138
+ const page = projectPageUrl(engagementId) ?? "this project's page on Halfcycle";
3139
+ return `To ask for access, open ${page} signed in as this account and choose "Request access". Then let the project's owner know, so they can approve it.`;
3140
+ }
3141
+
3024
3142
  // ../events/dist/join-refusal.js
3025
3143
  import { z as z17 } from "zod";
3026
3144
  var NOT_THIS_ACCOUNT_REASON = "not-this-account";
@@ -3036,15 +3154,6 @@ function isNotThisAccountRefusal(body) {
3036
3154
  return typeof body === "object" && body !== null && body.reason === NOT_THIS_ACCOUNT_REASON;
3037
3155
  }
3038
3156
 
3039
- // ../events/dist/project-page.js
3040
- function projectPageUrl(engagementId) {
3041
- return isEngagementId(engagementId) ? `${DASHBOARD_URL}/${encodeURIComponent(engagementId)}` : void 0;
3042
- }
3043
- function requestAccessSentence(engagementId) {
3044
- const page = projectPageUrl(engagementId) ?? "this project's page on Halfcycle";
3045
- return `To ask for access, open ${page} signed in as this account and choose "Request access". Then let the project's owner know, so they can approve it.`;
3046
- }
3047
-
3048
3157
  // ../events/dist/account-identity.js
3049
3158
  import { z as z18 } from "zod";
3050
3159
  var accountIdentitySchema = z18.object({
@@ -7037,12 +7146,13 @@ function machines(coverage, checks) {
7037
7146
  const line = `${label}${String(coverage.enrolled)}. Of these, ${String(coverage.conformant)} had reported Halfcycle's checks as fully set up.`;
7038
7147
  return checks === "not-observed" && coverage.conformant > 0 ? `${line} Set up is not the same as running: Halfcycle has no record of any check running during this phase.` : line;
7039
7148
  }
7040
- function walkLines(walk, p) {
7149
+ function walkLines(walk, p, closingRound = false) {
7041
7150
  const label = "> - Acceptance walk (the finished work tried as a user would, by someone other than its builder): ";
7042
7151
  if (walk === null)
7043
7152
  return [`${label}the person closing the phase reported none.`];
7153
+ const it = closingRound ? "the round that closed the phase" : "it";
7044
7154
  const lines = [
7045
- `${label}${walk.verdict === "clean" ? "the person closing the phase said it found no problems." : "the person closing the phase said it found problems."}`
7155
+ `${label}${walk.verdict === "clean" ? `the person closing the phase said ${it} found no problems.` : `the person closing the phase said ${it} found problems.`}`
7046
7156
  ];
7047
7157
  walk.findings.forEach((finding, i) => {
7048
7158
  lines.push(`> - Finding ${String(i + 1)}: ${p.text(finding)}`);
@@ -7061,12 +7171,12 @@ function decisionLine(attested) {
7061
7171
  }
7062
7172
  return attested.walk !== null && attested.walk.verdict === "defects" ? "> - Decision: close the phase as it stood, with the problems its acceptance walk found." : "> - Decision: the phase's agreed finish conditions were met.";
7063
7173
  }
7064
- function renderStated(attested, p) {
7174
+ function renderStated(attested, p, closingRound = false) {
7065
7175
  const lines = [
7066
7176
  decisionLine(attested),
7067
7177
  `> - Name given: ${attested.actor === null ? "removed with the account that made this close" : p.text(attested.actor)}`,
7068
7178
  `> - Reason given: ${attested.reason === null ? "none." : p.text(attested.reason)}`,
7069
- ...walkLines(attested.walk, p),
7179
+ ...walkLines(attested.walk, p, closingRound),
7070
7180
  `> - Code closed at (commit): ${statedOrNot(attested.closeCommit, p)}`,
7071
7181
  `> - What the phase set out to prove: ${statedOrNot(attested.statements.thesis, p)}`,
7072
7182
  `> - What came out of it: ${statedOrNot(attested.statements.outcome, p)}`
@@ -7082,7 +7192,7 @@ function renderStated(attested, p) {
7082
7192
  }
7083
7193
  return lines.join("\n");
7084
7194
  }
7085
- function renderClose(heading, observed, attested, checks, p) {
7195
+ function renderClose(heading, observed, attested, checks, p, closingRound = false) {
7086
7196
  return [
7087
7197
  heading,
7088
7198
  headline(observed, attested, checks),
@@ -7094,7 +7204,7 @@ function renderClose(heading, observed, attested, checks, p) {
7094
7204
  machines(observed.seatCoverage, checks)
7095
7205
  ].join("\n"),
7096
7206
  STATED_LABEL,
7097
- renderStated(attested, p)
7207
+ renderStated(attested, p, closingRound)
7098
7208
  ];
7099
7209
  }
7100
7210
  var REVIEW_LABEL = "**Recorded by Halfcycle from the owner's review.** Each part the review held, and how the project's owner decided it. A line marked as the agent's is the agent's own answer, not Halfcycle's.";
@@ -7110,7 +7220,7 @@ function decisionText(part, p) {
7110
7220
  case "accepted":
7111
7221
  return "Accepted";
7112
7222
  case "carried":
7113
- return part.carriedTo === null || part.carriedTo === void 0 ? `Carried to the next phase planned: ${p.text(part.reason ?? "")}` : `Carried to phase ${String(part.carriedTo.number)}: ${p.text(part.reason ?? "")}`;
7223
+ return part.carriedTo === null || part.carriedTo === void 0 ? `Carried to the next phase planned: ${p.text(part.reason ?? "")}` : `Carried to phase "${part.carriedTo.phase}": ${p.text(part.reason ?? "")}`;
7114
7224
  case "dropped":
7115
7225
  return `Dropped: ${p.text(part.reason ?? "")}`;
7116
7226
  }
@@ -7143,6 +7253,37 @@ function renderReview(record2, p) {
7143
7253
  blocks.push(removals);
7144
7254
  return blocks;
7145
7255
  }
7256
+ var EARLIER_ROUNDS_LABEL = `${STATED_LABEL} What was said each time the owner's review was asked for, before the round that closed the phase. What was said for that last round is under the close above. If the account that asked for a round is later deleted, the name given for that round is removed.`;
7257
+ function renderRoundStated(round, stated, p) {
7258
+ const label = "> - Acceptance walk (the finished work tried as a user would, by someone other than its builder): ";
7259
+ const walk = round.verdict === "clean" ? "the person asking for this round said it found no problems." : round.findings === 0 ? "the person asking for this round said it found problems." : `the person asking for this round said it found ${counted(round.findings, "problem", "problems")}.`;
7260
+ const lines = [
7261
+ `> - Name given: ${stated.actor === null ? "removed with the account that asked for this round" : p.text(stated.actor)}`,
7262
+ `${label}${walk}`,
7263
+ `> - What the phase set out to prove: ${statedOrNot(stated.statements.thesis, p)}`,
7264
+ `> - What came out of it: ${statedOrNot(stated.statements.outcome, p)}`
7265
+ ];
7266
+ const deviations = stated.statements.deviations;
7267
+ if (deviations.length === 0) {
7268
+ lines.push("> - Changes from the plan: None stated by the client.");
7269
+ } else {
7270
+ lines.push("> - Changes from the plan:");
7271
+ deviations.forEach((deviation, i) => {
7272
+ lines.push(`> - Change ${String(i + 1)}: ${p.text(deviation)}`);
7273
+ });
7274
+ }
7275
+ return lines.join("\n");
7276
+ }
7277
+ function renderEarlierRounds(record2, p) {
7278
+ const rounds = record2.review.rounds;
7279
+ const blocks = [];
7280
+ for (const round of rounds) {
7281
+ if (round.stated === void 0)
7282
+ continue;
7283
+ blocks.push(`### Round ${String(round.round)} of ${String(rounds.length)}`, renderRoundStated(round, round.stated, p));
7284
+ }
7285
+ return blocks.length === 0 ? [] : ["## Earlier rounds of the review", EARLIER_ROUNDS_LABEL, ...blocks];
7286
+ }
7146
7287
  function renderBuildRecordV2Markdown(record2) {
7147
7288
  const p = makePrinter();
7148
7289
  const attestedBySequence = /* @__PURE__ */ new Map();
@@ -7156,6 +7297,8 @@ function renderBuildRecordV2Markdown(record2) {
7156
7297
  const closes = [...record2.observed.closes].sort((a, b) => a.sequence - b.sequence);
7157
7298
  const total = closes.length;
7158
7299
  const latest = closes[total - 1];
7300
+ const rounds = record2.format === "halfcycle-build-record/v3" ? record2.review.rounds : [];
7301
+ const roundsAfter = rounds.length <= 1 ? null : `after ${String(rounds.length)} rounds of the owner's review.${rounds.some((r) => r.stated !== void 0) ? ' What was said for each earlier round is under "Earlier rounds of the review".' : ""}`;
7159
7302
  const body = [
7160
7303
  `Phase: ${phase} (the name the client gave it)`,
7161
7304
  OBSERVED_LABEL,
@@ -7164,12 +7307,12 @@ function renderBuildRecordV2Markdown(record2) {
7164
7307
  `- Project identifier: ${p.text(record2.observed.engagementId)}. Halfcycle keeps this project's records under this identifier.`,
7165
7308
  `- Record format: ${p.text(record2.format)}. This names the layout this file and the .json file beside it follow, for software that reads them.`
7166
7309
  ].join("\n"),
7167
- total === 1 ? "This phase was closed once." : `This phase was closed ${String(total)} times. The latest close is described first; each earlier close follows, as it was recorded when it was made.`,
7310
+ total === 1 ? roundsAfter === null ? "This phase was closed once." : `This phase was closed once, ${roundsAfter}` : `This phase was closed ${String(total)} times. The latest close is described first; each earlier close follows, as it was recorded when it was made.${roundsAfter === null ? "" : ` The latest close came ${roundsAfter}`}`,
7168
7311
  ...renderChecks(record2.observed.checks, p),
7169
- ...renderClose(total === 1 ? "## The close" : `## The latest close (close ${String(latest.sequence)} of ${String(total)})`, latest, attestedBySequence.get(latest.sequence), record2.observed.checks.state, p)
7312
+ ...renderClose(total === 1 ? "## The close" : `## The latest close (close ${String(latest.sequence)} of ${String(total)})`, latest, attestedBySequence.get(latest.sequence), record2.observed.checks.state, p, rounds.length > 1)
7170
7313
  ];
7171
7314
  if (record2.format === "halfcycle-build-record/v3")
7172
- body.push(...renderReview(record2, p));
7315
+ body.push(...renderReview(record2, p), ...renderEarlierRounds(record2, p));
7173
7316
  if (total > 1) {
7174
7317
  body.push("## Earlier closes");
7175
7318
  for (const close of closes.slice(0, -1)) {
@@ -7516,6 +7659,12 @@ var COULD_NOT_COUNT_LINE = "[halfcycle] Halfcycle could not count what this phas
7516
7659
  function typedReviewHintLine(offered) {
7517
7660
  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}`;
7518
7661
  }
7662
+ function systemViewReviewLine(offered, engagementId, phase) {
7663
+ const address = systemViewUrl(engagementId, phase);
7664
+ if (address === void 0)
7665
+ return void 0;
7666
+ return offered ? `[halfcycle] Or the owner reviews it in the System view: ${address}` : `[halfcycle] The owner reviews it in the System view: ${address}`;
7667
+ }
7519
7668
  function tasksOpenLine(phase, open, more) {
7520
7669
  const total = open.length + more;
7521
7670
  const shown2 = open.slice(0, TASK_TITLES_SHOWN);
@@ -7540,7 +7689,7 @@ function unfinishedStepsLines(phase, steps) {
7540
7689
  function cleanOverFindingsLine(phase, findings) {
7541
7690
  return findings === 1 ? `${REFUSED} the walk's verdict is clean, but 1 defect is recorded against phase "${phase}". Run this again with --verdict defects and a --finding for it. Nothing was sent to the owner.` : `${REFUSED} the walk's verdict is clean, but ${String(findings)} defects are recorded against phase "${phase}". Run this again with --verdict defects and a --finding for each. Nothing was sent to the owner.`;
7542
7691
  }
7543
- function closeStepReport(phase, attempt) {
7692
+ function closeStepReport(phase, attempt, engagementId) {
7544
7693
  const stdout = [];
7545
7694
  const stderr = [];
7546
7695
  let exitCode = 1;
@@ -7568,6 +7717,11 @@ function closeStepReport(phase, attempt) {
7568
7717
  if (!answer.counted)
7569
7718
  stdout.push(COULD_NOT_COUNT_LINE);
7570
7719
  stdout.push(typedReviewHintLine(answer.typedReviewOffered));
7720
+ {
7721
+ const viewLine = systemViewReviewLine(answer.typedReviewOffered, engagementId, phase);
7722
+ if (viewLine !== void 0)
7723
+ stdout.push(viewLine);
7724
+ }
7571
7725
  if (!attempt.closeCommit.read) {
7572
7726
  stderr.push(`${REFUSED} the commit this review starts from could not be read (${attempt.closeCommit.reason}), so the phase's Build Record will show the commit as not stated.`);
7573
7727
  }
@@ -8663,7 +8817,7 @@ function notAPhaseNameLine(verb, phase) {
8663
8817
  `;
8664
8818
  }
8665
8819
  function removalOwedLines(verb, parts) {
8666
- return parts.map((part) => `halfcycle ${verb}: docs/system.json still holds "${part.name}", which phase ${part.number}'s review ${part.act}. Take it out, run ${HALFCYCLE_COMMAND} system, then run this again.
8820
+ return parts.map((part) => `halfcycle ${verb}: docs/system.json still holds "${part.name}", which phase "${part.phase}"'s review ${part.act}. Take it out, run ${HALFCYCLE_COMMAND} system, then run this again.
8667
8821
  `).join("");
8668
8822
  }
8669
8823
  function redrawLine(repoRoot, phase) {
@@ -8688,28 +8842,27 @@ function planStateSentence(state) {
8688
8842
  return "The owner approved an earlier plan; the parts marked as changed wait for the owner's approval.";
8689
8843
  case "sent-back":
8690
8844
  return "The owner sent this plan back. Change it to answer the owner's notes, then run this again.";
8691
- case "withdrawn":
8692
- return "The owner withdrew their approval of this plan. It waits for the owner's approval again.";
8693
8845
  }
8694
8846
  }
8695
- function approvalLine2(phase, number, typedApprovalOffered, pinnedVersion, shown2) {
8696
- if (!typedApprovalOffered)
8697
- return TYPED_APPROVAL_OFF_SENTENCE;
8698
- if (!clientMeetsChatApprovalFloor(pinnedVersion))
8699
- return CLIENT_UPDATE_SENTENCE;
8700
- const byNumber = String(number);
8701
- const numberReachesThis = byNumber !== phase && resolvePhaseReference(byNumber, shown2)?.phase === phase;
8702
- const typed = numberReachesThis ? `\`approve ${phase}\` (or \`approve ${byNumber}\`)` : `\`approve ${phase}\``;
8703
- return `To approve it, the project's owner types ${typed} in their own Claude Code session. Only the owner can.`;
8847
+ function approvalLine2(phase, number, typedApprovalOffered, pinnedVersion, shown2, engagementId) {
8848
+ const address = systemViewUrl(engagementId, phase);
8849
+ const then = (sentence, viewSentence) => address === void 0 ? sentence : `${sentence} ${viewSentence} ${address}`;
8850
+ if (!typedApprovalOffered) {
8851
+ return then(TYPED_APPROVAL_OFF_SENTENCE, "The project's owner approves it in the System view:");
8852
+ }
8853
+ if (!clientMeetsChatApprovalFloor(pinnedVersion)) {
8854
+ return then(CLIENT_UPDATE_SENTENCE, "Or the project's owner approves it in the System view now:");
8855
+ }
8856
+ return then(`To approve it, the project's owner types \`approve ${phase}\` in their own Claude Code session. Only the owner can.`, "They can also approve it in the System view:");
8704
8857
  }
8705
8858
  function changeLines(plan, description, changed, carriedFrom = []) {
8706
8859
  const names = new Map(description.parts.map((part) => [part.id, part.name]));
8707
8860
  const marked = new Set(changed);
8708
- const carried = new Map(carriedFrom.map((entry) => [entry.part, entry.number]));
8861
+ const carried = new Map(carriedFrom.map((entry) => [entry.part, entry.phase]));
8709
8862
  return plan.changes.map((c) => {
8710
8863
  const mark = marked.has(c.part) ? " (changed since the owner last decided)" : "";
8711
- const carriedNumber = carried.get(c.part);
8712
- const carriedMark = carriedNumber === void 0 ? "" : ` (carried over from phase ${carriedNumber})`;
8864
+ const carriedPhase = carried.get(c.part);
8865
+ const carriedMark = carriedPhase === void 0 ? "" : ` (carried over from phase "${carriedPhase}")`;
8713
8866
  return ` - ${names.get(c.part) ?? c.part}${carriedMark} \xB7 ${PLAN_CHANGE_LABELS[c.change]} \xB7 ${c.summary}${mark}
8714
8867
  `;
8715
8868
  }).join("");
@@ -8756,7 +8909,7 @@ async function runPlan(repoRoot, phase, show, deps) {
8756
8909
  break;
8757
8910
  }
8758
8911
  const shownPhase = String(plan.phase);
8759
- const recorded = `[halfcycle] Plan for ${shownPhase} recorded: plan version ${outcome.version} (phase ${outcome.phaseNumber}).`;
8912
+ const recorded = `[halfcycle] Plan for "${shownPhase}" recorded: plan version ${outcome.version}.`;
8760
8913
  const stateSentence = planStateSentence(outcome.state);
8761
8914
  const redraw = redrawLine(repoRoot, phase) ?? "";
8762
8915
  if (!show)
@@ -8780,7 +8933,7 @@ ${redraw}`;
8780
8933
  exitCode: 1
8781
8934
  };
8782
8935
  }
8783
- const stdout = listed + `${approvalLine2(shownPhase, outcome.phaseNumber, outcome.typedApprovalOffered, deps.pinnedClientVersion(repoRoot), written.entries)}
8936
+ const stdout = listed + `${approvalLine2(shownPhase, outcome.phaseNumber, outcome.typedApprovalOffered, deps.pinnedClientVersion(repoRoot), written.entries, stage.credential.engagementId)}
8784
8937
  `;
8785
8938
  return { stdout, stderr: warnings, exitCode: 0 };
8786
8939
  }
@@ -8888,9 +9041,16 @@ var CANNOT_DRAW_PHASE_YET = `${P} this Halfcycle service cannot draw phase pictu
8888
9041
  function markdownAltText(text) {
8889
9042
  return text.replace(/[[\]\\]/g, (ch) => `\\${ch}`);
8890
9043
  }
9044
+ function closedAtWords(closedAt) {
9045
+ const m = /^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2})(?:\.\d+)?Z$/.exec(closedAt);
9046
+ return m === null ? closedAt : `${m[1]} ${m[2]} UTC`;
9047
+ }
9048
+ function closedPhraseFor(phase, closedAt) {
9049
+ return `phase "${phase}", as it closed on ${closedAtWords(closedAt)}`;
9050
+ }
8891
9051
  var ARCHITECTURE_PASTE = { doc: "docs", alt: "Architecture", base: ARCHITECTURE_BASE };
8892
9052
  function phasePaste(phase) {
8893
- return { doc: "docs/phases", alt: `Phase ${phase}`, base: phaseDiagramBase(phase) };
9053
+ return { doc: "docs/phases", alt: `Phase "${phase}"`, base: phaseDiagramBase(phase) };
8894
9054
  }
8895
9055
  function pasteLines(mermaid, target) {
8896
9056
  const block = mermaid.endsWith("\n") ? mermaid : `${mermaid}
@@ -8945,7 +9105,7 @@ async function runDraw(repoRoot, picture, deps) {
8945
9105
  return { stdout: "", stderr: credentialRead.line, exitCode: 1 };
8946
9106
  const closed = await deps.draw(credentialRead.credential, { picture: "closed-phase", phase });
8947
9107
  if (closed.kind === "drawn" || closed.kind === "refused") {
8948
- return writeDrawOutcome(repoRoot, deps, closed, phasePaste(phase), "", CANNOT_DRAW_PHASE_YET);
9108
+ return writeDrawOutcome(repoRoot, deps, closed, phasePaste(phase), "", CANNOT_DRAW_PHASE_YET, phase);
8949
9109
  }
8950
9110
  let checkedPlan = null;
8951
9111
  const beforeSend = (description) => {
@@ -8983,14 +9143,14 @@ async function runDraw(repoRoot, picture, deps) {
8983
9143
  case "recorded":
8984
9144
  break;
8985
9145
  }
8986
- return drawAndWrite(repoRoot, deps, stage.credential, { picture: "phase", phase: plan.phase, planVersion: reported.version }, phasePaste(phase), warnings, CANNOT_DRAW_PHASE_YET);
9146
+ return drawAndWrite(repoRoot, deps, stage.credential, { picture: "phase", phase: plan.phase, planVersion: reported.version }, phasePaste(phase), warnings, CANNOT_DRAW_PHASE_YET, phase);
8987
9147
  }
8988
- async function drawAndWrite(repoRoot, deps, credential, request, target, warnings, cannotDrawYet) {
9148
+ async function drawAndWrite(repoRoot, deps, credential, request, target, warnings, cannotDrawYet, phaseName) {
8989
9149
  const outcome = await deps.draw(credential, request);
8990
9150
  const usable = outcome.kind === "not-closed" ? { kind: "unavailable", why: "Halfcycle sent an answer this command could not use. Nothing was written; try again later." } : outcome;
8991
- return writeDrawOutcome(repoRoot, deps, usable, target, warnings, cannotDrawYet);
9151
+ return writeDrawOutcome(repoRoot, deps, usable, target, warnings, cannotDrawYet, phaseName);
8992
9152
  }
8993
- async function writeDrawOutcome(repoRoot, deps, outcome, target, warnings, cannotDrawYet) {
9153
+ async function writeDrawOutcome(repoRoot, deps, outcome, target, warnings, cannotDrawYet, phaseName) {
8994
9154
  const base = target.base;
8995
9155
  if (outcome.kind === "no-route")
8996
9156
  return { stdout: "", stderr: warnings + cannotDrawYet, exitCode: 1 };
@@ -9014,20 +9174,25 @@ async function writeDrawOutcome(repoRoot, deps, outcome, target, warnings, canno
9014
9174
  exitCode: 1
9015
9175
  };
9016
9176
  }
9177
+ const closedAt = outcome.drawn.closedAt;
9178
+ const closedLead = closedAt !== void 0 && phaseName !== void 0 ? `This is ${closedPhraseFor(phaseName, closedAt)}.
9179
+
9180
+ ` : "";
9181
+ const pasteTarget = closedAt !== void 0 && phaseName !== void 0 ? { ...target, alt: closedPhraseFor(phaseName, closedAt) } : target;
9017
9182
  const rel = (which) => diagramRel(base, which);
9018
9183
  const result = deps.write(repoRoot, base, checked.set);
9019
9184
  switch (result.kind) {
9020
9185
  case "up-to-date":
9021
9186
  return {
9022
- stdout: `[halfcycle] The pictures in ${DIAGRAM_DIR_REL}/ are up to date.
9023
- ` + pasteLines(outcome.drawn.mermaid, target),
9187
+ stdout: closedLead + `[halfcycle] The pictures in ${DIAGRAM_DIR_REL}/ are up to date.
9188
+ ` + pasteLines(outcome.drawn.mermaid, pasteTarget),
9024
9189
  stderr: warnings,
9025
9190
  exitCode: 0
9026
9191
  };
9027
9192
  case "written":
9028
9193
  return {
9029
- stdout: `[halfcycle] Wrote ${spokenList(DIAGRAM_FILES_AS_LISTED.map(rel))}.
9030
- ` + pasteLines(outcome.drawn.mermaid, target),
9194
+ stdout: closedLead + `[halfcycle] Wrote ${spokenList(DIAGRAM_FILES_AS_LISTED.map(rel))}.
9195
+ ` + pasteLines(outcome.drawn.mermaid, pasteTarget),
9031
9196
  stderr: warnings,
9032
9197
  exitCode: 0
9033
9198
  };
@@ -9094,8 +9259,7 @@ function parseReviewArgs(argv) {
9094
9259
  }
9095
9260
  function rv1Line(view) {
9096
9261
  const waiting = view.parts.filter((p) => p.decidable).length;
9097
- const lead = view.number === null ? `Phase "${view.phase}"` : `Phase ${view.number}, "${view.phase}"`;
9098
- return `${lead}, review round ${view.round}. Parts waiting for your decision: ${waiting} of ${view.parts.length}.`;
9262
+ return `Phase "${view.phase}", review round ${view.round}. Parts waiting for your decision: ${waiting} of ${view.parts.length}.`;
9099
9263
  }
9100
9264
  var RV1A_LINE = "Halfcycle could not count what this phase changed, so judge each part on what the agents reported alone.";
9101
9265
  var CHANGE_WORDS = {
@@ -9115,7 +9279,7 @@ function partStatus(part, round) {
9115
9279
  case "accepted":
9116
9280
  return "Accepted";
9117
9281
  case "carried":
9118
- return part.carriedTo === void 0 || part.carriedTo === null ? "Carried to the next phase planned" : `Carried to phase ${part.carriedTo.number}`;
9282
+ return part.carriedTo === void 0 || part.carriedTo === null ? "Carried to the next phase planned" : `Carried to phase "${part.carriedTo.phase}"`;
9119
9283
  case "dropped":
9120
9284
  return "Dropped";
9121
9285
  case void 0:
@@ -9162,6 +9326,9 @@ function partLines(part, view) {
9162
9326
  }
9163
9327
  const observed = view.observed ?? nothingCounted(view.phase);
9164
9328
  lines.push(` What Halfcycle saw: ${observedLines(observed, part.part, view.description).join("; ")}`);
9329
+ if (!part.built && part.tasks.length > 0 && part.tasks.every((t) => t.state === "completed") && partChangedUnderPaths(observed, part.part, view.description) === "unchanged") {
9330
+ lines.push(` ${NOTHING_COUNTED_DESPITE_FINISHED_LINE}`);
9331
+ }
9165
9332
  const code = codeLinkLine(part.codeLink);
9166
9333
  if (code !== void 0)
9167
9334
  lines.push(` ${code}`);
@@ -9511,6 +9678,7 @@ var REASON_PLACEHOLDER = {
9511
9678
  "open-phase": "<why you are opening it anyway>"
9512
9679
  };
9513
9680
  var OWN_WORDS = {
9681
+ "--actor": "name, the one you go by as the agent",
9514
9682
  "--reason": "reason",
9515
9683
  "--evidence": "evidence",
9516
9684
  "--finding": "finding"
@@ -11102,7 +11270,7 @@ halfcycle open-phase: until ${opened.stampFailure.path} can be written, guard ev
11102
11270
  acceptanceOutcome: { verdict, findings: findingsDetail.length, findingsDetail },
11103
11271
  stated
11104
11272
  }, () => runObserveChild(runner, credential, repoRoot), repoRoot);
11105
- const report = closeStepReport(phaseArg, attempt);
11273
+ const report = closeStepReport(phaseArg, attempt, credential.engagementId);
11106
11274
  for (const line of report.stdout)
11107
11275
  process.stdout.write(`${line}
11108
11276
  `);