halfcycle 0.4.5 → 0.4.7
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 +1325 -155
- package/commands/agent-teams-orchestration.md +1 -1
- package/commands/stack-assembly-worker.md +1 -1
- package/commands/task-execution.md +1 -1
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -1
- package/dist/build-record/obtain.d.ts +3 -1
- package/dist/build-record/obtain.d.ts.map +1 -1
- package/dist/build-record/write.d.ts +5 -1
- package/dist/build-record/write.d.ts.map +1 -1
- package/dist/cli-contract.d.ts +1 -1
- package/dist/cli-contract.d.ts.map +1 -1
- package/dist/cli.js +2326 -190
- package/dist/cli.js.map +3 -3
- package/dist/close-phase.d.ts +8 -1
- package/dist/close-phase.d.ts.map +1 -1
- package/dist/draw/index.d.ts +135 -0
- package/dist/draw/index.d.ts.map +1 -0
- package/dist/draw/write.d.ts +282 -0
- package/dist/draw/write.d.ts.map +1 -0
- package/dist/index.js +96 -17
- package/dist/index.js.map +3 -3
- package/dist/install.d.ts +33 -1
- package/dist/install.d.ts.map +1 -1
- package/dist/missing-route.d.ts +36 -0
- package/dist/missing-route.d.ts.map +1 -0
- package/dist/open-phase.d.ts +8 -1
- package/dist/open-phase.d.ts.map +1 -1
- package/dist/plan.d.ts +200 -0
- package/dist/plan.d.ts.map +1 -0
- package/dist/system-report.d.ts +179 -0
- package/dist/system-report.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -118,6 +118,7 @@ var VENDORED_BIN_REL = ".halfcycle/bin/bin.bundle.mjs";
|
|
|
118
118
|
var SESSION_START_MARKER_REL = ".claude/hooks/session-start-marker.sh";
|
|
119
119
|
var GUARD_RUNNER_REL = ".claude/hooks/guard-runner.sh";
|
|
120
120
|
var USER_PROMPT_REMINDER_REL = ".claude/hooks/user-prompt-reminder.sh";
|
|
121
|
+
var USER_PROMPT_SCOPE_REL = ".claude/hooks/user-prompt-scope.sh";
|
|
121
122
|
var SESSION_END_MARKER_REL = ".claude/hooks/session-end-marker.sh";
|
|
122
123
|
var WRITE_CAPABLE_TOOLS = ["Edit", "MultiEdit", "NotebookEdit", "Write"];
|
|
123
124
|
function hookCommand(script, verb) {
|
|
@@ -146,6 +147,13 @@ var HALFCYCLE_HOOK_WIRING = [
|
|
|
146
147
|
command: hookCommand(USER_PROMPT_REMINDER_REL),
|
|
147
148
|
script: USER_PROMPT_REMINDER_REL
|
|
148
149
|
},
|
|
150
|
+
{
|
|
151
|
+
event: "UserPromptSubmit",
|
|
152
|
+
matcher: null,
|
|
153
|
+
timeout: 10,
|
|
154
|
+
command: hookCommand(USER_PROMPT_SCOPE_REL),
|
|
155
|
+
script: USER_PROMPT_SCOPE_REL
|
|
156
|
+
},
|
|
149
157
|
{
|
|
150
158
|
event: "PostToolUse",
|
|
151
159
|
matcher: WRITE_CAPABLE_TOOLS.join("|"),
|
|
@@ -227,6 +235,9 @@ function quoteForShell(value) {
|
|
|
227
235
|
}
|
|
228
236
|
var HALFCYCLE_COMMAND = "npx halfcycle@latest";
|
|
229
237
|
|
|
238
|
+
// ../core/dist/release-version.js
|
|
239
|
+
var RELEASE_VERSION_PATTERN = /^\d+\.\d+\.\d+$/;
|
|
240
|
+
|
|
230
241
|
// ../events/dist/result.js
|
|
231
242
|
import { z as z2 } from "zod";
|
|
232
243
|
var firedGuardSchema = z2.object({
|
|
@@ -293,15 +304,12 @@ function hasLoneSurrogate(text) {
|
|
|
293
304
|
}
|
|
294
305
|
var LONE_SURROGATE_MESSAGE = "must be valid text: it holds half of a character pair with no other half";
|
|
295
306
|
|
|
296
|
-
// ../events/dist/build-record.js
|
|
297
|
-
import { z as z4 } from "zod";
|
|
298
|
-
|
|
299
307
|
// ../events/dist/state.js
|
|
300
308
|
import { z as z3 } from "zod";
|
|
301
309
|
var utcIso8601 = z3.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/, "must be a UTC ISO-8601 timestamp ending in Z");
|
|
302
310
|
var STATE_RECORD_FORMAT = "halfcycle-state-record/v1";
|
|
303
311
|
var stepIdSchema = z3.string().regex(/^(l[0-5](-l[0-5])?|xl)\.[a-z0-9-]+$/);
|
|
304
|
-
var methodVersionSchema = z3.string().regex(
|
|
312
|
+
var methodVersionSchema = z3.string().regex(RELEASE_VERSION_PATTERN);
|
|
305
313
|
var anchorPathRegex = /^(?!\/)(?!.*#)(?!method\/)(?!(.*\/)?docs\/method\/).+$/;
|
|
306
314
|
var recordKindSchema = z3.enum(["artefact", "step-run", "gate", "intervention"]);
|
|
307
315
|
var methodLayerSchema = z3.enum(["l0", "l1", "l2", "l3", "l3-l4", "l4", "l5", "xl"]);
|
|
@@ -478,6 +486,9 @@ var stateRecordSchema = z3.discriminatedUnion("recordKind", [
|
|
|
478
486
|
interventionRecordSchema
|
|
479
487
|
]);
|
|
480
488
|
|
|
489
|
+
// ../events/dist/build-record.js
|
|
490
|
+
import { z as z4 } from "zod";
|
|
491
|
+
|
|
481
492
|
// ../events/dist/control-characters.js
|
|
482
493
|
var CONTROL_CHARACTER_CLASS = "\\p{Cc}";
|
|
483
494
|
var CONTROL_CHARACTER = new RegExp(CONTROL_CHARACTER_CLASS, "u");
|
|
@@ -581,9 +592,11 @@ function statementText(max) {
|
|
|
581
592
|
});
|
|
582
593
|
}
|
|
583
594
|
var closeActorSchema = plainText(ACTOR_MAX);
|
|
595
|
+
var PHASE_NAME_RULE = `must be a phase name: not empty, no slash or backslash, not starting with a dot, no control characters, at most ${PHASE_NAME_MAX_BYTES} bytes`;
|
|
584
596
|
var phaseNameSchema = z4.string().refine((v) => isValidPhaseIdentity(v), {
|
|
585
|
-
message:
|
|
597
|
+
message: PHASE_NAME_RULE
|
|
586
598
|
});
|
|
599
|
+
var phaseIdentitySchema = z4.union([z4.string(), z4.number()], { error: "must be a phase name or a whole number" }).refine((v) => isValidPhaseIdentity(v), { message: PHASE_NAME_RULE });
|
|
587
600
|
var closeStatementsSchema = z4.object({
|
|
588
601
|
thesis: statementText(STATEMENT_MAX).nullable(),
|
|
589
602
|
outcome: statementText(STATEMENT_MAX).nullable(),
|
|
@@ -1356,7 +1369,7 @@ function createProjectIdentityFile(targetRepoRoot) {
|
|
|
1356
1369
|
}
|
|
1357
1370
|
|
|
1358
1371
|
// dist/install-manifest.js
|
|
1359
|
-
import { Buffer } from "node:buffer";
|
|
1372
|
+
import { Buffer as Buffer2 } from "node:buffer";
|
|
1360
1373
|
import { createHash } from "node:crypto";
|
|
1361
1374
|
var INSTALL_MANIFEST_REL = ".halfcycle/install-manifest.json";
|
|
1362
1375
|
var INSTALL_MANIFEST_FORMAT = "halfcycle-install-manifest/v1";
|
|
@@ -1367,7 +1380,7 @@ function lineKey(line) {
|
|
|
1367
1380
|
return line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
1368
1381
|
}
|
|
1369
1382
|
function fileSha256(bytes) {
|
|
1370
|
-
const text = typeof bytes === "string" ? bytes :
|
|
1383
|
+
const text = typeof bytes === "string" ? bytes : Buffer2.from(bytes).toString("utf-8");
|
|
1371
1384
|
return sha256Hex(text.split("\n").map(lineKey).join("\n"));
|
|
1372
1385
|
}
|
|
1373
1386
|
function canonicalJson(value) {
|
|
@@ -1813,6 +1826,8 @@ var WRITE_ALLOWLIST = [
|
|
|
1813
1826
|
BUNDLE_PIN_REL,
|
|
1814
1827
|
".halfcycle/bin",
|
|
1815
1828
|
"docs/build-records",
|
|
1829
|
+
// Architecture pictures the draw command writes from Halfcycle's answer. The installer never writes here; keep the entry so this path stays declared as one Halfcycle writes.
|
|
1830
|
+
"docs/diagrams",
|
|
1816
1831
|
"test/fixtures/captured",
|
|
1817
1832
|
MCP_REGISTRATION_REL,
|
|
1818
1833
|
".gitattributes",
|
|
@@ -2381,6 +2396,54 @@ printf '%s\\n' '${USER_PROMPT_REMINDER_SENTENCE}'
|
|
|
2381
2396
|
exit 0
|
|
2382
2397
|
`;
|
|
2383
2398
|
}
|
|
2399
|
+
var GENERATED_USER_PROMPT_SCOPE_HEADER = "# Typed scope approval \u2014 generated by";
|
|
2400
|
+
function generateUserPromptScopeHook() {
|
|
2401
|
+
return `#!/usr/bin/env bash
|
|
2402
|
+
${GENERATED_USER_PROMPT_SCOPE_HEADER} the Halfcycle installer.
|
|
2403
|
+
#
|
|
2404
|
+
# Lets the owner of this project approve a phase's scope, or send it back, by
|
|
2405
|
+
# typing "approve <phase>" or "send back <phase>: <note>" as the whole prompt in
|
|
2406
|
+
# their own Claude Code session.
|
|
2407
|
+
#
|
|
2408
|
+
# AN ORDINARY PROMPT STARTS NOTHING. This script reads the hook input once and
|
|
2409
|
+
# looks only at the start of the prompt's value. Unless the prompt begins, after
|
|
2410
|
+
# any whitespace (typed, or escaped in the input), with "approve " or
|
|
2411
|
+
# "send back " in any case, it exits 0 with no output, and Node never starts.
|
|
2412
|
+
# A quote inside any other field arrives escaped, so no other field (the folder
|
|
2413
|
+
# the session is in, for one) can read as the start of the prompt.
|
|
2414
|
+
#
|
|
2415
|
+
# Otherwise it finds the project root from its own location, never from the
|
|
2416
|
+
# folder the session has moved to, and hands the same input to the guard
|
|
2417
|
+
# wrapper beside it. That decides whether the line counts and prints at most
|
|
2418
|
+
# one plain line. Nothing here ever blocks a prompt.
|
|
2419
|
+
#
|
|
2420
|
+
# FAILS OPEN up to that hand-over: any error before it exits 0 with no output.
|
|
2421
|
+
# After it, the wrapper's own exit stands.
|
|
2422
|
+
trap 'exit 0' ERR
|
|
2423
|
+
set -e
|
|
2424
|
+
|
|
2425
|
+
# Read with the shell's own builtin, so the check below is the only program an
|
|
2426
|
+
# ordinary prompt starts.
|
|
2427
|
+
HC_INPUT=""
|
|
2428
|
+
IFS= read -r -d '' HC_INPUT || true
|
|
2429
|
+
|
|
2430
|
+
# What may come before the phrase: whitespace as typed, as a short escape, as a
|
|
2431
|
+
# \\u escape, or as the UTF-8 bytes of a Unicode space. Compared byte for byte.
|
|
2432
|
+
HC_LEAD='[[:space:]]|\\\\[nrtf]|\\\\u(000[9a-d]|0020|00a0|1680|200[0-9a]|202[89f]|205f|3000|feff)'
|
|
2433
|
+
HC_LEAD="$HC_LEAD|"$'\\xc2\\xa0|\\xe1\\x9a\\x80|\\xe2\\x80[\\x80-\\x8a\\xa8\\xa9\\xaf]|\\xe2\\x81\\x9f|\\xe3\\x80\\x80|\\xef\\xbb\\xbf'
|
|
2434
|
+
if ! printf '%s' "$HC_INPUT" | LC_ALL=C grep -Eiq "\\"prompt\\"[[:space:]]*:[[:space:]]*\\"($HC_LEAD)*(approve|send back) "; then
|
|
2435
|
+
exit 0
|
|
2436
|
+
fi
|
|
2437
|
+
|
|
2438
|
+
HC_SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
2439
|
+
HC_PROJECT_ROOT=$(CDPATH= cd -- "$HC_SCRIPT_DIR/../.." && pwd)
|
|
2440
|
+
export CLAUDE_PROJECT_DIR="$HC_PROJECT_ROOT"
|
|
2441
|
+
|
|
2442
|
+
trap - ERR
|
|
2443
|
+
set +e
|
|
2444
|
+
exec bash "$HC_PROJECT_ROOT/.claude/hooks/guard-runner.sh" hook user-prompt-submit <<<"$HC_INPUT"
|
|
2445
|
+
`;
|
|
2446
|
+
}
|
|
2384
2447
|
var CI_STANZA_KNOWN_HEADER_HASHES = [
|
|
2385
2448
|
"b225800b2d699811f32ac213238cbf81c35b5e6a74114bdf55e39cd9d7e2928f",
|
|
2386
2449
|
"674181735460f051af7c157222cf851fc3000fb3b8af3a10ea0bde9fe5eaba1f",
|
|
@@ -2456,6 +2519,7 @@ function removeRetiredMcpHeadersHelper(targetRepo) {
|
|
|
2456
2519
|
var OWNED_GENERATED_HEADERS = {
|
|
2457
2520
|
".claude/hooks/guard-runner.sh": GENERATED_GUARD_RUNNER_HEADER,
|
|
2458
2521
|
".claude/hooks/user-prompt-reminder.sh": GENERATED_USER_PROMPT_REMINDER_HEADER,
|
|
2522
|
+
".claude/hooks/user-prompt-scope.sh": GENERATED_USER_PROMPT_SCOPE_HEADER,
|
|
2459
2523
|
".claude/hooks/session-end-marker.sh": GENERATED_SESSION_END_HEADER,
|
|
2460
2524
|
".claude/hooks/session-start-marker.sh": GENERATED_SESSION_START_HEADER
|
|
2461
2525
|
};
|
|
@@ -2464,6 +2528,7 @@ function hookScriptsThisVersionWrites() {
|
|
|
2464
2528
|
[".claude/hooks/guard-runner.sh", generateGuardRunnerWrapper()],
|
|
2465
2529
|
[".claude/hooks/session-end-marker.sh", generateSessionEndMarker()],
|
|
2466
2530
|
[".claude/hooks/user-prompt-reminder.sh", generateUserPromptReminderHook()],
|
|
2531
|
+
[".claude/hooks/user-prompt-scope.sh", generateUserPromptScopeHook()],
|
|
2467
2532
|
[".claude/hooks/session-start-marker.sh", generateSessionStartMarker()]
|
|
2468
2533
|
];
|
|
2469
2534
|
}
|
|
@@ -3857,7 +3922,7 @@ function renderBuildRecordV2Markdown(record2) {
|
|
|
3857
3922
|
`Phase: ${phase} (the name the client gave it)`,
|
|
3858
3923
|
OBSERVED_LABEL,
|
|
3859
3924
|
[
|
|
3860
|
-
`- Method version: ${p.text(record2.observed.methodVersion)}. The Halfcycle method is updated from time to time, and each update has its own version number. This is the one the
|
|
3925
|
+
`- Method version: ${p.text(record2.observed.methodVersion)}. The Halfcycle method is updated from time to time, and each update has its own version number. This is the one the phase was run under. A project moves to a newer version only between phases.`,
|
|
3861
3926
|
`- Project identifier: ${p.text(record2.observed.engagementId)}. Halfcycle keeps this project's records under this identifier.`,
|
|
3862
3927
|
`- Record format: ${p.text(record2.format)}. This names the layout this file and the .json file beside it follow, for software that reads them.`
|
|
3863
3928
|
].join("\n"),
|
|
@@ -3889,10 +3954,14 @@ function assertInsideOutDir(outDir, candidate, identity) {
|
|
|
3889
3954
|
throw new InvalidPhaseIdentityError(identity, BUILD_RECORD_DIR);
|
|
3890
3955
|
}
|
|
3891
3956
|
}
|
|
3892
|
-
function createExclusiveOnDisk(path,
|
|
3957
|
+
function createExclusiveOnDisk(path, data) {
|
|
3958
|
+
const bytes = typeof data === "string" ? Buffer.from(data, "utf-8") : data;
|
|
3893
3959
|
const fd = openSync(path, "wx");
|
|
3894
3960
|
try {
|
|
3895
|
-
|
|
3961
|
+
let written = 0;
|
|
3962
|
+
while (written < bytes.length) {
|
|
3963
|
+
written += writeSync(fd, bytes, written, bytes.length - written);
|
|
3964
|
+
}
|
|
3896
3965
|
} catch (err) {
|
|
3897
3966
|
closeSync(fd);
|
|
3898
3967
|
rmSync3(path, { force: true });
|
|
@@ -4009,6 +4078,22 @@ function replaceOne(io, tmp, finalPath, text) {
|
|
|
4009
4078
|
|
|
4010
4079
|
// dist/build-record/obtain.js
|
|
4011
4080
|
import { relative as relative3 } from "node:path";
|
|
4081
|
+
|
|
4082
|
+
// dist/missing-route.js
|
|
4083
|
+
var MESSAGE = {
|
|
4084
|
+
GET: /^Route GET .* not found\.$/s,
|
|
4085
|
+
POST: /^Route POST .* not found\.$/s
|
|
4086
|
+
};
|
|
4087
|
+
function isMissingRoute(method, status, body) {
|
|
4088
|
+
if (status !== 404 || typeof body !== "object" || body === null)
|
|
4089
|
+
return false;
|
|
4090
|
+
const { error, message } = body;
|
|
4091
|
+
if (error !== "Not Found" || typeof message !== "string")
|
|
4092
|
+
return false;
|
|
4093
|
+
return MESSAGE[method].test(message);
|
|
4094
|
+
}
|
|
4095
|
+
|
|
4096
|
+
// dist/build-record/obtain.js
|
|
4012
4097
|
var REFUSAL_FOLLOW_UP = {
|
|
4013
4098
|
"still-open": "Nothing was written. The record is produced when the phase closes.",
|
|
4014
4099
|
"reopened": "Nothing was written. A new record is produced when the phase closes again.",
|
|
@@ -4020,12 +4105,6 @@ var REFUSAL_REASONS = phaseRecordRefusalReasonSchema.options;
|
|
|
4020
4105
|
function buildRecordUrl(credential, phase) {
|
|
4021
4106
|
return `${credential.serviceUrl}/engagements/${encodeURIComponent(credential.engagementId)}/phases/${encodeURIComponent(phase)}/build-record`;
|
|
4022
4107
|
}
|
|
4023
|
-
function isMissingRoute(status, body) {
|
|
4024
|
-
if (status !== 404 || typeof body !== "object" || body === null)
|
|
4025
|
-
return false;
|
|
4026
|
-
const { error, message } = body;
|
|
4027
|
-
return error === "Not Found" && typeof message === "string" && /^Route GET .* not found\.$/s.test(message);
|
|
4028
|
-
}
|
|
4029
4108
|
async function obtainBuildRecord(credential, repoRoot, phase, io = NODE_RECORD_PAIR_IO) {
|
|
4030
4109
|
const url = buildRecordUrl(credential, phase);
|
|
4031
4110
|
let res;
|
|
@@ -4082,7 +4161,7 @@ async function obtainBuildRecord(credential, repoRoot, phase, io = NODE_RECORD_P
|
|
|
4082
4161
|
headline: latestCloseHeadline(answer.record)
|
|
4083
4162
|
};
|
|
4084
4163
|
}
|
|
4085
|
-
if (isMissingRoute(res.status, raw)) {
|
|
4164
|
+
if (isMissingRoute("GET", res.status, raw)) {
|
|
4086
4165
|
return { kind: "unavailable", why: "this Halfcycle service does not provide Build Records yet." };
|
|
4087
4166
|
}
|
|
4088
4167
|
const message = typeof raw === "object" && raw !== null && typeof raw.message === "string" ? raw.message : null;
|