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/.claude-plugin/plugin.json +1 -1
- package/bin/bin.bundle.mjs +152 -137
- package/dist/bin.js +0 -0
- package/dist/build-record/render-v2.d.ts +5 -0
- package/dist/build-record/render-v2.d.ts.map +1 -1
- package/dist/cli-contract.d.ts.map +1 -1
- package/dist/cli.js +290 -122
- package/dist/cli.js.map +3 -3
- package/dist/close-phase.d.ts +10 -2
- package/dist/close-phase.d.ts.map +1 -1
- package/dist/draw/index.d.ts +4 -1
- package/dist/draw/index.d.ts.map +1 -1
- package/dist/index.js +137 -62
- package/dist/index.js.map +2 -2
- package/dist/plan.d.ts +21 -10
- package/dist/plan.d.ts.map +1 -1
- package/dist/review.d.ts +6 -3
- package/dist/review.d.ts.map +1 -1
- package/package.json +11 -11
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", "
|
|
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
|
|
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
|
|
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
|
-
// ──
|
|
1375
|
+
// ── leads ───────────────────────────────────────────────────────────────────
|
|
1339
1376
|
{
|
|
1340
1377
|
callsign: "ARGUS",
|
|
1341
|
-
group: "
|
|
1342
|
-
|
|
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: "
|
|
1349
|
-
|
|
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"
|
|
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
|
-
|
|
2412
|
-
|
|
2413
|
-
const
|
|
2414
|
-
|
|
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
|
|
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" ?
|
|
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 ${
|
|
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.
|
|
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
|
-
|
|
8697
|
-
|
|
8698
|
-
if (!
|
|
8699
|
-
return
|
|
8700
|
-
|
|
8701
|
-
|
|
8702
|
-
|
|
8703
|
-
|
|
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.
|
|
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
|
|
8712
|
-
const carriedMark =
|
|
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}
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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.
|
|
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
|
`);
|