halfcycle 0.3.38 → 0.3.40
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/README.md +1 -1
- package/bin/bin.bundle.mjs +4 -4
- package/dist/bin.js +111 -42
- package/dist/bin.js.map +1 -1
- package/dist/build-record/obtain.d.ts +4 -2
- package/dist/build-record/obtain.d.ts.map +1 -1
- package/dist/build-record/render-v2.d.ts +8 -0
- package/dist/build-record/render-v2.d.ts.map +1 -1
- package/dist/cli-contract.d.ts +24 -1
- package/dist/cli-contract.d.ts.map +1 -1
- package/dist/index.js +27 -12
- package/dist/index.js.map +1 -1
- package/dist/open-phase.d.ts +12 -0
- package/dist/open-phase.d.ts.map +1 -1
- package/dist/uninstall.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "halfcycle",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.40",
|
|
4
4
|
"description": "Halfcycle Method bundle — resolution-stub slash commands, remote method-delivery registration, and governance-hook wiring for a Halfcycle engagement repo. It ships NO worker role files: a project's roles are authored from that project's own recorded decisions at orchestration kickoff (FX-2, W3-F-27).",
|
|
5
5
|
"commands": [
|
|
6
6
|
{
|
package/README.md
CHANGED
|
@@ -146,7 +146,7 @@ This matters enough to be specific about.
|
|
|
146
146
|
|
|
147
147
|
**Halfcycle never holds or proxies your Claude credential.** Inference runs on your own Claude Code, on your own subscription or seats. We are not a hosted runtime reselling you inference.
|
|
148
148
|
|
|
149
|
-
**Your own Halfcycle credential lives outside this repository, in a file named `env` under your home directory — never in anything you'd commit.**
|
|
149
|
+
**Your own Halfcycle credential lives outside this repository, in a file named `env` under your home directory — never in anything you'd commit.** Installing writes it. Opening or closing a phase rewrites it, to record which phase is open, and the credential in it is also renewed there automatically to keep your connection current. Uninstalling with `--remove-credential` deletes it. Nothing else changes it.
|
|
150
150
|
|
|
151
151
|
**What the checks receive is the shape of a change, not its contents.** File paths, structural shapes, environment variable names and how they are used, the tables a write touches, route signatures, outbound host names. There is no file body and no diff in the request.
|
|
152
152
|
|
package/bin/bin.bundle.mjs
CHANGED
|
@@ -15358,8 +15358,8 @@ function hookReply(event) {
|
|
|
15358
15358
|
}
|
|
15359
15359
|
};
|
|
15360
15360
|
}
|
|
15361
|
-
function infraWarningText(
|
|
15362
|
-
return
|
|
15361
|
+
function infraWarningText() {
|
|
15362
|
+
return "[Halfcycle] WARNING: Halfcycle's check service could not be reached, so nothing was checked this time. The session continues without checks.";
|
|
15363
15363
|
}
|
|
15364
15364
|
function localFaultText(message) {
|
|
15365
15365
|
return `[Halfcycle] WARNING: Local fault \u2014 ${message}. The guard service was not contacted. Session continues (fail-open).`;
|
|
@@ -17668,7 +17668,7 @@ async function evaluateEdit(reply, readSeat) {
|
|
|
17668
17668
|
reply.notice(contractFaultText(failure.message));
|
|
17669
17669
|
return;
|
|
17670
17670
|
}
|
|
17671
|
-
reply.notice(infraWarningText(
|
|
17671
|
+
reply.notice(infraWarningText());
|
|
17672
17672
|
return;
|
|
17673
17673
|
}
|
|
17674
17674
|
const telemetryConfig = readTelemetryConfig();
|
|
@@ -17964,7 +17964,7 @@ async function evaluateSession(input, kind, reply, readSeat) {
|
|
|
17964
17964
|
reply.notice(contractFaultText(failure.message));
|
|
17965
17965
|
return;
|
|
17966
17966
|
}
|
|
17967
|
-
reply.notice(infraWarningText(
|
|
17967
|
+
reply.notice(infraWarningText());
|
|
17968
17968
|
return;
|
|
17969
17969
|
}
|
|
17970
17970
|
const telemetryConfig = readTelemetryConfig();
|
package/dist/bin.js
CHANGED
|
@@ -238,6 +238,7 @@ var MCP_SERVER_KEY = "halfcycle";
|
|
|
238
238
|
function quoteForShell(value) {
|
|
239
239
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
240
240
|
}
|
|
241
|
+
var HALFCYCLE_COMMAND = "npx halfcycle@latest";
|
|
241
242
|
|
|
242
243
|
// ../events/dist/result.js
|
|
243
244
|
import { z as z2 } from "zod";
|
|
@@ -4593,31 +4594,44 @@ function renderCouldNotRun(entries, p) {
|
|
|
4593
4594
|
}
|
|
4594
4595
|
return lines.join("\n");
|
|
4595
4596
|
}
|
|
4596
|
-
function
|
|
4597
|
+
function headlineText(observed, attested, checks) {
|
|
4597
4598
|
const met = attested.decision === "done-when-met";
|
|
4598
4599
|
const problemsReported = attested.walk !== null && attested.walk.verdict === "defects";
|
|
4599
4600
|
switch (observed.crossed.kind) {
|
|
4600
4601
|
case "none":
|
|
4601
4602
|
if (!met) {
|
|
4602
|
-
return "
|
|
4603
|
+
return ["Closed by override.", "Halfcycle saw nothing outstanding, and the person closing the phase chose to override rather than state its finish conditions were met."];
|
|
4603
4604
|
}
|
|
4604
4605
|
if (problemsReported) {
|
|
4605
|
-
return "
|
|
4606
|
+
return ["Closed with problems reported.", "Halfcycle saw nothing outstanding, and the person closing the phase reported problems found in its acceptance walk."];
|
|
4606
4607
|
}
|
|
4607
|
-
return checks === "not-observed" ? "
|
|
4608
|
+
return checks === "not-observed" ? ["Closed with nothing outstanding, but no check on record.", "Halfcycle saw nothing outstanding, and the person closing the phase stated its finish conditions were met, but Halfcycle has no record of any check running during this phase, so this is not a clean result."] : ["Closed cleanly.", "Halfcycle saw nothing outstanding, and the person closing the phase stated its finish conditions were met."];
|
|
4608
4609
|
case "unfinished-steps":
|
|
4609
4610
|
case "clean-over-findings":
|
|
4610
4611
|
if (!met) {
|
|
4611
|
-
return "
|
|
4612
|
+
return ["Closed over outstanding work.", "Halfcycle saw work still outstanding, and the person closing the phase chose to override it."];
|
|
4612
4613
|
}
|
|
4613
|
-
return problemsReported ? "
|
|
4614
|
+
return problemsReported ? ["Closed over outstanding work.", "Halfcycle saw work still outstanding, and the person closing the phase closed it as it stood, reporting problems found in its acceptance walk."] : ["Closed over outstanding work.", "Halfcycle saw work still outstanding, and the person closing the phase stated its finish conditions were met anyway."];
|
|
4614
4615
|
case "not-recorded":
|
|
4615
4616
|
if (!met) {
|
|
4616
|
-
return "
|
|
4617
|
+
return ["Outstanding work not recorded.", "Halfcycle has no record of what was outstanding at this close. The person closing the phase chose to override."];
|
|
4617
4618
|
}
|
|
4618
|
-
return problemsReported ? "
|
|
4619
|
+
return problemsReported ? ["Outstanding work not recorded.", "Halfcycle has no record of what was outstanding at this close. The person closing the phase reported problems found in its acceptance walk."] : ["Outstanding work not recorded.", "Halfcycle has no record of what was outstanding at this close. The person closing the phase stated its finish conditions were met."];
|
|
4619
4620
|
}
|
|
4620
4621
|
}
|
|
4622
|
+
function headline(observed, attested, checks) {
|
|
4623
|
+
const [title, sentence] = headlineText(observed, attested, checks);
|
|
4624
|
+
return `**${title}** ${sentence}`;
|
|
4625
|
+
}
|
|
4626
|
+
function latestCloseHeadline(record2) {
|
|
4627
|
+
const attested = new Map(record2.attested.closes.map((c) => [c.sequence, c]));
|
|
4628
|
+
const ordered = [...record2.observed.closes].sort((a, b) => a.sequence - b.sequence);
|
|
4629
|
+
const latest = ordered[ordered.length - 1];
|
|
4630
|
+
const stated = latest === void 0 ? void 0 : attested.get(latest.sequence);
|
|
4631
|
+
if (latest === void 0 || stated === void 0)
|
|
4632
|
+
throw new Error(MISMATCHED_HALVES);
|
|
4633
|
+
return headlineText(latest, stated, record2.observed.checks.state).join(" ");
|
|
4634
|
+
}
|
|
4621
4635
|
function closedAtText(closedAt, p) {
|
|
4622
4636
|
const m = /^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2})(?:\.\d+)?Z$/.exec(closedAt);
|
|
4623
4637
|
return m === null ? p.text(closedAt) : `${m[1]} ${m[2]} UTC`;
|
|
@@ -4670,9 +4684,9 @@ function machines(coverage, checks) {
|
|
|
4670
4684
|
function walkLines(walk, p) {
|
|
4671
4685
|
const label = "> - Acceptance walk (the finished work tried as a user would, by someone other than its builder): ";
|
|
4672
4686
|
if (walk === null)
|
|
4673
|
-
return [`${label}
|
|
4687
|
+
return [`${label}the person closing the phase reported none.`];
|
|
4674
4688
|
const lines = [
|
|
4675
|
-
`${label}${walk.verdict === "clean" ? "
|
|
4689
|
+
`${label}${walk.verdict === "clean" ? "the person closing the phase said it found no problems." : "the person closing the phase said it found problems."}`
|
|
4676
4690
|
];
|
|
4677
4691
|
walk.findings.forEach((finding, i) => {
|
|
4678
4692
|
lines.push(`> - Finding ${String(i + 1)}: ${p.text(finding)}`);
|
|
@@ -4963,7 +4977,8 @@ async function obtainBuildRecord(credential, repoRoot, phase, io = NODE_RECORD_P
|
|
|
4963
4977
|
return {
|
|
4964
4978
|
kind: "written",
|
|
4965
4979
|
jsonPath: relative3(repoRoot, result.jsonPath),
|
|
4966
|
-
mdPath: relative3(repoRoot, result.mdPath)
|
|
4980
|
+
mdPath: relative3(repoRoot, result.mdPath),
|
|
4981
|
+
headline: latestCloseHeadline(answer.record)
|
|
4967
4982
|
};
|
|
4968
4983
|
}
|
|
4969
4984
|
if (isMissingRoute(res.status, raw)) {
|
|
@@ -4982,12 +4997,22 @@ async function obtainBuildRecord(credential, repoRoot, phase, io = NODE_RECORD_P
|
|
|
4982
4997
|
};
|
|
4983
4998
|
}
|
|
4984
4999
|
function refetchCommand(phase) {
|
|
4985
|
-
return
|
|
5000
|
+
return `${HALFCYCLE_COMMAND} build-record ${quoteForShell(phase)}`;
|
|
4986
5001
|
}
|
|
4987
5002
|
|
|
4988
5003
|
// dist/open-phase.js
|
|
4989
5004
|
import { readFileSync as readFileSync7 } from "node:fs";
|
|
4990
5005
|
import { join as join7 } from "node:path";
|
|
5006
|
+
function describeOpen(opened) {
|
|
5007
|
+
const { phase, previousPhase } = opened;
|
|
5008
|
+
if (previousPhase === phase) {
|
|
5009
|
+
return phase === null ? "Already at project scope \u2014 nothing moved." : `Phase "${phase}" is already open \u2014 nothing moved.`;
|
|
5010
|
+
}
|
|
5011
|
+
const what = phase === null ? "Returned to project scope." : `Opened phase "${phase}".`;
|
|
5012
|
+
if (previousPhase === null)
|
|
5013
|
+
return phase === null ? what : `Moved from project scope to phase "${phase}".`;
|
|
5014
|
+
return opened.decision.blockingFinding === null ? `${what} Phase "${previousPhase}" before it had already been closed.` : `${what} Phase "${previousPhase}" before it was not closed, and this override is recorded.`;
|
|
5015
|
+
}
|
|
4991
5016
|
var OpenPhaseRefused = class extends Error {
|
|
4992
5017
|
status;
|
|
4993
5018
|
finding;
|
|
@@ -5302,13 +5327,41 @@ function requirementMessage(verb, flag) {
|
|
|
5302
5327
|
function runnableInvocation(verb, phase) {
|
|
5303
5328
|
const contract = contractFor(verb);
|
|
5304
5329
|
const flags = (contract?.required ?? []).map((req) => `${req.flag} ${PLACEHOLDERS[req.flag] ?? '"\u2026"'}`).join(" ");
|
|
5305
|
-
return
|
|
5330
|
+
return `${HALFCYCLE_COMMAND} ${verb} ${quoteForShell(phase)}${flags ? ` ${flags}` : ""}`;
|
|
5306
5331
|
}
|
|
5307
5332
|
var PLACEHOLDERS = {
|
|
5308
5333
|
"--verdict": "<clean|defects>",
|
|
5309
5334
|
"--actor": '"you"',
|
|
5310
|
-
"--evidence": '"
|
|
5335
|
+
"--evidence": '"<what shows this phase is ready to start>"'
|
|
5311
5336
|
};
|
|
5337
|
+
var REASON_PLACEHOLDER = {
|
|
5338
|
+
"open-phase": "<why you are opening it anyway>",
|
|
5339
|
+
"close-phase": "<why you are closing over this>"
|
|
5340
|
+
};
|
|
5341
|
+
function closeOverrideInvocation(phase, verdict, actor) {
|
|
5342
|
+
return `${HALFCYCLE_COMMAND} close-phase ${quoteForShell(phase)} --verdict ${verdict} --actor ${quoteForShell(actor)} --override --reason "${REASON_PLACEHOLDER["close-phase"]}"`;
|
|
5343
|
+
}
|
|
5344
|
+
var OWN_WORDS = {
|
|
5345
|
+
"--reason": "reason",
|
|
5346
|
+
"--evidence": "evidence",
|
|
5347
|
+
"--finding": "finding"
|
|
5348
|
+
};
|
|
5349
|
+
function isPlaceholder(value) {
|
|
5350
|
+
const v = value.trim();
|
|
5351
|
+
return v === "\u2026" || v === "..." || /^<[^<>]+>$/.test(v);
|
|
5352
|
+
}
|
|
5353
|
+
function placeholderRefusal(verb, tokens) {
|
|
5354
|
+
for (let i = 0; i < tokens.length - 1; i++) {
|
|
5355
|
+
const noun = OWN_WORDS[tokens[i]];
|
|
5356
|
+
const value = tokens[i + 1];
|
|
5357
|
+
if (noun === void 0 || value.startsWith("--"))
|
|
5358
|
+
continue;
|
|
5359
|
+
if (isPlaceholder(value)) {
|
|
5360
|
+
return `halfcycle ${verb}: ${tokens[i]} "${value.trim()}" is a placeholder from an example \u2014 replace it with your own ${noun}; nothing was sent.`;
|
|
5361
|
+
}
|
|
5362
|
+
}
|
|
5363
|
+
return null;
|
|
5364
|
+
}
|
|
5312
5365
|
|
|
5313
5366
|
// dist/uninstall.js
|
|
5314
5367
|
import { lstatSync as lstatSync2, readFileSync as readFileSync8, readdirSync, rmSync as rmSync3, rmdirSync, writeFileSync as writeFileSync5 } from "node:fs";
|
|
@@ -5370,7 +5423,7 @@ function readMergedJson(targetRepo, rel) {
|
|
|
5370
5423
|
value = void 0;
|
|
5371
5424
|
}
|
|
5372
5425
|
if (!isPlainObject2(value)) {
|
|
5373
|
-
throw new Refusal(`${rel} is not a JSON object, so Halfcycle cannot take its own entries out of it and nothing was removed. Fix it or delete it, then run "
|
|
5426
|
+
throw new Refusal(`${rel} is not a JSON object, so Halfcycle cannot take its own entries out of it and nothing was removed. Fix it or delete it, then run "${HALFCYCLE_COMMAND} uninstall" again.`);
|
|
5374
5427
|
}
|
|
5375
5428
|
return { state: "object", value };
|
|
5376
5429
|
}
|
|
@@ -5384,14 +5437,14 @@ function validate(options) {
|
|
|
5384
5437
|
if (!lexists(manifestPath)) {
|
|
5385
5438
|
if (!lexists(pinPath))
|
|
5386
5439
|
return "not-installed";
|
|
5387
|
-
throw new Refusal(`Halfcycle was installed here by a version that kept no record of what it wrote, so nothing was removed. Run "
|
|
5440
|
+
throw new Refusal(`Halfcycle was installed here by a version that kept no record of what it wrote, so nothing was removed. Run "${HALFCYCLE_COMMAND}" once \u2014 it writes that record \u2014 then run "${HALFCYCLE_COMMAND} uninstall".`);
|
|
5388
5441
|
}
|
|
5389
5442
|
let manifest;
|
|
5390
5443
|
try {
|
|
5391
5444
|
manifest = parseManifest(readFileSync8(manifestPath, "utf-8"));
|
|
5392
5445
|
} catch (err) {
|
|
5393
5446
|
const why = err instanceof UnreadableManifestError ? err.message : "it could not be read";
|
|
5394
|
-
throw new Refusal(`${INSTALL_MANIFEST_REL} cannot be read (${why}), so nothing was removed. Restore it from git, or run "
|
|
5447
|
+
throw new Refusal(`${INSTALL_MANIFEST_REL} cannot be read (${why}), so nothing was removed. Restore it from git, or run "${HALFCYCLE_COMMAND}" once to rewrite it, then run "${HALFCYCLE_COMMAND} uninstall" again.`);
|
|
5395
5448
|
}
|
|
5396
5449
|
let engagementId;
|
|
5397
5450
|
let pinReadable = true;
|
|
@@ -5907,13 +5960,13 @@ function renderUninstallReport(result, home) {
|
|
|
5907
5960
|
section("Kept, because it changed after Halfcycle wrote it \u2014 delete it yourself if you do not want it:", result.kept);
|
|
5908
5961
|
section('Kept in .gitignore, because the path each line covers still exists \u2014 without the line, "git add ." would commit it:', result.keptIgnoreLines);
|
|
5909
5962
|
section("Left alone \u2014 Halfcycle did not write these, or cannot tell that it did:", result.leftAlone);
|
|
5910
|
-
section(
|
|
5963
|
+
section(`Could not remove \u2014 fix the cause, then run "${HALFCYCLE_COMMAND} uninstall" again to finish:`, result.failed);
|
|
5911
5964
|
const c = result.credential;
|
|
5912
5965
|
switch (c.kind) {
|
|
5913
5966
|
case "still-held":
|
|
5914
5967
|
out.push(`[halfcycle] This machine still holds this project's credential, at ${shownPath(c.dir, home)}.`);
|
|
5915
5968
|
out.push(" Every checkout and worktree of this project on this machine uses it.");
|
|
5916
|
-
out.push(` To remove it too:
|
|
5969
|
+
out.push(` To remove it too: ${HALFCYCLE_COMMAND} uninstall ${REMOVE_CREDENTIAL_FLAG}`);
|
|
5917
5970
|
break;
|
|
5918
5971
|
case "invalid-id":
|
|
5919
5972
|
out.push(`[halfcycle] This machine may still hold a credential for this project, but the project id in ${BUNDLE_PIN_REL} is not valid, so it is not shown as a path.`);
|
|
@@ -5921,7 +5974,7 @@ function renderUninstallReport(result, home) {
|
|
|
5921
5974
|
case "removed":
|
|
5922
5975
|
out.push(`[halfcycle] Removed this machine's credential for this project, at ${shownPath(c.dir, home)}.`);
|
|
5923
5976
|
out.push(" Every other checkout and worktree of this project on this machine used it too. Each stops reaching");
|
|
5924
|
-
out.push(
|
|
5977
|
+
out.push(` Halfcycle until \`${HALFCYCLE_COMMAND}\` is run there again.`);
|
|
5925
5978
|
break;
|
|
5926
5979
|
case "not-held":
|
|
5927
5980
|
out.push(`[halfcycle] This machine holds no credential for this project (nothing at ${shownPath(c.dir, home)}).`);
|
|
@@ -5930,7 +5983,7 @@ function renderUninstallReport(result, home) {
|
|
|
5930
5983
|
out.push(`[halfcycle] No credential was removed: ${BUNDLE_PIN_REL} is missing, so there is no project id to find it by.`);
|
|
5931
5984
|
break;
|
|
5932
5985
|
case "failed":
|
|
5933
|
-
out.push(`[halfcycle] Could not remove this machine's credential for this project, at ${shownPath(c.dir, home)}: ${c.reason}. Run "
|
|
5986
|
+
out.push(`[halfcycle] Could not remove this machine's credential for this project, at ${shownPath(c.dir, home)}: ${c.reason}. Run "${HALFCYCLE_COMMAND} uninstall ${REMOVE_CREDENTIAL_FLAG}" again to finish.`);
|
|
5934
5987
|
break;
|
|
5935
5988
|
case "nothing-to-say":
|
|
5936
5989
|
break;
|
|
@@ -6079,17 +6132,17 @@ function reportClaudeCodeVersion(verbose2, deps = {}) {
|
|
|
6079
6132
|
function shortId(id) {
|
|
6080
6133
|
return id.length > 10 ? `${id.slice(0, 8)}\u2026` : id;
|
|
6081
6134
|
}
|
|
6082
|
-
var USAGE = `
|
|
6135
|
+
var USAGE = ` ${HALFCYCLE_COMMAND} [install] [target-repo] [engagement-id] [self-build|client]
|
|
6083
6136
|
install into target (default: the current directory)
|
|
6084
6137
|
--yes / -y: use this machine's saved sign-in without being asked
|
|
6085
|
-
|
|
6086
|
-
|
|
6138
|
+
${HALFCYCLE_COMMAND} check-drift <target-repo>
|
|
6139
|
+
${HALFCYCLE_COMMAND} build-record <phase> [--repo <root>]
|
|
6087
6140
|
fetch a closed phase's Build Record again and write it into docs/build-records/
|
|
6088
|
-
|
|
6089
|
-
|
|
6141
|
+
${HALFCYCLE_COMMAND} open-phase <phase|--none> --actor "\u2026" (--evidence "\u2026" | --override --reason "\u2026") [--repo <root>]
|
|
6142
|
+
${HALFCYCLE_COMMAND} close-phase <phase> --verdict <clean|defects> --actor "\u2026" [--finding "\u2026"]\u2026 [--override --reason "\u2026"] [--repo <root>]
|
|
6090
6143
|
[--thesis "\u2026"] [--outcome "\u2026"] [--deviation "\u2026"]\u2026
|
|
6091
6144
|
close the phase and write its Build Record into docs/build-records/
|
|
6092
|
-
|
|
6145
|
+
${HALFCYCLE_COMMAND} uninstall [${REMOVE_CREDENTIAL_FLAG}]
|
|
6093
6146
|
take out what the installer added to this project, keeping anything you changed
|
|
6094
6147
|
${REMOVE_CREDENTIAL_FLAG}: also remove this machine's credential for the project, which every
|
|
6095
6148
|
checkout and worktree of that project on this machine shares
|
|
@@ -6470,13 +6523,23 @@ ${USAGE}`);
|
|
|
6470
6523
|
const toProjectScope = rest.includes("--none");
|
|
6471
6524
|
const phaseArg = firstPositional(rest);
|
|
6472
6525
|
if (!toProjectScope && !phaseArg) {
|
|
6473
|
-
process.stderr.write(
|
|
6526
|
+
process.stderr.write(`halfcycle open-phase: name the phase to open, or pass --none to return to project scope.
|
|
6527
|
+
${HALFCYCLE_COMMAND} open-phase <phase> --actor "you" --evidence "<what shows this phase is ready to start>"
|
|
6528
|
+
${HALFCYCLE_COMMAND} open-phase <phase> --actor "you" --override --reason "${REASON_PLACEHOLDER["open-phase"]}"
|
|
6529
|
+
`);
|
|
6474
6530
|
process.exit(2);
|
|
6475
6531
|
return;
|
|
6476
6532
|
}
|
|
6477
6533
|
const openShortfalls = missingRequirements("open-phase", rest);
|
|
6478
6534
|
if (openShortfalls.length > 0) {
|
|
6479
6535
|
process.stderr.write(`${openShortfalls[0].message}
|
|
6536
|
+
`);
|
|
6537
|
+
process.exit(2);
|
|
6538
|
+
return;
|
|
6539
|
+
}
|
|
6540
|
+
const openPlaceholder = placeholderRefusal("open-phase", rest);
|
|
6541
|
+
if (openPlaceholder !== null) {
|
|
6542
|
+
process.stderr.write(`${openPlaceholder}
|
|
6480
6543
|
`);
|
|
6481
6544
|
process.exit(2);
|
|
6482
6545
|
return;
|
|
@@ -6491,15 +6554,8 @@ ${USAGE}`);
|
|
|
6491
6554
|
try {
|
|
6492
6555
|
const credential = readRepoCredential(repoRoot);
|
|
6493
6556
|
const opened = await openPhase(credential, toProjectScope ? null : phaseArg, entry);
|
|
6494
|
-
|
|
6495
|
-
const from = opened.previousPhase === null ? "project scope" : `phase "${opened.previousPhase}"`;
|
|
6496
|
-
if (opened.previousPhase === opened.phase) {
|
|
6497
|
-
process.stdout.write(opened.phase === null ? "[halfcycle] Already at project scope \u2014 nothing moved.\n" : `[halfcycle] Phase "${opened.phase}" is already open \u2014 nothing moved.
|
|
6498
|
-
`);
|
|
6499
|
-
} else {
|
|
6500
|
-
process.stdout.write(`[halfcycle] Moved from ${from} to ${what}.
|
|
6557
|
+
process.stdout.write(`[halfcycle] ${describeOpen(opened)}
|
|
6501
6558
|
`);
|
|
6502
|
-
}
|
|
6503
6559
|
process.stdout.write(`[halfcycle] Recorded as ${opened.decision.decision} by ${opened.decision.actor} at ${opened.decision.decidedAt}.
|
|
6504
6560
|
`);
|
|
6505
6561
|
if (opened.decision.blockingFinding) {
|
|
@@ -6529,7 +6585,7 @@ halfcycle open-phase: until ${opened.stampFailure.path} can be written, guard ev
|
|
|
6529
6585
|
`);
|
|
6530
6586
|
}
|
|
6531
6587
|
if (err.status === 409) {
|
|
6532
|
-
process.stderr.write(`halfcycle open-phase: close the phase first \u2014 \`${runnableInvocation("close-phase", err.previousPhase ?? "<phase>")}\` (add --finding "
|
|
6588
|
+
process.stderr.write(`halfcycle open-phase: close the phase first \u2014 \`${runnableInvocation("close-phase", err.previousPhase ?? "<phase>")}\` (add --finding "<what is still wrong>" once per finding when the verdict is defects) \u2014 or re-run with --override --reason "${REASON_PLACEHOLDER["open-phase"]}", which is recorded, not waived.
|
|
6533
6589
|
`);
|
|
6534
6590
|
}
|
|
6535
6591
|
process.exit(1);
|
|
@@ -6545,13 +6601,23 @@ halfcycle open-phase: until ${opened.stampFailure.path} can be written, guard ev
|
|
|
6545
6601
|
const repoRoot = flagValue(rest, "--repo") ?? process.cwd();
|
|
6546
6602
|
const phaseArg = firstPositional(rest);
|
|
6547
6603
|
if (!phaseArg) {
|
|
6548
|
-
process.stderr.write(
|
|
6604
|
+
process.stderr.write(`halfcycle close-phase: name the phase you are closing.
|
|
6605
|
+
${HALFCYCLE_COMMAND} close-phase <phase> --verdict clean --actor "you"
|
|
6606
|
+
${HALFCYCLE_COMMAND} close-phase <phase> --verdict defects --actor "you" --finding "<what is still wrong>"
|
|
6607
|
+
`);
|
|
6549
6608
|
process.exit(2);
|
|
6550
6609
|
return;
|
|
6551
6610
|
}
|
|
6552
6611
|
const closeShortfalls = missingRequirements("close-phase", rest);
|
|
6553
6612
|
if (closeShortfalls.length > 0) {
|
|
6554
6613
|
process.stderr.write(`${closeShortfalls[0].message}
|
|
6614
|
+
`);
|
|
6615
|
+
process.exit(2);
|
|
6616
|
+
return;
|
|
6617
|
+
}
|
|
6618
|
+
const closePlaceholder = placeholderRefusal("close-phase", rest);
|
|
6619
|
+
if (closePlaceholder !== null) {
|
|
6620
|
+
process.stderr.write(`${closePlaceholder}
|
|
6555
6621
|
`);
|
|
6556
6622
|
process.exit(2);
|
|
6557
6623
|
return;
|
|
@@ -6607,7 +6673,7 @@ halfcycle open-phase: until ${opened.stampFailure.path} can be written, guard ev
|
|
|
6607
6673
|
findings: findingsDetail.length,
|
|
6608
6674
|
findingsDetail
|
|
6609
6675
|
}, closeDecision, void 0, repoRoot, stated);
|
|
6610
|
-
process.stdout.write(`[halfcycle] Phase "${phaseArg}" closed
|
|
6676
|
+
process.stdout.write(`[halfcycle] Phase "${phaseArg}" closed by ${closeDecision.actor}${findingsDetail.length > 0 ? `, who reported ${findingsDetail.length} finding(s)` : ""}. The next phase can be opened.
|
|
6611
6677
|
`);
|
|
6612
6678
|
if (closeDecision.decision === "override") {
|
|
6613
6679
|
process.stdout.write(`[halfcycle] This close was an OVERRIDE: ${closeDecision.reason}
|
|
@@ -6624,7 +6690,7 @@ halfcycle open-phase: until ${opened.stampFailure.path} can be written, guard ev
|
|
|
6624
6690
|
} else {
|
|
6625
6691
|
process.stderr.write(notWrittenLines("close-phase", obtained, phaseArg));
|
|
6626
6692
|
}
|
|
6627
|
-
process.stdout.write(`[halfcycle] Open the next phase with:
|
|
6693
|
+
process.stdout.write(`[halfcycle] Open the next phase with: ${HALFCYCLE_COMMAND} open-phase <phase> --actor "you" --evidence "<what shows this phase is ready to start>"
|
|
6628
6694
|
`);
|
|
6629
6695
|
if (closed.stampFailure) {
|
|
6630
6696
|
process.stderr.write(`halfcycle close-phase: the phase closed, but this machine's record of it could not be cleared: ${closed.stampFailure.reason}
|
|
@@ -6645,7 +6711,7 @@ halfcycle close-phase: until ${closed.stampFailure.path} can be written, guard e
|
|
|
6645
6711
|
`);
|
|
6646
6712
|
process.stderr.write(`halfcycle close-phase: outstanding work \u2014 ${plainOutstandingFinding(err.finding)}
|
|
6647
6713
|
`);
|
|
6648
|
-
process.stderr.write(`halfcycle close-phase: close it anyway with
|
|
6714
|
+
process.stderr.write(`halfcycle close-phase: close it anyway with \`${closeOverrideInvocation(phaseArg, verdict, closeActor)}\` \u2014 recorded, not waived.
|
|
6649
6715
|
`);
|
|
6650
6716
|
process.exit(1);
|
|
6651
6717
|
return;
|
|
@@ -6656,7 +6722,9 @@ halfcycle close-phase: until ${closed.stampFailure.path} can be written, guard e
|
|
|
6656
6722
|
process.stderr.write("halfcycle close-phase: Nothing was closed.\n");
|
|
6657
6723
|
}
|
|
6658
6724
|
if (err.code === "PhaseAlreadyClosed") {
|
|
6659
|
-
process.stderr.write(
|
|
6725
|
+
process.stderr.write(`halfcycle close-phase: to close a phase again, open it again first.
|
|
6726
|
+
halfcycle close-phase: To get a closed phase's Build Record: ${HALFCYCLE_COMMAND} build-record <the phase that closed>
|
|
6727
|
+
`);
|
|
6660
6728
|
}
|
|
6661
6729
|
process.exit(1);
|
|
6662
6730
|
return;
|
|
@@ -6701,6 +6769,7 @@ function writtenLines(written) {
|
|
|
6701
6769
|
return `[halfcycle] Build Record written:
|
|
6702
6770
|
${written.mdPath}
|
|
6703
6771
|
${written.jsonPath}
|
|
6772
|
+
[halfcycle] The record says: ${written.headline}
|
|
6704
6773
|
`;
|
|
6705
6774
|
}
|
|
6706
6775
|
function notWrittenLines(verb, outcome, phase) {
|