halfcycle 0.11.1 → 0.12.0-rc.2
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 +964 -351
- package/bin/pre-tool-use-filter.sh +4 -0
- package/commands/agent-teams-orchestration.md +1 -1
- package/commands/consistency-check.md +1 -1
- package/commands/context-update.md +1 -1
- package/commands/halfcycle-design.md +1 -1
- package/commands/halfcycle-setup.md +1 -1
- package/commands/stack-assembly-worker.md +1 -1
- package/commands/task-execution.md +1 -1
- package/commands/task-graph.md +1 -1
- package/commands/write-spec.md +1 -1
- package/dist/banner-facts.d.ts +7 -2
- package/dist/banner-facts.d.ts.map +1 -1
- package/dist/build-record/obtain.d.ts.map +1 -1
- package/dist/change.d.ts.map +1 -1
- package/dist/check-criteria.d.ts +1 -1
- package/dist/check-criteria.d.ts.map +1 -1
- package/dist/check-report.d.ts +1 -1
- package/dist/check-report.d.ts.map +1 -1
- package/dist/check-tasks.d.ts +10 -1
- package/dist/check-tasks.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 +1412 -873
- package/dist/cli.js.map +3 -3
- package/dist/close-phase.d.ts +5 -3
- package/dist/close-phase.d.ts.map +1 -1
- package/dist/confirm-identity.d.ts +3 -0
- package/dist/confirm-identity.d.ts.map +1 -1
- package/dist/crew.d.ts +37 -0
- package/dist/crew.d.ts.map +1 -0
- package/dist/draw/index.d.ts +1 -1
- package/dist/draw/index.d.ts.map +1 -1
- package/dist/engagement-credential.d.ts +4 -0
- package/dist/engagement-credential.d.ts.map +1 -1
- package/dist/failure-line.d.ts +3 -2
- package/dist/failure-line.d.ts.map +1 -1
- package/dist/folder-refusals.d.ts.map +1 -1
- package/dist/index.js +330 -224
- package/dist/index.js.map +3 -3
- package/dist/install-manifest.d.ts +5 -1
- package/dist/install-manifest.d.ts.map +1 -1
- package/dist/install-plan.d.ts.map +1 -1
- package/dist/install.d.ts +45 -11
- package/dist/install.d.ts.map +1 -1
- package/dist/local-allow.d.ts +72 -3
- package/dist/local-allow.d.ts.map +1 -1
- package/dist/loopback-signin.d.ts +6 -5
- package/dist/loopback-signin.d.ts.map +1 -1
- package/dist/merge-settings.d.ts +16 -1
- package/dist/merge-settings.d.ts.map +1 -1
- package/dist/open-phase.d.ts +1 -1
- package/dist/open-phase.d.ts.map +1 -1
- package/dist/own-version.d.ts +42 -0
- package/dist/own-version.d.ts.map +1 -0
- package/dist/pace.d.ts +2 -2
- package/dist/pace.d.ts.map +1 -1
- package/dist/plan.d.ts +4 -3
- package/dist/plan.d.ts.map +1 -1
- package/dist/pre-create-checks.d.ts.map +1 -1
- package/dist/preview.d.ts +7 -2
- package/dist/preview.d.ts.map +1 -1
- package/dist/ready.d.ts +1 -1
- package/dist/ready.d.ts.map +1 -1
- package/dist/regression.d.ts +48 -0
- package/dist/regression.d.ts.map +1 -0
- package/dist/request-shapes-verb.d.ts +1 -1
- package/dist/request-shapes-verb.d.ts.map +1 -1
- package/dist/restore.d.ts +1 -1
- package/dist/restore.d.ts.map +1 -1
- package/dist/review.d.ts +2 -2
- package/dist/review.d.ts.map +1 -1
- package/dist/route.d.ts +1 -1
- package/dist/route.d.ts.map +1 -1
- package/dist/size.d.ts.map +1 -1
- package/dist/uninstall.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -285,12 +285,71 @@ function looksLikeSecret(text) {
|
|
|
285
285
|
var MCP_REGISTRATION_REL = ".mcp.json";
|
|
286
286
|
var MCP_SERVER_KEY = "halfcycle";
|
|
287
287
|
|
|
288
|
+
// ../core/dist/release-version.js
|
|
289
|
+
var RELEASE_VERSION_PATTERN = /^\d+\.\d+\.\d+$/;
|
|
290
|
+
function isReleaseVersion(value) {
|
|
291
|
+
return typeof value === "string" && RELEASE_VERSION_PATTERN.test(value);
|
|
292
|
+
}
|
|
293
|
+
function significant(part) {
|
|
294
|
+
const trimmed = part.replace(/^0+/, "");
|
|
295
|
+
return trimmed === "" ? "0" : trimmed;
|
|
296
|
+
}
|
|
297
|
+
function compareReleaseVersions(a, b) {
|
|
298
|
+
if (!isReleaseVersion(a) || !isReleaseVersion(b)) {
|
|
299
|
+
throw new TypeError("compareReleaseVersions takes two MAJOR.MINOR.PATCH versions");
|
|
300
|
+
}
|
|
301
|
+
const pa = a.split(".").map(significant);
|
|
302
|
+
const pb = b.split(".").map(significant);
|
|
303
|
+
for (let i = 0; i < 3; i++) {
|
|
304
|
+
const x = pa[i];
|
|
305
|
+
const y = pb[i];
|
|
306
|
+
if (x.length !== y.length)
|
|
307
|
+
return x.length - y.length;
|
|
308
|
+
if (x !== y)
|
|
309
|
+
return x < y ? -1 : 1;
|
|
310
|
+
}
|
|
311
|
+
return 0;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// ../core/dist/release-tag.js
|
|
315
|
+
var PRE_RELEASE_VERSION_PATTERN = /^(\d+\.\d+\.\d+)-rc\.\d+$/;
|
|
316
|
+
var PACKAGE_COMMAND = "npx halfcycle";
|
|
317
|
+
var COMMAND_FORMS = /npx halfcycle(?:@latest|(?![@\p{L}\p{N}-]))/gu;
|
|
318
|
+
function isPreReleaseVersion(value) {
|
|
319
|
+
return typeof value === "string" && PRE_RELEASE_VERSION_PATTERN.test(value);
|
|
320
|
+
}
|
|
321
|
+
function releaseOfVersion(value) {
|
|
322
|
+
if (typeof value !== "string")
|
|
323
|
+
return void 0;
|
|
324
|
+
if (isReleaseVersion(value))
|
|
325
|
+
return value;
|
|
326
|
+
const match = PRE_RELEASE_VERSION_PATTERN.exec(value);
|
|
327
|
+
return match === null ? void 0 : match[1];
|
|
328
|
+
}
|
|
329
|
+
function releaseTagOfVersion(version) {
|
|
330
|
+
return isPreReleaseVersion(version) ? "next" : "latest";
|
|
331
|
+
}
|
|
332
|
+
function halfcycleCommand(version) {
|
|
333
|
+
return `${PACKAGE_COMMAND}@${releaseTagOfVersion(version)}`;
|
|
334
|
+
}
|
|
335
|
+
function bareHalfcycleCommand(version) {
|
|
336
|
+
return releaseTagOfVersion(version) === "next" ? `${PACKAGE_COMMAND}@next` : PACKAGE_COMMAND;
|
|
337
|
+
}
|
|
338
|
+
function rewriteHalfcycleCommands(text, tag) {
|
|
339
|
+
if (tag === "latest")
|
|
340
|
+
return text;
|
|
341
|
+
return text.replace(COMMAND_FORMS, `${PACKAGE_COMMAND}@${tag}`);
|
|
342
|
+
}
|
|
343
|
+
|
|
288
344
|
// ../core/dist/shell-quote.js
|
|
289
345
|
function quoteForShell(value) {
|
|
290
346
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
291
347
|
}
|
|
292
|
-
var HALFCYCLE_COMMAND =
|
|
293
|
-
|
|
348
|
+
var HALFCYCLE_COMMAND = halfcycleCommand();
|
|
349
|
+
function doctorLine(version) {
|
|
350
|
+
return `Run \`${bareHalfcycleCommand(version)} doctor\` to check set up, sign-in, connection, checks and version.`;
|
|
351
|
+
}
|
|
352
|
+
var DOCTOR_LINE = doctorLine();
|
|
294
353
|
|
|
295
354
|
// ../core/dist/folder-trust.js
|
|
296
355
|
var CLAUDE_STATE_FILENAME = ".claude.json";
|
|
@@ -561,32 +620,6 @@ function typedPhaseReference(phase, number) {
|
|
|
561
620
|
return phase.includes(":") && number !== null ? String(number) : phase;
|
|
562
621
|
}
|
|
563
622
|
|
|
564
|
-
// ../core/dist/release-version.js
|
|
565
|
-
var RELEASE_VERSION_PATTERN = /^\d+\.\d+\.\d+$/;
|
|
566
|
-
function isReleaseVersion(value) {
|
|
567
|
-
return typeof value === "string" && RELEASE_VERSION_PATTERN.test(value);
|
|
568
|
-
}
|
|
569
|
-
function significant(part) {
|
|
570
|
-
const trimmed = part.replace(/^0+/, "");
|
|
571
|
-
return trimmed === "" ? "0" : trimmed;
|
|
572
|
-
}
|
|
573
|
-
function compareReleaseVersions(a, b) {
|
|
574
|
-
if (!isReleaseVersion(a) || !isReleaseVersion(b)) {
|
|
575
|
-
throw new TypeError("compareReleaseVersions takes two MAJOR.MINOR.PATCH versions");
|
|
576
|
-
}
|
|
577
|
-
const pa = a.split(".").map(significant);
|
|
578
|
-
const pb = b.split(".").map(significant);
|
|
579
|
-
for (let i = 0; i < 3; i++) {
|
|
580
|
-
const x = pa[i];
|
|
581
|
-
const y = pb[i];
|
|
582
|
-
if (x.length !== y.length)
|
|
583
|
-
return x.length - y.length;
|
|
584
|
-
if (x !== y)
|
|
585
|
-
return x < y ? -1 : 1;
|
|
586
|
-
}
|
|
587
|
-
return 0;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
623
|
// ../core/dist/github-address.js
|
|
591
624
|
var GITHUB_REPOSITORY_ADDRESS = /^https:\/\/github\.com\/[A-Za-z0-9][A-Za-z0-9-]{0,38}\/(?!\.{1,2}$)[A-Za-z0-9._-]{1,100}$/;
|
|
592
625
|
var HOST = "[Gg][Ii][Tt][Hh][Uu][Bb]\\.[Cc][Oo][Mm]";
|
|
@@ -603,8 +636,14 @@ var REMOTE_FORMS = [
|
|
|
603
636
|
];
|
|
604
637
|
|
|
605
638
|
// ../core/dist/sign-in.js
|
|
606
|
-
|
|
607
|
-
|
|
639
|
+
function signInCommand(version) {
|
|
640
|
+
return `"${bareHalfcycleCommand(version)}"`;
|
|
641
|
+
}
|
|
642
|
+
var SIGN_IN_COMMAND = signInCommand();
|
|
643
|
+
function hookRemedy(version) {
|
|
644
|
+
return `Run ${signInCommand(version)} in the project folder to sign in.`;
|
|
645
|
+
}
|
|
646
|
+
var HOOK_REMEDY = hookRemedy();
|
|
608
647
|
|
|
609
648
|
// ../core/dist/task-plan.js
|
|
610
649
|
import { z as z2 } from "zod";
|
|
@@ -1977,7 +2016,7 @@ function codexHookCommand(script, args2) {
|
|
|
1977
2016
|
const invocation = `cd "$(git rev-parse --show-toplevel)" && bash ./${script}`;
|
|
1978
2017
|
return args2 === void 0 ? invocation : `${invocation} ${args2}`;
|
|
1979
2018
|
}
|
|
1980
|
-
var CODEX_WINDOWS_NOT_SET_UP =
|
|
2019
|
+
var CODEX_WINDOWS_NOT_SET_UP = `Halfcycle checks did not run because Halfcycle is not set up in this folder or any folder above it; run ${bareHalfcycleCommand()} in the project folder.`;
|
|
1981
2020
|
var CODEX_WINDOWS_NO_GIT_BASH = "Halfcycle checks did not run because Git for Windows was not found; install Git for Windows, then open a new session.";
|
|
1982
2021
|
function codexHookCommandWindows(script, args2) {
|
|
1983
2022
|
const tick = (text) => "`" + text + "`";
|
|
@@ -2118,26 +2157,52 @@ function parseWorkerReportFields(blockLines) {
|
|
|
2118
2157
|
}
|
|
2119
2158
|
return fields;
|
|
2120
2159
|
}
|
|
2160
|
+
function backtickRun(text, at) {
|
|
2161
|
+
let n = 0;
|
|
2162
|
+
while (text[at + n] === "`")
|
|
2163
|
+
n++;
|
|
2164
|
+
return n;
|
|
2165
|
+
}
|
|
2121
2166
|
function splitOutsideBackticks(text) {
|
|
2122
2167
|
const parts = [];
|
|
2123
2168
|
let buf = "";
|
|
2124
|
-
let
|
|
2125
|
-
|
|
2169
|
+
let i = 0;
|
|
2170
|
+
while (i < text.length) {
|
|
2126
2171
|
const ch = text[i];
|
|
2127
|
-
if (ch === "`")
|
|
2128
|
-
|
|
2129
|
-
|
|
2172
|
+
if (ch === "`") {
|
|
2173
|
+
const open = backtickRun(text, i);
|
|
2174
|
+
let close = -1;
|
|
2175
|
+
for (let j = i + open; j < text.length; ) {
|
|
2176
|
+
if (text[j] !== "`") {
|
|
2177
|
+
j++;
|
|
2178
|
+
continue;
|
|
2179
|
+
}
|
|
2180
|
+
const run = backtickRun(text, j);
|
|
2181
|
+
if (run === open) {
|
|
2182
|
+
close = j;
|
|
2183
|
+
break;
|
|
2184
|
+
}
|
|
2185
|
+
j += run;
|
|
2186
|
+
}
|
|
2187
|
+
const end = close === -1 ? i + open : close + open;
|
|
2188
|
+
buf += text.slice(i, end);
|
|
2189
|
+
i = end;
|
|
2190
|
+
continue;
|
|
2191
|
+
}
|
|
2192
|
+
if (ch === ";") {
|
|
2130
2193
|
parts.push(buf);
|
|
2131
2194
|
buf = "";
|
|
2195
|
+
i += 1;
|
|
2132
2196
|
continue;
|
|
2133
2197
|
}
|
|
2134
|
-
if (
|
|
2198
|
+
if (text.startsWith(" \xB7 ", i)) {
|
|
2135
2199
|
parts.push(buf);
|
|
2136
2200
|
buf = "";
|
|
2137
|
-
i +=
|
|
2201
|
+
i += 3;
|
|
2138
2202
|
continue;
|
|
2139
2203
|
}
|
|
2140
2204
|
buf += ch;
|
|
2205
|
+
i += 1;
|
|
2141
2206
|
}
|
|
2142
2207
|
parts.push(buf);
|
|
2143
2208
|
return parts.map((p) => p.trim()).filter(Boolean);
|
|
@@ -2487,15 +2552,14 @@ if (switchedOn) {
|
|
|
2487
2552
|
}
|
|
2488
2553
|
|
|
2489
2554
|
// dist/bin.js
|
|
2490
|
-
import { existsSync as
|
|
2555
|
+
import { existsSync as existsSync17, statSync as statSync3, writeSync as writeSync4 } from "node:fs";
|
|
2491
2556
|
|
|
2492
2557
|
// dist/install.js
|
|
2493
2558
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
2494
2559
|
import { createHash as createHash2 } from "node:crypto";
|
|
2495
|
-
import { existsSync as
|
|
2560
|
+
import { existsSync as existsSync6, lstatSync, mkdirSync as mkdirSync5, readFileSync as readFileSync7, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "node:fs";
|
|
2496
2561
|
import { homedir as homedir3 } from "node:os";
|
|
2497
|
-
import { dirname as
|
|
2498
|
-
import { fileURLToPath } from "node:url";
|
|
2562
|
+
import { dirname as dirname4, isAbsolute as isAbsolute2, join as join8, posix as posix2, relative, resolve } from "node:path";
|
|
2499
2563
|
|
|
2500
2564
|
// ../events/dist/result.js
|
|
2501
2565
|
import { z as z3 } from "zod";
|
|
@@ -4141,8 +4205,8 @@ var TIME_MEASURED_SENTENCE = "Measured by Halfcycle from what happened in your s
|
|
|
4141
4205
|
function timePhaseElapsedSentenceOf(elapsedSeconds) {
|
|
4142
4206
|
return `${timeDurationLabelOf(elapsedSeconds)} since this phase opened.`;
|
|
4143
4207
|
}
|
|
4144
|
-
function timeUsageTotalTokensOf(
|
|
4145
|
-
return
|
|
4208
|
+
function timeUsageTotalTokensOf(usage5) {
|
|
4209
|
+
return usage5.inputTokens + usage5.outputTokens + usage5.cacheWriteTokens + usage5.cacheReadTokens;
|
|
4146
4210
|
}
|
|
4147
4211
|
var TIME_NO_RECORD_SENTENCE = "Halfcycle has not recorded any time for this phase yet. It records at the end of each reply in a session on this project.";
|
|
4148
4212
|
function timeSummaryIsEmpty(summary) {
|
|
@@ -4176,25 +4240,25 @@ var TIME_PERMISSION_PROMPT_LABEL = "Waiting for you to allow an action";
|
|
|
4176
4240
|
function timePermissionPromptLineOf(seconds) {
|
|
4177
4241
|
return seconds === 0 ? void 0 : `${TIME_PERMISSION_PROMPT_LABEL} ${timeDurationLabelOf(seconds)}`;
|
|
4178
4242
|
}
|
|
4179
|
-
function timeUsageNotCachedTokensOf(
|
|
4180
|
-
return
|
|
4243
|
+
function timeUsageNotCachedTokensOf(usage5) {
|
|
4244
|
+
return usage5.inputTokens + usage5.outputTokens + usage5.cacheWriteTokens;
|
|
4181
4245
|
}
|
|
4182
|
-
function timeUsageLeadSentenceOf(
|
|
4183
|
-
if (
|
|
4246
|
+
function timeUsageLeadSentenceOf(usage5) {
|
|
4247
|
+
if (usage5.state === "not-available") {
|
|
4184
4248
|
return "Usage not available: your coding agent keeps no usage record Halfcycle can read.";
|
|
4185
4249
|
}
|
|
4186
|
-
const n = timeCountLabelOf(timeUsageNotCachedTokensOf(
|
|
4187
|
-
if (
|
|
4250
|
+
const n = timeCountLabelOf(timeUsageNotCachedTokensOf(usage5));
|
|
4251
|
+
if (usage5.state === "partly-recorded") {
|
|
4188
4252
|
return `At least ${n} tokens not read from the cache, as your coding agent recorded them. Some sessions' usage could not be read.`;
|
|
4189
4253
|
}
|
|
4190
4254
|
return `${n} tokens not read from the cache, as your coding agent recorded them.`;
|
|
4191
4255
|
}
|
|
4192
|
-
function timeUsageInAllSentenceOf(
|
|
4193
|
-
if (
|
|
4256
|
+
function timeUsageInAllSentenceOf(usage5) {
|
|
4257
|
+
if (usage5.state === "not-available")
|
|
4194
4258
|
return void 0;
|
|
4195
|
-
const all = timeCountLabelOf(timeUsageTotalTokensOf(
|
|
4196
|
-
const cached = timeCountLabelOf(
|
|
4197
|
-
const lead =
|
|
4259
|
+
const all = timeCountLabelOf(timeUsageTotalTokensOf(usage5));
|
|
4260
|
+
const cached = timeCountLabelOf(usage5.cacheReadTokens);
|
|
4261
|
+
const lead = usage5.state === "partly-recorded" ? `At least ${all}` : all;
|
|
4198
4262
|
return `${lead} in all; ${cached} of them were read back from the cache.`;
|
|
4199
4263
|
}
|
|
4200
4264
|
function timeReviewsSentenceOf(reviews) {
|
|
@@ -5487,7 +5551,9 @@ var openPhaseRequestSchema = z18.object({
|
|
|
5487
5551
|
entry: phaseEntryDecisionSchema,
|
|
5488
5552
|
startCommit: closeCommitSchema.optional(),
|
|
5489
5553
|
sortingId: sortingIdSchema.optional(),
|
|
5490
|
-
sizeId: projectSizeIdSchema.optional()
|
|
5554
|
+
sizeId: projectSizeIdSchema.optional(),
|
|
5555
|
+
// Present, and true, only when this phase is opened as a regression phase.
|
|
5556
|
+
regression: z18.literal(true).optional()
|
|
5491
5557
|
}).strict().refine((v) => v.phase !== null || v.startCommit === void 0, {
|
|
5492
5558
|
message: "is sent only when a phase is opened",
|
|
5493
5559
|
path: ["startCommit"]
|
|
@@ -5497,6 +5563,9 @@ var openPhaseRequestSchema = z18.object({
|
|
|
5497
5563
|
}).refine((v) => v.phase !== null || v.sizeId === void 0, {
|
|
5498
5564
|
message: "is sent only when a phase is opened",
|
|
5499
5565
|
path: ["sizeId"]
|
|
5566
|
+
}).refine((v) => v.phase !== null || v.regression === void 0, {
|
|
5567
|
+
message: "is sent only when a phase is opened",
|
|
5568
|
+
path: ["regression"]
|
|
5500
5569
|
});
|
|
5501
5570
|
var phaseEntryDecisionRecordSchema = z18.object({
|
|
5502
5571
|
decisionId: z18.string(),
|
|
@@ -5799,6 +5868,11 @@ var SMALL_CREW = Object.freeze([
|
|
|
5799
5868
|
function isSmallCrewCallsign(callsign) {
|
|
5800
5869
|
return SMALL_CREW.includes(callsign);
|
|
5801
5870
|
}
|
|
5871
|
+
function crewOfProjectSize(size) {
|
|
5872
|
+
if (size !== "small")
|
|
5873
|
+
return CREW_ROSTER;
|
|
5874
|
+
return CREW_ROSTER.filter((member) => isSmallCrewCallsign(member.callsign));
|
|
5875
|
+
}
|
|
5802
5876
|
var crewLayerSchema = methodLayerSchema.extract(["l0", "l1", "l2", "l3", "l4", "l5"]);
|
|
5803
5877
|
var crewRoleLayersSchema = z19.object({
|
|
5804
5878
|
leads: z19.array(crewLayerSchema),
|
|
@@ -6502,9 +6576,8 @@ function paceClockOf(at, now) {
|
|
|
6502
6576
|
}
|
|
6503
6577
|
return `${day} ${clock}`;
|
|
6504
6578
|
}
|
|
6505
|
-
function paceP1Of(n,
|
|
6506
|
-
|
|
6507
|
-
return minutes === null ? first : `${first} The work should take about ${timeDurationLabelOf(minutes * 60)}.`;
|
|
6579
|
+
function paceP1Of(n, _minutes) {
|
|
6580
|
+
return `Halfcycle suggests running ${paceAgentsOf(n)} at once.`;
|
|
6508
6581
|
}
|
|
6509
6582
|
var PACE_NO_BUILD_PLAN = "Halfcycle has no build plan for this phase yet, so it cannot say how long the work will take.";
|
|
6510
6583
|
var PACE_P2 = "This is a careful start, so your allowance lasts. It leaves room for your own work.";
|
|
@@ -6517,27 +6590,30 @@ function paceP6Of(k) {
|
|
|
6517
6590
|
return `It will probably pause ${k === 1 ? "once" : `${String(k)} times`} while your usage window resets.`;
|
|
6518
6591
|
}
|
|
6519
6592
|
var PACE_P7 = 'Or say how much of your allowance to keep free, like "keep 30% free", or how much this build may use, like "use up to 70%".';
|
|
6520
|
-
|
|
6521
|
-
|
|
6593
|
+
function paceP8Of(version) {
|
|
6594
|
+
return `For these to work, run "${bareHalfcycleCommand(version)}" once more in this project.`;
|
|
6595
|
+
}
|
|
6596
|
+
var PACE_P8 = paceP8Of();
|
|
6597
|
+
function paceStartLinesOf(s, promptScriptBehind2 = false, version) {
|
|
6522
6598
|
const limit = s.limitTokens !== void 0 && s.expectedTokens !== void 0 ? { expected: s.expectedTokens, limit: s.limitTokens, multiple: s.multiple ?? null, setBy: s.limitSetBy ?? "default" } : null;
|
|
6523
|
-
const
|
|
6524
|
-
const lines = [paceP1Of(s.number, s.minutes), ...
|
|
6599
|
+
const usage5 = buildStartUsageLinesOf({ workMinutes: s.workMinutes, limit, weekCeilingTokens: s.weekCeilingTokens });
|
|
6600
|
+
const lines = [paceP1Of(s.number, s.minutes), ...usage5.work];
|
|
6525
6601
|
if (s.minutes === null)
|
|
6526
6602
|
lines.push(PACE_NO_BUILD_PLAN);
|
|
6527
6603
|
if (s.keepFreePercent === 0)
|
|
6528
6604
|
lines.push(PACE_P5);
|
|
6529
6605
|
else
|
|
6530
6606
|
lines.push(s.knownWindow !== null ? paceP3Of(s.keepFreePercent) : PACE_P2);
|
|
6531
|
-
if (
|
|
6532
|
-
lines.push(...
|
|
6607
|
+
if (usage5.limit.length > 0) {
|
|
6608
|
+
lines.push(...usage5.limit);
|
|
6533
6609
|
if (s.firstGuess === true)
|
|
6534
6610
|
lines.push(LIMIT_L1B);
|
|
6535
6611
|
}
|
|
6536
6612
|
if (s.pauses > 0)
|
|
6537
6613
|
lines.push(paceP6Of(s.pauses));
|
|
6538
|
-
lines.push(PACE_P4, PACE_P7, ...
|
|
6614
|
+
lines.push(PACE_P4, PACE_P7, ...usage5.change);
|
|
6539
6615
|
if (promptScriptBehind2)
|
|
6540
|
-
lines.push(
|
|
6616
|
+
lines.push(paceP8Of(version));
|
|
6541
6617
|
return lines;
|
|
6542
6618
|
}
|
|
6543
6619
|
function paceS1Of(window, k, until, now, keepFreePercent) {
|
|
@@ -6703,6 +6779,17 @@ function aboutShareOf(percent) {
|
|
|
6703
6779
|
return percent < 0.5 ? planSharePercentWordsOf(percent) : `about ${planSharePercentWordsOf(percent)}`;
|
|
6704
6780
|
}
|
|
6705
6781
|
var PLAN_USAGE_CAVEAT = "These figures are estimates. Many things affect usage, and some of them are outside Halfcycle's control.";
|
|
6782
|
+
var SMALL_PROJECT_WORK_START = "A small project like this is usually about ";
|
|
6783
|
+
var SMALL_PROJECT_WORK_NOTE = "When your plan is written, this phase gets its own figure, counted from the time each task in it is given.";
|
|
6784
|
+
function sizeAnswerPrintedLinesOf(usage5) {
|
|
6785
|
+
const out = [];
|
|
6786
|
+
for (const line of usage5) {
|
|
6787
|
+
out.push(line);
|
|
6788
|
+
if (line.startsWith(SMALL_PROJECT_WORK_START))
|
|
6789
|
+
out.push(SMALL_PROJECT_WORK_NOTE);
|
|
6790
|
+
}
|
|
6791
|
+
return out;
|
|
6792
|
+
}
|
|
6706
6793
|
function phaseWorkLineOf(minutes) {
|
|
6707
6794
|
return `This phase is about ${timeDurationLabelOf(minutes * 60)} of agent work.`;
|
|
6708
6795
|
}
|
|
@@ -6809,19 +6896,43 @@ function limitUsedShareTextOf(used, limit, weekCeilingTokens) {
|
|
|
6809
6896
|
return figureThenCaveat(`This phase has used ${ofYourWeek(used, weekCeilingTokens)}, out of a limit of ${aboutShareOf(weekPercentOf(limit, weekCeilingTokens))}.`);
|
|
6810
6897
|
}
|
|
6811
6898
|
|
|
6899
|
+
// ../events/dist/regression-line.js
|
|
6900
|
+
import { z as z26 } from "zod";
|
|
6901
|
+
var REGRESSION_EVERY_MIN = 1;
|
|
6902
|
+
var REGRESSION_EVERY_MAX = 20;
|
|
6903
|
+
var REGRESSION_EVERY_RANGE_REFUSAL = "The number of phases between regressions must be from 1 to 20. Nothing was changed.";
|
|
6904
|
+
var regressionEverySchema = z26.number().int({ message: REGRESSION_EVERY_RANGE_REFUSAL }).min(REGRESSION_EVERY_MIN, { message: REGRESSION_EVERY_RANGE_REFUSAL }).max(REGRESSION_EVERY_MAX, { message: REGRESSION_EVERY_RANGE_REFUSAL });
|
|
6905
|
+
var regressionClosedSinceSchema = z26.number().int().min(0);
|
|
6906
|
+
var REGRESSION_LINE_MAX = 200;
|
|
6907
|
+
var regressionLineSchema = z26.string().min(1).max(REGRESSION_LINE_MAX);
|
|
6908
|
+
var regressionEveryRequestSchema = z26.object({
|
|
6909
|
+
// How many phases may close before a regression phase is due, 1 to 20.
|
|
6910
|
+
every: regressionEverySchema
|
|
6911
|
+
}).strict();
|
|
6912
|
+
var regressionEveryResponseSchema = z26.object({
|
|
6913
|
+
// The number now stored.
|
|
6914
|
+
every: regressionEverySchema,
|
|
6915
|
+
// The line as it reads now, or null when there is none to show.
|
|
6916
|
+
line: regressionLineSchema.nullable()
|
|
6917
|
+
}).strict();
|
|
6918
|
+
|
|
6812
6919
|
// ../events/dist/client-version.js
|
|
6813
6920
|
var CHAT_APPROVAL_CLIENT_VERSION = "0.4.7";
|
|
6814
6921
|
function clientMeetsChatApprovalFloor(version) {
|
|
6815
|
-
|
|
6922
|
+
const release = releaseOfVersion(version);
|
|
6923
|
+
return release !== void 0 && compareReleaseVersions(release, CHAT_APPROVAL_CLIENT_VERSION) >= 0;
|
|
6816
6924
|
}
|
|
6817
6925
|
var TYPED_APPROVAL_OFF_SENTENCE = "Approving by typing is switched off for now.";
|
|
6818
6926
|
var HEADLESS_CANNOT_APPROVE = "(a headless session cannot approve, by design)";
|
|
6819
6927
|
var TYPED_REVIEW_OFF_SENTENCE = "Reviewing by typing is switched off for now.";
|
|
6820
|
-
|
|
6928
|
+
function clientUpdateSentence(version) {
|
|
6929
|
+
return `To approve by typing in a session, Halfcycle needs updating: run \`${halfcycleCommand(version)}\` in this project's folder, then reopen your session and ask again.`;
|
|
6930
|
+
}
|
|
6931
|
+
var CLIENT_UPDATE_SENTENCE = clientUpdateSentence();
|
|
6821
6932
|
|
|
6822
6933
|
// ../events/dist/device-auth.js
|
|
6823
|
-
import { z as
|
|
6824
|
-
var utcIso86013 =
|
|
6934
|
+
import { z as z27 } from "zod";
|
|
6935
|
+
var utcIso86013 = z27.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");
|
|
6825
6936
|
var DEVICE_AUTH_STATUS = {
|
|
6826
6937
|
/** The poll: minted, nobody has acted yet. Keep polling. 200. */
|
|
6827
6938
|
PENDING: "pending",
|
|
@@ -6862,25 +6973,25 @@ var DEVICE_POLL_REFUSALS = [
|
|
|
6862
6973
|
DEVICE_AUTH_STATUS.EXPIRED,
|
|
6863
6974
|
DEVICE_AUTH_STATUS.UNKNOWN
|
|
6864
6975
|
];
|
|
6865
|
-
var deviceAuthStartResponseSchema =
|
|
6866
|
-
deviceCode:
|
|
6867
|
-
userCode:
|
|
6868
|
-
verificationUrl:
|
|
6976
|
+
var deviceAuthStartResponseSchema = z27.object({
|
|
6977
|
+
deviceCode: z27.string().min(1),
|
|
6978
|
+
userCode: z27.string().min(1),
|
|
6979
|
+
verificationUrl: z27.string().min(1),
|
|
6869
6980
|
expiresAt: utcIso86013,
|
|
6870
|
-
pollIntervalMs:
|
|
6981
|
+
pollIntervalMs: z27.number().int().positive()
|
|
6871
6982
|
}).strict();
|
|
6872
|
-
var devicePollPendingSchema =
|
|
6873
|
-
status:
|
|
6874
|
-
pollIntervalMs:
|
|
6983
|
+
var devicePollPendingSchema = z27.object({
|
|
6984
|
+
status: z27.literal(DEVICE_AUTH_STATUS.PENDING),
|
|
6985
|
+
pollIntervalMs: z27.number().int().positive()
|
|
6875
6986
|
}).strict();
|
|
6876
|
-
var devicePollApprovedSchema =
|
|
6877
|
-
status:
|
|
6878
|
-
accountId:
|
|
6879
|
-
credential:
|
|
6987
|
+
var devicePollApprovedSchema = z27.object({
|
|
6988
|
+
status: z27.literal(DEVICE_AUTH_STATUS.APPROVED),
|
|
6989
|
+
accountId: z27.string().min(1),
|
|
6990
|
+
credential: z27.string().min(1),
|
|
6880
6991
|
expiresAt: utcIso86013
|
|
6881
6992
|
}).strict();
|
|
6882
|
-
var deviceAuthRefusalSchema =
|
|
6883
|
-
status:
|
|
6993
|
+
var deviceAuthRefusalSchema = z27.object({
|
|
6994
|
+
status: z27.enum([
|
|
6884
6995
|
DEVICE_AUTH_STATUS.DECLINED,
|
|
6885
6996
|
DEVICE_AUTH_STATUS.EXPIRED,
|
|
6886
6997
|
DEVICE_AUTH_STATUS.UNKNOWN,
|
|
@@ -6891,25 +7002,25 @@ var deviceAuthRefusalSchema = z26.object({
|
|
|
6891
7002
|
DEVICE_AUTH_STATUS.ALREADY_DECIDED,
|
|
6892
7003
|
DEVICE_AUTH_STATUS.TERMS_REQUIRED
|
|
6893
7004
|
]),
|
|
6894
|
-
message:
|
|
7005
|
+
message: z27.string().min(1)
|
|
6895
7006
|
}).strict();
|
|
6896
|
-
var devicePollResponseSchema =
|
|
7007
|
+
var devicePollResponseSchema = z27.union([
|
|
6897
7008
|
devicePollPendingSchema,
|
|
6898
7009
|
devicePollApprovedSchema,
|
|
6899
7010
|
deviceAuthRefusalSchema
|
|
6900
7011
|
]);
|
|
6901
|
-
var deviceConfirmRequestSchema =
|
|
6902
|
-
userCode:
|
|
6903
|
-
decision:
|
|
6904
|
-
externalAuthId:
|
|
6905
|
-
email:
|
|
6906
|
-
acceptTerms:
|
|
7012
|
+
var deviceConfirmRequestSchema = z27.object({
|
|
7013
|
+
userCode: z27.string().min(1),
|
|
7014
|
+
decision: z27.enum(["approve", "decline"]),
|
|
7015
|
+
externalAuthId: z27.string().min(1).optional(),
|
|
7016
|
+
email: z27.string().optional(),
|
|
7017
|
+
acceptTerms: z27.boolean().optional()
|
|
6907
7018
|
}).strict();
|
|
6908
|
-
var deviceConfirmRecordedSchema =
|
|
6909
|
-
status:
|
|
6910
|
-
accountId:
|
|
7019
|
+
var deviceConfirmRecordedSchema = z27.object({
|
|
7020
|
+
status: z27.literal(DEVICE_AUTH_STATUS.RECORDED),
|
|
7021
|
+
accountId: z27.string().min(1).optional()
|
|
6911
7022
|
}).strict();
|
|
6912
|
-
var deviceConfirmResponseSchema =
|
|
7023
|
+
var deviceConfirmResponseSchema = z27.union([
|
|
6913
7024
|
deviceConfirmRecordedSchema,
|
|
6914
7025
|
deviceAuthRefusalSchema
|
|
6915
7026
|
]);
|
|
@@ -6938,67 +7049,87 @@ function loopbackVerificationUrl(verificationUrl, target) {
|
|
|
6938
7049
|
}
|
|
6939
7050
|
|
|
6940
7051
|
// ../events/dist/project-page.js
|
|
6941
|
-
|
|
6942
|
-
|
|
7052
|
+
var SERVICE_HOST = /^(?:control|delivery)(-[a-z0-9]+)?\.halfcycle\.ai$/;
|
|
7053
|
+
function consoleOrigin(serviceAddress) {
|
|
7054
|
+
const trimmed = serviceAddress?.trim();
|
|
7055
|
+
if (!trimmed)
|
|
7056
|
+
return DASHBOARD_URL;
|
|
7057
|
+
let host;
|
|
7058
|
+
try {
|
|
7059
|
+
host = new URL(trimmed.includes("://") ? trimmed : `https://${trimmed}`).hostname.toLowerCase();
|
|
7060
|
+
} catch {
|
|
7061
|
+
return DASHBOARD_URL;
|
|
7062
|
+
}
|
|
7063
|
+
const suffix = SERVICE_HOST.exec(host)?.[1];
|
|
7064
|
+
return suffix === void 0 ? DASHBOARD_URL : `https://app${suffix}.halfcycle.ai`;
|
|
7065
|
+
}
|
|
7066
|
+
function projectPageUrl(engagementId, serviceAddress) {
|
|
7067
|
+
return isEngagementId(engagementId) ? `${consoleOrigin(serviceAddress)}/${encodeURIComponent(engagementId)}` : void 0;
|
|
6943
7068
|
}
|
|
6944
|
-
function systemViewUrl(engagementId, phase) {
|
|
6945
|
-
const page = projectPageUrl(engagementId);
|
|
7069
|
+
function systemViewUrl(engagementId, phase, serviceAddress) {
|
|
7070
|
+
const page = projectPageUrl(engagementId, serviceAddress);
|
|
6946
7071
|
if (page === void 0)
|
|
6947
7072
|
return void 0;
|
|
6948
7073
|
return `${page}/console/system-view?${new URLSearchParams({ phase }).toString()}`;
|
|
6949
7074
|
}
|
|
6950
|
-
function requestAccessSentence(engagementId) {
|
|
6951
|
-
const page = projectPageUrl(engagementId) ?? "this project's page on Halfcycle";
|
|
7075
|
+
function requestAccessSentence(engagementId, serviceAddress) {
|
|
7076
|
+
const page = projectPageUrl(engagementId, serviceAddress) ?? "this project's page on Halfcycle";
|
|
6952
7077
|
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.`;
|
|
6953
7078
|
}
|
|
6954
7079
|
|
|
6955
7080
|
// ../events/dist/join-refusal.js
|
|
6956
|
-
import { z as
|
|
7081
|
+
import { z as z28 } from "zod";
|
|
6957
7082
|
var NOT_THIS_ACCOUNT_REASON = "not-this-account";
|
|
6958
7083
|
var JOIN_REFUSAL_STATUS = 403;
|
|
6959
|
-
var notThisAccountRefusalSchema =
|
|
6960
|
-
statusCode:
|
|
6961
|
-
error:
|
|
6962
|
-
message:
|
|
6963
|
-
reason:
|
|
7084
|
+
var notThisAccountRefusalSchema = z28.object({
|
|
7085
|
+
statusCode: z28.literal(JOIN_REFUSAL_STATUS),
|
|
7086
|
+
error: z28.string(),
|
|
7087
|
+
message: z28.string(),
|
|
7088
|
+
reason: z28.literal(NOT_THIS_ACCOUNT_REASON)
|
|
6964
7089
|
}).strict();
|
|
6965
|
-
var joinRefusalSchema =
|
|
7090
|
+
var joinRefusalSchema = z28.union([notThisAccountRefusalSchema, wireErrorSchema]);
|
|
6966
7091
|
function isNotThisAccountRefusal(body) {
|
|
6967
7092
|
return typeof body === "object" && body !== null && body.reason === NOT_THIS_ACCOUNT_REASON;
|
|
6968
7093
|
}
|
|
6969
7094
|
|
|
6970
7095
|
// ../events/dist/account-identity.js
|
|
6971
|
-
import { z as
|
|
6972
|
-
var accountIdentitySchema =
|
|
6973
|
-
accountId:
|
|
6974
|
-
email:
|
|
7096
|
+
import { z as z29 } from "zod";
|
|
7097
|
+
var accountIdentitySchema = z29.object({
|
|
7098
|
+
accountId: z29.string().min(1),
|
|
7099
|
+
email: z29.string().min(1).optional()
|
|
6975
7100
|
}).strict();
|
|
6976
7101
|
function safeParseAccountIdentity(payload) {
|
|
6977
7102
|
return accountIdentitySchema.safeParse(payload);
|
|
6978
7103
|
}
|
|
6979
7104
|
|
|
6980
7105
|
// ../events/dist/credential-wire.js
|
|
6981
|
-
import { z as
|
|
6982
|
-
var engagementCredentialResponseSchema =
|
|
6983
|
-
engagementId:
|
|
6984
|
-
sessionToken:
|
|
6985
|
-
mcpUrl:
|
|
6986
|
-
guardUrl:
|
|
6987
|
-
controlTelemetryUrl:
|
|
6988
|
-
methodVersion:
|
|
6989
|
-
consoleUrl:
|
|
7106
|
+
import { z as z30 } from "zod";
|
|
7107
|
+
var engagementCredentialResponseSchema = z30.object({
|
|
7108
|
+
engagementId: z30.string(),
|
|
7109
|
+
sessionToken: z30.string(),
|
|
7110
|
+
mcpUrl: z30.string(),
|
|
7111
|
+
guardUrl: z30.string(),
|
|
7112
|
+
controlTelemetryUrl: z30.string(),
|
|
7113
|
+
methodVersion: z30.string().optional(),
|
|
7114
|
+
consoleUrl: z30.string().optional()
|
|
6990
7115
|
}).strict();
|
|
6991
|
-
var ciTokenExchangeResponseSchema =
|
|
6992
|
-
token:
|
|
7116
|
+
var ciTokenExchangeResponseSchema = z30.object({
|
|
7117
|
+
token: z30.string(),
|
|
6993
7118
|
expiresAt: utcIso8601,
|
|
6994
|
-
engagementId:
|
|
7119
|
+
engagementId: z30.string()
|
|
6995
7120
|
}).strict();
|
|
6996
7121
|
var NO_CREDENTIAL_PLACEHOLDER = "halfcycle:no-credential";
|
|
6997
7122
|
var NOT_THIS_ACCOUNT_PLACEHOLDER = "halfcycle:not-this-account";
|
|
6998
7123
|
var UPDATE_CLAUDE_CODE_PLACEHOLDER = "halfcycle:update-claude-code";
|
|
6999
7124
|
var SWITCH_ACCOUNT_ANSWER = 'answer "s" when asked';
|
|
7000
|
-
|
|
7001
|
-
|
|
7125
|
+
function noCredentialSentence(version) {
|
|
7126
|
+
return `Halfcycle is not connected: this machine has no working Halfcycle credential for this project. Run "${bareHalfcycleCommand(version)}" in the project folder to sign in again.`;
|
|
7127
|
+
}
|
|
7128
|
+
var NO_CREDENTIAL_SENTENCE = noCredentialSentence();
|
|
7129
|
+
function notThisAccountSentence(version) {
|
|
7130
|
+
return `Halfcycle is not connected: this project belongs to another Halfcycle account. To switch, run "${bareHalfcycleCommand(version)}" in this project and ${SWITCH_ACCOUNT_ANSWER}.`;
|
|
7131
|
+
}
|
|
7132
|
+
var NOT_THIS_ACCOUNT_SENTENCE = notThisAccountSentence();
|
|
7002
7133
|
var UPDATE_CLAUDE_CODE_SENTENCE = "Halfcycle is not connected: this Claude Code version does not name the server it connects to, so no credential was sent. Update Claude Code, then reopen.";
|
|
7003
7134
|
var NO_NODE_SENTENCE = "Halfcycle is not connected: Node is not installed, or not on the path this session uses, so nothing here is checked. Install Node, then reopen.";
|
|
7004
7135
|
var FOLDER_NOT_TRUSTED_SENTENCE = "Folder not yet trusted. Open this folder once in interactive Claude Code and trust it; then headless runs work.";
|
|
@@ -7009,19 +7140,19 @@ var PLACEHOLDER_SENTENCES = Object.freeze({
|
|
|
7009
7140
|
});
|
|
7010
7141
|
|
|
7011
7142
|
// ../events/dist/project-engagement.js
|
|
7012
|
-
import { z as
|
|
7013
|
-
var projectIdSchema =
|
|
7014
|
-
var projectEngagementRequestSchema =
|
|
7015
|
-
derivedName:
|
|
7143
|
+
import { z as z31 } from "zod";
|
|
7144
|
+
var projectIdSchema = z31.string().uuid();
|
|
7145
|
+
var projectEngagementRequestSchema = z31.object({
|
|
7146
|
+
derivedName: z31.string()
|
|
7016
7147
|
}).strict();
|
|
7017
7148
|
|
|
7018
7149
|
// ../events/dist/phase-record.js
|
|
7019
|
-
import { z as
|
|
7150
|
+
import { z as z33 } from "zod";
|
|
7020
7151
|
|
|
7021
7152
|
// ../events/dist/build-record-v4.js
|
|
7022
|
-
import { z as
|
|
7153
|
+
import { z as z32 } from "zod";
|
|
7023
7154
|
var BUILD_RECORD_V4_FORMAT = "halfcycle-build-record/v4";
|
|
7024
|
-
var recordReviewClaimSchema =
|
|
7155
|
+
var recordReviewClaimSchema = z32.object({
|
|
7025
7156
|
...reviewClaimAnswerSchema.shape,
|
|
7026
7157
|
note: reviewNoteSchema.nullable()
|
|
7027
7158
|
}).strict().superRefine((c, ctx) => {
|
|
@@ -7029,19 +7160,19 @@ var recordReviewClaimSchema = z31.object({
|
|
|
7029
7160
|
ctx.addIssue({ code: "custom", path: ["note"], message: "carries the owner's note exactly on a reject" });
|
|
7030
7161
|
}
|
|
7031
7162
|
});
|
|
7032
|
-
var recordCallSchema =
|
|
7033
|
-
var recordReviewV4Schema =
|
|
7163
|
+
var recordCallSchema = z32.object(workCallSchema.shape).omit({ at: true }).strict().superRefine((c, ctx) => checkReadCallHow(c, ctx));
|
|
7164
|
+
var recordReviewV4Schema = z32.object({
|
|
7034
7165
|
...recordReviewSchema.shape,
|
|
7035
|
-
claims:
|
|
7036
|
-
calls:
|
|
7166
|
+
claims: z32.array(recordReviewClaimSchema).max(REVIEW_CLAIMS_MAX),
|
|
7167
|
+
calls: z32.array(recordCallSchema)
|
|
7037
7168
|
}).strict().superRefine((v, ctx) => checkRecordReview(v, ctx));
|
|
7038
|
-
var buildRecordV4Schema =
|
|
7169
|
+
var buildRecordV4Schema = z32.object({
|
|
7039
7170
|
...buildRecordV3Schema.shape,
|
|
7040
|
-
format:
|
|
7171
|
+
format: z32.literal(BUILD_RECORD_V4_FORMAT),
|
|
7041
7172
|
path: phasePathSchema,
|
|
7042
7173
|
review: recordReviewV4Schema
|
|
7043
7174
|
}).strict().superRefine((record2, ctx) => checkReviewedRecord(record2, ctx));
|
|
7044
|
-
var buildRecordSchema =
|
|
7175
|
+
var buildRecordSchema = z32.discriminatedUnion("format", [
|
|
7045
7176
|
buildRecordV2Schema,
|
|
7046
7177
|
buildRecordV3Schema,
|
|
7047
7178
|
buildRecordV4Schema
|
|
@@ -7073,81 +7204,81 @@ function removalsOf(record2) {
|
|
|
7073
7204
|
}
|
|
7074
7205
|
|
|
7075
7206
|
// ../events/dist/phase-record.js
|
|
7076
|
-
var phaseRecordRefusalReasonSchema =
|
|
7207
|
+
var phaseRecordRefusalReasonSchema = z33.enum([
|
|
7077
7208
|
"still-open",
|
|
7078
7209
|
"reopened",
|
|
7079
7210
|
"never-opened",
|
|
7080
7211
|
"never-closed",
|
|
7081
7212
|
"closed-before-records"
|
|
7082
7213
|
]);
|
|
7083
|
-
var phaseRecordRecordSchema =
|
|
7084
|
-
outcome:
|
|
7214
|
+
var phaseRecordRecordSchema = z33.object({
|
|
7215
|
+
outcome: z33.literal("record"),
|
|
7085
7216
|
phase: phaseNameSchema,
|
|
7086
7217
|
record: buildRecordSchema
|
|
7087
7218
|
}).strict().refine((body) => body.record.phase === body.phase, {
|
|
7088
7219
|
message: "the record is for a different phase than the one asked for",
|
|
7089
7220
|
path: ["record", "phase"]
|
|
7090
7221
|
});
|
|
7091
|
-
var phaseRecordWithheldSchema =
|
|
7092
|
-
outcome:
|
|
7222
|
+
var phaseRecordWithheldSchema = z33.object({
|
|
7223
|
+
outcome: z33.literal("withheld"),
|
|
7093
7224
|
phase: phaseNameSchema,
|
|
7094
|
-
sequence:
|
|
7225
|
+
sequence: z33.number().int().min(1),
|
|
7095
7226
|
closedAt: utcIso8601,
|
|
7096
|
-
message:
|
|
7227
|
+
message: z33.string()
|
|
7097
7228
|
}).strict();
|
|
7098
7229
|
var phaseRecordRefusedSchema = wireErrorSchema.extend({
|
|
7099
|
-
outcome:
|
|
7230
|
+
outcome: z33.literal("refused"),
|
|
7100
7231
|
phase: phaseNameSchema,
|
|
7101
|
-
statusCode:
|
|
7102
|
-
error:
|
|
7232
|
+
statusCode: z33.literal(409),
|
|
7233
|
+
error: z33.literal("PhaseRecordRefused"),
|
|
7103
7234
|
reason: phaseRecordRefusalReasonSchema
|
|
7104
7235
|
}).strict();
|
|
7105
|
-
var phaseRecordResponseSchema =
|
|
7236
|
+
var phaseRecordResponseSchema = z33.discriminatedUnion("outcome", [
|
|
7106
7237
|
phaseRecordRecordSchema,
|
|
7107
7238
|
phaseRecordWithheldSchema,
|
|
7108
7239
|
phaseRecordRefusedSchema
|
|
7109
7240
|
]);
|
|
7110
7241
|
|
|
7111
7242
|
// ../events/dist/scope-chat.js
|
|
7112
|
-
import { z as
|
|
7113
|
-
var chatScopeApproveRequestSchema =
|
|
7114
|
-
act:
|
|
7243
|
+
import { z as z34 } from "zod";
|
|
7244
|
+
var chatScopeApproveRequestSchema = z34.object({
|
|
7245
|
+
act: z34.literal("approve"),
|
|
7115
7246
|
reference: phaseIdentitySchema,
|
|
7116
7247
|
planVersion: planVersionSchema
|
|
7117
7248
|
}).strict();
|
|
7118
|
-
var chatScopeSendBackRequestSchema =
|
|
7119
|
-
act:
|
|
7249
|
+
var chatScopeSendBackRequestSchema = z34.object({
|
|
7250
|
+
act: z34.literal("send-back"),
|
|
7120
7251
|
reference: phaseIdentitySchema,
|
|
7121
7252
|
planVersion: planVersionSchema,
|
|
7122
7253
|
note: scopeNoteWireSchema.optional()
|
|
7123
7254
|
}).strict();
|
|
7124
|
-
var chatScopeRequestSchema =
|
|
7255
|
+
var chatScopeRequestSchema = z34.discriminatedUnion("act", [
|
|
7125
7256
|
chatScopeApproveRequestSchema,
|
|
7126
7257
|
chatScopeSendBackRequestSchema
|
|
7127
7258
|
]);
|
|
7128
7259
|
var CHAT_SCOPE_ACTS = SCOPE_ACTS;
|
|
7129
|
-
var chatScopeRecordedSchema =
|
|
7130
|
-
outcome:
|
|
7131
|
-
act:
|
|
7260
|
+
var chatScopeRecordedSchema = z34.object({
|
|
7261
|
+
outcome: z34.literal("recorded"),
|
|
7262
|
+
act: z34.enum(CHAT_SCOPE_ACTS),
|
|
7132
7263
|
phase: scopePhaseSchema,
|
|
7133
7264
|
planVersion: planVersionSchema
|
|
7134
7265
|
}).strict();
|
|
7135
|
-
var chatScopeAlreadyApprovedSchema =
|
|
7136
|
-
outcome:
|
|
7137
|
-
replanNeeded:
|
|
7266
|
+
var chatScopeAlreadyApprovedSchema = z34.object({
|
|
7267
|
+
outcome: z34.literal("already-approved"),
|
|
7268
|
+
replanNeeded: z34.boolean()
|
|
7138
7269
|
}).strict();
|
|
7139
|
-
var chatScopeRefusedSchema =
|
|
7140
|
-
outcome:
|
|
7141
|
-
problems:
|
|
7270
|
+
var chatScopeRefusedSchema = z34.object({
|
|
7271
|
+
outcome: z34.literal("refused"),
|
|
7272
|
+
problems: z34.array(systemDescriptionProblemSchema).min(1)
|
|
7142
7273
|
}).strict();
|
|
7143
|
-
var chatAmbiguousSchema =
|
|
7144
|
-
outcome:
|
|
7145
|
-
phases:
|
|
7274
|
+
var chatAmbiguousSchema = z34.object({
|
|
7275
|
+
outcome: z34.literal("ambiguous"),
|
|
7276
|
+
phases: z34.array(scopePhaseSchema).min(2)
|
|
7146
7277
|
}).strict();
|
|
7147
7278
|
function bareOutcome(outcome) {
|
|
7148
|
-
return
|
|
7279
|
+
return z34.object({ outcome: z34.literal(outcome) }).strict();
|
|
7149
7280
|
}
|
|
7150
|
-
var chatScopeResultSchema =
|
|
7281
|
+
var chatScopeResultSchema = z34.discriminatedUnion("outcome", [
|
|
7151
7282
|
chatScopeRecordedSchema,
|
|
7152
7283
|
chatScopeAlreadyApprovedSchema,
|
|
7153
7284
|
chatScopeRefusedSchema,
|
|
@@ -7162,19 +7293,19 @@ var chatScopeResultSchema = z33.discriminatedUnion("outcome", [
|
|
|
7162
7293
|
]);
|
|
7163
7294
|
|
|
7164
7295
|
// ../events/dist/review-chat.js
|
|
7165
|
-
import { z as
|
|
7166
|
-
var chatReviewAcceptRequestSchema =
|
|
7167
|
-
act:
|
|
7296
|
+
import { z as z35 } from "zod";
|
|
7297
|
+
var chatReviewAcceptRequestSchema = z35.object({
|
|
7298
|
+
act: z35.literal("accept"),
|
|
7168
7299
|
reference: phaseIdentitySchema,
|
|
7169
|
-
reviewVersion:
|
|
7170
|
-
parts:
|
|
7171
|
-
waitingClaims:
|
|
7300
|
+
reviewVersion: z35.number().int().min(0),
|
|
7301
|
+
parts: z35.array(systemPartIdSchema).max(REVIEW_PARTS_MAX),
|
|
7302
|
+
waitingClaims: z35.literal(true).optional()
|
|
7172
7303
|
}).strict();
|
|
7173
7304
|
function notedRequest(act) {
|
|
7174
|
-
return
|
|
7175
|
-
act:
|
|
7305
|
+
return z35.object({
|
|
7306
|
+
act: z35.literal(act),
|
|
7176
7307
|
reference: phaseIdentitySchema,
|
|
7177
|
-
reviewVersion:
|
|
7308
|
+
reviewVersion: z35.number().int().min(0),
|
|
7178
7309
|
part: systemPartIdSchema,
|
|
7179
7310
|
note: reviewNoteWireSchema
|
|
7180
7311
|
}).strict();
|
|
@@ -7183,14 +7314,14 @@ var chatReviewQuestionRequestSchema = notedRequest("question");
|
|
|
7183
7314
|
var chatReviewSendBackRequestSchema = notedRequest("send-back");
|
|
7184
7315
|
var chatReviewCarryRequestSchema = notedRequest("carry");
|
|
7185
7316
|
var chatReviewDropRequestSchema = notedRequest("drop");
|
|
7186
|
-
var chatReviewRejectRequestSchema =
|
|
7187
|
-
act:
|
|
7317
|
+
var chatReviewRejectRequestSchema = z35.object({
|
|
7318
|
+
act: z35.literal("reject"),
|
|
7188
7319
|
reference: phaseIdentitySchema,
|
|
7189
|
-
reviewVersion:
|
|
7320
|
+
reviewVersion: z35.number().int().min(0),
|
|
7190
7321
|
task: buildTaskIdSchema,
|
|
7191
7322
|
note: reviewNoteWireSchema
|
|
7192
7323
|
}).strict();
|
|
7193
|
-
var chatReviewRequestSchema =
|
|
7324
|
+
var chatReviewRequestSchema = z35.discriminatedUnion("act", [
|
|
7194
7325
|
chatReviewAcceptRequestSchema,
|
|
7195
7326
|
chatReviewQuestionRequestSchema,
|
|
7196
7327
|
chatReviewSendBackRequestSchema,
|
|
@@ -7199,12 +7330,12 @@ var chatReviewRequestSchema = z34.discriminatedUnion("act", [
|
|
|
7199
7330
|
chatReviewRejectRequestSchema
|
|
7200
7331
|
]);
|
|
7201
7332
|
function bareOutcome2(outcome) {
|
|
7202
|
-
return
|
|
7333
|
+
return z35.object({ outcome: z35.literal(outcome) }).strict();
|
|
7203
7334
|
}
|
|
7204
7335
|
function notTheConsoleNotDecidable(option) {
|
|
7205
7336
|
return option !== reviewNotDecidableSchema;
|
|
7206
7337
|
}
|
|
7207
|
-
var chatReviewResultSchema =
|
|
7338
|
+
var chatReviewResultSchema = z35.discriminatedUnion("outcome", [
|
|
7208
7339
|
reviewPartsNotDecidableSchema,
|
|
7209
7340
|
...reviewDecisionResultSchema.options.filter(notTheConsoleNotDecidable),
|
|
7210
7341
|
bareOutcome2("show-first"),
|
|
@@ -7212,15 +7343,15 @@ var chatReviewResultSchema = z34.discriminatedUnion("outcome", [
|
|
|
7212
7343
|
chatAmbiguousSchema,
|
|
7213
7344
|
bareOutcome2("nothing-waiting")
|
|
7214
7345
|
]);
|
|
7215
|
-
var reviewAnswerRequestSchema =
|
|
7346
|
+
var reviewAnswerRequestSchema = z35.object({
|
|
7216
7347
|
part: systemPartIdSchema,
|
|
7217
7348
|
answer: statementText(REVIEW_ANSWER_MAX)
|
|
7218
7349
|
}).strict();
|
|
7219
|
-
var reviewAnswerRecordedSchema =
|
|
7220
|
-
outcome:
|
|
7350
|
+
var reviewAnswerRecordedSchema = z35.object({
|
|
7351
|
+
outcome: z35.literal("recorded"),
|
|
7221
7352
|
name: systemPartSchema.shape.name
|
|
7222
7353
|
}).strict();
|
|
7223
|
-
var reviewAnswerResultSchema =
|
|
7354
|
+
var reviewAnswerResultSchema = z35.discriminatedUnion("outcome", [
|
|
7224
7355
|
reviewAnswerRecordedSchema,
|
|
7225
7356
|
bareOutcome2("no-question"),
|
|
7226
7357
|
reviewRefusedSchema,
|
|
@@ -7228,27 +7359,27 @@ var reviewAnswerResultSchema = z34.discriminatedUnion("outcome", [
|
|
|
7228
7359
|
]);
|
|
7229
7360
|
|
|
7230
7361
|
// ../events/dist/owner-waits.js
|
|
7231
|
-
import { z as
|
|
7362
|
+
import { z as z37 } from "zod";
|
|
7232
7363
|
|
|
7233
7364
|
// ../events/dist/owner-wait-answer.js
|
|
7234
|
-
import { z as
|
|
7235
|
-
var ownerWaitShortIdSchema =
|
|
7365
|
+
import { z as z36 } from "zod";
|
|
7366
|
+
var ownerWaitShortIdSchema = z36.string().regex(/^[0-9a-f]{8}$/);
|
|
7236
7367
|
var OWNER_WAIT_ANSWER_ACTS = ["approve", "decline"];
|
|
7237
|
-
var ownerWaitAnswerActSchema =
|
|
7238
|
-
var ownerWaitChatRequestSchema =
|
|
7239
|
-
|
|
7240
|
-
|
|
7368
|
+
var ownerWaitAnswerActSchema = z36.enum(OWNER_WAIT_ANSWER_ACTS);
|
|
7369
|
+
var ownerWaitChatRequestSchema = z36.discriminatedUnion("act", [
|
|
7370
|
+
z36.object({ act: z36.literal("approve"), wait: ownerWaitShortIdSchema }).strict(),
|
|
7371
|
+
z36.object({ act: z36.literal("decline"), wait: ownerWaitShortIdSchema, note: scopeNoteWireSchema }).strict()
|
|
7241
7372
|
]);
|
|
7242
7373
|
function bareOutcome3(outcome) {
|
|
7243
|
-
return
|
|
7374
|
+
return z36.object({ outcome: z36.literal(outcome) }).strict();
|
|
7244
7375
|
}
|
|
7245
|
-
var ownerWaitChatResultSchema =
|
|
7246
|
-
|
|
7247
|
-
outcome:
|
|
7376
|
+
var ownerWaitChatResultSchema = z36.discriminatedUnion("outcome", [
|
|
7377
|
+
z36.object({
|
|
7378
|
+
outcome: z36.literal("recorded"),
|
|
7248
7379
|
act: ownerWaitAnswerActSchema,
|
|
7249
|
-
question:
|
|
7380
|
+
question: z36.string().min(1)
|
|
7250
7381
|
}).strict(),
|
|
7251
|
-
|
|
7382
|
+
z36.object({ outcome: z36.literal("refused"), problems: z36.array(systemDescriptionProblemSchema).min(1) }).strict(),
|
|
7252
7383
|
bareOutcome3("not-owner"),
|
|
7253
7384
|
bareOutcome3("no-owner"),
|
|
7254
7385
|
bareOutcome3("not-found"),
|
|
@@ -7270,7 +7401,7 @@ var OWNER_WAIT_ITEMS = Object.freeze([
|
|
|
7270
7401
|
Object.freeze({ id: "change-standing-rule", words: "Changing a standing rule." })
|
|
7271
7402
|
]);
|
|
7272
7403
|
var OWNER_WAIT_ITEM_IDS = Object.freeze(OWNER_WAIT_ITEMS.map((item) => item.id));
|
|
7273
|
-
var ownerWaitItemIdSchema =
|
|
7404
|
+
var ownerWaitItemIdSchema = z37.enum(OWNER_WAIT_ITEM_IDS);
|
|
7274
7405
|
var OWNER_WAIT_FIXED_COUNT = OWNER_WAIT_ITEMS.length;
|
|
7275
7406
|
var OWNER_WAIT_ADDITIONS_MAX = 20;
|
|
7276
7407
|
var OWNER_WAIT_TEXT_MAX = 240;
|
|
@@ -7280,23 +7411,23 @@ function ownerWaitItemOf(id) {
|
|
|
7280
7411
|
throw new Error(`no owner-wait item ${id}`);
|
|
7281
7412
|
return item;
|
|
7282
7413
|
}
|
|
7283
|
-
var ownerWaitItemRefSchema =
|
|
7284
|
-
|
|
7285
|
-
|
|
7414
|
+
var ownerWaitItemRefSchema = z37.union([
|
|
7415
|
+
z37.number().int().min(1),
|
|
7416
|
+
z37.string().uuid({ message: "must be a number from 1 to 6, or the id shown beside an added item" })
|
|
7286
7417
|
]);
|
|
7287
7418
|
var ownerWaitText = descriptionText(OWNER_WAIT_TEXT_MAX);
|
|
7288
|
-
var ownerWaitAddRequestSchema =
|
|
7289
|
-
engagementId:
|
|
7419
|
+
var ownerWaitAddRequestSchema = z37.object({
|
|
7420
|
+
engagementId: z37.string().min(1),
|
|
7290
7421
|
text: ownerWaitText,
|
|
7291
7422
|
reason: ownerWaitText
|
|
7292
7423
|
}).strict();
|
|
7293
|
-
var ownerWaitAddResultSchema =
|
|
7294
|
-
|
|
7295
|
-
|
|
7424
|
+
var ownerWaitAddResultSchema = z37.discriminatedUnion("outcome", [
|
|
7425
|
+
z37.object({ outcome: z37.literal("added"), id: z37.string().uuid(), addedAt: utcIso8601 }).strict(),
|
|
7426
|
+
z37.object({ outcome: z37.literal("refused"), reason: z37.literal("too-many") }).strict()
|
|
7296
7427
|
]);
|
|
7297
|
-
var ownerWaitUndoableSchema =
|
|
7298
|
-
var ownerWaitDeclareRequestSchema =
|
|
7299
|
-
engagementId:
|
|
7428
|
+
var ownerWaitUndoableSchema = z37.enum(["yes", "no"]);
|
|
7429
|
+
var ownerWaitDeclareRequestSchema = z37.object({
|
|
7430
|
+
engagementId: z37.string().min(1),
|
|
7300
7431
|
item: ownerWaitItemRefSchema,
|
|
7301
7432
|
question: ownerWaitText,
|
|
7302
7433
|
recommendation: ownerWaitText,
|
|
@@ -7309,47 +7440,47 @@ var ownerWaitDeclareRequestSchema = z36.object({
|
|
|
7309
7440
|
path: ["how"]
|
|
7310
7441
|
});
|
|
7311
7442
|
var OWNER_WAIT_DECLARE_REFUSALS = ["no-open-phase", "unknown-item", "say-if-undoable"];
|
|
7312
|
-
var ownerWaitDeclareResultSchema =
|
|
7313
|
-
|
|
7314
|
-
|
|
7315
|
-
outcome:
|
|
7316
|
-
id:
|
|
7317
|
-
callId:
|
|
7318
|
-
say:
|
|
7443
|
+
var ownerWaitDeclareResultSchema = z37.discriminatedUnion("outcome", [
|
|
7444
|
+
z37.object({ outcome: z37.literal("recorded"), id: z37.string().uuid(), say: z37.string().min(1) }).strict(),
|
|
7445
|
+
z37.object({
|
|
7446
|
+
outcome: z37.literal("go-ahead"),
|
|
7447
|
+
id: z37.string().uuid(),
|
|
7448
|
+
callId: z37.number().int().positive(),
|
|
7449
|
+
say: z37.string().min(1)
|
|
7319
7450
|
}).strict(),
|
|
7320
|
-
|
|
7451
|
+
z37.object({ outcome: z37.literal("refused"), reason: z37.enum(OWNER_WAIT_DECLARE_REFUSALS) }).strict()
|
|
7321
7452
|
]);
|
|
7322
|
-
var ownerWaitAddedItemSchema =
|
|
7323
|
-
id:
|
|
7324
|
-
words:
|
|
7325
|
-
reason:
|
|
7453
|
+
var ownerWaitAddedItemSchema = z37.object({
|
|
7454
|
+
id: z37.string().uuid(),
|
|
7455
|
+
words: z37.string().min(1),
|
|
7456
|
+
reason: z37.string().min(1),
|
|
7326
7457
|
addedAt: utcIso8601
|
|
7327
7458
|
}).strict();
|
|
7328
|
-
var ownerWaitListSchema =
|
|
7329
|
-
fixed:
|
|
7330
|
-
added:
|
|
7459
|
+
var ownerWaitListSchema = z37.object({
|
|
7460
|
+
fixed: z37.array(z37.object({ id: ownerWaitItemIdSchema, words: z37.string().min(1) }).strict()).length(OWNER_WAIT_FIXED_COUNT),
|
|
7461
|
+
added: z37.array(ownerWaitAddedItemSchema).max(OWNER_WAIT_ADDITIONS_MAX)
|
|
7331
7462
|
}).strict();
|
|
7332
|
-
var ownerWaitDeclarationSchema =
|
|
7333
|
-
id:
|
|
7463
|
+
var ownerWaitDeclarationSchema = z37.object({
|
|
7464
|
+
id: z37.string().uuid(),
|
|
7334
7465
|
fixedItem: ownerWaitItemIdSchema.nullable(),
|
|
7335
|
-
addition:
|
|
7336
|
-
question:
|
|
7337
|
-
recommendation:
|
|
7466
|
+
addition: z37.object({ id: z37.string().uuid(), words: z37.string().min(1) }).strict().nullable(),
|
|
7467
|
+
question: z37.string().min(1),
|
|
7468
|
+
recommendation: z37.string().min(1),
|
|
7338
7469
|
task: buildTaskIdSchema.nullable(),
|
|
7339
7470
|
declaredAt: utcIso8601
|
|
7340
7471
|
}).strict().refine((d) => d.fixedItem === null !== (d.addition === null), {
|
|
7341
7472
|
message: "names exactly one item: a fixed one or an added one",
|
|
7342
7473
|
path: ["fixedItem"]
|
|
7343
7474
|
});
|
|
7344
|
-
var ownerWaitDeclarationsSchema =
|
|
7345
|
-
var ownerWaitOpenSchema =
|
|
7475
|
+
var ownerWaitDeclarationsSchema = z37.object({ declarations: z37.array(ownerWaitDeclarationSchema) }).strict();
|
|
7476
|
+
var ownerWaitOpenSchema = z37.object({
|
|
7346
7477
|
wait: ownerWaitShortIdSchema,
|
|
7347
7478
|
task: buildTaskIdSchema.nullable(),
|
|
7348
|
-
question:
|
|
7349
|
-
recommendation:
|
|
7479
|
+
question: z37.string().min(1),
|
|
7480
|
+
recommendation: z37.string().min(1),
|
|
7350
7481
|
declaredAt: utcIso8601
|
|
7351
7482
|
}).strict();
|
|
7352
|
-
var ownerWaitsOpenSchema =
|
|
7483
|
+
var ownerWaitsOpenSchema = z37.object({ waits: z37.array(ownerWaitOpenSchema) }).strict();
|
|
7353
7484
|
function ownerWaitItemPhraseOf(item) {
|
|
7354
7485
|
const cut = item.words.split(", such as ")[0] ?? item.words;
|
|
7355
7486
|
const bare = cut.endsWith(".") ? cut.slice(0, -1) : cut;
|
|
@@ -7368,15 +7499,15 @@ function ownerWaitClosedLineOf(item, question2) {
|
|
|
7368
7499
|
var OWNER_WAIT_DECLARATIONS_READ_FAILED = "Halfcycle could not read the decisions that waited for you.";
|
|
7369
7500
|
|
|
7370
7501
|
// ../events/dist/autonomy.js
|
|
7371
|
-
import { z as
|
|
7502
|
+
import { z as z38 } from "zod";
|
|
7372
7503
|
var AUTONOMY_CHANGED_IN = ["console", "chat"];
|
|
7373
|
-
var autonomyChangedInSchema =
|
|
7374
|
-
var autonomySettingsSchema =
|
|
7375
|
-
on:
|
|
7504
|
+
var autonomyChangedInSchema = z38.enum(AUTONOMY_CHANGED_IN);
|
|
7505
|
+
var autonomySettingsSchema = z38.object({
|
|
7506
|
+
on: z38.boolean(),
|
|
7376
7507
|
// whether builds run on their own
|
|
7377
|
-
mayPublish:
|
|
7508
|
+
mayPublish: z38.boolean(),
|
|
7378
7509
|
// whether builds may publish or release without waiting
|
|
7379
|
-
maySpend:
|
|
7510
|
+
maySpend: z38.boolean(),
|
|
7380
7511
|
// whether builds may spend money or sign up without waiting
|
|
7381
7512
|
changedAt: utcIso8601.nullable(),
|
|
7382
7513
|
// when the setting last changed; null if never
|
|
@@ -7386,37 +7517,37 @@ var autonomySettingsSchema = z37.object({
|
|
|
7386
7517
|
message: "says when and where it changed, or neither",
|
|
7387
7518
|
path: ["changedIn"]
|
|
7388
7519
|
});
|
|
7389
|
-
var autonomyChangeRequestSchema =
|
|
7390
|
-
on:
|
|
7520
|
+
var autonomyChangeRequestSchema = z38.object({
|
|
7521
|
+
on: z38.boolean(),
|
|
7391
7522
|
// the switch
|
|
7392
|
-
mayPublish:
|
|
7523
|
+
mayPublish: z38.boolean(),
|
|
7393
7524
|
// the publish box
|
|
7394
|
-
maySpend:
|
|
7525
|
+
maySpend: z38.boolean()
|
|
7395
7526
|
// the spend box
|
|
7396
7527
|
}).strict();
|
|
7397
7528
|
var AUTONOMY_CHAT_ACTS = ["on", "off"];
|
|
7398
|
-
var autonomyChatActSchema =
|
|
7399
|
-
var autonomyChatRequestSchema =
|
|
7529
|
+
var autonomyChatActSchema = z38.enum(AUTONOMY_CHAT_ACTS);
|
|
7530
|
+
var autonomyChatRequestSchema = z38.object({
|
|
7400
7531
|
act: autonomyChatActSchema
|
|
7401
7532
|
// what the owner typed
|
|
7402
7533
|
}).strict();
|
|
7403
|
-
var autonomyChatResultSchema =
|
|
7404
|
-
|
|
7405
|
-
outcome:
|
|
7406
|
-
on:
|
|
7534
|
+
var autonomyChatResultSchema = z38.discriminatedUnion("outcome", [
|
|
7535
|
+
z38.object({
|
|
7536
|
+
outcome: z38.literal("recorded"),
|
|
7537
|
+
on: z38.boolean()
|
|
7407
7538
|
// whether builds now run on their own
|
|
7408
7539
|
}).strict(),
|
|
7409
|
-
|
|
7410
|
-
|
|
7411
|
-
|
|
7412
|
-
|
|
7540
|
+
z38.object({ outcome: z38.literal("not-owner") }).strict(),
|
|
7541
|
+
z38.object({ outcome: z38.literal("no-owner") }).strict(),
|
|
7542
|
+
z38.object({ outcome: z38.literal("not-offered") }).strict(),
|
|
7543
|
+
z38.object({ outcome: z38.literal("unavailable") }).strict()
|
|
7413
7544
|
]);
|
|
7414
|
-
var autonomyStateSchema =
|
|
7415
|
-
on:
|
|
7545
|
+
var autonomyStateSchema = z38.object({
|
|
7546
|
+
on: z38.boolean(),
|
|
7416
7547
|
// whether builds run on their own
|
|
7417
|
-
mayPublish:
|
|
7548
|
+
mayPublish: z38.boolean(),
|
|
7418
7549
|
// whether builds may publish or release without waiting
|
|
7419
|
-
maySpend:
|
|
7550
|
+
maySpend: z38.boolean()
|
|
7420
7551
|
// whether builds may spend money or sign up without waiting
|
|
7421
7552
|
}).strict();
|
|
7422
7553
|
var autonomyStateFileSchema = autonomyStateSchema.extend({
|
|
@@ -7425,8 +7556,8 @@ var autonomyStateFileSchema = autonomyStateSchema.extend({
|
|
|
7425
7556
|
}).strict();
|
|
7426
7557
|
var AUTONOMY_STATE_FILE_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
7427
7558
|
var AUTONOMY_ACTIVITY_KINDS = ["went-ahead", "waiting", "approved", "closed", "waiting-on-owner"];
|
|
7428
|
-
var autonomyActivityKindSchema =
|
|
7429
|
-
var autonomyActivityRowSchema =
|
|
7559
|
+
var autonomyActivityKindSchema = z38.enum(AUTONOMY_ACTIVITY_KINDS);
|
|
7560
|
+
var autonomyActivityRowSchema = z38.object({
|
|
7430
7561
|
kind: autonomyActivityKindSchema,
|
|
7431
7562
|
// what happened
|
|
7432
7563
|
phase: scopePhaseSchema,
|
|
@@ -7477,11 +7608,11 @@ var autonomyActivityRowSchema = z37.object({
|
|
|
7477
7608
|
ctx.addIssue({ code: "custom", message: "says how to undo it exactly when it can be undone", path: ["how"] });
|
|
7478
7609
|
}
|
|
7479
7610
|
});
|
|
7480
|
-
var autonomyActivitySchema =
|
|
7481
|
-
rows:
|
|
7611
|
+
var autonomyActivitySchema = z38.object({
|
|
7612
|
+
rows: z38.array(autonomyActivityRowSchema)
|
|
7482
7613
|
// newest first
|
|
7483
7614
|
}).strict();
|
|
7484
|
-
var savedIdSchema =
|
|
7615
|
+
var savedIdSchema = z38.string().min(1).max(64).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "must be lower-case letters, digits and single hyphens");
|
|
7485
7616
|
var SAVED_REF_PREFIX = "refs/halfcycle/saved/";
|
|
7486
7617
|
function savedRefOf(id) {
|
|
7487
7618
|
return `${SAVED_REF_PREFIX}${id}`;
|
|
@@ -7520,33 +7651,33 @@ function isGitFolderSegment(segment) {
|
|
|
7520
7651
|
return t === ".git" || /^git~\d+$/.test(t);
|
|
7521
7652
|
}
|
|
7522
7653
|
var SAVED_PATH_KEPT_IN = ["commit", "copy"];
|
|
7523
|
-
var savedPathSchema =
|
|
7524
|
-
path:
|
|
7654
|
+
var savedPathSchema = z38.object({
|
|
7655
|
+
path: z38.string().max(4096).refine(isProjectRelativePath, { message: "must be a path inside the project" }),
|
|
7525
7656
|
// the file, relative to the project
|
|
7526
|
-
keptIn:
|
|
7657
|
+
keptIn: z38.enum(SAVED_PATH_KEPT_IN)
|
|
7527
7658
|
// in the save's commit, or copied beside this file
|
|
7528
7659
|
}).strict();
|
|
7529
|
-
var savedCommitSchema =
|
|
7530
|
-
var savedBranchSchema =
|
|
7531
|
-
name:
|
|
7660
|
+
var savedCommitSchema = z38.string().regex(/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/, "must be a full commit hash");
|
|
7661
|
+
var savedBranchSchema = z38.object({
|
|
7662
|
+
name: z38.string().min(1).max(255).refine((n) => !hasControlCharacter(n) && !n.startsWith("-") && !n.includes("..") && n !== "HEAD", {
|
|
7532
7663
|
message: "must be a branch name"
|
|
7533
7664
|
}),
|
|
7534
7665
|
// the branch's name
|
|
7535
7666
|
commit: savedCommitSchema
|
|
7536
7667
|
// the commit it pointed at
|
|
7537
7668
|
}).strict();
|
|
7538
|
-
var savedSchema =
|
|
7669
|
+
var savedSchema = z38.object({
|
|
7539
7670
|
id: savedIdSchema,
|
|
7540
7671
|
// what restore is typed with
|
|
7541
7672
|
savedAt: utcIso8601,
|
|
7542
7673
|
// when the copy was made
|
|
7543
|
-
ref:
|
|
7674
|
+
ref: z38.string().min(1),
|
|
7544
7675
|
// where the saved commit is kept
|
|
7545
|
-
what:
|
|
7676
|
+
what: z38.string().min(1).max(2e3),
|
|
7546
7677
|
// what the action would have changed, in words
|
|
7547
|
-
paths:
|
|
7678
|
+
paths: z38.array(savedPathSchema),
|
|
7548
7679
|
// every saved file
|
|
7549
|
-
branches:
|
|
7680
|
+
branches: z38.array(savedBranchSchema)
|
|
7550
7681
|
// every saved line of work
|
|
7551
7682
|
}).strict().refine((s) => s.ref === savedRefOf(s.id), { message: "must be the ref of this save", path: ["ref"] });
|
|
7552
7683
|
function autonomyA13(accepted, carried) {
|
|
@@ -7600,23 +7731,23 @@ function recordedClaimLine(claim) {
|
|
|
7600
7731
|
var NO_UNCHECKED_CLAIMS_LINE = "No agent said a check passes that Halfcycle never ran.";
|
|
7601
7732
|
|
|
7602
7733
|
// ../events/dist/relayed-choices.js
|
|
7603
|
-
import { z as
|
|
7734
|
+
import { z as z39 } from "zod";
|
|
7604
7735
|
var RELAYED_CHOICES_FILENAME = "relayed.json";
|
|
7605
7736
|
var RELAYED_SAID_MAX = 80;
|
|
7606
|
-
var relayedChoiceSchema =
|
|
7737
|
+
var relayedChoiceSchema = z39.object({
|
|
7607
7738
|
at: utcIso8601,
|
|
7608
|
-
said:
|
|
7739
|
+
said: z39.string().min(1).max(RELAYED_SAID_MAX)
|
|
7609
7740
|
}).strict();
|
|
7610
|
-
var relayedChoicesFileSchema =
|
|
7741
|
+
var relayedChoicesFileSchema = z39.object({
|
|
7611
7742
|
phase: phaseNameSchema,
|
|
7612
|
-
entries:
|
|
7743
|
+
entries: z39.array(relayedChoiceSchema)
|
|
7613
7744
|
}).strict();
|
|
7614
7745
|
function relayedChoiceLineOf(entry) {
|
|
7615
7746
|
return `On ${paceWhenOf(new Date(entry.at))} "${entry.said}" reached Halfcycle through Codex, which cannot show who typed it. Halfcycle could not confirm that you typed it, and it let this phase use more of your allowance.`;
|
|
7616
7747
|
}
|
|
7617
7748
|
|
|
7618
7749
|
// ../events/dist/tier-figures.js
|
|
7619
|
-
import { z as
|
|
7750
|
+
import { z as z40 } from "zod";
|
|
7620
7751
|
var TIER_ORDER = ["fast", "standard", "deep"];
|
|
7621
7752
|
function tierRankOf(tier) {
|
|
7622
7753
|
return TIER_ORDER.indexOf(tier);
|
|
@@ -7649,15 +7780,15 @@ function routeNoSuchTaskLineOf(task) {
|
|
|
7649
7780
|
return `"${task}" is not a task in the plan. Check the task id, then run this again.`;
|
|
7650
7781
|
}
|
|
7651
7782
|
var ROUTING_FILENAME = "routing.json";
|
|
7652
|
-
var routingRecordSchema =
|
|
7653
|
-
phase:
|
|
7654
|
-
cheapStarted:
|
|
7783
|
+
var routingRecordSchema = z40.object({
|
|
7784
|
+
phase: z40.string().min(1).max(200),
|
|
7785
|
+
cheapStarted: z40.array(z40.string().min(1).max(200)).max(2e3)
|
|
7655
7786
|
}).strict();
|
|
7656
7787
|
var PHASE_MODEL_NAME_MAX = 64;
|
|
7657
|
-
var phaseModelsFileSchema =
|
|
7658
|
-
phase:
|
|
7659
|
-
openedAt:
|
|
7660
|
-
tasks:
|
|
7788
|
+
var phaseModelsFileSchema = z40.object({
|
|
7789
|
+
phase: z40.string().min(1).max(200),
|
|
7790
|
+
openedAt: z40.string().min(1).max(64),
|
|
7791
|
+
tasks: z40.record(z40.string().min(1).max(200), z40.record(z40.string().min(1).max(PHASE_MODEL_NAME_MAX), z40.number().int().min(0)))
|
|
7661
7792
|
}).strict();
|
|
7662
7793
|
function inAttemptOrder(attempts) {
|
|
7663
7794
|
return [...attempts].sort((a, b) => a.attempt - b.attempt);
|
|
@@ -7715,9 +7846,9 @@ var CHECKS_IF_STILL_UNREPORTED = "If still unreported: Codex, approve in /hooks;
|
|
|
7715
7846
|
var CHECKS_GONE_QUIET = "Halfcycle's checks have not reported from this machine lately.";
|
|
7716
7847
|
|
|
7717
7848
|
// dist/engagement-credential.js
|
|
7718
|
-
import { chmodSync as chmodSync2, mkdirSync as mkdirSync2, readFileSync as
|
|
7849
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
7719
7850
|
import { platform as platform2 } from "node:os";
|
|
7720
|
-
import { join as
|
|
7851
|
+
import { join as join4 } from "node:path";
|
|
7721
7852
|
|
|
7722
7853
|
// dist/account-credential.js
|
|
7723
7854
|
import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
@@ -7776,6 +7907,46 @@ function forgetStoredCredential(serviceUrl, home) {
|
|
|
7776
7907
|
return true;
|
|
7777
7908
|
}
|
|
7778
7909
|
|
|
7910
|
+
// dist/own-version.js
|
|
7911
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
7912
|
+
import { dirname as dirname2, join as join3 } from "node:path";
|
|
7913
|
+
import { fileURLToPath } from "node:url";
|
|
7914
|
+
var moduleDir = dirname2(fileURLToPath(import.meta.url));
|
|
7915
|
+
function findBundleRoot(startDir) {
|
|
7916
|
+
let candidate = startDir;
|
|
7917
|
+
for (let i = 0; i < 4; i++) {
|
|
7918
|
+
const manifest = join3(candidate, ".claude-plugin", "plugin.json");
|
|
7919
|
+
try {
|
|
7920
|
+
if (existsSync2(manifest))
|
|
7921
|
+
return candidate;
|
|
7922
|
+
} catch {
|
|
7923
|
+
}
|
|
7924
|
+
candidate = dirname2(candidate);
|
|
7925
|
+
}
|
|
7926
|
+
return join3(startDir, "..");
|
|
7927
|
+
}
|
|
7928
|
+
var BUNDLE_ROOT = findBundleRoot(moduleDir);
|
|
7929
|
+
function bundleVersion() {
|
|
7930
|
+
try {
|
|
7931
|
+
const pkg = JSON.parse(readFileSync2(join3(BUNDLE_ROOT, "package.json"), "utf-8"));
|
|
7932
|
+
return typeof pkg.version === "string" ? pkg.version : void 0;
|
|
7933
|
+
} catch {
|
|
7934
|
+
return void 0;
|
|
7935
|
+
}
|
|
7936
|
+
}
|
|
7937
|
+
var cachedVersion;
|
|
7938
|
+
function ownVersion() {
|
|
7939
|
+
if (cachedVersion === void 0)
|
|
7940
|
+
cachedVersion = { value: bundleVersion() };
|
|
7941
|
+
return cachedVersion.value;
|
|
7942
|
+
}
|
|
7943
|
+
function ownCommand() {
|
|
7944
|
+
return halfcycleCommand(ownVersion());
|
|
7945
|
+
}
|
|
7946
|
+
function ownBareCommand() {
|
|
7947
|
+
return bareHalfcycleCommand(ownVersion());
|
|
7948
|
+
}
|
|
7949
|
+
|
|
7779
7950
|
// dist/engagement-credential.js
|
|
7780
7951
|
var ENGAGEMENTS_DIR = ENGAGEMENTS_DIR_NAME;
|
|
7781
7952
|
var ENV_FILENAME = ENGAGEMENT_ENV_FILENAME;
|
|
@@ -7783,7 +7954,7 @@ var HALFCYCLE_DIR = HALFCYCLE_DIR_NAME;
|
|
|
7783
7954
|
var InvalidEngagementIdError = class extends Error {
|
|
7784
7955
|
constructor(engagementId) {
|
|
7785
7956
|
const shown2 = JSON.stringify(engagementId.length > 80 ? `${engagementId.slice(0, 80)}\u2026` : engagementId);
|
|
7786
|
-
super(`${shown2} is not a Halfcycle project id, so it cannot name a folder on this machine and nothing was written. A project id looks like 3f2a1c4e-8b7d-4e2f-9a61-0c5d7e8f9b10. If it is the "${PIN_ENGAGEMENT_ID_FIELD}" in .halfcycle/bundle.json, delete that file and run "
|
|
7957
|
+
super(`${shown2} is not a Halfcycle project id, so it cannot name a folder on this machine and nothing was written. A project id looks like 3f2a1c4e-8b7d-4e2f-9a61-0c5d7e8f9b10. If it is the "${PIN_ENGAGEMENT_ID_FIELD}" in .halfcycle/bundle.json, delete that file and run "${ownBareCommand()}" again: this repository is then set up as a new Halfcycle project.`);
|
|
7787
7958
|
this.name = "InvalidEngagementIdError";
|
|
7788
7959
|
}
|
|
7789
7960
|
};
|
|
@@ -7793,13 +7964,13 @@ function assertEngagementId(engagementId) {
|
|
|
7793
7964
|
}
|
|
7794
7965
|
function engagementStateDir(engagementId, home) {
|
|
7795
7966
|
assertEngagementId(engagementId);
|
|
7796
|
-
return
|
|
7967
|
+
return join4(halfcycleHome(home), ENGAGEMENTS_DIR, engagementId);
|
|
7797
7968
|
}
|
|
7798
7969
|
function engagementEnvPath(engagementId, home) {
|
|
7799
|
-
return
|
|
7970
|
+
return join4(engagementStateDir(engagementId, home), ENV_FILENAME);
|
|
7800
7971
|
}
|
|
7801
7972
|
function notThisAccountMarkerPath(engagementId, home) {
|
|
7802
|
-
return
|
|
7973
|
+
return join4(engagementStateDir(engagementId, home), NOT_THIS_ACCOUNT_MARKER_FILENAME);
|
|
7803
7974
|
}
|
|
7804
7975
|
function writeNotThisAccountMarker(engagementId, home) {
|
|
7805
7976
|
const dir = engagementStateDir(engagementId, home);
|
|
@@ -7829,7 +8000,7 @@ function reconcileEnvText2(existing, values, keys = ENGAGEMENT_ENV_KEYS) {
|
|
|
7829
8000
|
}
|
|
7830
8001
|
function readEngagementEnv(engagementId, home) {
|
|
7831
8002
|
try {
|
|
7832
|
-
return parseEnvText(
|
|
8003
|
+
return parseEnvText(readFileSync3(engagementEnvPath(engagementId, home), "utf-8"));
|
|
7833
8004
|
} catch {
|
|
7834
8005
|
return null;
|
|
7835
8006
|
}
|
|
@@ -7839,7 +8010,7 @@ function writeEngagementEnv(engagementId, values, home, keys = ENGAGEMENT_ENV_KE
|
|
|
7839
8010
|
const dir = engagementStateDir(engagementId, home);
|
|
7840
8011
|
let existing;
|
|
7841
8012
|
try {
|
|
7842
|
-
existing =
|
|
8013
|
+
existing = readFileSync3(path, "utf-8");
|
|
7843
8014
|
} catch {
|
|
7844
8015
|
existing = null;
|
|
7845
8016
|
}
|
|
@@ -7870,7 +8041,7 @@ function engagementResolutionShell() {
|
|
|
7870
8041
|
return `# --- Halfcycle credential resolution (generated; do not edit) ----------------
|
|
7871
8042
|
# THE CREDENTIAL IS NOT IN THIS REPOSITORY. It lives at
|
|
7872
8043
|
# $HOME/${HALFCYCLE_DIR}/${ENGAGEMENTS_DIR}/<engagement-id>/${ENV_FILENAME}
|
|
7873
|
-
# written owner-only by
|
|
8044
|
+
# written owner-only by \`${bareHalfcycleCommand()}\`. What this repository holds is the
|
|
7874
8045
|
# engagement id, in the committed .halfcycle/bundle.json. So: read the id out of
|
|
7875
8046
|
# the pin, then name the file. There is no jq here and node is not dependable on a
|
|
7876
8047
|
# desktop session's PATH, so the id is cut out with sed \u2014 after flattening the
|
|
@@ -7924,8 +8095,8 @@ halfcycle_env_file() {
|
|
|
7924
8095
|
|
|
7925
8096
|
// dist/identity.js
|
|
7926
8097
|
import { execFileSync } from "node:child_process";
|
|
7927
|
-
import { existsSync as
|
|
7928
|
-
import { dirname as
|
|
8098
|
+
import { existsSync as existsSync3, linkSync, mkdirSync as mkdirSync3, readFileSync as readFileSync4, renameSync, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
8099
|
+
import { dirname as dirname3, isAbsolute, join as join5 } from "node:path";
|
|
7929
8100
|
import { randomUUID } from "node:crypto";
|
|
7930
8101
|
var PROJECT_IDENTITY_NOTE = 'projectId is NOT the engagement id \u2014 it identifies this repository/worktree to Halfcycle, not an engagement. The engagement id lives in .halfcycle/bundle.json, in its "engagementId" field.';
|
|
7931
8102
|
var PROJECT_IDENTITY_REL = ".halfcycle/project.json";
|
|
@@ -8056,7 +8227,7 @@ function readIgnoredPaths(targetRepoRoot, paths) {
|
|
|
8056
8227
|
const toplevel = git(targetRepoRoot, ["rev-parse", "--show-toplevel"]);
|
|
8057
8228
|
if (toplevel === null)
|
|
8058
8229
|
return null;
|
|
8059
|
-
return ignored.map((entry) => isAbsolute(entry.source) ? entry : { ...entry, source:
|
|
8230
|
+
return ignored.map((entry) => isAbsolute(entry.source) ? entry : { ...entry, source: join5(toplevel, entry.source) });
|
|
8060
8231
|
}
|
|
8061
8232
|
function mintIdentity(targetRepoRoot) {
|
|
8062
8233
|
return {
|
|
@@ -8067,10 +8238,10 @@ function mintIdentity(targetRepoRoot) {
|
|
|
8067
8238
|
};
|
|
8068
8239
|
}
|
|
8069
8240
|
function plannedRemoteRepair(targetRepoRoot) {
|
|
8070
|
-
const path =
|
|
8241
|
+
const path = join5(targetRepoRoot, PROJECT_IDENTITY_REL);
|
|
8071
8242
|
let onDisk;
|
|
8072
8243
|
try {
|
|
8073
|
-
const parsed = JSON.parse(
|
|
8244
|
+
const parsed = JSON.parse(readFileSync4(path, "utf-8"));
|
|
8074
8245
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
8075
8246
|
return null;
|
|
8076
8247
|
onDisk = parsed;
|
|
@@ -8089,7 +8260,7 @@ function plannedRemoteRepair(targetRepoRoot) {
|
|
|
8089
8260
|
});
|
|
8090
8261
|
}
|
|
8091
8262
|
function replaceProjectIdentityFile(targetRepoRoot, text) {
|
|
8092
|
-
const path =
|
|
8263
|
+
const path = join5(targetRepoRoot, PROJECT_IDENTITY_REL);
|
|
8093
8264
|
const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
8094
8265
|
try {
|
|
8095
8266
|
writeFileSync3(temporary, text, { encoding: "utf-8", flag: "wx" });
|
|
@@ -8100,8 +8271,8 @@ function replaceProjectIdentityFile(targetRepoRoot, text) {
|
|
|
8100
8271
|
}
|
|
8101
8272
|
}
|
|
8102
8273
|
function planProjectIdentity(targetRepoRoot) {
|
|
8103
|
-
const path =
|
|
8104
|
-
if (!
|
|
8274
|
+
const path = join5(targetRepoRoot, PROJECT_IDENTITY_REL);
|
|
8275
|
+
if (!existsSync3(path)) {
|
|
8105
8276
|
return { action: "add", after: serializeProjectIdentity(mintIdentity(targetRepoRoot)) };
|
|
8106
8277
|
}
|
|
8107
8278
|
const repaired = plannedRemoteRepair(targetRepoRoot);
|
|
@@ -8109,7 +8280,7 @@ function planProjectIdentity(targetRepoRoot) {
|
|
|
8109
8280
|
return { action: "change", after: repaired };
|
|
8110
8281
|
let current;
|
|
8111
8282
|
try {
|
|
8112
|
-
current =
|
|
8283
|
+
current = readFileSync4(path, "utf-8");
|
|
8113
8284
|
} catch {
|
|
8114
8285
|
current = null;
|
|
8115
8286
|
}
|
|
@@ -8119,8 +8290,8 @@ function serializeProjectIdentity(identity) {
|
|
|
8119
8290
|
return JSON.stringify(identity, null, 2) + "\n";
|
|
8120
8291
|
}
|
|
8121
8292
|
function createProjectIdentityFile(targetRepoRoot, text) {
|
|
8122
|
-
const path =
|
|
8123
|
-
mkdirSync3(
|
|
8293
|
+
const path = join5(targetRepoRoot, PROJECT_IDENTITY_REL);
|
|
8294
|
+
mkdirSync3(dirname3(path), { recursive: true });
|
|
8124
8295
|
const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
8125
8296
|
try {
|
|
8126
8297
|
writeFileSync3(temporary, text ?? serializeProjectIdentity(mintIdentity(targetRepoRoot)), {
|
|
@@ -8139,8 +8310,8 @@ function createProjectIdentityFile(targetRepoRoot, text) {
|
|
|
8139
8310
|
}
|
|
8140
8311
|
|
|
8141
8312
|
// dist/install-plan.js
|
|
8142
|
-
import { existsSync as
|
|
8143
|
-
import { join as
|
|
8313
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "node:fs";
|
|
8314
|
+
import { join as join7, posix } from "node:path";
|
|
8144
8315
|
|
|
8145
8316
|
// dist/install-manifest.js
|
|
8146
8317
|
import { Buffer as Buffer2 } from "node:buffer";
|
|
@@ -8278,11 +8449,15 @@ function parseManifest(text) {
|
|
|
8278
8449
|
"hooksCreated",
|
|
8279
8450
|
"permissionsCreated",
|
|
8280
8451
|
"denyCreated",
|
|
8281
|
-
"statusLineSha256"
|
|
8452
|
+
"statusLineSha256",
|
|
8453
|
+
"attributionSha256"
|
|
8282
8454
|
], '"settings"');
|
|
8283
8455
|
if (s["statusLineSha256"] !== void 0 && (typeof s["statusLineSha256"] !== "string" || !HEX64.test(s["statusLineSha256"]))) {
|
|
8284
8456
|
throw new UnreadableManifestError('"settings.statusLineSha256" is not a hash');
|
|
8285
8457
|
}
|
|
8458
|
+
if (s["attributionSha256"] !== void 0 && (typeof s["attributionSha256"] !== "string" || !HEX64.test(s["attributionSha256"]))) {
|
|
8459
|
+
throw new UnreadableManifestError('"settings.attributionSha256" is not a hash');
|
|
8460
|
+
}
|
|
8286
8461
|
const settings = {
|
|
8287
8462
|
created: bool(s["created"], "settings.created"),
|
|
8288
8463
|
adopted: bool(s["adopted"], "settings.adopted"),
|
|
@@ -8293,7 +8468,8 @@ function parseManifest(text) {
|
|
|
8293
8468
|
hooksCreated: bool(s["hooksCreated"], "settings.hooksCreated"),
|
|
8294
8469
|
permissionsCreated: bool(s["permissionsCreated"], "settings.permissionsCreated"),
|
|
8295
8470
|
denyCreated: bool(s["denyCreated"], "settings.denyCreated"),
|
|
8296
|
-
...typeof s["statusLineSha256"] === "string" ? { statusLineSha256: s["statusLineSha256"] } : {}
|
|
8471
|
+
...typeof s["statusLineSha256"] === "string" ? { statusLineSha256: s["statusLineSha256"] } : {},
|
|
8472
|
+
...typeof s["attributionSha256"] === "string" ? { attributionSha256: s["attributionSha256"] } : {}
|
|
8297
8473
|
};
|
|
8298
8474
|
let mcp;
|
|
8299
8475
|
if (raw["mcp"] !== void 0) {
|
|
@@ -8400,6 +8576,7 @@ function recordSettings(draft, observed) {
|
|
|
8400
8576
|
return;
|
|
8401
8577
|
}
|
|
8402
8578
|
const statusLineSha256 = observed.statusLineSha256 ?? prev.statusLineSha256;
|
|
8579
|
+
const attributionSha256 = observed.attributionSha256 ?? prev.attributionSha256;
|
|
8403
8580
|
draft.settings = {
|
|
8404
8581
|
created: prev.created,
|
|
8405
8582
|
adopted: prev.adopted,
|
|
@@ -8410,7 +8587,8 @@ function recordSettings(draft, observed) {
|
|
|
8410
8587
|
hooksCreated: prev.hooksCreated,
|
|
8411
8588
|
permissionsCreated: prev.permissionsCreated,
|
|
8412
8589
|
denyCreated: prev.denyCreated,
|
|
8413
|
-
...statusLineSha256 !== void 0 ? { statusLineSha256 } : {}
|
|
8590
|
+
...statusLineSha256 !== void 0 ? { statusLineSha256 } : {},
|
|
8591
|
+
...attributionSha256 !== void 0 ? { attributionSha256 } : {}
|
|
8414
8592
|
};
|
|
8415
8593
|
}
|
|
8416
8594
|
function recordDenyRetired(draft, hashes) {
|
|
@@ -8491,8 +8669,8 @@ function union(a, b) {
|
|
|
8491
8669
|
}
|
|
8492
8670
|
|
|
8493
8671
|
// dist/scan.js
|
|
8494
|
-
import { existsSync as
|
|
8495
|
-
import { join as
|
|
8672
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
8673
|
+
import { join as join6 } from "node:path";
|
|
8496
8674
|
var HALFCYCLE_STATE_FORMAT = "halfcycle-state-file/v1";
|
|
8497
8675
|
var HALFCYCLE_STATE_NOTE = [
|
|
8498
8676
|
"This file is a local cache written ONCE when Halfcycle was installed. It is not a",
|
|
@@ -8515,7 +8693,7 @@ var LAYER_ARTEFACT_PATHS = {
|
|
|
8515
8693
|
function scanLayers(targetRepo, scannedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
8516
8694
|
const layers = {};
|
|
8517
8695
|
for (const [layer, paths] of Object.entries(LAYER_ARTEFACT_PATHS)) {
|
|
8518
|
-
const found = paths.filter((p) =>
|
|
8696
|
+
const found = paths.filter((p) => existsSync4(join6(targetRepo, p)));
|
|
8519
8697
|
if (found.length > 0) {
|
|
8520
8698
|
layers[layer] = {
|
|
8521
8699
|
value: "asserted",
|
|
@@ -8529,17 +8707,17 @@ function scanLayers(targetRepo, scannedAt = (/* @__PURE__ */ new Date()).toISOSt
|
|
|
8529
8707
|
}
|
|
8530
8708
|
var BOOTSTRAP_STATE_REL = ".halfcycle/state.json";
|
|
8531
8709
|
function planBootstrapScan(targetRepo) {
|
|
8532
|
-
const statePath =
|
|
8533
|
-
if (
|
|
8534
|
-
const existing = JSON.parse(
|
|
8710
|
+
const statePath = join6(targetRepo, BOOTSTRAP_STATE_REL);
|
|
8711
|
+
if (existsSync4(statePath)) {
|
|
8712
|
+
const existing = JSON.parse(readFileSync5(statePath, "utf-8"));
|
|
8535
8713
|
return { state: existing, text: null };
|
|
8536
8714
|
}
|
|
8537
8715
|
const state = scanLayers(targetRepo);
|
|
8538
8716
|
return { state, text: JSON.stringify(state, null, 2) + "\n" };
|
|
8539
8717
|
}
|
|
8540
8718
|
function writeBootstrapState(targetRepo, text) {
|
|
8541
|
-
mkdirSync4(
|
|
8542
|
-
writeFileSync4(
|
|
8719
|
+
mkdirSync4(join6(targetRepo, ".halfcycle"), { recursive: true });
|
|
8720
|
+
writeFileSync4(join6(targetRepo, BOOTSTRAP_STATE_REL), text, "utf-8");
|
|
8543
8721
|
}
|
|
8544
8722
|
|
|
8545
8723
|
// dist/install-plan.js
|
|
@@ -8681,7 +8859,7 @@ function kindOf(rel) {
|
|
|
8681
8859
|
}
|
|
8682
8860
|
var STEPS = /* @__PURE__ */ new WeakMap();
|
|
8683
8861
|
function planInstall(options) {
|
|
8684
|
-
const { targetRepo, engagementId, engagementType, credential, home, env, createdByAccountId } = options;
|
|
8862
|
+
const { targetRepo, engagementId, engagementType, credential, home, env, createdByAccountId, serviceUrl } = options;
|
|
8685
8863
|
const preview = engagementId === void 0;
|
|
8686
8864
|
if (!preview) {
|
|
8687
8865
|
assertEngagementId(engagementId);
|
|
@@ -8689,12 +8867,12 @@ function planInstall(options) {
|
|
|
8689
8867
|
throw new Error("[halfcycle] a plan for a project needs its kind as well as its id.");
|
|
8690
8868
|
}
|
|
8691
8869
|
}
|
|
8692
|
-
if (!
|
|
8870
|
+
if (!existsSync5(targetRepo)) {
|
|
8693
8871
|
throw new Error(`[halfcycle] Target repo does not exist: ${targetRepo}`);
|
|
8694
8872
|
}
|
|
8695
8873
|
const writeSet = closedWriteSet();
|
|
8696
8874
|
const previous = readPreviousManifest(targetRepo);
|
|
8697
|
-
const dirsBefore = new Set([...writeSet.dirs].filter((dir) =>
|
|
8875
|
+
const dirsBefore = new Set([...writeSet.dirs].filter((dir) => existsSync5(join7(targetRepo, dir))));
|
|
8698
8876
|
const { version, commands } = planCommandStubs(targetRepo, previous, writeSet);
|
|
8699
8877
|
const codexSkills = planCodexSkills(targetRepo, previous, writeSet);
|
|
8700
8878
|
const vendored = planVendoredBinary(targetRepo);
|
|
@@ -8716,12 +8894,12 @@ function planInstall(options) {
|
|
|
8716
8894
|
const gitattributes = planGitattributes(targetRepo);
|
|
8717
8895
|
const identity = planProjectIdentity(targetRepo);
|
|
8718
8896
|
if (identity.after === null)
|
|
8719
|
-
|
|
8897
|
+
readFileSync6(join7(targetRepo, PROJECT_IDENTITY_REL));
|
|
8720
8898
|
const credentialPlan = !preview && credential !== void 0 ? planEngagementCredential(credential, engagementId, home) : null;
|
|
8721
8899
|
const credentialWrite = credentialPlan === null ? null : { outcome: credentialPlan.outcome, existed: credentialPlan.existed, path: engagementEnvPath(engagementId, home) };
|
|
8722
8900
|
const storedAfter = credentialPlan?.storedAfter ?? (preview ? Object.fromEntries(ENGAGEMENT_ENV_KEYS.map((key) => [key, "supplied at sign-in"])) : readEngagementEnv(engagementId, home) ?? {});
|
|
8723
8901
|
const legacy = migrateLegacyEnvLocal(targetRepo, storedAfter);
|
|
8724
|
-
const pin = preview ? planBundlePinPreview(targetRepo, version, home) : planBundlePin(targetRepo, version, engagementId, engagementType, createdByAccountId, credential?.methodVersion);
|
|
8902
|
+
const pin = preview ? planBundlePinPreview(targetRepo, version, home) : planBundlePin(targetRepo, version, engagementId, engagementType, createdByAccountId, credential?.methodVersion, credential?.serviceUrl ?? serviceUrl);
|
|
8725
8903
|
const crew = planCrewRoster(targetRepo);
|
|
8726
8904
|
const crewProfiles = planCrewProfiles(targetRepo, previous, writeSet);
|
|
8727
8905
|
const scan = { text: planBootstrapScan(targetRepo).text };
|
|
@@ -8849,7 +9027,7 @@ function entriesOf(steps, record2) {
|
|
|
8849
9027
|
}
|
|
8850
9028
|
};
|
|
8851
9029
|
const pending = (rel) => {
|
|
8852
|
-
const there =
|
|
9030
|
+
const there = existsSync5(join7(targetRepo, rel));
|
|
8853
9031
|
entries.push({ path: rel, action: there ? "change" : "add", kind: kindOf(rel), bytes: PENDING });
|
|
8854
9032
|
};
|
|
8855
9033
|
steps.commands.forEach(fromFile);
|
|
@@ -8897,7 +9075,7 @@ function entriesOf(steps, record2) {
|
|
|
8897
9075
|
action: "leave",
|
|
8898
9076
|
kind: "codex-server",
|
|
8899
9077
|
bytes: null,
|
|
8900
|
-
...
|
|
9078
|
+
...existsSync5(join7(targetRepo, CODEX_CONFIG_REL2)) ? { yours: true } : {}
|
|
8901
9079
|
});
|
|
8902
9080
|
break;
|
|
8903
9081
|
}
|
|
@@ -8976,10 +9154,10 @@ function applyInstallPlan(plan) {
|
|
|
8976
9154
|
putPlannedCodexHooks(targetRepo, steps.codexHooks);
|
|
8977
9155
|
putPlannedCodexConfig(targetRepo, steps.codexConfig);
|
|
8978
9156
|
const gitignore = putPlannedGitignore(targetRepo, steps.gitignore);
|
|
8979
|
-
const gitignorePath =
|
|
8980
|
-
const gitignoreAfter =
|
|
9157
|
+
const gitignorePath = join7(targetRepo, GITIGNORE_REL);
|
|
9158
|
+
const gitignoreAfter = existsSync5(gitignorePath) ? readFileSync6(gitignorePath, "utf-8") : null;
|
|
8981
9159
|
const gitattributes = putPlannedGitattributes(targetRepo, steps.gitattributes);
|
|
8982
|
-
const gitattributesAfter = readTextOrNull(
|
|
9160
|
+
const gitattributesAfter = readTextOrNull(join7(targetRepo, GITATTRIBUTES_REL));
|
|
8983
9161
|
writeProjectIdentity(targetRepo, steps.identity);
|
|
8984
9162
|
const credential = steps.credential;
|
|
8985
9163
|
const credentialOutcome = steps.credentialWrite !== null && credential !== void 0 ? writeEngagementCredential(credential, engagementId, steps.home) : null;
|
|
@@ -9000,9 +9178,9 @@ function applyInstallPlan(plan) {
|
|
|
9000
9178
|
gitattributesAfter,
|
|
9001
9179
|
credential: credentialOutcome,
|
|
9002
9180
|
legacy,
|
|
9003
|
-
bytesAfter: (rel) =>
|
|
9004
|
-
dirExistsAfter: (dir) =>
|
|
9005
|
-
stateExistsAfter:
|
|
9181
|
+
bytesAfter: (rel) => readFileSync6(join7(targetRepo, rel)),
|
|
9182
|
+
dirExistsAfter: (dir) => existsSync5(join7(targetRepo, dir)),
|
|
9183
|
+
stateExistsAfter: existsSync5(join7(targetRepo, BOOTSTRAP_STATE_REL))
|
|
9006
9184
|
});
|
|
9007
9185
|
const record2 = planInstallRecord(targetRepo, manifestText);
|
|
9008
9186
|
putPlannedFile(targetRepo, record2);
|
|
@@ -9057,7 +9235,7 @@ function withoutHalfcycleCommands(entry, halfcycleCommands) {
|
|
|
9057
9235
|
const remaining = entry.hooks.filter((h) => !isHalfcycleCommand(h, halfcycleCommands));
|
|
9058
9236
|
return remaining.length > 0 ? { ...entry, hooks: remaining } : null;
|
|
9059
9237
|
}
|
|
9060
|
-
function mergeSettings(existing, generated, retiredOurs, statusLine) {
|
|
9238
|
+
function mergeSettings(existing, generated, retiredOurs, statusLine, attribution) {
|
|
9061
9239
|
const merged = { ...existing };
|
|
9062
9240
|
if (generated.$schema !== void 0) {
|
|
9063
9241
|
merged.$schema = generated.$schema;
|
|
@@ -9083,6 +9261,9 @@ function mergeSettings(existing, generated, retiredOurs, statusLine) {
|
|
|
9083
9261
|
if (statusLine !== void 0 && !("statusLine" in existing)) {
|
|
9084
9262
|
merged.statusLine = { ...statusLine };
|
|
9085
9263
|
}
|
|
9264
|
+
if (attribution !== void 0 && !("attribution" in existing) && !("includeCoAuthoredBy" in existing)) {
|
|
9265
|
+
merged.attribution = { ...attribution };
|
|
9266
|
+
}
|
|
9086
9267
|
return merged;
|
|
9087
9268
|
}
|
|
9088
9269
|
function isPlainRecord(value) {
|
|
@@ -9360,8 +9541,8 @@ function crewProfileRel(agent, callsign) {
|
|
|
9360
9541
|
}
|
|
9361
9542
|
function crewProfileTarget(targetRepo, rel) {
|
|
9362
9543
|
const folder2 = rel.startsWith(`${CLAUDE_AGENTS_DIR}/`) ? CLAUDE_AGENTS_DIR : CODEX_AGENTS_DIR;
|
|
9363
|
-
const target = resolve(
|
|
9364
|
-
if (
|
|
9544
|
+
const target = resolve(join8(targetRepo, rel));
|
|
9545
|
+
if (dirname4(target) !== resolve(targetRepo, folder2)) {
|
|
9365
9546
|
throw new Error(`[halfcycle] crew profile "${rel}" does not sit directly in ${folder2}.`);
|
|
9366
9547
|
}
|
|
9367
9548
|
return target;
|
|
@@ -9444,41 +9625,25 @@ function renderCrewProfiles(roster = CREW_ROSTER) {
|
|
|
9444
9625
|
])
|
|
9445
9626
|
];
|
|
9446
9627
|
}
|
|
9447
|
-
var __filename = fileURLToPath(import.meta.url);
|
|
9448
|
-
var __dirname = dirname3(__filename);
|
|
9449
|
-
function findBundleRoot(startDir) {
|
|
9450
|
-
let candidate = startDir;
|
|
9451
|
-
for (let i = 0; i < 4; i++) {
|
|
9452
|
-
const manifest = join7(candidate, ".claude-plugin", "plugin.json");
|
|
9453
|
-
try {
|
|
9454
|
-
if (existsSync5(manifest))
|
|
9455
|
-
return candidate;
|
|
9456
|
-
} catch {
|
|
9457
|
-
}
|
|
9458
|
-
candidate = dirname3(candidate);
|
|
9459
|
-
}
|
|
9460
|
-
return join7(startDir, "..");
|
|
9461
|
-
}
|
|
9462
|
-
var BUNDLE_ROOT = findBundleRoot(__dirname);
|
|
9463
9628
|
function resolveVendoredBinary() {
|
|
9464
9629
|
const candidates = [
|
|
9465
|
-
|
|
9466
|
-
|
|
9630
|
+
join8(BUNDLE_ROOT, "bin", "bin.bundle.mjs"),
|
|
9631
|
+
join8(BUNDLE_ROOT, "..", "runner", "dist", "bin.bundle.mjs")
|
|
9467
9632
|
];
|
|
9468
9633
|
for (const candidate of candidates) {
|
|
9469
|
-
if (
|
|
9634
|
+
if (existsSync6(candidate))
|
|
9470
9635
|
return candidate;
|
|
9471
9636
|
}
|
|
9472
9637
|
throw new Error(`[halfcycle] Cannot find the self-contained guard binary bin.bundle.mjs. This copy of the halfcycle package is incomplete \u2014 reinstall it, or rebuild it if you are working on it. Looked in: ${candidates.join(", ")}.`);
|
|
9473
9638
|
}
|
|
9474
9639
|
function resolvePreFilterShell() {
|
|
9475
9640
|
const candidates = [
|
|
9476
|
-
|
|
9477
|
-
|
|
9641
|
+
join8(BUNDLE_ROOT, "bin", PRE_FILTER_SHELL_FILENAME),
|
|
9642
|
+
join8(BUNDLE_ROOT, "..", "runner", "dist", PRE_FILTER_SHELL_FILENAME)
|
|
9478
9643
|
];
|
|
9479
9644
|
for (const candidate of candidates) {
|
|
9480
|
-
if (
|
|
9481
|
-
return
|
|
9645
|
+
if (existsSync6(candidate))
|
|
9646
|
+
return readFileSync7(candidate, "utf-8");
|
|
9482
9647
|
}
|
|
9483
9648
|
throw new Error(`[halfcycle] Cannot find ${PRE_FILTER_SHELL_FILENAME}. This copy of the halfcycle package is incomplete \u2014 reinstall it, or rebuild it if you are working on it. Looked in: ${candidates.join(", ")}.`);
|
|
9484
9649
|
}
|
|
@@ -9517,7 +9682,7 @@ function isAllowlisted(targetRelPath) {
|
|
|
9517
9682
|
var CREW_ROSTER_REL = ".halfcycle/crew.json";
|
|
9518
9683
|
var CAPTURED_INDEX_REL = "test/fixtures/captured/manifest.json";
|
|
9519
9684
|
function capturedIndexScaffold() {
|
|
9520
|
-
return
|
|
9685
|
+
return join8(BUNDLE_ROOT, "scaffolding", "test", "fixtures", "captured", "manifest.json");
|
|
9521
9686
|
}
|
|
9522
9687
|
var GITIGNORE_REL = ".gitignore";
|
|
9523
9688
|
var GITATTRIBUTES_REL = ".gitattributes";
|
|
@@ -9586,20 +9751,22 @@ function closedWriteSet() {
|
|
|
9586
9751
|
...lfRules([...Object.keys(OWNED_GENERATED_HEADERS), ...RETIRED_WRITE_SET_FILES])
|
|
9587
9752
|
]),
|
|
9588
9753
|
generatedSha256: sha256OfWhatThisVersionWrites(commands),
|
|
9589
|
-
statusLineSha256: /* @__PURE__ */ new Set([canonicalSha256(HALFCYCLE_STATUS_LINE)])
|
|
9754
|
+
statusLineSha256: /* @__PURE__ */ new Set([canonicalSha256(HALFCYCLE_STATUS_LINE)]),
|
|
9755
|
+
attributionSha256: /* @__PURE__ */ new Set([canonicalSha256(HALFCYCLE_ATTRIBUTION)])
|
|
9590
9756
|
};
|
|
9591
9757
|
}
|
|
9592
9758
|
function sha256OfWhatThisVersionWrites(commands) {
|
|
9593
9759
|
const out = /* @__PURE__ */ new Map();
|
|
9594
9760
|
const fromFile = (rel, src) => {
|
|
9595
|
-
if (
|
|
9596
|
-
out.set(rel, fileSha256(
|
|
9761
|
+
if (existsSync6(src))
|
|
9762
|
+
out.set(rel, fileSha256(readFileSync7(src)));
|
|
9597
9763
|
};
|
|
9598
9764
|
for (const cmd of commands) {
|
|
9599
|
-
const srcFile =
|
|
9600
|
-
|
|
9601
|
-
|
|
9602
|
-
out.set(
|
|
9765
|
+
const srcFile = join8(BUNDLE_ROOT, ".claude-plugin", cmd.path);
|
|
9766
|
+
if (existsSync6(srcFile)) {
|
|
9767
|
+
const text = commandFileText(readFileSync7(srcFile, "utf-8"), ownVersion());
|
|
9768
|
+
out.set(commandStubRel(cmd.name), fileSha256(Buffer.from(text, "utf-8")));
|
|
9769
|
+
out.set(skillRel(cmd.name), fileSha256(Buffer.from(codexSkillContent(cmd.name, text), "utf-8")));
|
|
9603
9770
|
}
|
|
9604
9771
|
}
|
|
9605
9772
|
for (const [rel, content] of hookScriptsThisVersionWrites())
|
|
@@ -9616,7 +9783,7 @@ function bytesAreOurs(path, bytes, recorded, writeSet) {
|
|
|
9616
9783
|
}
|
|
9617
9784
|
function readPreviousManifest(targetRepo) {
|
|
9618
9785
|
try {
|
|
9619
|
-
return parseManifest(
|
|
9786
|
+
return parseManifest(readFileSync7(join8(targetRepo, INSTALL_MANIFEST_REL), "utf-8"));
|
|
9620
9787
|
} catch {
|
|
9621
9788
|
return null;
|
|
9622
9789
|
}
|
|
@@ -9646,7 +9813,8 @@ function recordSettingsMerge(draft, existing, generated, preexisted, merged) {
|
|
|
9646
9813
|
hooksCreated: existing.hooks === void 0,
|
|
9647
9814
|
permissionsCreated: existing.permissions === void 0,
|
|
9648
9815
|
denyCreated: existing.permissions?.deny === void 0,
|
|
9649
|
-
...ourStatusLineSha256(merged) !== void 0 ? { statusLineSha256: ourStatusLineSha256(merged) } : {}
|
|
9816
|
+
...ourStatusLineSha256(merged) !== void 0 ? { statusLineSha256: ourStatusLineSha256(merged) } : {},
|
|
9817
|
+
...attributionWrittenThisRun(existing, merged) ? { attributionSha256: canonicalSha256(HALFCYCLE_ATTRIBUTION) } : {}
|
|
9650
9818
|
});
|
|
9651
9819
|
}
|
|
9652
9820
|
function ourStatusLineSha256(settings) {
|
|
@@ -9655,6 +9823,11 @@ function ourStatusLineSha256(settings) {
|
|
|
9655
9823
|
const hash = canonicalSha256(settings["statusLine"]);
|
|
9656
9824
|
return hash === canonicalSha256(HALFCYCLE_STATUS_LINE) ? hash : void 0;
|
|
9657
9825
|
}
|
|
9826
|
+
function attributionWrittenThisRun(existing, merged) {
|
|
9827
|
+
if ("attribution" in existing || "includeCoAuthoredBy" in existing)
|
|
9828
|
+
return false;
|
|
9829
|
+
return "attribution" in merged && canonicalSha256(merged["attribution"]) === canonicalSha256(HALFCYCLE_ATTRIBUTION);
|
|
9830
|
+
}
|
|
9658
9831
|
function recordMcpMerge(draft, existingText, writtenText) {
|
|
9659
9832
|
const before = existingText === null ? void 0 : JSON.parse(existingText);
|
|
9660
9833
|
const servers = isPlainObject3(before) ? before["mcpServers"] : void 0;
|
|
@@ -9720,8 +9893,8 @@ function findNotShared(targetRepo, manifest, collidedPaths) {
|
|
|
9720
9893
|
for (let dir = posix2.dirname(rel); dir !== "."; dir = posix2.dirname(dir)) {
|
|
9721
9894
|
let plain = dirVerdicts.get(dir);
|
|
9722
9895
|
if (plain === void 0) {
|
|
9723
|
-
const abs =
|
|
9724
|
-
plain = !lstatOrNull(abs)?.isSymbolicLink() && lstatOrNull(
|
|
9896
|
+
const abs = join8(targetRepo, dir);
|
|
9897
|
+
plain = !lstatOrNull(abs)?.isSymbolicLink() && lstatOrNull(join8(abs, ".git")) === null;
|
|
9725
9898
|
dirVerdicts.set(dir, plain);
|
|
9726
9899
|
}
|
|
9727
9900
|
if (!plain)
|
|
@@ -9735,7 +9908,7 @@ function findNotShared(targetRepo, manifest, collidedPaths) {
|
|
|
9735
9908
|
...merged,
|
|
9736
9909
|
INSTALL_MANIFEST_REL
|
|
9737
9910
|
])
|
|
9738
|
-
].filter((rel) => !collided.has(rel)).filter((rel) => lstatOrNull(
|
|
9911
|
+
].filter((rel) => !collided.has(rel)).filter((rel) => lstatOrNull(join8(targetRepo, rel)) !== null).filter(travelsThroughPlainDirs).sort();
|
|
9739
9912
|
const ignored = readIgnoredPaths(targetRepo, paths);
|
|
9740
9913
|
if (ignored === null)
|
|
9741
9914
|
return null;
|
|
@@ -9758,7 +9931,7 @@ function lstatOrNull(path) {
|
|
|
9758
9931
|
}
|
|
9759
9932
|
function readFileOrNull(path) {
|
|
9760
9933
|
try {
|
|
9761
|
-
return
|
|
9934
|
+
return readFileSync7(path, "utf-8");
|
|
9762
9935
|
} catch {
|
|
9763
9936
|
return null;
|
|
9764
9937
|
}
|
|
@@ -9767,8 +9940,8 @@ function isPlainObject3(value) {
|
|
|
9767
9940
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
9768
9941
|
}
|
|
9769
9942
|
function readPluginManifest() {
|
|
9770
|
-
const manifestPath =
|
|
9771
|
-
const raw =
|
|
9943
|
+
const manifestPath = join8(BUNDLE_ROOT, ".claude-plugin", "plugin.json");
|
|
9944
|
+
const raw = readFileSync7(manifestPath, "utf-8");
|
|
9772
9945
|
return JSON.parse(raw);
|
|
9773
9946
|
}
|
|
9774
9947
|
function parseCommandStub(raw, name) {
|
|
@@ -9811,6 +9984,9 @@ function codexSkillBody(name, sourceBody) {
|
|
|
9811
9984
|
}
|
|
9812
9985
|
return transformed;
|
|
9813
9986
|
}
|
|
9987
|
+
function commandFileText(source, version) {
|
|
9988
|
+
return rewriteHalfcycleCommands(source, releaseTagOfVersion(version));
|
|
9989
|
+
}
|
|
9814
9990
|
function codexSkillContent(name, sourceRaw) {
|
|
9815
9991
|
const { description, body } = parseCommandStub(sourceRaw, name);
|
|
9816
9992
|
const transformed = codexSkillBody(name, body);
|
|
@@ -9823,14 +9999,14 @@ ${transformed}`;
|
|
|
9823
9999
|
function planManifestCommands(manifest, targetRepo, previous, writeSet) {
|
|
9824
10000
|
const recordedFiles = new Map((previous?.files ?? []).filter((file) => writeSet.files.has(file.path)).map((file) => [file.path, file]));
|
|
9825
10001
|
return (manifest.commands ?? []).map((cmd) => {
|
|
9826
|
-
const srcFile =
|
|
9827
|
-
if (!
|
|
10002
|
+
const srcFile = join8(BUNDLE_ROOT, ".claude-plugin", cmd.path);
|
|
10003
|
+
if (!existsSync6(srcFile)) {
|
|
9828
10004
|
throw new Error(`[halfcycle] plugin.json declares command "${cmd.name}" at "${cmd.path}", but no file exists there (${srcFile}). The manifest and commands/ must agree.`);
|
|
9829
10005
|
}
|
|
9830
10006
|
const rel = commandStubRel(cmd.name);
|
|
9831
|
-
const content =
|
|
10007
|
+
const content = commandFileText(readFileSync7(srcFile, "utf-8"), ownVersion());
|
|
9832
10008
|
const recorded = recordedFiles.get(rel);
|
|
9833
|
-
const planned = decideCollisionSafe(
|
|
10009
|
+
const planned = decideCollisionSafe(join8(targetRepo, rel), targetRepo, content, (current) => bytesAreOurs(rel, current, recorded, writeSet));
|
|
9834
10010
|
return { ...planned, editedCollided: planned.outcome === "collided" && recorded !== void 0 };
|
|
9835
10011
|
});
|
|
9836
10012
|
}
|
|
@@ -9840,8 +10016,8 @@ function putPlannedFile(targetRepoRoot, planned) {
|
|
|
9840
10016
|
if (!isAllowlisted(planned.rel)) {
|
|
9841
10017
|
throw new Error(`[halfcycle] Write-allowlist violation: attempted to write "${planned.rel}". Only the method surface and scaffolding paths are writable.`);
|
|
9842
10018
|
}
|
|
9843
|
-
const target =
|
|
9844
|
-
mkdirSync5(
|
|
10019
|
+
const target = join8(targetRepoRoot, planned.rel);
|
|
10020
|
+
mkdirSync5(dirname4(target), { recursive: true });
|
|
9845
10021
|
writeFileSync5(target, planned.text, "utf-8");
|
|
9846
10022
|
}
|
|
9847
10023
|
function writeAllowlisted(targetAbsPath, targetRepoRoot, content, writtenPaths) {
|
|
@@ -9851,7 +10027,7 @@ function writeAllowlisted(targetAbsPath, targetRepoRoot, content, writtenPaths)
|
|
|
9851
10027
|
}
|
|
9852
10028
|
if (readFileOrNull(targetAbsPath) === content)
|
|
9853
10029
|
return;
|
|
9854
|
-
mkdirSync5(
|
|
10030
|
+
mkdirSync5(dirname4(targetAbsPath), { recursive: true });
|
|
9855
10031
|
writeFileSync5(targetAbsPath, content, "utf-8");
|
|
9856
10032
|
writtenPaths.push(rel);
|
|
9857
10033
|
}
|
|
@@ -9860,7 +10036,7 @@ function decideCollisionSafe(targetAbsPath, targetRepoRoot, content, isOurs) {
|
|
|
9860
10036
|
if (!isAllowlisted(rel)) {
|
|
9861
10037
|
throw new Error(`[halfcycle] Write-allowlist violation: attempted to write "${rel}". Only the method surface and scaffolding paths are writable.`);
|
|
9862
10038
|
}
|
|
9863
|
-
const current =
|
|
10039
|
+
const current = existsSync6(targetAbsPath) ? readFileSync7(targetAbsPath) : null;
|
|
9864
10040
|
const text = inLineEndingOf(current?.toString("utf-8") ?? null, content);
|
|
9865
10041
|
if (current === null)
|
|
9866
10042
|
return { rel, text, outcome: "written", replacedExisting: false };
|
|
@@ -9888,7 +10064,7 @@ function decideOwned(targetAbsPath, targetRepoRoot, content) {
|
|
|
9888
10064
|
const text = inLineEndingOf(current, content);
|
|
9889
10065
|
if (current === text)
|
|
9890
10066
|
return { rel, text, outcome: "skipped", replacedExisting: false };
|
|
9891
|
-
return { rel, text, outcome: "written", replacedExisting:
|
|
10067
|
+
return { rel, text, outcome: "written", replacedExisting: existsSync6(targetAbsPath) };
|
|
9892
10068
|
}
|
|
9893
10069
|
var GENERATED_HEADER_LINE_INDEX = 1;
|
|
9894
10070
|
function decideOwnedGenerated(targetAbsPath, targetRepoRoot, content, generatedMarker) {
|
|
@@ -9896,9 +10072,9 @@ function decideOwnedGenerated(targetAbsPath, targetRepoRoot, content, generatedM
|
|
|
9896
10072
|
if (!isAllowlisted(rel)) {
|
|
9897
10073
|
throw new Error(`[halfcycle] Write-allowlist violation: attempted to write "${rel}". Only the method surface and scaffolding paths are writable.`);
|
|
9898
10074
|
}
|
|
9899
|
-
if (!
|
|
10075
|
+
if (!existsSync6(targetAbsPath))
|
|
9900
10076
|
return { rel, text: content, outcome: "written", replacedExisting: false };
|
|
9901
|
-
const current =
|
|
10077
|
+
const current = readFileSync7(targetAbsPath, "utf-8");
|
|
9902
10078
|
if (current === content)
|
|
9903
10079
|
return { rel, text: content, outcome: "skipped", replacedExisting: false };
|
|
9904
10080
|
const headerLine = current.split("\n")[GENERATED_HEADER_LINE_INDEX] ?? "";
|
|
@@ -9914,7 +10090,7 @@ function recordOwnedGenerated(report, planned) {
|
|
|
9914
10090
|
return planned.outcome;
|
|
9915
10091
|
}
|
|
9916
10092
|
function recordOwned(report, targetAbsPath, targetRepoRoot, content, rel) {
|
|
9917
|
-
const existedBefore =
|
|
10093
|
+
const existedBefore = existsSync6(targetAbsPath);
|
|
9918
10094
|
const outcome = writeOwned(targetAbsPath, targetRepoRoot, content);
|
|
9919
10095
|
record(report, outcome, rel);
|
|
9920
10096
|
if (outcome === "written" && existedBefore)
|
|
@@ -9936,8 +10112,8 @@ var DENY_PATTERNS = [
|
|
|
9936
10112
|
];
|
|
9937
10113
|
var USER_PROMPT_REMINDER_SENTENCE = "Where this project stands, and what order to work in, is recorded in the Halfcycle service and nowhere in this repository \u2014 call halfcycle_resolve before you answer or plan, because the files here will look complete and current and still be wrong about position.";
|
|
9938
10114
|
function readExistingSettings(targetRepo) {
|
|
9939
|
-
const settingsPath =
|
|
9940
|
-
return
|
|
10115
|
+
const settingsPath = join8(targetRepo, SETTINGS_REL);
|
|
10116
|
+
return existsSync6(settingsPath) ? JSON.parse(readFileSync7(settingsPath, "utf-8")) : void 0;
|
|
9941
10117
|
}
|
|
9942
10118
|
var SettingsParseRefused = class extends Error {
|
|
9943
10119
|
constructor(message) {
|
|
@@ -9946,33 +10122,37 @@ var SettingsParseRefused = class extends Error {
|
|
|
9946
10122
|
}
|
|
9947
10123
|
};
|
|
9948
10124
|
function settingsParseRefusal(targetRepo, err) {
|
|
9949
|
-
const file =
|
|
10125
|
+
const file = join8(targetRepo, SETTINGS_REL);
|
|
9950
10126
|
const offset = /position (\d+)/.exec(err instanceof Error ? err.message : "")?.[1];
|
|
9951
10127
|
let where = "";
|
|
9952
10128
|
if (offset !== void 0) {
|
|
9953
|
-
const before =
|
|
10129
|
+
const before = readFileSync7(file, "utf-8").slice(0, Number(offset)).split("\n");
|
|
9954
10130
|
where = ` (line ${before.length}, column ${before[before.length - 1].length + 1})`;
|
|
9955
10131
|
}
|
|
9956
|
-
return `${file} could not be read as JSON${where} \u2014 fix it and run "
|
|
10132
|
+
return `${file} could not be read as JSON${where} \u2014 fix it and run "${ownBareCommand()}" again.`;
|
|
9957
10133
|
}
|
|
9958
10134
|
var SETTINGS_SCHEMA = "https://json.schemastore.org/claude-code-settings.json";
|
|
9959
10135
|
var HALFCYCLE_STATUS_LINE = Object.freeze({
|
|
9960
10136
|
type: "command",
|
|
9961
10137
|
command: 'bash "$CLAUDE_PROJECT_DIR"/.claude/hooks/guard-runner.sh statusline'
|
|
9962
10138
|
});
|
|
10139
|
+
var HALFCYCLE_ATTRIBUTION = Object.freeze({
|
|
10140
|
+
commit: "",
|
|
10141
|
+
pr: ""
|
|
10142
|
+
});
|
|
9963
10143
|
function statusLineSetElsewhere(targetRepo, home = homedir3(), env = process.env) {
|
|
9964
|
-
const places = [
|
|
10144
|
+
const places = [join8(targetRepo, ".claude", "settings.local.json"), join8(home, ".claude", "settings.json")];
|
|
9965
10145
|
const configDir = env["CLAUDE_CONFIG_DIR"];
|
|
9966
10146
|
if (configDir !== void 0 && configDir !== "") {
|
|
9967
|
-
const expanded = configDir === "~" ? home : configDir.startsWith("~/") ?
|
|
9968
|
-
places.push(
|
|
10147
|
+
const expanded = configDir === "~" ? home : configDir.startsWith("~/") ? join8(home, configDir.slice(2)) : configDir;
|
|
10148
|
+
places.push(join8(isAbsolute2(expanded) ? expanded : resolve(expanded), "settings.json"));
|
|
9969
10149
|
}
|
|
9970
10150
|
return places.some((path) => {
|
|
9971
|
-
if (!
|
|
10151
|
+
if (!existsSync6(path))
|
|
9972
10152
|
return false;
|
|
9973
10153
|
let value;
|
|
9974
10154
|
try {
|
|
9975
|
-
value = JSON.parse(
|
|
10155
|
+
value = JSON.parse(readFileSync7(path, "utf-8"));
|
|
9976
10156
|
} catch {
|
|
9977
10157
|
return true;
|
|
9978
10158
|
}
|
|
@@ -10315,8 +10495,10 @@ ${GENERATED_USER_PROMPT_SCOPE_HEADER} the Halfcycle installer, now also for the
|
|
|
10315
10495
|
# "autonomy on" or "autonomy off". Also lets the person who is typing let one
|
|
10316
10496
|
# step Halfcycle paused run once, by typing "allow", or "allow" and the step's
|
|
10317
10497
|
# number ("allow 2"), as the whole prompt, and change
|
|
10318
|
-
# how a build
|
|
10319
|
-
#
|
|
10498
|
+
# how many agents a build runs, by typing "faster", "run 5 agents", "run 20
|
|
10499
|
+
# agents at once", "max pace" or "as many agents as possible", or how much is
|
|
10500
|
+
# kept free, by typing "keep 30% free" or "use up to 70%", as the whole prompt,
|
|
10501
|
+
# with or without a build running, and answer a phase's usage limit by
|
|
10320
10502
|
# typing "keep going", "stop", "limit it to 1.5 times" or "limit it to 10% of
|
|
10321
10503
|
# the week" as the whole prompt.
|
|
10322
10504
|
#
|
|
@@ -10325,7 +10507,7 @@ ${GENERATED_USER_PROMPT_SCOPE_HEADER} the Halfcycle installer, now also for the
|
|
|
10325
10507
|
# any whitespace (typed, or escaped in the input), with "approve ", "send back ",
|
|
10326
10508
|
# "accept ", "question ", "carry ", "drop ", "reject " or "autonomy ", in any case,
|
|
10327
10509
|
# or is "allow" (alone, or with spaces and a number of up to four digits), one
|
|
10328
|
-
# of the
|
|
10510
|
+
# of the pacing phrases or one of the usage-limit phrases
|
|
10329
10511
|
# and nothing else but whitespace,
|
|
10330
10512
|
# it exits 0 with no output, and Node never starts. A quote inside any other field arrives
|
|
10331
10513
|
# escaped, so no other field (the folder the session is in, for one) can read as
|
|
@@ -10353,7 +10535,11 @@ IFS= read -r -d '' HC_INPUT || true
|
|
|
10353
10535
|
# \\u escape, or as the UTF-8 bytes of a Unicode space. Compared byte for byte.
|
|
10354
10536
|
HC_LEAD='[[:space:]]|\\\\[nrtf]|\\\\u(000[9a-d]|0020|00a0|1680|200[0-9a]|202[89f]|205f|3000|feff)'
|
|
10355
10537
|
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'
|
|
10356
|
-
|
|
10538
|
+
# The pace phrases, matched whole after an optional "please" (the verb's own
|
|
10539
|
+
# reading is the same set or narrower): a number of agents, the most, faster, a
|
|
10540
|
+
# vague amount it answers with an ask for a number, or a free share.
|
|
10541
|
+
HC_PACE='((run|use|start|go)( +with| +at)? +)?(up +to +|the +)?(faster|more +agents|(-?[0-9]+(\\.[0-9]+)?|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen|twenty) +agents?|(max|maximum|full|top) +(pace|speed)|(max|maximum)( +possible)?( +number +of)? +agents|as +many +agents +as +(possible|you +can)|(lots +of|a +lot +of|loads +of|plenty +of|many|some|several|a +few|a +bunch +of) +agents)( +(at +once|at +a +time|in +parallel|concurrently|simultaneously|together))?|keep +[0-9]{1,3}% +free|use +up +to +[0-9]{1,3}%'
|
|
10542
|
+
if ! printf '%s' "$HC_INPUT" | LC_ALL=C grep -Eiq "\\"prompt\\"[[:space:]]*:[[:space:]]*\\"($HC_LEAD)*((approve|send back|accept|question|carry|drop|reject|autonomy|decline) |allow( +[0-9]{1,4})?($HC_LEAD)*\\"|(keep going|stop|stop here|limit it to [0-9]{1,9}(\\.[0-9]{1,2})? (times|million|billion|million tokens|billion tokens)|limit it to [0-9]{1,3}% of the week)($HC_LEAD)*\\"|(please,? +)?($HC_PACE)(,? +please)?[.!]*($HC_LEAD)*\\")"; then
|
|
10357
10543
|
exit 0
|
|
10358
10544
|
fi
|
|
10359
10545
|
|
|
@@ -10446,12 +10632,12 @@ function removeRetiredGeneratedFile(targetRepo, rel, headerLineIndex, knownHeade
|
|
|
10446
10632
|
return decided === "removed" ? removePlannedRetiredFile(targetRepo, rel) : decided;
|
|
10447
10633
|
}
|
|
10448
10634
|
function decideRetiredRemoval(targetRepo, rel, headerLineIndex, knownHeaderHashes) {
|
|
10449
|
-
const path =
|
|
10450
|
-
if (!
|
|
10635
|
+
const path = join8(targetRepo, rel);
|
|
10636
|
+
if (!existsSync6(path))
|
|
10451
10637
|
return "absent";
|
|
10452
10638
|
let raw;
|
|
10453
10639
|
try {
|
|
10454
|
-
raw =
|
|
10640
|
+
raw = readFileSync7(path, "utf-8");
|
|
10455
10641
|
} catch {
|
|
10456
10642
|
return "failed";
|
|
10457
10643
|
}
|
|
@@ -10465,7 +10651,7 @@ function decideRetiredRemoval(targetRepo, rel, headerLineIndex, knownHeaderHashe
|
|
|
10465
10651
|
}
|
|
10466
10652
|
function removePlannedRetiredFile(targetRepo, rel) {
|
|
10467
10653
|
try {
|
|
10468
|
-
rmSync2(
|
|
10654
|
+
rmSync2(join8(targetRepo, rel));
|
|
10469
10655
|
} catch {
|
|
10470
10656
|
return "failed";
|
|
10471
10657
|
}
|
|
@@ -10486,7 +10672,7 @@ var MCP_HEADERS_HELPER_COMMAND = `node -e "${nodeFloorProgram([
|
|
|
10486
10672
|
`const u=p.dirname(d);`,
|
|
10487
10673
|
`if(u===d)break;`,
|
|
10488
10674
|
`d=u}`,
|
|
10489
|
-
`console.error('halfcycle: no ${VENDORED_BIN_REL} at or above '+process.cwd()+'; run
|
|
10675
|
+
`console.error('halfcycle: no ${VENDORED_BIN_REL} at or above '+process.cwd()+'; run ${ownBareCommand()} in the project folder');`,
|
|
10490
10676
|
`process.exit(1)`
|
|
10491
10677
|
].join(""))}"`;
|
|
10492
10678
|
var CODEX_ADDRESS_PATTERN = /^https?:\/\/[A-Za-z0-9.-]+(:[0-9]{1,5})?(\/[A-Za-z0-9._-]+)*\/mcp$/;
|
|
@@ -10510,7 +10696,7 @@ function codexMcpHeadersHelperCommand(address) {
|
|
|
10510
10696
|
`if(u===d)break;`,
|
|
10511
10697
|
`d=u}`,
|
|
10512
10698
|
`if(b===''){`,
|
|
10513
|
-
`console.error('halfcycle: no ${VENDORED_BIN_REL} at or above '+process.cwd()+'; run
|
|
10699
|
+
`console.error('halfcycle: no ${VENDORED_BIN_REL} at or above '+process.cwd()+'; run ${ownBareCommand()} in the project folder');`,
|
|
10514
10700
|
`n()}else{`,
|
|
10515
10701
|
`const r=c.spawnSync(process.execPath,[b,'mcp-headers','--url','${address}'],{stdio:['ignore','pipe','inherit']});`,
|
|
10516
10702
|
`if(r.status===0&&r.stdout&&r.stdout.length){o=1;process.stdout.write(r.stdout)}else{n()}}`,
|
|
@@ -10848,9 +11034,9 @@ function gitignoreCovers(content) {
|
|
|
10848
11034
|
return REQUIRED_GITIGNORE_ENTRIES.every((entry) => lines.has(entry));
|
|
10849
11035
|
}
|
|
10850
11036
|
function planGitignore(targetRepo) {
|
|
10851
|
-
const gitignorePath =
|
|
11037
|
+
const gitignorePath = join8(targetRepo, GITIGNORE_REL);
|
|
10852
11038
|
const header = GITIGNORE_HEADER;
|
|
10853
|
-
const before =
|
|
11039
|
+
const before = existsSync6(gitignorePath) ? readFileSync7(gitignorePath, "utf-8") : null;
|
|
10854
11040
|
if (before === null) {
|
|
10855
11041
|
return { before, after: [header, ...REQUIRED_GITIGNORE_ENTRIES].join("\n") + "\n", outcome: "written" };
|
|
10856
11042
|
}
|
|
@@ -10866,7 +11052,7 @@ function putPlannedGitignore(targetRepo, planned) {
|
|
|
10866
11052
|
if (planned.outcome !== "written" || planned.after === null)
|
|
10867
11053
|
return planned.outcome;
|
|
10868
11054
|
try {
|
|
10869
|
-
writeFileSync5(
|
|
11055
|
+
writeFileSync5(join8(targetRepo, GITIGNORE_REL), planned.after, "utf-8");
|
|
10870
11056
|
return "written";
|
|
10871
11057
|
} catch {
|
|
10872
11058
|
return "failed";
|
|
@@ -10875,7 +11061,7 @@ function putPlannedGitignore(targetRepo, planned) {
|
|
|
10875
11061
|
var GITATTRIBUTES_HEADER = "# Halfcycle \u2014 generated scripts keep LF line endings";
|
|
10876
11062
|
function readTextOrNull(path) {
|
|
10877
11063
|
try {
|
|
10878
|
-
return
|
|
11064
|
+
return readFileSync7(path, "utf-8");
|
|
10879
11065
|
} catch {
|
|
10880
11066
|
return null;
|
|
10881
11067
|
}
|
|
@@ -10885,12 +11071,12 @@ function lfRules(paths) {
|
|
|
10885
11071
|
}
|
|
10886
11072
|
var REQUIRED_GITATTRIBUTES_RULES = lfRules(Object.keys(OWNED_GENERATED_HEADERS));
|
|
10887
11073
|
function planGitattributes(targetRepo) {
|
|
10888
|
-
const path =
|
|
11074
|
+
const path = join8(targetRepo, GITATTRIBUTES_REL);
|
|
10889
11075
|
const rules = REQUIRED_GITATTRIBUTES_RULES;
|
|
10890
11076
|
const before = readTextOrNull(path);
|
|
10891
11077
|
if (lstatOrNull(path)?.isSymbolicLink() === true)
|
|
10892
11078
|
return { before, after: before, outcome: "failed" };
|
|
10893
|
-
if (!
|
|
11079
|
+
if (!existsSync6(path)) {
|
|
10894
11080
|
return { before, after: [GITATTRIBUTES_HEADER, ...rules].join("\n") + "\n", outcome: "written" };
|
|
10895
11081
|
}
|
|
10896
11082
|
if (before === null)
|
|
@@ -10908,7 +11094,7 @@ function putPlannedGitattributes(targetRepo, planned) {
|
|
|
10908
11094
|
if (planned.outcome !== "written" || planned.after === null)
|
|
10909
11095
|
return planned.outcome;
|
|
10910
11096
|
try {
|
|
10911
|
-
writeAllowlisted(
|
|
11097
|
+
writeAllowlisted(join8(targetRepo, GITATTRIBUTES_REL), targetRepo, planned.after, []);
|
|
10912
11098
|
return "written";
|
|
10913
11099
|
} catch {
|
|
10914
11100
|
return "failed";
|
|
@@ -10927,12 +11113,12 @@ function previousPinForEngagement(targetRepoRoot, engagementId) {
|
|
|
10927
11113
|
function nonEmpty(value) {
|
|
10928
11114
|
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
10929
11115
|
}
|
|
10930
|
-
function planBundlePin(targetRepoRoot, version, engagementId, engagementType, createdByAccountId, methodVersion) {
|
|
11116
|
+
function planBundlePin(targetRepoRoot, version, engagementId, engagementType, createdByAccountId, methodVersion, serviceUrl) {
|
|
10931
11117
|
const previous = previousPinForEngagement(targetRepoRoot, engagementId);
|
|
10932
11118
|
const carried = pinCreatedByAccountId(previous) ?? nonEmpty(createdByAccountId);
|
|
10933
11119
|
const carriedMethodVersion = nonEmpty(methodVersion) ?? nonEmpty(previous?.methodVersion);
|
|
10934
11120
|
const installedAt = nonEmpty(previous?.installedAt) ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
10935
|
-
const projectPage = projectPageUrl(engagementId);
|
|
11121
|
+
const projectPage = projectPageUrl(engagementId, serviceUrl);
|
|
10936
11122
|
const pin = {
|
|
10937
11123
|
version,
|
|
10938
11124
|
engagementId,
|
|
@@ -10942,7 +11128,7 @@ function planBundlePin(targetRepoRoot, version, engagementId, engagementType, cr
|
|
|
10942
11128
|
...carriedMethodVersion !== void 0 ? { methodVersion: carriedMethodVersion } : {},
|
|
10943
11129
|
...projectPage !== void 0 ? { projectPage } : {}
|
|
10944
11130
|
};
|
|
10945
|
-
const current = readFileOrNull(
|
|
11131
|
+
const current = readFileOrNull(join8(targetRepoRoot, BUNDLE_PIN_REL));
|
|
10946
11132
|
const text = inLineEndingOf(current, JSON.stringify(pin, null, 2) + "\n");
|
|
10947
11133
|
return {
|
|
10948
11134
|
rel: BUNDLE_PIN_REL,
|
|
@@ -10970,10 +11156,10 @@ function planBundlePinPreview(targetRepoRoot, version, home) {
|
|
|
10970
11156
|
if (pinned === null || pinned.engagementId !== existing.engagementId || pinned.credential === void 0) {
|
|
10971
11157
|
return null;
|
|
10972
11158
|
}
|
|
10973
|
-
return planBundlePin(targetRepoRoot, version, existing.engagementId, existing.engagementType, void 0, void 0);
|
|
11159
|
+
return planBundlePin(targetRepoRoot, version, existing.engagementId, existing.engagementType, void 0, void 0, pinned.credential.serviceUrl);
|
|
10974
11160
|
}
|
|
10975
11161
|
function writeBundlePin(targetRepoRoot, planned, writtenPaths) {
|
|
10976
|
-
const pinPath =
|
|
11162
|
+
const pinPath = join8(targetRepoRoot, BUNDLE_PIN_REL);
|
|
10977
11163
|
writeAllowlisted(pinPath, targetRepoRoot, planned.text, writtenPaths);
|
|
10978
11164
|
}
|
|
10979
11165
|
function updatePinnedMethodVersion(repoRoot, engagementId, version) {
|
|
@@ -10985,7 +11171,7 @@ function updatePinnedMethodVersion(repoRoot, engagementId, version) {
|
|
|
10985
11171
|
}
|
|
10986
11172
|
if (pin === null || pin.engagementId !== engagementId)
|
|
10987
11173
|
return;
|
|
10988
|
-
const pinPath =
|
|
11174
|
+
const pinPath = join8(repoRoot, BUNDLE_PIN_REL);
|
|
10989
11175
|
const updated = { ...pin, methodVersion: version };
|
|
10990
11176
|
const pinText = inLineEndingOf(readFileOrNull(pinPath), JSON.stringify(updated, null, 2) + "\n");
|
|
10991
11177
|
try {
|
|
@@ -11005,17 +11191,17 @@ function renderCrewRoster() {
|
|
|
11005
11191
|
}
|
|
11006
11192
|
function writeCrewRoster(targetRepoRoot, report) {
|
|
11007
11193
|
const rendered = renderCrewRoster();
|
|
11008
|
-
const crewPath =
|
|
11194
|
+
const crewPath = join8(targetRepoRoot, CREW_ROSTER_REL);
|
|
11009
11195
|
return recordOwned(report, crewPath, targetRepoRoot, rendered, CREW_ROSTER_REL);
|
|
11010
11196
|
}
|
|
11011
11197
|
function pinCreatedByAccountId(pin) {
|
|
11012
11198
|
return nonEmpty(pin?.createdByAccountId) ?? nonEmpty(pin?.accountId);
|
|
11013
11199
|
}
|
|
11014
11200
|
function readBundlePin(targetRepoRoot) {
|
|
11015
|
-
const pinPath =
|
|
11016
|
-
if (!
|
|
11201
|
+
const pinPath = join8(targetRepoRoot, BUNDLE_PIN_REL);
|
|
11202
|
+
if (!existsSync6(pinPath))
|
|
11017
11203
|
return null;
|
|
11018
|
-
return JSON.parse(
|
|
11204
|
+
return JSON.parse(readFileSync7(pinPath, "utf-8"));
|
|
11019
11205
|
}
|
|
11020
11206
|
function readPinnedEngagement(targetRepoRoot, home) {
|
|
11021
11207
|
const pin = readBundlePin(targetRepoRoot);
|
|
@@ -11038,7 +11224,7 @@ function readPinnedEngagement(targetRepoRoot, home) {
|
|
|
11038
11224
|
}
|
|
11039
11225
|
function readLegacyEnvLocal(targetRepoRoot) {
|
|
11040
11226
|
try {
|
|
11041
|
-
return parseEnvText(
|
|
11227
|
+
return parseEnvText(readFileSync7(join8(targetRepoRoot, LEGACY_ENV_LOCAL_REL), "utf-8"));
|
|
11042
11228
|
} catch {
|
|
11043
11229
|
return null;
|
|
11044
11230
|
}
|
|
@@ -11090,7 +11276,7 @@ function writeEngagementCredential(credential, engagementId, home) {
|
|
|
11090
11276
|
function planEngagementCredential(credential, engagementId, home) {
|
|
11091
11277
|
let existing;
|
|
11092
11278
|
try {
|
|
11093
|
-
existing =
|
|
11279
|
+
existing = readFileSync7(engagementEnvPath(engagementId, home), "utf-8");
|
|
11094
11280
|
} catch {
|
|
11095
11281
|
existing = null;
|
|
11096
11282
|
}
|
|
@@ -11133,12 +11319,12 @@ function engagementCredentialValues(credential, engagementId) {
|
|
|
11133
11319
|
}
|
|
11134
11320
|
var LEGACY_ENV_LOCAL_REL = ".env.local";
|
|
11135
11321
|
function migrateLegacyEnvLocal(targetRepo, stored) {
|
|
11136
|
-
const path =
|
|
11137
|
-
if (!
|
|
11322
|
+
const path = join8(targetRepo, LEGACY_ENV_LOCAL_REL);
|
|
11323
|
+
if (!existsSync6(path))
|
|
11138
11324
|
return { outcome: "absent", unbacked: [], missingForAdoption: [] };
|
|
11139
11325
|
let raw;
|
|
11140
11326
|
try {
|
|
11141
|
-
raw =
|
|
11327
|
+
raw = readFileSync7(path, "utf-8");
|
|
11142
11328
|
} catch {
|
|
11143
11329
|
return { outcome: "failed", unbacked: [], missingForAdoption: [] };
|
|
11144
11330
|
}
|
|
@@ -11164,7 +11350,7 @@ function migrateLegacyEnvLocal(targetRepo, stored) {
|
|
|
11164
11350
|
return { outcome: "stripped", unbacked: [], missingForAdoption: [], keptText: kept.join("\n") };
|
|
11165
11351
|
}
|
|
11166
11352
|
function applyLegacyMigration(targetRepo, decided) {
|
|
11167
|
-
const path =
|
|
11353
|
+
const path = join8(targetRepo, LEGACY_ENV_LOCAL_REL);
|
|
11168
11354
|
try {
|
|
11169
11355
|
if (decided.outcome === "removed") {
|
|
11170
11356
|
rmSync2(path);
|
|
@@ -11190,23 +11376,23 @@ function planCodexSkills(targetRepo, previous, writeSet) {
|
|
|
11190
11376
|
const manifest = readPluginManifest();
|
|
11191
11377
|
const recordedFiles = new Map((previous?.files ?? []).filter((file) => writeSet.files.has(file.path)).map((file) => [file.path, file]));
|
|
11192
11378
|
return (manifest.commands ?? []).map((cmd) => {
|
|
11193
|
-
const srcFile =
|
|
11194
|
-
if (!
|
|
11379
|
+
const srcFile = join8(BUNDLE_ROOT, ".claude-plugin", cmd.path);
|
|
11380
|
+
if (!existsSync6(srcFile)) {
|
|
11195
11381
|
throw new Error(`[halfcycle] plugin.json declares command "${cmd.name}" at "${cmd.path}", but no file exists there (${srcFile}). The manifest and commands/ must agree.`);
|
|
11196
11382
|
}
|
|
11197
11383
|
const rel = skillRel(cmd.name);
|
|
11198
|
-
const content = codexSkillContent(cmd.name,
|
|
11384
|
+
const content = codexSkillContent(cmd.name, commandFileText(readFileSync7(srcFile, "utf-8"), ownVersion()));
|
|
11199
11385
|
const recorded = recordedFiles.get(rel);
|
|
11200
|
-
const planned = decideCollisionSafe(
|
|
11386
|
+
const planned = decideCollisionSafe(join8(targetRepo, rel), targetRepo, content, (current) => bytesAreOurs(rel, current, recorded, writeSet));
|
|
11201
11387
|
return { ...planned, editedCollided: planned.outcome === "collided" && recorded !== void 0 };
|
|
11202
11388
|
});
|
|
11203
11389
|
}
|
|
11204
11390
|
function planVendoredBinary(targetRepo) {
|
|
11205
|
-
return decideOwned(
|
|
11391
|
+
return decideOwned(join8(targetRepo, ".halfcycle", "bin", "bin.bundle.mjs"), targetRepo, readFileSync7(resolveVendoredBinary(), "utf-8"));
|
|
11206
11392
|
}
|
|
11207
11393
|
function planSettings(targetRepo, previous, home, env) {
|
|
11208
|
-
const settingsPath =
|
|
11209
|
-
const preexisted =
|
|
11394
|
+
const settingsPath = join8(targetRepo, SETTINGS_REL);
|
|
11395
|
+
const preexisted = existsSync6(settingsPath);
|
|
11210
11396
|
const generated = JSON.parse(generateSettingsJson());
|
|
11211
11397
|
let existing;
|
|
11212
11398
|
try {
|
|
@@ -11216,7 +11402,7 @@ function planSettings(targetRepo, previous, home, env) {
|
|
|
11216
11402
|
}
|
|
11217
11403
|
const retiredOurs = retiredRulesThisInstallAdded(previous);
|
|
11218
11404
|
const statusLine = statusLineSetElsewhere(targetRepo, home, env) ? void 0 : HALFCYCLE_STATUS_LINE;
|
|
11219
|
-
const merged = mergeSettings(existing, generated, retiredOurs, statusLine);
|
|
11405
|
+
const merged = mergeSettings(existing, generated, retiredOurs, statusLine, HALFCYCLE_ATTRIBUTION);
|
|
11220
11406
|
const current = readFileOrNull(settingsPath);
|
|
11221
11407
|
const text = inLineEndingOf(current, JSON.stringify(merged, null, 2) + "\n");
|
|
11222
11408
|
const file = {
|
|
@@ -11230,11 +11416,11 @@ function planSettings(targetRepo, previous, home, env) {
|
|
|
11230
11416
|
return { file, preexisted, existing, generated, merged, retiredOurs };
|
|
11231
11417
|
}
|
|
11232
11418
|
function planHookScripts(targetRepo) {
|
|
11233
|
-
return hookScriptsThisVersionWrites().map(([rel, content]) => decideOwnedGenerated(
|
|
11419
|
+
return hookScriptsThisVersionWrites().map(([rel, content]) => decideOwnedGenerated(join8(targetRepo, rel), targetRepo, content, OWNED_GENERATED_HEADERS[rel]));
|
|
11234
11420
|
}
|
|
11235
11421
|
function planMcpRegistration(targetRepo, credential) {
|
|
11236
|
-
const mcpPath =
|
|
11237
|
-
const existing =
|
|
11422
|
+
const mcpPath = join8(targetRepo, MCP_REGISTRATION_REL);
|
|
11423
|
+
const existing = existsSync6(mcpPath) ? readFileSync7(mcpPath, "utf-8") : null;
|
|
11238
11424
|
if (credential === void 0)
|
|
11239
11425
|
return { kind: "none", existing };
|
|
11240
11426
|
if (credential === "pending")
|
|
@@ -11246,11 +11432,11 @@ function planMcpRegistration(targetRepo, credential) {
|
|
|
11246
11432
|
function putPlannedMcp(targetRepo, planned) {
|
|
11247
11433
|
if (planned.kind !== "write" || planned.outcome === "skipped")
|
|
11248
11434
|
return;
|
|
11249
|
-
writeAllowlisted(
|
|
11435
|
+
writeAllowlisted(join8(targetRepo, MCP_REGISTRATION_REL), targetRepo, planned.text, []);
|
|
11250
11436
|
}
|
|
11251
11437
|
function fileInFolderPlace(root, rel) {
|
|
11252
11438
|
for (let dir = posix2.dirname(rel); dir !== "."; dir = posix2.dirname(dir)) {
|
|
11253
|
-
const found = lstatOrNull(
|
|
11439
|
+
const found = lstatOrNull(join8(root, dir));
|
|
11254
11440
|
if (found !== null && !found.isDirectory())
|
|
11255
11441
|
return true;
|
|
11256
11442
|
}
|
|
@@ -11259,7 +11445,7 @@ function fileInFolderPlace(root, rel) {
|
|
|
11259
11445
|
function symlinkOnPath(root, rel) {
|
|
11260
11446
|
let current = root;
|
|
11261
11447
|
for (const segment of rel.split("/")) {
|
|
11262
|
-
current =
|
|
11448
|
+
current = join8(current, segment);
|
|
11263
11449
|
try {
|
|
11264
11450
|
if (lstatSync(current).isSymbolicLink())
|
|
11265
11451
|
return true;
|
|
@@ -11280,14 +11466,14 @@ function planCodexHooks(targetRepo) {
|
|
|
11280
11466
|
});
|
|
11281
11467
|
if (symlinkOnPath(targetRepo, CODEX_HOOKS_REL2))
|
|
11282
11468
|
return leave("link", null);
|
|
11283
|
-
const folder2 = lstatOrNull(
|
|
11469
|
+
const folder2 = lstatOrNull(join8(targetRepo, posix2.dirname(CODEX_HOOKS_REL2)));
|
|
11284
11470
|
if (folder2 !== null && !folder2.isDirectory())
|
|
11285
11471
|
return leave("unreadable", null);
|
|
11286
|
-
const path =
|
|
11472
|
+
const path = join8(targetRepo, CODEX_HOOKS_REL2);
|
|
11287
11473
|
let before = null;
|
|
11288
11474
|
if (lstatOrNull(path) !== null) {
|
|
11289
11475
|
try {
|
|
11290
|
-
before =
|
|
11476
|
+
before = readFileSync7(path, "utf-8");
|
|
11291
11477
|
} catch {
|
|
11292
11478
|
return leave("unreadable", null);
|
|
11293
11479
|
}
|
|
@@ -11321,13 +11507,13 @@ function planCodexHooks(targetRepo) {
|
|
|
11321
11507
|
function putPlannedCodexHooks(targetRepo, planned) {
|
|
11322
11508
|
if (planned.outcome !== "written" || planned.text === null)
|
|
11323
11509
|
return;
|
|
11324
|
-
writeAllowlisted(
|
|
11510
|
+
writeAllowlisted(join8(targetRepo, CODEX_HOOKS_REL2), targetRepo, planned.text, []);
|
|
11325
11511
|
}
|
|
11326
11512
|
function readCodexConfig(targetRepo) {
|
|
11327
|
-
const folder2 = lstatOrNull(
|
|
11513
|
+
const folder2 = lstatOrNull(join8(targetRepo, posix2.dirname(CODEX_CONFIG_REL2)));
|
|
11328
11514
|
if (folder2 !== null && !folder2.isDirectory())
|
|
11329
11515
|
return "unreadable";
|
|
11330
|
-
const path =
|
|
11516
|
+
const path = join8(targetRepo, CODEX_CONFIG_REL2);
|
|
11331
11517
|
const stat = lstatOrNull(path);
|
|
11332
11518
|
if (stat === null)
|
|
11333
11519
|
return { text: null };
|
|
@@ -11335,7 +11521,7 @@ function readCodexConfig(targetRepo) {
|
|
|
11335
11521
|
return "unreadable";
|
|
11336
11522
|
let bytes;
|
|
11337
11523
|
try {
|
|
11338
|
-
bytes =
|
|
11524
|
+
bytes = readFileSync7(path);
|
|
11339
11525
|
} catch {
|
|
11340
11526
|
return "unreadable";
|
|
11341
11527
|
}
|
|
@@ -11381,10 +11567,10 @@ function planCodexConfig(targetRepo, credential) {
|
|
|
11381
11567
|
function putPlannedCodexConfig(targetRepo, planned) {
|
|
11382
11568
|
if (planned.outcome !== "written" || planned.text === null)
|
|
11383
11569
|
return;
|
|
11384
|
-
writeAllowlisted(
|
|
11570
|
+
writeAllowlisted(join8(targetRepo, CODEX_CONFIG_REL2), targetRepo, planned.text, []);
|
|
11385
11571
|
}
|
|
11386
11572
|
function planCrewRoster(targetRepo) {
|
|
11387
|
-
return decideOwned(
|
|
11573
|
+
return decideOwned(join8(targetRepo, CREW_ROSTER_REL), targetRepo, renderCrewRoster());
|
|
11388
11574
|
}
|
|
11389
11575
|
function planCrewProfiles(targetRepo, previous, writeSet) {
|
|
11390
11576
|
const recordedFiles = new Map((previous?.files ?? []).filter((file) => writeSet.files.has(file.path)).map((file) => [file.path, file]));
|
|
@@ -11399,7 +11585,7 @@ function planCrewProfiles(targetRepo, previous, writeSet) {
|
|
|
11399
11585
|
});
|
|
11400
11586
|
}
|
|
11401
11587
|
function planInstallRecord(targetRepo, text) {
|
|
11402
|
-
return decideOwned(
|
|
11588
|
+
return decideOwned(join8(targetRepo, INSTALL_MANIFEST_REL), targetRepo, text);
|
|
11403
11589
|
}
|
|
11404
11590
|
function accountInstall(steps, actual) {
|
|
11405
11591
|
const { targetRepo, writeSet, pin } = steps;
|
|
@@ -11627,7 +11813,7 @@ function mcpHasHalfcycleEntry(text) {
|
|
|
11627
11813
|
function writeMissingSelfBuildCrewProfiles(targetRepoRoot, report) {
|
|
11628
11814
|
for (const { rel, text } of renderCrewProfiles()) {
|
|
11629
11815
|
const target = crewProfileTarget(targetRepoRoot, rel);
|
|
11630
|
-
if (
|
|
11816
|
+
if (existsSync6(target))
|
|
11631
11817
|
continue;
|
|
11632
11818
|
writeAllowlisted(target, targetRepoRoot, text, report.writtenPaths);
|
|
11633
11819
|
}
|
|
@@ -11635,7 +11821,7 @@ function writeMissingSelfBuildCrewProfiles(targetRepoRoot, report) {
|
|
|
11635
11821
|
function installSelfBuild(options) {
|
|
11636
11822
|
const { targetRepo, engagementId, credential, freshlyMinted, home } = options;
|
|
11637
11823
|
assertEngagementId(engagementId);
|
|
11638
|
-
if (!
|
|
11824
|
+
if (!existsSync6(targetRepo)) {
|
|
11639
11825
|
throw new Error(`[halfcycle] Target repo does not exist: ${targetRepo}`);
|
|
11640
11826
|
}
|
|
11641
11827
|
const report = {
|
|
@@ -11646,7 +11832,7 @@ function installSelfBuild(options) {
|
|
|
11646
11832
|
editedCollidedPaths: [],
|
|
11647
11833
|
replacedPaths: []
|
|
11648
11834
|
};
|
|
11649
|
-
const at = (rel) =>
|
|
11835
|
+
const at = (rel) => join8(targetRepo, rel);
|
|
11650
11836
|
const version = readPluginManifest().version;
|
|
11651
11837
|
const existingMcp = readFileOrNull(at(MCP_REGISTRATION_REL));
|
|
11652
11838
|
const mcpText = existingMcp === null || !mcpHasHalfcycleEntry(existingMcp) ? inLineEndingOf(existingMcp, generateMcpRegistration(existingMcp, credential.mcpUrl)) : null;
|
|
@@ -11657,18 +11843,18 @@ function installSelfBuild(options) {
|
|
|
11657
11843
|
if (mcpText !== null) {
|
|
11658
11844
|
writeAllowlisted(at(MCP_REGISTRATION_REL), targetRepo, mcpText, report.writtenPaths);
|
|
11659
11845
|
}
|
|
11660
|
-
if (!
|
|
11661
|
-
recordOwned(report, at(VENDORED_BIN_REL), targetRepo,
|
|
11846
|
+
if (!existsSync6(at(VENDORED_BIN_REL))) {
|
|
11847
|
+
recordOwned(report, at(VENDORED_BIN_REL), targetRepo, readFileSync7(resolveVendoredBinary(), "utf-8"), VENDORED_BIN_REL);
|
|
11662
11848
|
}
|
|
11663
|
-
if (!
|
|
11849
|
+
if (!existsSync6(at(SELF_BUILD_GITIGNORE_REL))) {
|
|
11664
11850
|
writeAllowlisted(at(SELF_BUILD_GITIGNORE_REL), targetRepo, SELF_BUILD_GITIGNORE_TEXT, report.writtenPaths);
|
|
11665
11851
|
}
|
|
11666
|
-
if (!
|
|
11852
|
+
if (!existsSync6(at(CREW_ROSTER_REL))) {
|
|
11667
11853
|
writeCrewRoster(targetRepo, report);
|
|
11668
11854
|
}
|
|
11669
11855
|
writeMissingSelfBuildCrewProfiles(targetRepo, report);
|
|
11670
|
-
if (!
|
|
11671
|
-
writeBundlePin(targetRepo, planBundlePin(targetRepo, version, engagementId, "self-build", void 0, credential.methodVersion), report.writtenPaths);
|
|
11856
|
+
if (!existsSync6(at(BUNDLE_PIN_REL))) {
|
|
11857
|
+
writeBundlePin(targetRepo, planBundlePin(targetRepo, version, engagementId, "self-build", void 0, credential.methodVersion, credential.serviceUrl), report.writtenPaths);
|
|
11672
11858
|
}
|
|
11673
11859
|
return { writtenPaths: report.writtenPaths, credentialWritten };
|
|
11674
11860
|
}
|
|
@@ -11695,20 +11881,20 @@ import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
|
11695
11881
|
var LOOPBACK_PORT_ENV = "HALFCYCLE_LOOPBACK_PORT";
|
|
11696
11882
|
var DEFAULT_STILL_WAITING_MS = 15e3;
|
|
11697
11883
|
var COMPLETION_REDIRECT_SECONDS = 2;
|
|
11698
|
-
function onwardTarget(engagementId) {
|
|
11699
|
-
const projectPage = projectPageUrl(engagementId);
|
|
11884
|
+
function onwardTarget(engagementId, serviceUrl) {
|
|
11885
|
+
const projectPage = projectPageUrl(engagementId, serviceUrl);
|
|
11700
11886
|
if (projectPage !== void 0) {
|
|
11701
11887
|
return { href: projectPage, label: "Go to your project's page" };
|
|
11702
11888
|
}
|
|
11703
|
-
return { href:
|
|
11889
|
+
return { href: consoleOrigin(serviceUrl), label: "Go to your projects" };
|
|
11704
11890
|
}
|
|
11705
11891
|
function escapeHtml(value) {
|
|
11706
11892
|
return value.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
|
|
11707
11893
|
}
|
|
11708
|
-
function completionPage(result, engagementId) {
|
|
11894
|
+
function completionPage(result, engagementId, serviceUrl) {
|
|
11709
11895
|
const approved = result === LOOPBACK_RESULT.APPROVED;
|
|
11710
11896
|
const headline2 = approved ? "Signed in. You can close this tab and return to your terminal." : "Sign-in declined. Nothing was created. You can close this tab.";
|
|
11711
|
-
const onward = approved ? onwardTarget(engagementId) : null;
|
|
11897
|
+
const onward = approved ? onwardTarget(engagementId, serviceUrl) : null;
|
|
11712
11898
|
const href = onward === null ? "" : escapeHtml(onward.href);
|
|
11713
11899
|
const refresh = onward === null ? "" : `<meta http-equiv="refresh" content="${COMPLETION_REDIRECT_SECONDS};url=${href}">`;
|
|
11714
11900
|
const successLink = onward === null ? "" : `<p><a href="${href}" style="color:#19d3ff">${escapeHtml(onward.label)}</a></p>`;
|
|
@@ -11727,7 +11913,7 @@ function statesMatch(presented, expected) {
|
|
|
11727
11913
|
return false;
|
|
11728
11914
|
return timingSafeEqual(a, b);
|
|
11729
11915
|
}
|
|
11730
|
-
function createCallbackHandler(state, onCallback, engagementId) {
|
|
11916
|
+
function createCallbackHandler(state, onCallback, engagementId, serviceUrl) {
|
|
11731
11917
|
return (req, res) => {
|
|
11732
11918
|
if (!isLoopbackPeer(req.socket.remoteAddress)) {
|
|
11733
11919
|
res.writeHead(403).end();
|
|
@@ -11751,7 +11937,7 @@ function createCallbackHandler(state, onCallback, engagementId) {
|
|
|
11751
11937
|
const declared = url.searchParams.get(LOOPBACK_QUERY_RESULT);
|
|
11752
11938
|
const result = declared === LOOPBACK_RESULT.DECLINED ? LOOPBACK_RESULT.DECLINED : LOOPBACK_RESULT.APPROVED;
|
|
11753
11939
|
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
11754
|
-
res.end(completionPage(result, engagementId));
|
|
11940
|
+
res.end(completionPage(result, engagementId, serviceUrl));
|
|
11755
11941
|
onCallback({ result });
|
|
11756
11942
|
};
|
|
11757
11943
|
}
|
|
@@ -11770,7 +11956,7 @@ function requestedPort(env) {
|
|
|
11770
11956
|
}
|
|
11771
11957
|
return { port };
|
|
11772
11958
|
}
|
|
11773
|
-
async function bindLoopback(env = process.env, engagementId) {
|
|
11959
|
+
async function bindLoopback(env = process.env, engagementId, serviceUrl) {
|
|
11774
11960
|
const wanted = requestedPort(env);
|
|
11775
11961
|
if ("problem" in wanted)
|
|
11776
11962
|
return { bound: false, reason: wanted.problem };
|
|
@@ -11783,7 +11969,7 @@ async function bindLoopback(env = process.env, engagementId) {
|
|
|
11783
11969
|
deliver(callback);
|
|
11784
11970
|
else
|
|
11785
11971
|
pending = callback;
|
|
11786
|
-
}, engagementId));
|
|
11972
|
+
}, engagementId, serviceUrl));
|
|
11787
11973
|
const bindProblem = await new Promise((resolve9) => {
|
|
11788
11974
|
const onError = (err) => {
|
|
11789
11975
|
resolve9(`${err.code ?? "bind failed"} on ${LOOPBACK_REDIRECT_HOST}:${wanted.port}`);
|
|
@@ -11841,12 +12027,12 @@ function closeServer(server) {
|
|
|
11841
12027
|
}
|
|
11842
12028
|
|
|
11843
12029
|
// dist/sign-in-attempt.js
|
|
11844
|
-
import { mkdirSync as mkdirSync6, readFileSync as
|
|
12030
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync8, realpathSync, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "node:fs";
|
|
11845
12031
|
import { homedir as homedir4 } from "node:os";
|
|
11846
|
-
import { join as
|
|
12032
|
+
import { join as join9 } from "node:path";
|
|
11847
12033
|
var ATTEMPT_FILENAME = "sign-in-attempt.json";
|
|
11848
12034
|
function attemptPath(home) {
|
|
11849
|
-
return
|
|
12035
|
+
return join9(home ?? homedir4(), HALFCYCLE_DIR_NAME, ATTEMPT_FILENAME);
|
|
11850
12036
|
}
|
|
11851
12037
|
function realFolder(folder2) {
|
|
11852
12038
|
try {
|
|
@@ -11858,7 +12044,7 @@ function realFolder(folder2) {
|
|
|
11858
12044
|
function recordSignInAttempt(folder2, expiresAt, home) {
|
|
11859
12045
|
try {
|
|
11860
12046
|
const path = attemptPath(home);
|
|
11861
|
-
mkdirSync6(
|
|
12047
|
+
mkdirSync6(join9(home ?? homedir4(), HALFCYCLE_DIR_NAME), { recursive: true, mode: 448 });
|
|
11862
12048
|
const record2 = { folder: realFolder(folder2), expiresAt };
|
|
11863
12049
|
writeFileSync6(path, `${JSON.stringify(record2)}
|
|
11864
12050
|
`, { mode: 384 });
|
|
@@ -11875,7 +12061,7 @@ function clearSignInAttempt(home) {
|
|
|
11875
12061
|
}
|
|
11876
12062
|
function readSignInAttempt(home) {
|
|
11877
12063
|
try {
|
|
11878
|
-
const parsed = JSON.parse(
|
|
12064
|
+
const parsed = JSON.parse(readFileSync8(attemptPath(home), "utf-8"));
|
|
11879
12065
|
if (typeof parsed !== "object" || parsed === null)
|
|
11880
12066
|
return null;
|
|
11881
12067
|
const { folder: folder2, expiresAt } = parsed;
|
|
@@ -12144,7 +12330,7 @@ async function signIn(serviceUrl, deps = {}) {
|
|
|
12144
12330
|
}
|
|
12145
12331
|
write2(`[halfcycle] Halfcycle needs an account before it can ${deps.reason ?? DEFAULT_SIGN_IN_REASON} \u2014 signing you in through your browser.
|
|
12146
12332
|
`);
|
|
12147
|
-
const bind = await bindLoopback(env, deps.onwardEngagementId);
|
|
12333
|
+
const bind = await bindLoopback(env, deps.onwardEngagementId, serviceUrl);
|
|
12148
12334
|
let closed = false;
|
|
12149
12335
|
const closeListener = async () => {
|
|
12150
12336
|
if (bind.bound && !closed) {
|
|
@@ -12716,7 +12902,7 @@ function installFailureLine() {
|
|
|
12716
12902
|
if (code === 0 || verdict)
|
|
12717
12903
|
return;
|
|
12718
12904
|
try {
|
|
12719
|
-
writeSync(2, `${
|
|
12905
|
+
writeSync(2, `${doctorLine(ownVersion())}
|
|
12720
12906
|
`);
|
|
12721
12907
|
} catch {
|
|
12722
12908
|
}
|
|
@@ -12917,6 +13103,9 @@ function onlyDate(iso) {
|
|
|
12917
13103
|
return match?.[1] ?? iso;
|
|
12918
13104
|
}
|
|
12919
13105
|
function actClause(act) {
|
|
13106
|
+
if (act.kind === "create" && act.folderAlreadyHasProject === true) {
|
|
13107
|
+
return `pick up this folder's project for that account, and install into ${act.targetRepo}`;
|
|
13108
|
+
}
|
|
12920
13109
|
return act.kind === "create" ? `create a NEW project for that account, and install into ${act.targetRepo}` : `join the project this repository is pinned to (${act.engagementId}) as that account, and install into ${act.targetRepo}`;
|
|
12921
13110
|
}
|
|
12922
13111
|
function wrongAccountConsequence(act) {
|
|
@@ -13095,7 +13284,7 @@ function isProjectOwnedElsewhere(result) {
|
|
|
13095
13284
|
}
|
|
13096
13285
|
async function createOwnedEngagement(serviceUrl, targetRepo, projectId, callerDeps = {}) {
|
|
13097
13286
|
const deps = { ...callerDeps, attemptFolder: targetRepo };
|
|
13098
|
-
let credential = await confirmActingIdentity(serviceUrl, await obtainCredential(serviceUrl, deps), { kind: "create", targetRepo }, deps);
|
|
13287
|
+
let credential = await confirmActingIdentity(serviceUrl, await obtainCredential(serviceUrl, deps), { kind: "create", targetRepo, folderAlreadyHasProject: deps.folderAlreadyHasProject }, deps);
|
|
13099
13288
|
for (; ; ) {
|
|
13100
13289
|
try {
|
|
13101
13290
|
return {
|
|
@@ -13169,9 +13358,9 @@ async function joinPinnedEngagement(serviceUrl, engagementId, targetRepo, deps =
|
|
|
13169
13358
|
|
|
13170
13359
|
// dist/pre-create-checks.js
|
|
13171
13360
|
import { spawnSync } from "node:child_process";
|
|
13172
|
-
import { existsSync as
|
|
13173
|
-
import { delimiter, dirname as
|
|
13174
|
-
var KEPT_FOLDER_ID_LINE =
|
|
13361
|
+
import { existsSync as existsSync7, readFileSync as readFileSync9, realpathSync as realpathSync2 } from "node:fs";
|
|
13362
|
+
import { delimiter, dirname as dirname5, join as join10 } from "node:path";
|
|
13363
|
+
var KEPT_FOLDER_ID_LINE = `Nothing on the Halfcycle service needs undoing, and this folder keeps its project id, so running "${ownBareCommand()}" again picks up where it left off.`;
|
|
13175
13364
|
var PreCreateRefused = class extends Error {
|
|
13176
13365
|
constructor(message) {
|
|
13177
13366
|
super(message);
|
|
@@ -13207,14 +13396,14 @@ function refuseSystemFolder(targetRepo, platform6 = process.platform) {
|
|
|
13207
13396
|
};
|
|
13208
13397
|
const target = key(targetRepo);
|
|
13209
13398
|
if (SYSTEM_FOLDERS.some((folder2) => key(folder2) === target)) {
|
|
13210
|
-
throw new PreCreateRefused(`${targetRepo} is a system folder \u2014 run "
|
|
13399
|
+
throw new PreCreateRefused(`${targetRepo} is a system folder \u2014 run "${ownBareCommand()}" inside a project folder.`);
|
|
13211
13400
|
}
|
|
13212
13401
|
}
|
|
13213
13402
|
function refuseInsideProject(targetRepo) {
|
|
13214
13403
|
let folder2 = realOrLiteral(targetRepo);
|
|
13215
|
-
for (let parent =
|
|
13216
|
-
if (
|
|
13217
|
-
throw new PreCreateRefused(`${targetRepo} is inside the Halfcycle project at ${parent} \u2014 open that folder in your coding agent instead, or run "
|
|
13404
|
+
for (let parent = dirname5(folder2); parent !== folder2; folder2 = parent, parent = dirname5(parent)) {
|
|
13405
|
+
if (existsSync7(join10(parent, BUNDLE_PIN_REL))) {
|
|
13406
|
+
throw new PreCreateRefused(`${targetRepo} is inside the Halfcycle project at ${parent} \u2014 open that folder in your coding agent instead, or run "${ownBareCommand()}" in a folder outside it.`);
|
|
13218
13407
|
}
|
|
13219
13408
|
}
|
|
13220
13409
|
}
|
|
@@ -13234,16 +13423,16 @@ async function offerGitForWindows() {
|
|
|
13234
13423
|
const yes = isInteractiveRun() && /^\s*y(es)?\s*$/i.test(await askLine("Git for Windows is not installed, and Claude Code needs it to run Halfcycle. Install it now with winget? [y/N] "));
|
|
13235
13424
|
if (yes && runWingetInstall())
|
|
13236
13425
|
return true;
|
|
13237
|
-
throw new PreCreateRefused(`Git for Windows is needed and is not installed \u2014 install it with "${GIT_INSTALL_COMMAND}" or from ${GIT_DOWNLOAD_PAGE}, then open a new terminal and run "
|
|
13426
|
+
throw new PreCreateRefused(`Git for Windows is needed and is not installed \u2014 install it with "${GIT_INSTALL_COMMAND}" or from ${GIT_DOWNLOAD_PAGE}, then open a new terminal and run "${ownBareCommand()}" again.`);
|
|
13238
13427
|
}
|
|
13239
13428
|
function gitBashPath(env) {
|
|
13240
|
-
const onPath = (env["PATH"] ?? "").split(delimiter).filter((dir) => dir !== "" &&
|
|
13429
|
+
const onPath = (env["PATH"] ?? "").split(delimiter).filter((dir) => dir !== "" && existsSync7(join10(dir, "git.exe"))).map((dir) => join10(dir, "..", "bin", "bash.exe"));
|
|
13241
13430
|
const candidates = [
|
|
13242
13431
|
env["CLAUDE_CODE_GIT_BASH_PATH"],
|
|
13243
13432
|
...onPath,
|
|
13244
|
-
env["ProgramFiles"] === void 0 ? void 0 :
|
|
13433
|
+
env["ProgramFiles"] === void 0 ? void 0 : join10(env["ProgramFiles"], "Git", "bin", "bash.exe")
|
|
13245
13434
|
];
|
|
13246
|
-
return candidates.find((file) => file !== void 0 && file !== "" &&
|
|
13435
|
+
return candidates.find((file) => file !== void 0 && file !== "" && existsSync7(file));
|
|
13247
13436
|
}
|
|
13248
13437
|
function hasGitBash(env) {
|
|
13249
13438
|
return gitBashPath(env) !== void 0;
|
|
@@ -13252,10 +13441,10 @@ function runWingetInstall() {
|
|
|
13252
13441
|
return spawnSync("winget", [...GIT_INSTALL_ARGS], { stdio: "inherit" }).status === 0;
|
|
13253
13442
|
}
|
|
13254
13443
|
function unreadableProjectRefusal(targetRepo) {
|
|
13255
|
-
return `${
|
|
13444
|
+
return `${join10(targetRepo, PROJECT_IDENTITY_REL)} could not be read as a Halfcycle project file \u2014 delete the .halfcycle folder in ${targetRepo} and run "${ownBareCommand()}" again to start a new project here.`;
|
|
13256
13445
|
}
|
|
13257
13446
|
function cannotWriteRefusal(targetRepo) {
|
|
13258
|
-
return `cannot write to ${targetRepo} \u2014 run "
|
|
13447
|
+
return `cannot write to ${targetRepo} \u2014 run "${ownBareCommand()}" in a folder you can write to.`;
|
|
13259
13448
|
}
|
|
13260
13449
|
function claimFolderId(targetRepo) {
|
|
13261
13450
|
const planned = planProjectIdentity(targetRepo);
|
|
@@ -13272,7 +13461,7 @@ function claimFolderId(targetRepo) {
|
|
|
13272
13461
|
function readFolderId(targetRepo) {
|
|
13273
13462
|
let folderId;
|
|
13274
13463
|
try {
|
|
13275
|
-
const text =
|
|
13464
|
+
const text = readFileSync9(join10(targetRepo, PROJECT_IDENTITY_REL), "utf-8");
|
|
13276
13465
|
folderId = JSON.parse(text)?.projectId;
|
|
13277
13466
|
} catch {
|
|
13278
13467
|
return void 0;
|
|
@@ -13295,7 +13484,7 @@ function folderMissingRefusal(targetRepo) {
|
|
|
13295
13484
|
return `halfcycle: no such directory: ${targetRepo}`;
|
|
13296
13485
|
}
|
|
13297
13486
|
function folderIsFileRefusal(targetRepo) {
|
|
13298
|
-
return `halfcycle: ${targetRepo} is a file \u2014 run "
|
|
13487
|
+
return `halfcycle: ${targetRepo} is a file \u2014 run "${ownBareCommand()}" with a project folder instead.`;
|
|
13299
13488
|
}
|
|
13300
13489
|
function monorepoRefusal(targetRepo) {
|
|
13301
13490
|
return `halfcycle: refusing to install into the Halfcycle monorepo itself (${targetRepo}).
|
|
@@ -13305,7 +13494,7 @@ function monorepoRefusal(targetRepo) {
|
|
|
13305
13494
|
To install into a real project: halfcycle <path-to-that-repo>`;
|
|
13306
13495
|
}
|
|
13307
13496
|
function homeFolderRefusal(targetRepo) {
|
|
13308
|
-
return `halfcycle: refusing to set up in your home folder (${targetRepo}). Run "
|
|
13497
|
+
return `halfcycle: refusing to set up in your home folder (${targetRepo}). Run "${ownBareCommand()}" inside a project folder.`;
|
|
13309
13498
|
}
|
|
13310
13499
|
function isHomeFolder(dir) {
|
|
13311
13500
|
try {
|
|
@@ -13318,16 +13507,16 @@ function isHomeFolder(dir) {
|
|
|
13318
13507
|
}
|
|
13319
13508
|
|
|
13320
13509
|
// dist/preview.js
|
|
13321
|
-
import { accessSync, constants, existsSync as
|
|
13322
|
-
import { join as
|
|
13510
|
+
import { accessSync, constants, existsSync as existsSync9, readFileSync as readFileSync12, statSync } from "node:fs";
|
|
13511
|
+
import { join as join13 } from "node:path";
|
|
13323
13512
|
|
|
13324
13513
|
// dist/halfcycle-monorepo.js
|
|
13325
|
-
import { readFileSync as
|
|
13326
|
-
import { join as
|
|
13514
|
+
import { readFileSync as readFileSync10 } from "node:fs";
|
|
13515
|
+
import { join as join11 } from "node:path";
|
|
13327
13516
|
var MONOREPO_PACKAGE_NAME = "halfcycle-monorepo";
|
|
13328
13517
|
function isHalfcycleMonorepo(dir) {
|
|
13329
13518
|
try {
|
|
13330
|
-
const pkg = JSON.parse(
|
|
13519
|
+
const pkg = JSON.parse(readFileSync10(join11(dir, "package.json"), "utf-8"));
|
|
13331
13520
|
return pkg.name === MONOREPO_PACKAGE_NAME;
|
|
13332
13521
|
} catch {
|
|
13333
13522
|
return false;
|
|
@@ -13337,131 +13526,10 @@ function isSelfBuildInstallForm(engagementIdArg, engagementTypeArg) {
|
|
|
13337
13526
|
return engagementIdArg !== void 0 && engagementTypeArg === "self-build";
|
|
13338
13527
|
}
|
|
13339
13528
|
|
|
13340
|
-
// dist/preview.js
|
|
13341
|
-
var CLAUDE_MD_REL = "CLAUDE.md";
|
|
13342
|
-
var AGENTS_IMPORT_LINE = "@AGENTS.md";
|
|
13343
|
-
var WORDS = {
|
|
13344
|
-
intro: "Halfcycle would set up this folder like this. Nothing has been written.",
|
|
13345
|
-
remove: "an older Halfcycle file this version no longer uses",
|
|
13346
|
-
claudeMdWillAdd: "already holds your own instructions; your first session will add one line to it, pointing to AGENTS.md",
|
|
13347
|
-
claudeMdAlreadyPoints: "already holds your own instructions, including the line pointing to AGENTS.md",
|
|
13348
|
-
signIn: " This project's sign-in, kept on this computer for you only, in your home folder's .halfcycle folder.",
|
|
13349
|
-
newProject: " A new project on Halfcycle's service for this folder, made when you sign in.",
|
|
13350
|
-
gitForWindows: " Git for Windows, which Claude Code needs: setting up will ask before installing it with winget.",
|
|
13351
|
-
firstSession: "Your first session will then write your project's own files as you answer its questions, starting with AGENTS.md and CLAUDE.md, which hold your project's own instructions.",
|
|
13352
|
-
toSetUp: `To set up for real, run: ${HALFCYCLE_COMMAND}`,
|
|
13353
|
-
planFailed: "halfcycle: setting up this folder would stop on a file that could not be read. Nothing has been written; fix that file, then run the preview again."
|
|
13354
|
-
};
|
|
13355
|
-
function runPreview(targetRepo, options) {
|
|
13356
|
-
const refusal3 = previewRefusal(targetRepo, options);
|
|
13357
|
-
if (refusal3 !== void 0)
|
|
13358
|
-
return { refusal: refusal3 };
|
|
13359
|
-
let plan;
|
|
13360
|
-
try {
|
|
13361
|
-
plan = planInstall({ targetRepo, env: options.env, home: options.home });
|
|
13362
|
-
} catch {
|
|
13363
|
-
return { refusal: WORDS.planFailed };
|
|
13364
|
-
}
|
|
13365
|
-
if (plan.refusal !== void 0)
|
|
13366
|
-
return { refusal: plan.refusal };
|
|
13367
|
-
return {
|
|
13368
|
-
text: renderPreview(plan, gitBashPath(options.env) === void 0 && options.platform === "win32", claudeMdLine(targetRepo))
|
|
13369
|
-
};
|
|
13370
|
-
}
|
|
13371
|
-
function claudeMdLine(targetRepo) {
|
|
13372
|
-
const path = join11(targetRepo, CLAUDE_MD_REL);
|
|
13373
|
-
if (!existsSync7(path))
|
|
13374
|
-
return void 0;
|
|
13375
|
-
let text;
|
|
13376
|
-
try {
|
|
13377
|
-
text = readFileSync10(path, "utf-8");
|
|
13378
|
-
} catch {
|
|
13379
|
-
return WORDS.claudeMdWillAdd;
|
|
13380
|
-
}
|
|
13381
|
-
return text.includes(AGENTS_IMPORT_LINE) ? WORDS.claudeMdAlreadyPoints : WORDS.claudeMdWillAdd;
|
|
13382
|
-
}
|
|
13383
|
-
function previewRefusal(targetRepo, options) {
|
|
13384
|
-
if (!existsSync7(targetRepo))
|
|
13385
|
-
return folderMissingRefusal(targetRepo);
|
|
13386
|
-
if (!statSync(targetRepo).isDirectory())
|
|
13387
|
-
return folderIsFileRefusal(targetRepo);
|
|
13388
|
-
if (isHalfcycleMonorepo(targetRepo))
|
|
13389
|
-
return monorepoRefusal(targetRepo);
|
|
13390
|
-
if (isHomeFolder(targetRepo))
|
|
13391
|
-
return homeFolderRefusal(targetRepo);
|
|
13392
|
-
try {
|
|
13393
|
-
refuseSystemFolder(targetRepo, options.platform);
|
|
13394
|
-
refuseInsideProject(targetRepo);
|
|
13395
|
-
} catch (err) {
|
|
13396
|
-
if (err instanceof PreCreateRefused)
|
|
13397
|
-
return err.message;
|
|
13398
|
-
throw err;
|
|
13399
|
-
}
|
|
13400
|
-
try {
|
|
13401
|
-
accessSync(targetRepo, constants.W_OK);
|
|
13402
|
-
} catch {
|
|
13403
|
-
return cannotWriteRefusal(targetRepo);
|
|
13404
|
-
}
|
|
13405
|
-
if (existsSync7(join11(targetRepo, PROJECT_IDENTITY_REL)) && readFolderId(targetRepo) === void 0) {
|
|
13406
|
-
return unreadableProjectRefusal(targetRepo);
|
|
13407
|
-
}
|
|
13408
|
-
try {
|
|
13409
|
-
checkSettingsParse(targetRepo);
|
|
13410
|
-
} catch (err) {
|
|
13411
|
-
if (err instanceof PreCreateRefused)
|
|
13412
|
-
return err.message;
|
|
13413
|
-
throw err;
|
|
13414
|
-
}
|
|
13415
|
-
return void 0;
|
|
13416
|
-
}
|
|
13417
|
-
function wordsOf(entry) {
|
|
13418
|
-
const words = PREVIEW_WORDS[entry.kind];
|
|
13419
|
-
switch (entry.action) {
|
|
13420
|
-
case "add":
|
|
13421
|
-
return words.add;
|
|
13422
|
-
case "change":
|
|
13423
|
-
return words.change;
|
|
13424
|
-
case "remove":
|
|
13425
|
-
return WORDS.remove;
|
|
13426
|
-
case "leave":
|
|
13427
|
-
return entry.yours === true ? words.yours : words.leave;
|
|
13428
|
-
}
|
|
13429
|
-
}
|
|
13430
|
-
var HEADINGS = [
|
|
13431
|
-
["add", "Add:"],
|
|
13432
|
-
["change", "Change:"],
|
|
13433
|
-
["remove", "Remove:"],
|
|
13434
|
-
["leave", "Leave alone:"]
|
|
13435
|
-
];
|
|
13436
|
-
function outsideLine2(outside) {
|
|
13437
|
-
return outside.kind === "sign-in" ? WORDS.signIn : WORDS.newProject;
|
|
13438
|
-
}
|
|
13439
|
-
function renderPreview(plan, missingGitForWindows, claudeMdWords) {
|
|
13440
|
-
const lines = [WORDS.intro];
|
|
13441
|
-
for (const [action, heading] of HEADINGS) {
|
|
13442
|
-
const rows = plan.entries.filter((entry) => entry.action === action);
|
|
13443
|
-
const claudeMdRow = action === "leave" && claudeMdWords !== void 0;
|
|
13444
|
-
if (rows.length === 0 && !claudeMdRow)
|
|
13445
|
-
continue;
|
|
13446
|
-
lines.push("", heading);
|
|
13447
|
-
for (const entry of rows)
|
|
13448
|
-
lines.push(` ${entry.path} \u2014 ${wordsOf(entry)}`);
|
|
13449
|
-
if (claudeMdRow)
|
|
13450
|
-
lines.push(` ${CLAUDE_MD_REL} \u2014 ${claudeMdWords}`);
|
|
13451
|
-
}
|
|
13452
|
-
lines.push("", "Outside this folder:");
|
|
13453
|
-
for (const outside of plan.outside)
|
|
13454
|
-
lines.push(outsideLine2(outside));
|
|
13455
|
-
if (missingGitForWindows)
|
|
13456
|
-
lines.push(WORDS.gitForWindows);
|
|
13457
|
-
lines.push("", WORDS.firstSession, "", WORDS.toSetUp);
|
|
13458
|
-
return lines.join("\n");
|
|
13459
|
-
}
|
|
13460
|
-
|
|
13461
13529
|
// dist/local-allow.js
|
|
13462
|
-
import {
|
|
13530
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
13463
13531
|
import { existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync11, rmSync as rmSync4, writeFileSync as writeFileSync7 } from "node:fs";
|
|
13464
|
-
import { dirname as
|
|
13532
|
+
import { dirname as dirname6, join as join12 } from "node:path";
|
|
13465
13533
|
var LOCAL_SETTINGS_REL2 = ".claude/settings.local.json";
|
|
13466
13534
|
var LOCAL_ALLOW_RECORD_REL = ".halfcycle/local-allow.json";
|
|
13467
13535
|
var LOCAL_ALLOW_EDIT_MODE = "acceptEdits";
|
|
@@ -13469,6 +13537,7 @@ var SCRIPTS = ["test", "build", "lint", "typecheck"];
|
|
|
13469
13537
|
var SCRIPT_RUNNERS = ["npm run", "pnpm", "pnpm run", "yarn", "bun run"];
|
|
13470
13538
|
var HALFCYCLE_TOOLS_RULE = `mcp__${MCP_SERVER_KEY}`;
|
|
13471
13539
|
var bash = (command) => `Bash(${command}:*)`;
|
|
13540
|
+
var INSTALLED_HOOK_SCRIPTS = Object.keys(OWNED_GENERATED_HEADERS).filter((path) => path.startsWith(".claude/hooks/"));
|
|
13472
13541
|
var LOCAL_ALLOW_RULES = [
|
|
13473
13542
|
...SCRIPT_RUNNERS.flatMap((runner) => SCRIPTS.map((script) => bash(`${runner} ${script}`))),
|
|
13474
13543
|
bash("npm test"),
|
|
@@ -13478,8 +13547,14 @@ var LOCAL_ALLOW_RULES = [
|
|
|
13478
13547
|
bash("cargo test"),
|
|
13479
13548
|
bash("cargo build"),
|
|
13480
13549
|
bash("cargo check"),
|
|
13550
|
+
bash("cargo run"),
|
|
13551
|
+
bash("go run"),
|
|
13481
13552
|
bash("pytest"),
|
|
13482
13553
|
bash("python -m pytest"),
|
|
13554
|
+
bash("bun test"),
|
|
13555
|
+
bash("bun build"),
|
|
13556
|
+
bash("deno test"),
|
|
13557
|
+
bash("deno check"),
|
|
13483
13558
|
bash("make test"),
|
|
13484
13559
|
bash("make build"),
|
|
13485
13560
|
bash("git status"),
|
|
@@ -13509,11 +13584,20 @@ var LOCAL_ALLOW_RULES = [
|
|
|
13509
13584
|
bash("go mod tidy"),
|
|
13510
13585
|
bash("go get"),
|
|
13511
13586
|
bash("cargo add"),
|
|
13587
|
+
bash("pip install"),
|
|
13588
|
+
bash("pip3 install"),
|
|
13589
|
+
bash("bundle install"),
|
|
13512
13590
|
bash("node"),
|
|
13513
13591
|
bash("npx"),
|
|
13514
13592
|
bash("pnpm exec"),
|
|
13515
13593
|
bash("bunx"),
|
|
13594
|
+
bash("python3"),
|
|
13595
|
+
bash("python"),
|
|
13596
|
+
bash("ruby"),
|
|
13597
|
+
bash("deno run"),
|
|
13598
|
+
bash("bundle exec"),
|
|
13516
13599
|
"Bash(node_modules/.bin/*)",
|
|
13600
|
+
...INSTALLED_HOOK_SCRIPTS.flatMap((path) => [bash(`./${path}`), bash(`bash ${path}`), bash(`bash ./${path}`)]),
|
|
13517
13601
|
bash("mkdir"),
|
|
13518
13602
|
bash("touch"),
|
|
13519
13603
|
bash("cp"),
|
|
@@ -13537,10 +13621,10 @@ var LOCAL_ALLOW_RULES = [
|
|
|
13537
13621
|
HALFCYCLE_TOOLS_RULE
|
|
13538
13622
|
];
|
|
13539
13623
|
var LOCAL_ALLOW_QUESTION = "Let your coding agent work without asking on this computer? It would run your tests and builds, install packages, run local tools, make and move files, use git locally, run Halfcycle, and edit files in this project without a permission prompt. Pushing, publishing, deploying to production, deleting and changing anything on GitHub still ask. This is saved only on this computer, in .claude/settings.local.json, which is not committed.\nType yes to allow it, or press Enter to keep asking: ";
|
|
13540
|
-
var LOCAL_ALLOW_ON_LINE =
|
|
13541
|
-
var LOCAL_ALLOW_OFF_LINE =
|
|
13624
|
+
var LOCAL_ALLOW_ON_LINE = `Your coding agent can now work without asking on this computer. To undo it, run: ${ownCommand()} allow --remove`;
|
|
13625
|
+
var LOCAL_ALLOW_OFF_LINE = `Your coding agent will ask before it runs commands. To stop most of those questions on this computer, run: ${ownCommand()} allow`;
|
|
13542
13626
|
var LOCAL_ALLOW_TRACKED_LINE = "Your .claude/settings.local.json is committed to git, so Halfcycle will not add rules to it. Remove it from git first if you want them.";
|
|
13543
|
-
var LOCAL_ALLOW_UNREADABLE_LINE =
|
|
13627
|
+
var LOCAL_ALLOW_UNREADABLE_LINE = `Halfcycle did not change .claude/settings.local.json because it is not a JSON object it can add to. Fix the file, then run: ${ownCommand()} allow`;
|
|
13544
13628
|
var LOCAL_ALLOW_NOTHING_TO_REMOVE_LINE = "Halfcycle has not added any rules on this computer, so there is nothing to remove.";
|
|
13545
13629
|
var LOCAL_ALLOW_REMOVED_LINE = "Halfcycle took out the rules it added. Everything you had stays.";
|
|
13546
13630
|
function isYes(answer) {
|
|
@@ -13550,12 +13634,12 @@ function isObject2(value) {
|
|
|
13550
13634
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13551
13635
|
}
|
|
13552
13636
|
function trackedByGit(repo) {
|
|
13553
|
-
|
|
13554
|
-
|
|
13555
|
-
|
|
13556
|
-
|
|
13557
|
-
|
|
13558
|
-
|
|
13637
|
+
try {
|
|
13638
|
+
execFileSync3("git", ["-C", repo, "ls-files", "--error-unmatch", "--", LOCAL_SETTINGS_REL2], { stdio: "ignore" });
|
|
13639
|
+
return true;
|
|
13640
|
+
} catch {
|
|
13641
|
+
return false;
|
|
13642
|
+
}
|
|
13559
13643
|
}
|
|
13560
13644
|
function readRecord2(repo) {
|
|
13561
13645
|
const path = join12(repo, LOCAL_ALLOW_RECORD_REL);
|
|
@@ -13576,7 +13660,7 @@ function readRecord2(repo) {
|
|
|
13576
13660
|
}
|
|
13577
13661
|
function writeRecord(repo, record2) {
|
|
13578
13662
|
const path = join12(repo, LOCAL_ALLOW_RECORD_REL);
|
|
13579
|
-
mkdirSync7(
|
|
13663
|
+
mkdirSync7(dirname6(path), { recursive: true });
|
|
13580
13664
|
writeFileSync7(path, JSON.stringify({ rules: record2.rules, defaultMode: record2.defaultMode, before: record2.before }, null, 2) + "\n");
|
|
13581
13665
|
}
|
|
13582
13666
|
function holdsLocalAllowRecord(repo) {
|
|
@@ -13627,7 +13711,7 @@ function writeLocalAllow(repo) {
|
|
|
13627
13711
|
if (setsMode)
|
|
13628
13712
|
permissions["defaultMode"] = LOCAL_ALLOW_EDIT_MODE;
|
|
13629
13713
|
const next = { ...settings, permissions };
|
|
13630
|
-
mkdirSync7(
|
|
13714
|
+
mkdirSync7(dirname6(path), { recursive: true });
|
|
13631
13715
|
writeFileSync7(path, render(next, text));
|
|
13632
13716
|
const earlier = readRecord2(repo);
|
|
13633
13717
|
writeRecord(repo, {
|
|
@@ -13733,12 +13817,257 @@ function localAllowClosingLine(repo, said) {
|
|
|
13733
13817
|
return writeLocalAllow(repo).line;
|
|
13734
13818
|
return holdsLocalAllowRecord(repo) ? LOCAL_ALLOW_ON_LINE : LOCAL_ALLOW_OFF_LINE;
|
|
13735
13819
|
}
|
|
13820
|
+
var ENABLED_SERVERS_KEY = "enabledMcpjsonServers";
|
|
13821
|
+
var DISABLED_SERVERS_KEY = "disabledMcpjsonServers";
|
|
13822
|
+
function registersOwnServer(repo) {
|
|
13823
|
+
const path = join12(repo, MCP_REGISTRATION_REL);
|
|
13824
|
+
const registration = parseObject(existsSync8(path) ? readFileSync11(path, "utf8") : null);
|
|
13825
|
+
const servers = registration?.["mcpServers"];
|
|
13826
|
+
return isObject2(servers) && isObject2(servers[MCP_SERVER_KEY]);
|
|
13827
|
+
}
|
|
13828
|
+
function stringsIn(value) {
|
|
13829
|
+
return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
|
|
13830
|
+
}
|
|
13831
|
+
function approveOwnServer(repo) {
|
|
13832
|
+
const plan = planOwnServerApproval(repo, registersOwnServer(repo));
|
|
13833
|
+
if (plan.after !== null) {
|
|
13834
|
+
const path = join12(repo, LOCAL_SETTINGS_REL2);
|
|
13835
|
+
mkdirSync7(dirname6(path), { recursive: true });
|
|
13836
|
+
writeFileSync7(path, plan.after);
|
|
13837
|
+
}
|
|
13838
|
+
return plan.outcome;
|
|
13839
|
+
}
|
|
13840
|
+
function planOwnServerApproval(repo, registered) {
|
|
13841
|
+
const path = join12(repo, LOCAL_SETTINGS_REL2);
|
|
13842
|
+
const text = existsSync8(path) ? readFileSync11(path, "utf8") : null;
|
|
13843
|
+
const unchanged = (outcome) => ({ outcome, before: text, after: null });
|
|
13844
|
+
if (!registered)
|
|
13845
|
+
return unchanged("no-server");
|
|
13846
|
+
if (trackedByGit(repo))
|
|
13847
|
+
return unchanged("tracked");
|
|
13848
|
+
const settings = text === null ? {} : parseObject(text);
|
|
13849
|
+
if (settings === null)
|
|
13850
|
+
return unchanged("unreadable");
|
|
13851
|
+
const permissionsRaw = settings["permissions"];
|
|
13852
|
+
if (permissionsRaw !== void 0 && !isObject2(permissionsRaw))
|
|
13853
|
+
return unchanged("unreadable");
|
|
13854
|
+
const allowRaw = permissionsRaw?.["allow"];
|
|
13855
|
+
const enabledRaw = settings[ENABLED_SERVERS_KEY];
|
|
13856
|
+
if (allowRaw !== void 0 && !Array.isArray(allowRaw))
|
|
13857
|
+
return unchanged("unreadable");
|
|
13858
|
+
if (enabledRaw !== void 0 && !Array.isArray(enabledRaw))
|
|
13859
|
+
return unchanged("unreadable");
|
|
13860
|
+
if (stringsIn(settings[DISABLED_SERVERS_KEY]).includes(MCP_SERVER_KEY))
|
|
13861
|
+
return unchanged("declined");
|
|
13862
|
+
const enables = !stringsIn(enabledRaw).includes(MCP_SERVER_KEY);
|
|
13863
|
+
const allows = !stringsIn(allowRaw).includes(HALFCYCLE_TOOLS_RULE);
|
|
13864
|
+
if (!enables && !allows)
|
|
13865
|
+
return unchanged("approved");
|
|
13866
|
+
const next = { ...settings };
|
|
13867
|
+
if (enables)
|
|
13868
|
+
next[ENABLED_SERVERS_KEY] = [...Array.isArray(enabledRaw) ? enabledRaw : [], MCP_SERVER_KEY];
|
|
13869
|
+
if (allows) {
|
|
13870
|
+
const permissions = { ...permissionsRaw ?? {} };
|
|
13871
|
+
permissions["allow"] = [...Array.isArray(allowRaw) ? allowRaw : [], HALFCYCLE_TOOLS_RULE];
|
|
13872
|
+
next["permissions"] = permissions;
|
|
13873
|
+
}
|
|
13874
|
+
const after = render(next, text);
|
|
13875
|
+
return { outcome: "approved", before: text, after: after === text ? null : after };
|
|
13876
|
+
}
|
|
13877
|
+
function removeOwnServerApproval(repo) {
|
|
13878
|
+
const path = join12(repo, LOCAL_SETTINGS_REL2);
|
|
13879
|
+
if (!existsSync8(path) || trackedByGit(repo))
|
|
13880
|
+
return false;
|
|
13881
|
+
const text = readFileSync11(path, "utf8");
|
|
13882
|
+
const settings = parseObject(text);
|
|
13883
|
+
if (settings === null)
|
|
13884
|
+
return false;
|
|
13885
|
+
const next = { ...settings };
|
|
13886
|
+
let changed = false;
|
|
13887
|
+
const enabled = next[ENABLED_SERVERS_KEY];
|
|
13888
|
+
if (Array.isArray(enabled) && enabled.includes(MCP_SERVER_KEY)) {
|
|
13889
|
+
const kept = enabled.filter((name) => name !== MCP_SERVER_KEY);
|
|
13890
|
+
if (kept.length > 0)
|
|
13891
|
+
next[ENABLED_SERVERS_KEY] = kept;
|
|
13892
|
+
else
|
|
13893
|
+
delete next[ENABLED_SERVERS_KEY];
|
|
13894
|
+
changed = true;
|
|
13895
|
+
}
|
|
13896
|
+
const permissions = next["permissions"];
|
|
13897
|
+
if (isObject2(permissions) && Array.isArray(permissions["allow"]) && permissions["allow"].includes(HALFCYCLE_TOOLS_RULE)) {
|
|
13898
|
+
const nextPermissions = { ...permissions };
|
|
13899
|
+
const kept = permissions["allow"].filter((rule) => rule !== HALFCYCLE_TOOLS_RULE);
|
|
13900
|
+
if (kept.length > 0)
|
|
13901
|
+
nextPermissions["allow"] = kept;
|
|
13902
|
+
else
|
|
13903
|
+
delete nextPermissions["allow"];
|
|
13904
|
+
if (Object.keys(nextPermissions).length > 0)
|
|
13905
|
+
next["permissions"] = nextPermissions;
|
|
13906
|
+
else
|
|
13907
|
+
delete next["permissions"];
|
|
13908
|
+
changed = true;
|
|
13909
|
+
}
|
|
13910
|
+
if (!changed)
|
|
13911
|
+
return false;
|
|
13912
|
+
if (Object.keys(next).length === 0)
|
|
13913
|
+
rmSync4(path, { force: true });
|
|
13914
|
+
else
|
|
13915
|
+
writeFileSync7(path, render(next, text));
|
|
13916
|
+
return true;
|
|
13917
|
+
}
|
|
13918
|
+
|
|
13919
|
+
// dist/preview.js
|
|
13920
|
+
var CLAUDE_MD_REL = "CLAUDE.md";
|
|
13921
|
+
var AGENTS_IMPORT_LINE = "@AGENTS.md";
|
|
13922
|
+
var WORDS = {
|
|
13923
|
+
intro: "Halfcycle would set up this folder like this. Nothing has been written.",
|
|
13924
|
+
remove: "an older Halfcycle file this version no longer uses",
|
|
13925
|
+
claudeMdWillAdd: "already holds your own instructions; your first session will add one line to it, pointing to AGENTS.md",
|
|
13926
|
+
claudeMdAlreadyPoints: "already holds your own instructions, including the line pointing to AGENTS.md",
|
|
13927
|
+
ownServerAdd: "lets Claude Code use Halfcycle's tools on this computer without asking each time; kept out of git",
|
|
13928
|
+
ownServerChange: "adds the lines that let Claude Code use Halfcycle's tools without asking; your own lines stay as they are",
|
|
13929
|
+
ownServerLeave: "already lets Claude Code use Halfcycle's tools without asking",
|
|
13930
|
+
ownServerDeclined: "you turned Halfcycle's tools off in this folder; that choice stays",
|
|
13931
|
+
ownServerYours: "a file of yours Halfcycle will not add to; Claude Code will ask before using Halfcycle's tools",
|
|
13932
|
+
signIn: " This project's sign-in, kept on this computer for you only, in your home folder's .halfcycle folder.",
|
|
13933
|
+
newProject: " A new project on Halfcycle's service for this folder, made when you sign in.",
|
|
13934
|
+
gitForWindows: " Git for Windows, which Claude Code needs: setting up will ask before installing it with winget.",
|
|
13935
|
+
firstSession: "Your first session will then write your project's own files as you answer its questions, starting with AGENTS.md and CLAUDE.md, which hold your project's own instructions.",
|
|
13936
|
+
toSetUp: `To set up for real, run: ${ownCommand()}`,
|
|
13937
|
+
planFailed: "halfcycle: setting up this folder would stop on a file that could not be read. Nothing has been written; fix that file, then run the preview again."
|
|
13938
|
+
};
|
|
13939
|
+
function runPreview(targetRepo, options) {
|
|
13940
|
+
const refusal3 = previewRefusal(targetRepo, options);
|
|
13941
|
+
if (refusal3 !== void 0)
|
|
13942
|
+
return { refusal: refusal3 };
|
|
13943
|
+
let plan;
|
|
13944
|
+
try {
|
|
13945
|
+
plan = planInstall({ targetRepo, env: options.env, home: options.home });
|
|
13946
|
+
} catch {
|
|
13947
|
+
return { refusal: WORDS.planFailed };
|
|
13948
|
+
}
|
|
13949
|
+
if (plan.refusal !== void 0)
|
|
13950
|
+
return { refusal: plan.refusal };
|
|
13951
|
+
return {
|
|
13952
|
+
text: renderPreview(plan, gitBashPath(options.env) === void 0 && options.platform === "win32", claudeMdLine(targetRepo), ownServerRow(targetRepo, plan))
|
|
13953
|
+
};
|
|
13954
|
+
}
|
|
13955
|
+
function ownServerRow(targetRepo, plan) {
|
|
13956
|
+
const serverEntry = plan.entries.find((entry) => entry.path === MCP_REGISTRATION_REL);
|
|
13957
|
+
const registered = serverEntry !== void 0 && (serverEntry.action === "add" || serverEntry.action === "change") ? true : registersOwnServer(targetRepo);
|
|
13958
|
+
const decided = planOwnServerApproval(targetRepo, registered);
|
|
13959
|
+
switch (decided.outcome) {
|
|
13960
|
+
case "no-server":
|
|
13961
|
+
return void 0;
|
|
13962
|
+
case "approved":
|
|
13963
|
+
if (decided.after === null)
|
|
13964
|
+
return { action: "leave", words: WORDS.ownServerLeave };
|
|
13965
|
+
return decided.before === null ? { action: "add", words: WORDS.ownServerAdd } : { action: "change", words: WORDS.ownServerChange };
|
|
13966
|
+
case "declined":
|
|
13967
|
+
return { action: "leave", words: WORDS.ownServerDeclined };
|
|
13968
|
+
case "tracked":
|
|
13969
|
+
case "unreadable":
|
|
13970
|
+
return { action: "leave", words: WORDS.ownServerYours };
|
|
13971
|
+
}
|
|
13972
|
+
}
|
|
13973
|
+
function claudeMdLine(targetRepo) {
|
|
13974
|
+
const path = join13(targetRepo, CLAUDE_MD_REL);
|
|
13975
|
+
if (!existsSync9(path))
|
|
13976
|
+
return void 0;
|
|
13977
|
+
let text;
|
|
13978
|
+
try {
|
|
13979
|
+
text = readFileSync12(path, "utf-8");
|
|
13980
|
+
} catch {
|
|
13981
|
+
return WORDS.claudeMdWillAdd;
|
|
13982
|
+
}
|
|
13983
|
+
return text.includes(AGENTS_IMPORT_LINE) ? WORDS.claudeMdAlreadyPoints : WORDS.claudeMdWillAdd;
|
|
13984
|
+
}
|
|
13985
|
+
function previewRefusal(targetRepo, options) {
|
|
13986
|
+
if (!existsSync9(targetRepo))
|
|
13987
|
+
return folderMissingRefusal(targetRepo);
|
|
13988
|
+
if (!statSync(targetRepo).isDirectory())
|
|
13989
|
+
return folderIsFileRefusal(targetRepo);
|
|
13990
|
+
if (isHalfcycleMonorepo(targetRepo))
|
|
13991
|
+
return monorepoRefusal(targetRepo);
|
|
13992
|
+
if (isHomeFolder(targetRepo))
|
|
13993
|
+
return homeFolderRefusal(targetRepo);
|
|
13994
|
+
try {
|
|
13995
|
+
refuseSystemFolder(targetRepo, options.platform);
|
|
13996
|
+
refuseInsideProject(targetRepo);
|
|
13997
|
+
} catch (err) {
|
|
13998
|
+
if (err instanceof PreCreateRefused)
|
|
13999
|
+
return err.message;
|
|
14000
|
+
throw err;
|
|
14001
|
+
}
|
|
14002
|
+
try {
|
|
14003
|
+
accessSync(targetRepo, constants.W_OK);
|
|
14004
|
+
} catch {
|
|
14005
|
+
return cannotWriteRefusal(targetRepo);
|
|
14006
|
+
}
|
|
14007
|
+
if (existsSync9(join13(targetRepo, PROJECT_IDENTITY_REL)) && readFolderId(targetRepo) === void 0) {
|
|
14008
|
+
return unreadableProjectRefusal(targetRepo);
|
|
14009
|
+
}
|
|
14010
|
+
try {
|
|
14011
|
+
checkSettingsParse(targetRepo);
|
|
14012
|
+
} catch (err) {
|
|
14013
|
+
if (err instanceof PreCreateRefused)
|
|
14014
|
+
return err.message;
|
|
14015
|
+
throw err;
|
|
14016
|
+
}
|
|
14017
|
+
return void 0;
|
|
14018
|
+
}
|
|
14019
|
+
function wordsOf(entry) {
|
|
14020
|
+
const words = PREVIEW_WORDS[entry.kind];
|
|
14021
|
+
switch (entry.action) {
|
|
14022
|
+
case "add":
|
|
14023
|
+
return words.add;
|
|
14024
|
+
case "change":
|
|
14025
|
+
return words.change;
|
|
14026
|
+
case "remove":
|
|
14027
|
+
return WORDS.remove;
|
|
14028
|
+
case "leave":
|
|
14029
|
+
return entry.yours === true ? words.yours : words.leave;
|
|
14030
|
+
}
|
|
14031
|
+
}
|
|
14032
|
+
var HEADINGS = [
|
|
14033
|
+
["add", "Add:"],
|
|
14034
|
+
["change", "Change:"],
|
|
14035
|
+
["remove", "Remove:"],
|
|
14036
|
+
["leave", "Leave alone:"]
|
|
14037
|
+
];
|
|
14038
|
+
function outsideLine2(outside) {
|
|
14039
|
+
return outside.kind === "sign-in" ? WORDS.signIn : WORDS.newProject;
|
|
14040
|
+
}
|
|
14041
|
+
function renderPreview(plan, missingGitForWindows, claudeMdWords, ownServer) {
|
|
14042
|
+
const lines = [WORDS.intro];
|
|
14043
|
+
for (const [action, heading] of HEADINGS) {
|
|
14044
|
+
const rows = plan.entries.filter((entry) => entry.action === action);
|
|
14045
|
+
const claudeMdRow = action === "leave" && claudeMdWords !== void 0;
|
|
14046
|
+
const ownServerHere = ownServer !== void 0 && ownServer.action === action;
|
|
14047
|
+
if (rows.length === 0 && !claudeMdRow && !ownServerHere)
|
|
14048
|
+
continue;
|
|
14049
|
+
lines.push("", heading);
|
|
14050
|
+
for (const entry of rows)
|
|
14051
|
+
lines.push(` ${entry.path} \u2014 ${wordsOf(entry)}`);
|
|
14052
|
+
if (ownServerHere)
|
|
14053
|
+
lines.push(` ${LOCAL_SETTINGS_REL2} \u2014 ${ownServer.words}`);
|
|
14054
|
+
if (claudeMdRow)
|
|
14055
|
+
lines.push(` ${CLAUDE_MD_REL} \u2014 ${claudeMdWords}`);
|
|
14056
|
+
}
|
|
14057
|
+
lines.push("", "Outside this folder:");
|
|
14058
|
+
for (const outside of plan.outside)
|
|
14059
|
+
lines.push(outsideLine2(outside));
|
|
14060
|
+
if (missingGitForWindows)
|
|
14061
|
+
lines.push(WORDS.gitForWindows);
|
|
14062
|
+
lines.push("", WORDS.firstSession, "", WORDS.toSetUp);
|
|
14063
|
+
return lines.join("\n");
|
|
14064
|
+
}
|
|
13736
14065
|
|
|
13737
14066
|
// dist/doctor.js
|
|
13738
|
-
import { spawnSync as
|
|
13739
|
-
import { existsSync as
|
|
14067
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
14068
|
+
import { existsSync as existsSync10, lstatSync as lstatSync2, readFileSync as readFileSync13, realpathSync as realpathSync4, statSync as statSync2 } from "node:fs";
|
|
13740
14069
|
import { homedir as homedir6 } from "node:os";
|
|
13741
|
-
import { join as
|
|
14070
|
+
import { join as join14 } from "node:path";
|
|
13742
14071
|
var GUARD_RUNNER_REL2 = ".claude/hooks/guard-runner.sh";
|
|
13743
14072
|
var CONNECTION_VERB = "check-connection";
|
|
13744
14073
|
var WRAPPER_TIMEOUT_MS = 3e4;
|
|
@@ -13777,13 +14106,13 @@ function runDoctor(root, options) {
|
|
|
13777
14106
|
};
|
|
13778
14107
|
}
|
|
13779
14108
|
function setUpLine(root, upToDate) {
|
|
13780
|
-
if (!
|
|
14109
|
+
if (!existsSync10(join14(root, BUNDLE_PIN_REL))) {
|
|
13781
14110
|
return {
|
|
13782
14111
|
state: "no",
|
|
13783
14112
|
hooksMissing: false,
|
|
13784
14113
|
line: {
|
|
13785
14114
|
ok: false,
|
|
13786
|
-
text: `Set up: no. Halfcycle is not set up in this folder. To set it up, run ${
|
|
14115
|
+
text: `Set up: no. Halfcycle is not set up in this folder. To set it up, run ${ownCommand()} here.`
|
|
13787
14116
|
}
|
|
13788
14117
|
};
|
|
13789
14118
|
}
|
|
@@ -13791,9 +14120,9 @@ function setUpLine(root, upToDate) {
|
|
|
13791
14120
|
const missing = [];
|
|
13792
14121
|
if (!pinNamesProject(root))
|
|
13793
14122
|
missing.push("Halfcycle's project record");
|
|
13794
|
-
if (!
|
|
14123
|
+
if (!existsSync10(join14(root, INSTALL_MANIFEST_REL)))
|
|
13795
14124
|
missing.push("the install record");
|
|
13796
|
-
if (!
|
|
14125
|
+
if (!existsSync10(join14(root, VENDORED_BIN_REL)))
|
|
13797
14126
|
missing.push("Halfcycle's check program");
|
|
13798
14127
|
if (!mcpNamesServer(root))
|
|
13799
14128
|
missing.push("Halfcycle's server entry");
|
|
@@ -13806,7 +14135,7 @@ function setUpLine(root, upToDate) {
|
|
|
13806
14135
|
hooksMissing,
|
|
13807
14136
|
line: {
|
|
13808
14137
|
ok: false,
|
|
13809
|
-
text: `Set up: not complete. Missing: ${naturalList(missing)}. To put it back, run ${
|
|
14138
|
+
text: `Set up: not complete. Missing: ${naturalList(missing)}. To put it back, run ${ownCommand()} here; nothing of yours is changed.`
|
|
13810
14139
|
}
|
|
13811
14140
|
};
|
|
13812
14141
|
}
|
|
@@ -13819,14 +14148,14 @@ function pinNamesProject(root) {
|
|
|
13819
14148
|
}
|
|
13820
14149
|
}
|
|
13821
14150
|
function mcpNamesServer(root) {
|
|
13822
|
-
const parsed = readJson(
|
|
14151
|
+
const parsed = readJson(join14(root, MCP_REGISTRATION_REL));
|
|
13823
14152
|
if (!isObject3(parsed) || !isObject3(parsed["mcpServers"]))
|
|
13824
14153
|
return false;
|
|
13825
14154
|
const entry = parsed["mcpServers"][MCP_SERVER_KEY];
|
|
13826
14155
|
return isObject3(entry) && typeof entry["url"] === "string";
|
|
13827
14156
|
}
|
|
13828
14157
|
function settingsRegisterEveryCommand(root) {
|
|
13829
|
-
const parsed = readJson(
|
|
14158
|
+
const parsed = readJson(join14(root, SETTINGS_REL));
|
|
13830
14159
|
if (!isObject3(parsed) || !isObject3(parsed["hooks"]))
|
|
13831
14160
|
return false;
|
|
13832
14161
|
const registered = /* @__PURE__ */ new Set();
|
|
@@ -13857,10 +14186,10 @@ function startWrapper(root, options) {
|
|
|
13857
14186
|
}
|
|
13858
14187
|
shell = gitBash;
|
|
13859
14188
|
}
|
|
13860
|
-
const wrapper =
|
|
13861
|
-
if (!
|
|
14189
|
+
const wrapper = join14(root, GUARD_RUNNER_REL2);
|
|
14190
|
+
if (!existsSync10(wrapper))
|
|
13862
14191
|
return failed(repairSentence());
|
|
13863
|
-
const run =
|
|
14192
|
+
const run = spawnSync2(shell, [wrapper, CONNECTION_VERB], {
|
|
13864
14193
|
cwd: root,
|
|
13865
14194
|
env: { ...options.env, CLAUDE_PROJECT_DIR: root },
|
|
13866
14195
|
encoding: "utf-8",
|
|
@@ -13885,7 +14214,7 @@ function startWrapper(root, options) {
|
|
|
13885
14214
|
}
|
|
13886
14215
|
function settingsDisablingHooks(root) {
|
|
13887
14216
|
for (const rel of [SETTINGS_REL, LOCAL_SETTINGS_REL]) {
|
|
13888
|
-
const parsed = readJson(
|
|
14217
|
+
const parsed = readJson(join14(root, rel));
|
|
13889
14218
|
if (isObject3(parsed) && parsed["disableAllHooks"] === true)
|
|
13890
14219
|
return rel;
|
|
13891
14220
|
}
|
|
@@ -13915,7 +14244,7 @@ function parseAnswer(stdout) {
|
|
|
13915
14244
|
function usageOf(said) {
|
|
13916
14245
|
if (!Array.isArray(said))
|
|
13917
14246
|
return [];
|
|
13918
|
-
const
|
|
14247
|
+
const usage5 = [];
|
|
13919
14248
|
for (const figure of said) {
|
|
13920
14249
|
if (!isObject3(figure))
|
|
13921
14250
|
continue;
|
|
@@ -13928,9 +14257,9 @@ function usageOf(said) {
|
|
|
13928
14257
|
continue;
|
|
13929
14258
|
if (typeof resetsAt !== "string" || Number.isNaN(Date.parse(resetsAt)))
|
|
13930
14259
|
continue;
|
|
13931
|
-
|
|
14260
|
+
usage5.push({ agent, window, usedPercentage, resetsAt: new Date(resetsAt) });
|
|
13932
14261
|
}
|
|
13933
|
-
return
|
|
14262
|
+
return usage5;
|
|
13934
14263
|
}
|
|
13935
14264
|
function checksRunningLine(running, hooksMissing) {
|
|
13936
14265
|
if (running.state === "not-run")
|
|
@@ -13947,7 +14276,7 @@ function failed(text) {
|
|
|
13947
14276
|
return { state: "failed", line: { ok: false, text } };
|
|
13948
14277
|
}
|
|
13949
14278
|
function repairSentence() {
|
|
13950
|
-
return `Checks running: no. Halfcycle's check could not start. To repair it, run ${
|
|
14279
|
+
return `Checks running: no. Halfcycle's check could not start. To repair it, run ${ownCommand()} here.`;
|
|
13951
14280
|
}
|
|
13952
14281
|
function connectionLines(running) {
|
|
13953
14282
|
if (running.state !== "answered") {
|
|
@@ -13975,8 +14304,8 @@ function folderTrustLine(root, options) {
|
|
|
13975
14304
|
if (!mcpNamesServer(root))
|
|
13976
14305
|
return void 0;
|
|
13977
14306
|
const configFolder = options.env["CLAUDE_CONFIG_DIR"];
|
|
13978
|
-
const stateFile =
|
|
13979
|
-
const trust = trustOfWorkingFolder(root, stateFile, options.platform, { lstatSync: lstatSync2, readFileSync:
|
|
14307
|
+
const stateFile = join14(configFolder !== void 0 && configFolder !== "" ? configFolder : options.home ?? homedir6(), CLAUDE_STATE_FILENAME);
|
|
14308
|
+
const trust = trustOfWorkingFolder(root, stateFile, options.platform, { lstatSync: lstatSync2, readFileSync: readFileSync13, realpathSync: realpathSync4 });
|
|
13980
14309
|
return trust === "not-trusted" ? { ok: true, text: `Folder trust: ${FOLDER_NOT_TRUSTED_SENTENCE}` } : void 0;
|
|
13981
14310
|
}
|
|
13982
14311
|
function signInCodeLine(root, running, now, options) {
|
|
@@ -13986,7 +14315,7 @@ function signInCodeLine(root, running, now, options) {
|
|
|
13986
14315
|
return void 0;
|
|
13987
14316
|
return {
|
|
13988
14317
|
ok: true,
|
|
13989
|
-
text: `Sign-in code: it ran out before it was confirmed. To get a new one, run ${
|
|
14318
|
+
text: `Sign-in code: it ran out before it was confirmed. To get a new one, run ${ownCommand()} here.`
|
|
13990
14319
|
};
|
|
13991
14320
|
}
|
|
13992
14321
|
var AGENT_NAMES = { "claude-code": "Claude Code", codex: "Codex" };
|
|
@@ -14035,9 +14364,9 @@ function installedVersion(root) {
|
|
|
14035
14364
|
}
|
|
14036
14365
|
function versionNoLine(installed, current) {
|
|
14037
14366
|
if (installed === null) {
|
|
14038
|
-
return `Up to date: no. This folder has no Halfcycle version recorded; the newest is ${current}. To set it up, run ${
|
|
14367
|
+
return `Up to date: no. This folder has no Halfcycle version recorded; the newest is ${current}. To set it up, run ${ownCommand()} here.`;
|
|
14039
14368
|
}
|
|
14040
|
-
return `Up to date: no. This folder has Halfcycle ${installed}; the newest is ${current}. To update, run ${
|
|
14369
|
+
return `Up to date: no. This folder has Halfcycle ${installed}; the newest is ${current}. To update, run ${ownCommand()} here.`;
|
|
14041
14370
|
}
|
|
14042
14371
|
var CODEX_EVENT_SNAKE = {
|
|
14043
14372
|
SessionStart: "session_start",
|
|
@@ -14048,13 +14377,26 @@ var CODEX_EVENT_SNAKE = {
|
|
|
14048
14377
|
SubagentStop: "subagent_stop",
|
|
14049
14378
|
SessionEnd: "session_end"
|
|
14050
14379
|
};
|
|
14380
|
+
function codexHomeDir(options) {
|
|
14381
|
+
const codexHome = options.env["CODEX_HOME"];
|
|
14382
|
+
return codexHome !== void 0 && codexHome !== "" ? codexHome : join14(homedir6(), ".codex");
|
|
14383
|
+
}
|
|
14384
|
+
function isDirectory(path) {
|
|
14385
|
+
try {
|
|
14386
|
+
return statSync2(path).isDirectory();
|
|
14387
|
+
} catch {
|
|
14388
|
+
return false;
|
|
14389
|
+
}
|
|
14390
|
+
}
|
|
14051
14391
|
function codexLine(root, options) {
|
|
14052
|
-
const hooksPath =
|
|
14053
|
-
if (!
|
|
14392
|
+
const hooksPath = join14(root, CODEX_HOOKS_REL2);
|
|
14393
|
+
if (!existsSync10(hooksPath))
|
|
14394
|
+
return void 0;
|
|
14395
|
+
if (!isDirectory(codexHomeDir(options)))
|
|
14054
14396
|
return void 0;
|
|
14055
14397
|
const positions = codexEntryPositions(hooksPath);
|
|
14056
14398
|
if (positions === void 0) {
|
|
14057
|
-
return { ok: false, text: `Codex: not complete. To repair it, run ${
|
|
14399
|
+
return { ok: false, text: `Codex: not complete. To repair it, run ${ownCommand()} here.` };
|
|
14058
14400
|
}
|
|
14059
14401
|
return codexApprovalStatus(root, positions, options) === "approved" ? { ok: true, text: "Codex: set up, and its checks are approved." } : {
|
|
14060
14402
|
ok: false,
|
|
@@ -14090,17 +14432,16 @@ function codexEntryPositions(hooksPath) {
|
|
|
14090
14432
|
return positions;
|
|
14091
14433
|
}
|
|
14092
14434
|
function codexApprovalStatus(root, positions, options) {
|
|
14093
|
-
const
|
|
14094
|
-
const configPath = join13(codexHome !== void 0 && codexHome !== "" ? codexHome : join13(homedir6(), ".codex"), "config.toml");
|
|
14435
|
+
const configPath = join14(codexHomeDir(options), "config.toml");
|
|
14095
14436
|
let text;
|
|
14096
14437
|
try {
|
|
14097
|
-
text =
|
|
14438
|
+
text = readFileSync13(configPath, "utf-8");
|
|
14098
14439
|
} catch {
|
|
14099
14440
|
return "not-approved";
|
|
14100
14441
|
}
|
|
14101
14442
|
let hooksPath;
|
|
14102
14443
|
try {
|
|
14103
|
-
hooksPath =
|
|
14444
|
+
hooksPath = join14(realpathSync4(root), CODEX_HOOKS_REL2);
|
|
14104
14445
|
} catch {
|
|
14105
14446
|
return "not-approved";
|
|
14106
14447
|
}
|
|
@@ -14130,10 +14471,10 @@ function naturalList(items) {
|
|
|
14130
14471
|
return `${items.slice(0, -1).join(", ")} and ${items[items.length - 1]}`;
|
|
14131
14472
|
}
|
|
14132
14473
|
function readJson(path) {
|
|
14133
|
-
if (!
|
|
14474
|
+
if (!existsSync10(path))
|
|
14134
14475
|
return void 0;
|
|
14135
14476
|
try {
|
|
14136
|
-
return JSON.parse(
|
|
14477
|
+
return JSON.parse(readFileSync13(path, "utf-8"));
|
|
14137
14478
|
} catch {
|
|
14138
14479
|
return void 0;
|
|
14139
14480
|
}
|
|
@@ -14143,12 +14484,12 @@ function isObject3(value) {
|
|
|
14143
14484
|
}
|
|
14144
14485
|
|
|
14145
14486
|
// dist/claude-version-probe.js
|
|
14146
|
-
import { execFileSync as
|
|
14147
|
-
import { existsSync as
|
|
14148
|
-
import { delimiter as delimiter2, join as
|
|
14487
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
14488
|
+
import { existsSync as existsSync11, realpathSync as realpathSync5 } from "node:fs";
|
|
14489
|
+
import { delimiter as delimiter2, join as join15 } from "node:path";
|
|
14149
14490
|
function execVersionCommand(command, timeoutMs, deps) {
|
|
14150
14491
|
const platform6 = deps.platform ?? process.platform;
|
|
14151
|
-
const exec = deps.exec ??
|
|
14492
|
+
const exec = deps.exec ?? execFileSync4;
|
|
14152
14493
|
try {
|
|
14153
14494
|
return platform6 === "win32" ? exec("cmd.exe", ["/d", "/s", "/c", `"${command} --version"`], {
|
|
14154
14495
|
encoding: "utf-8",
|
|
@@ -14188,7 +14529,7 @@ var INSTALL_METHOD_RULES = [
|
|
|
14188
14529
|
function probeClaudeCodeInstallMethod(deps = {}) {
|
|
14189
14530
|
const env = deps.env ?? process.env;
|
|
14190
14531
|
const platform6 = deps.platform ?? process.platform;
|
|
14191
|
-
const exists = deps.exists ??
|
|
14532
|
+
const exists = deps.exists ?? existsSync11;
|
|
14192
14533
|
const realpath = deps.realpath ?? ((file) => {
|
|
14193
14534
|
try {
|
|
14194
14535
|
return realpathSync5(file);
|
|
@@ -14200,7 +14541,7 @@ function probeClaudeCodeInstallMethod(deps = {}) {
|
|
|
14200
14541
|
const dirs = (env["PATH"] ?? "").split(delimiter2).filter((dir) => dir !== "");
|
|
14201
14542
|
for (const dir of dirs) {
|
|
14202
14543
|
for (const name of names) {
|
|
14203
|
-
const file =
|
|
14544
|
+
const file = join15(dir, name);
|
|
14204
14545
|
if (!exists(file))
|
|
14205
14546
|
continue;
|
|
14206
14547
|
const real = realpath(file);
|
|
@@ -14235,8 +14576,8 @@ function codexInstallLine(codex) {
|
|
|
14235
14576
|
}
|
|
14236
14577
|
|
|
14237
14578
|
// dist/build-record/write.js
|
|
14238
|
-
import { existsSync as
|
|
14239
|
-
import { basename as basename2, join as
|
|
14579
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync8, rmSync as rmSync6 } from "node:fs";
|
|
14580
|
+
import { basename as basename2, join as join16, resolve as resolve3, relative as relative2, isAbsolute as isAbsolute3 } from "node:path";
|
|
14240
14581
|
|
|
14241
14582
|
// dist/build-record/render-v2.js
|
|
14242
14583
|
var HIDDEN = /[\p{Cc}\p{Zl}\p{Zp}\p{Default_Ignorable_Code_Point}]/u;
|
|
@@ -14827,7 +15168,7 @@ function renderBuildRecordV2Markdown(record2) {
|
|
|
14827
15168
|
}
|
|
14828
15169
|
|
|
14829
15170
|
// dist/build-record/pair-io.js
|
|
14830
|
-
import { closeSync, openSync, readFileSync as
|
|
15171
|
+
import { closeSync, openSync, readFileSync as readFileSync14, renameSync as renameSync2, rmSync as rmSync5, writeSync as writeSync2 } from "node:fs";
|
|
14831
15172
|
function createExclusiveOnDisk(path, data) {
|
|
14832
15173
|
const bytes = typeof data === "string" ? Buffer.from(data, "utf-8") : data;
|
|
14833
15174
|
const fd = openSync(path, "wx");
|
|
@@ -14858,7 +15199,7 @@ function isEexist(err) {
|
|
|
14858
15199
|
}
|
|
14859
15200
|
function readIfPresent(path) {
|
|
14860
15201
|
try {
|
|
14861
|
-
return
|
|
15202
|
+
return readFileSync14(path, "utf-8");
|
|
14862
15203
|
} catch {
|
|
14863
15204
|
return null;
|
|
14864
15205
|
}
|
|
@@ -14890,7 +15231,7 @@ function replaceOne(io, tmp, finalPath, text) {
|
|
|
14890
15231
|
}
|
|
14891
15232
|
|
|
14892
15233
|
// dist/build-record/write.js
|
|
14893
|
-
var BUILD_RECORD_DIR =
|
|
15234
|
+
var BUILD_RECORD_DIR = join16("docs", "build-records");
|
|
14894
15235
|
function assertInsideOutDir(outDir, candidate, identity) {
|
|
14895
15236
|
const rel = relative2(outDir, resolve3(candidate));
|
|
14896
15237
|
const isPlainFilename = rel !== "" && rel !== ".." && !isAbsolute3(rel) && rel === basename2(rel);
|
|
@@ -14928,8 +15269,8 @@ function writeRecordPair(repoRoot, phase, rawRecord, record2, io = NODE_RECORD_P
|
|
|
14928
15269
|
}
|
|
14929
15270
|
const outDir = resolve3(repoRoot, BUILD_RECORD_DIR);
|
|
14930
15271
|
const segment = renderPhaseSegment(phase);
|
|
14931
|
-
const jsonPath =
|
|
14932
|
-
const mdPath =
|
|
15272
|
+
const jsonPath = join16(outDir, `${segment}.json`);
|
|
15273
|
+
const mdPath = join16(outDir, `${segment}.md`);
|
|
14933
15274
|
const jsonTmp = `${jsonPath}.tmp`;
|
|
14934
15275
|
const mdTmp = `${mdPath}.tmp`;
|
|
14935
15276
|
for (const candidate of [jsonPath, mdPath, jsonTmp, mdTmp]) {
|
|
@@ -14943,7 +15284,7 @@ function writeRecordPair(repoRoot, phase, rawRecord, record2, io = NODE_RECORD_P
|
|
|
14943
15284
|
}
|
|
14944
15285
|
const existingJson = readIfPresent(jsonPath);
|
|
14945
15286
|
if (existingJson === null) {
|
|
14946
|
-
return { kind: "in-the-way", path:
|
|
15287
|
+
return { kind: "in-the-way", path: existsSync12(jsonPath) ? jsonPath : mdPath };
|
|
14947
15288
|
}
|
|
14948
15289
|
if (existingJson === jsonText) {
|
|
14949
15290
|
replaceOne(io, mdTmp, mdPath, mdText);
|
|
@@ -14970,21 +15311,21 @@ function writeRecordPair(repoRoot, phase, rawRecord, record2, io = NODE_RECORD_P
|
|
|
14970
15311
|
import { relative as relative5 } from "node:path";
|
|
14971
15312
|
|
|
14972
15313
|
// dist/open-phase.js
|
|
14973
|
-
import { readFileSync as
|
|
14974
|
-
import { join as
|
|
15314
|
+
import { readFileSync as readFileSync20 } from "node:fs";
|
|
15315
|
+
import { join as join24 } from "node:path";
|
|
14975
15316
|
|
|
14976
15317
|
// dist/close-phase.js
|
|
14977
|
-
import { execFile, execFileSync as
|
|
14978
|
-
import { readFileSync as
|
|
14979
|
-
import { join as
|
|
15318
|
+
import { execFile, execFileSync as execFileSync6 } from "node:child_process";
|
|
15319
|
+
import { readFileSync as readFileSync19 } from "node:fs";
|
|
15320
|
+
import { join as join23 } from "node:path";
|
|
14980
15321
|
|
|
14981
15322
|
// dist/plan.js
|
|
14982
|
-
import { existsSync as
|
|
14983
|
-
import { join as
|
|
15323
|
+
import { existsSync as existsSync14, readFileSync as readFileSync18 } from "node:fs";
|
|
15324
|
+
import { join as join22 } from "node:path";
|
|
14984
15325
|
|
|
14985
15326
|
// dist/draw/write.js
|
|
14986
|
-
import { closeSync as closeSync2, lstatSync as lstatSync3, mkdirSync as mkdirSync9, openSync as openSync2, readFileSync as
|
|
14987
|
-
import { dirname as
|
|
15327
|
+
import { closeSync as closeSync2, lstatSync as lstatSync3, mkdirSync as mkdirSync9, openSync as openSync2, readFileSync as readFileSync15, readSync, realpathSync as realpathSync6, rmSync as rmSync7 } from "node:fs";
|
|
15328
|
+
import { dirname as dirname7, isAbsolute as isAbsolute4, join as join17, relative as relative3, resolve as resolve4, sep } from "node:path";
|
|
14988
15329
|
var DOCS_DIR = "docs";
|
|
14989
15330
|
var DIAGRAMS_DIR = "diagrams";
|
|
14990
15331
|
var DIAGRAM_DIR_REL = `${DOCS_DIR}/${DIAGRAMS_DIR}`;
|
|
@@ -15040,9 +15381,9 @@ function embedImageLink(base, doc) {
|
|
|
15040
15381
|
}
|
|
15041
15382
|
function diagramPaths(repoRoot, base) {
|
|
15042
15383
|
const root = resolve4(repoRoot);
|
|
15043
|
-
const dir =
|
|
15384
|
+
const dir = join17(root, DOCS_DIR, DIAGRAMS_DIR);
|
|
15044
15385
|
const names = fileNames(base);
|
|
15045
|
-
const file = Object.fromEntries(DIAGRAM_FILES.map((which) => [which,
|
|
15386
|
+
const file = Object.fromEntries(DIAGRAM_FILES.map((which) => [which, join17(dir, names[which])]));
|
|
15046
15387
|
return { repoRoot: root, dir, file };
|
|
15047
15388
|
}
|
|
15048
15389
|
function diagramRel(base, which) {
|
|
@@ -15077,7 +15418,7 @@ function checkDiagramPlace(paths) {
|
|
|
15077
15418
|
}
|
|
15078
15419
|
let ancestor = paths.dir;
|
|
15079
15420
|
while (lstatOrNull2(ancestor) === null) {
|
|
15080
|
-
const parent =
|
|
15421
|
+
const parent = dirname7(ancestor);
|
|
15081
15422
|
if (parent === ancestor)
|
|
15082
15423
|
break;
|
|
15083
15424
|
ancestor = parent;
|
|
@@ -15137,7 +15478,7 @@ function readExistingSet(paths) {
|
|
|
15137
15478
|
if (!stat.isFile())
|
|
15138
15479
|
return { ok: false, inTheWay: which, temp: false };
|
|
15139
15480
|
try {
|
|
15140
|
-
found[which] =
|
|
15481
|
+
found[which] = readFileSync15(paths.file[which]);
|
|
15141
15482
|
} catch {
|
|
15142
15483
|
return { ok: false, inTheWay: which, temp: false };
|
|
15143
15484
|
}
|
|
@@ -15315,10 +15656,10 @@ function isMissingRoute(method, status, body) {
|
|
|
15315
15656
|
}
|
|
15316
15657
|
|
|
15317
15658
|
// dist/change.js
|
|
15318
|
-
import { join as
|
|
15659
|
+
import { join as join20 } from "node:path";
|
|
15319
15660
|
|
|
15320
15661
|
// dist/cli-contract.js
|
|
15321
|
-
var CLI_VERBS = ["install", "check-drift", "build-record", "open-phase", "close-phase", "system", "draw", "plan", "review", "check-tasks", "check-criteria", "check-report", "ready", "uninstall", "change", "pace", "route", "prove", "preview", "doctor", "restore", "request-shapes", "allow", "size"];
|
|
15662
|
+
var CLI_VERBS = ["install", "check-drift", "build-record", "open-phase", "close-phase", "system", "draw", "plan", "review", "check-tasks", "check-criteria", "check-report", "ready", "uninstall", "change", "pace", "route", "prove", "preview", "doctor", "restore", "request-shapes", "allow", "size", "regression", "crew"];
|
|
15322
15663
|
var CONTRACTS = [
|
|
15323
15664
|
{ verb: "install", required: [], conditional: [] },
|
|
15324
15665
|
{ verb: "check-drift", required: [], conditional: [] },
|
|
@@ -15522,7 +15863,7 @@ function requirementMessage(verb, flag) {
|
|
|
15522
15863
|
function runnableInvocation(verb, phase) {
|
|
15523
15864
|
const contract = contractFor(verb);
|
|
15524
15865
|
const flags = (contract?.required ?? []).map((req) => `${req.flag} ${PLACEHOLDERS[req.flag] ?? '"\u2026"'}`).join(" ");
|
|
15525
|
-
return `${
|
|
15866
|
+
return `${ownCommand()} ${verb} ${quoteForShell(phase)}${flags ? ` ${flags}` : ""}`;
|
|
15526
15867
|
}
|
|
15527
15868
|
var PLACEHOLDERS = {
|
|
15528
15869
|
"--verdict": "<clean|defects>",
|
|
@@ -15558,13 +15899,13 @@ function placeholderRefusal(verb, tokens) {
|
|
|
15558
15899
|
}
|
|
15559
15900
|
|
|
15560
15901
|
// dist/system-report.js
|
|
15561
|
-
import { execFileSync as
|
|
15562
|
-
import { readFileSync as
|
|
15563
|
-
import { join as
|
|
15902
|
+
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
15903
|
+
import { readFileSync as readFileSync16 } from "node:fs";
|
|
15904
|
+
import { join as join18 } from "node:path";
|
|
15564
15905
|
function readDescriptionFile(repoRoot) {
|
|
15565
15906
|
let text;
|
|
15566
15907
|
try {
|
|
15567
|
-
text =
|
|
15908
|
+
text = readFileSync16(join18(repoRoot, SYSTEM_DESCRIPTION_REL), "utf-8");
|
|
15568
15909
|
} catch (err) {
|
|
15569
15910
|
const code = err.code;
|
|
15570
15911
|
return code === "ENOENT" || code === "ENOTDIR" ? { kind: "missing" } : { kind: "unreadable" };
|
|
@@ -15602,7 +15943,7 @@ function problemLines(raw, problems) {
|
|
|
15602
15943
|
}
|
|
15603
15944
|
var listTrackedFiles = (repoRoot) => {
|
|
15604
15945
|
try {
|
|
15605
|
-
const out =
|
|
15946
|
+
const out = execFileSync5("git", ["-C", repoRoot, "ls-files", "-z"], {
|
|
15606
15947
|
encoding: "utf-8",
|
|
15607
15948
|
env: gitEnvForNamedRepo(),
|
|
15608
15949
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -15699,7 +16040,7 @@ function readCredentialOrLine(repoRoot, deps, verb) {
|
|
|
15699
16040
|
} catch {
|
|
15700
16041
|
return {
|
|
15701
16042
|
ok: false,
|
|
15702
|
-
line: `halfcycle ${verb}: this project is not connected to Halfcycle on this machine. Run ${
|
|
16043
|
+
line: `halfcycle ${verb}: this project is not connected to Halfcycle on this machine. Run ${ownCommand()} here first, then run this again. Nothing was sent.
|
|
15703
16044
|
`
|
|
15704
16045
|
};
|
|
15705
16046
|
}
|
|
@@ -15788,8 +16129,8 @@ async function runSystem(repoRoot, deps) {
|
|
|
15788
16129
|
}
|
|
15789
16130
|
|
|
15790
16131
|
// dist/build-record/write-change.js
|
|
15791
|
-
import { existsSync as
|
|
15792
|
-
import { basename as basename3, isAbsolute as isAbsolute5, join as
|
|
16132
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync10 } from "node:fs";
|
|
16133
|
+
import { basename as basename3, isAbsolute as isAbsolute5, join as join19, relative as relative4, resolve as resolve5 } from "node:path";
|
|
15793
16134
|
|
|
15794
16135
|
// dist/build-record/render-change.js
|
|
15795
16136
|
function counted2(n, one, many) {
|
|
@@ -15934,7 +16275,7 @@ function renderChangeRecordMarkdown(record2) {
|
|
|
15934
16275
|
}
|
|
15935
16276
|
|
|
15936
16277
|
// dist/build-record/write-change.js
|
|
15937
|
-
var CHANGE_RECORD_DIR =
|
|
16278
|
+
var CHANGE_RECORD_DIR = join19("docs", "build-records", "changes");
|
|
15938
16279
|
var InvalidChangeIdError = class extends Error {
|
|
15939
16280
|
id;
|
|
15940
16281
|
constructor(id) {
|
|
@@ -15954,8 +16295,8 @@ function writeChangeRecordPair(repoRoot, sortingId, rawRecord, record2, io = NOD
|
|
|
15954
16295
|
if (!checked.success)
|
|
15955
16296
|
throw new InvalidChangeIdError(sortingId);
|
|
15956
16297
|
const outDir = resolve5(repoRoot, CHANGE_RECORD_DIR);
|
|
15957
|
-
const jsonPath =
|
|
15958
|
-
const mdPath =
|
|
16298
|
+
const jsonPath = join19(outDir, `${checked.data}.json`);
|
|
16299
|
+
const mdPath = join19(outDir, `${checked.data}.md`);
|
|
15959
16300
|
const mdTmp = `${mdPath}.tmp`;
|
|
15960
16301
|
for (const candidate of [jsonPath, mdPath, mdTmp]) {
|
|
15961
16302
|
assertInsideChangeDir(outDir, candidate, sortingId);
|
|
@@ -15968,7 +16309,7 @@ function writeChangeRecordPair(repoRoot, sortingId, rawRecord, record2, io = NOD
|
|
|
15968
16309
|
}
|
|
15969
16310
|
const existingJson = readIfPresent(jsonPath);
|
|
15970
16311
|
if (existingJson === null) {
|
|
15971
|
-
return { kind: "in-the-way", path:
|
|
16312
|
+
return { kind: "in-the-way", path: existsSync13(jsonPath) ? jsonPath : mdPath };
|
|
15972
16313
|
}
|
|
15973
16314
|
if (existingJson !== jsonText)
|
|
15974
16315
|
return { kind: "in-the-way", path: jsonPath };
|
|
@@ -15977,9 +16318,9 @@ function writeChangeRecordPair(repoRoot, sortingId, rawRecord, record2, io = NOD
|
|
|
15977
16318
|
}
|
|
15978
16319
|
|
|
15979
16320
|
// dist/change.js
|
|
15980
|
-
var CHANGE_USAGE = `${
|
|
15981
|
-
${
|
|
15982
|
-
${
|
|
16321
|
+
var CHANGE_USAGE = `${ownCommand()} change sort --request "<one line>" --touches <path>\u2026 [--contract <from>:<to>]\u2026 [--stored-data] [--standing-rule] [--new-part] [--repo <root>]
|
|
16322
|
+
${ownCommand()} change up <mini|full> (--change <id> | --phase <phase>) [--repo <root>]
|
|
16323
|
+
${ownCommand()} change done <id> [--repo <root>]`;
|
|
15983
16324
|
function usage(line) {
|
|
15984
16325
|
return { ok: false, line: `${line}
|
|
15985
16326
|
Usage:
|
|
@@ -16176,7 +16517,7 @@ ${nextSayLine(sorted.next.say)}
|
|
|
16176
16517
|
`;
|
|
16177
16518
|
}
|
|
16178
16519
|
function changeDoneCommand(sortingId) {
|
|
16179
|
-
return `${
|
|
16520
|
+
return `${ownCommand()} change done ${quoteForShell(sortingId)}`;
|
|
16180
16521
|
}
|
|
16181
16522
|
function recordOutcomeLines(record2) {
|
|
16182
16523
|
switch (record2.outcome) {
|
|
@@ -16231,7 +16572,7 @@ function writePairLines(deps, repoRoot, sortingId, found) {
|
|
|
16231
16572
|
};
|
|
16232
16573
|
}
|
|
16233
16574
|
return {
|
|
16234
|
-
stdout: `[halfcycle] Its record is in ${
|
|
16575
|
+
stdout: `[halfcycle] Its record is in ${join20(CHANGE_RECORD_DIR, `${sortingId}.md`)}.
|
|
16235
16576
|
`,
|
|
16236
16577
|
stderr: "",
|
|
16237
16578
|
ok: true
|
|
@@ -16445,7 +16786,7 @@ async function runChangeDone(repoRoot, sortingId, deps) {
|
|
|
16445
16786
|
case "not-configured":
|
|
16446
16787
|
return {
|
|
16447
16788
|
stdout: "",
|
|
16448
|
-
stderr: `${p} this project is not connected to Halfcycle on this machine. Run ${
|
|
16789
|
+
stderr: `${p} this project is not connected to Halfcycle on this machine. Run ${ownCommand()} here first, then run this again. Nothing was sent.
|
|
16449
16790
|
`,
|
|
16450
16791
|
exitCode: 1
|
|
16451
16792
|
};
|
|
@@ -16470,24 +16811,24 @@ function runChange(repoRoot, args2, deps) {
|
|
|
16470
16811
|
}
|
|
16471
16812
|
|
|
16472
16813
|
// dist/shown-record.js
|
|
16473
|
-
import { chmodSync as chmodSync3, mkdirSync as mkdirSync11, readFileSync as
|
|
16814
|
+
import { chmodSync as chmodSync3, mkdirSync as mkdirSync11, readFileSync as readFileSync17, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync8 } from "node:fs";
|
|
16474
16815
|
import { platform as platform4 } from "node:os";
|
|
16475
|
-
import { join as
|
|
16816
|
+
import { join as join21 } from "node:path";
|
|
16476
16817
|
function updateShownRecord(engagementId, update, home) {
|
|
16477
16818
|
let temp = null;
|
|
16478
16819
|
try {
|
|
16479
16820
|
const dir = engagementStateDir(engagementId, home);
|
|
16480
|
-
const path =
|
|
16821
|
+
const path = join21(dir, SHOWN_PLANS_FILENAME);
|
|
16481
16822
|
let current;
|
|
16482
16823
|
try {
|
|
16483
|
-
current = parseShownRecord(
|
|
16824
|
+
current = parseShownRecord(readFileSync17(path, "utf-8"));
|
|
16484
16825
|
} catch {
|
|
16485
16826
|
current = { entries: [], reviews: [] };
|
|
16486
16827
|
}
|
|
16487
16828
|
const next = update(current);
|
|
16488
16829
|
const body = serialiseShownRecord(next);
|
|
16489
16830
|
mkdirSync11(dir, { recursive: true, mode: 448 });
|
|
16490
|
-
temp =
|
|
16831
|
+
temp = join21(dir, `.${SHOWN_PLANS_FILENAME}.${process.pid}.tmp`);
|
|
16491
16832
|
writeFileSync8(temp, body, { mode: 384 });
|
|
16492
16833
|
if (platform4() !== "win32")
|
|
16493
16834
|
chmodSync3(temp, 384);
|
|
@@ -16505,7 +16846,7 @@ function updateShownRecord(engagementId, update, home) {
|
|
|
16505
16846
|
}
|
|
16506
16847
|
|
|
16507
16848
|
// dist/plan.js
|
|
16508
|
-
var PLAN_USAGE = `${
|
|
16849
|
+
var PLAN_USAGE = `${ownCommand()} plan <phase> [--show] [--repo <root>]`;
|
|
16509
16850
|
var PLAN_DIR_REL = "docs/phases";
|
|
16510
16851
|
function planFileRel(phase) {
|
|
16511
16852
|
return `${PLAN_DIR_REL}/${renderPhaseSegment(phase)}.plan.json`;
|
|
@@ -16564,7 +16905,7 @@ function readAndCheckPlan(repoRoot, phase, description, verb) {
|
|
|
16564
16905
|
const fileRel = planFileRel(phase);
|
|
16565
16906
|
let text;
|
|
16566
16907
|
try {
|
|
16567
|
-
text =
|
|
16908
|
+
text = readFileSync18(join22(repoRoot, fileRel), "utf-8");
|
|
16568
16909
|
} catch (err) {
|
|
16569
16910
|
const code = err.code;
|
|
16570
16911
|
if (code === "ENOENT" || code === "ENOTDIR") {
|
|
@@ -16714,19 +17055,19 @@ function notAPhaseNameLine(verb, phase) {
|
|
|
16714
17055
|
`;
|
|
16715
17056
|
}
|
|
16716
17057
|
function removalOwedLines(verb, parts) {
|
|
16717
|
-
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 ${
|
|
17058
|
+
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 ${ownCommand()} system, then run this again.
|
|
16718
17059
|
`).join("");
|
|
16719
17060
|
}
|
|
16720
17061
|
function redrawLine(repoRoot, phase) {
|
|
16721
17062
|
let exists;
|
|
16722
17063
|
try {
|
|
16723
|
-
exists =
|
|
17064
|
+
exists = existsSync14(join22(repoRoot, DIAGRAM_DIR_REL, embedImageName(phaseDiagramBase(phase))));
|
|
16724
17065
|
} catch {
|
|
16725
17066
|
return null;
|
|
16726
17067
|
}
|
|
16727
17068
|
if (!exists)
|
|
16728
17069
|
return null;
|
|
16729
|
-
return `This phase's picture in your docs may show an earlier plan. Draw it again with: ${
|
|
17070
|
+
return `This phase's picture in your docs may show an earlier plan. Draw it again with: ${ownCommand()} draw phase ${quoteForShell(phase)}
|
|
16730
17071
|
`;
|
|
16731
17072
|
}
|
|
16732
17073
|
function movedUpLines(movedUp) {
|
|
@@ -16746,14 +17087,14 @@ function planStateSentence(state, givenIn) {
|
|
|
16746
17087
|
return "The owner sent this plan back. Change it to answer the owner's notes, then run this again.";
|
|
16747
17088
|
}
|
|
16748
17089
|
}
|
|
16749
|
-
function approvalLine2(phase, number, typedApprovalOffered, pinnedVersion, shown2, engagementId) {
|
|
16750
|
-
const address = systemViewUrl(engagementId, phase);
|
|
17090
|
+
function approvalLine2(phase, number, typedApprovalOffered, pinnedVersion, shown2, engagementId, serviceUrl) {
|
|
17091
|
+
const address = systemViewUrl(engagementId, phase, serviceUrl);
|
|
16751
17092
|
const then = (sentence, viewSentence) => address === void 0 ? sentence : `${sentence} ${viewSentence} ${address}`;
|
|
16752
17093
|
if (!typedApprovalOffered) {
|
|
16753
17094
|
return then(TYPED_APPROVAL_OFF_SENTENCE, "The project's owner approves it in the System view:");
|
|
16754
17095
|
}
|
|
16755
17096
|
if (!clientMeetsChatApprovalFloor(pinnedVersion)) {
|
|
16756
|
-
return then(
|
|
17097
|
+
return then(clientUpdateSentence(ownVersion()), "Or the project's owner approves it in the System view now:");
|
|
16757
17098
|
}
|
|
16758
17099
|
return then(`To approve it, the project's owner types \`approve ${phase}\` in their own interactive session ${HEADLESS_CANNOT_APPROVE}. Only the owner can.`, "They can also approve it in the System view:");
|
|
16759
17100
|
}
|
|
@@ -16836,13 +17177,13 @@ ${redraw}`;
|
|
|
16836
17177
|
exitCode: 1
|
|
16837
17178
|
};
|
|
16838
17179
|
}
|
|
16839
|
-
const stdout = outcome.state === "approved" ? listed : listed + `${approvalLine2(shownPhase, outcome.phaseNumber, outcome.typedApprovalOffered, deps.pinnedClientVersion(repoRoot), written.entries, stage.credential.engagementId)}
|
|
17180
|
+
const stdout = outcome.state === "approved" ? listed : listed + `${approvalLine2(shownPhase, outcome.phaseNumber, outcome.typedApprovalOffered, deps.pinnedClientVersion(repoRoot), written.entries, stage.credential.engagementId, stage.credential.serviceUrl)}
|
|
16840
17181
|
`;
|
|
16841
17182
|
return { stdout, stderr: warnings, exitCode: 0 };
|
|
16842
17183
|
}
|
|
16843
17184
|
|
|
16844
17185
|
// dist/review.js
|
|
16845
|
-
var REVIEW_USAGE = `${
|
|
17186
|
+
var REVIEW_USAGE = `${ownCommand()} review <phase> (--show | --answer <part> "<answer>") [--repo <root>]`;
|
|
16846
17187
|
function parseReviewArgs(argv) {
|
|
16847
17188
|
const positionals = [];
|
|
16848
17189
|
let repoRoot;
|
|
@@ -17061,7 +17402,7 @@ function rv8Line(view) {
|
|
|
17061
17402
|
}
|
|
17062
17403
|
return `halfcycle review: phase "${view.phase}" has no review open. Its review opens when its build is finished and the close step runs.`;
|
|
17063
17404
|
}
|
|
17064
|
-
var RV9_LINE = `halfcycle review: this project is not signed in on this computer. ${
|
|
17405
|
+
var RV9_LINE = `halfcycle review: this project is not signed in on this computer. ${hookRemedy(ownVersion())}`;
|
|
17065
17406
|
var RV10_LINE = "halfcycle review: Halfcycle could not be reached just now. Check this computer's connection, then run this again.";
|
|
17066
17407
|
var RV11_LINE = "halfcycle review: the list just shown could not be recorded on this computer, so a typed decision cannot use it. Run this again.";
|
|
17067
17408
|
function an1Line(name) {
|
|
@@ -17230,7 +17571,7 @@ function readHeadCommit(repoRoot) {
|
|
|
17230
17571
|
}
|
|
17231
17572
|
let out;
|
|
17232
17573
|
try {
|
|
17233
|
-
out =
|
|
17574
|
+
out = execFileSync6("git", ["-C", repoRoot, "rev-parse", "HEAD"], {
|
|
17234
17575
|
encoding: "utf-8",
|
|
17235
17576
|
env: gitEnvForNamedRepo(),
|
|
17236
17577
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -17241,7 +17582,7 @@ function readHeadCommit(repoRoot) {
|
|
|
17241
17582
|
return { read: false, cause: "unreadable", missingGit: true };
|
|
17242
17583
|
}
|
|
17243
17584
|
try {
|
|
17244
|
-
|
|
17585
|
+
execFileSync6("git", ["-C", repoRoot, "rev-parse", "--git-dir"], {
|
|
17245
17586
|
encoding: "utf-8",
|
|
17246
17587
|
env: gitEnvForNamedRepo(),
|
|
17247
17588
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -17448,8 +17789,8 @@ function typedReviewHintLine(offered, phase) {
|
|
|
17448
17789
|
const lines = accept === null ? "then type one decision per line, as the whole message." : `then type ${accept} as the whole message to accept every built part, or one line per part, such as ${accept}/<part>; the list gives each part's lines.`;
|
|
17449
17790
|
return `[halfcycle] The owner reviews each part in their own coding agent's session: they say "review what was built" to see the list, ${lines}`;
|
|
17450
17791
|
}
|
|
17451
|
-
function systemViewReviewLine(offered, engagementId, phase) {
|
|
17452
|
-
const address = systemViewUrl(engagementId, phase);
|
|
17792
|
+
function systemViewReviewLine(offered, engagementId, phase, serviceUrl) {
|
|
17793
|
+
const address = systemViewUrl(engagementId, phase, serviceUrl);
|
|
17453
17794
|
if (address === void 0)
|
|
17454
17795
|
return void 0;
|
|
17455
17796
|
if (!offered)
|
|
@@ -17482,7 +17823,7 @@ function unfinishedStepsLines(phase, steps) {
|
|
|
17482
17823
|
function cleanOverFindingsLine(phase, findings) {
|
|
17483
17824
|
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.`;
|
|
17484
17825
|
}
|
|
17485
|
-
function closeStepReport(phase, attempt2, engagementId) {
|
|
17826
|
+
function closeStepReport(phase, attempt2, engagementId, serviceUrl) {
|
|
17486
17827
|
const stdout = [];
|
|
17487
17828
|
const stderr = [];
|
|
17488
17829
|
let exitCode = 1;
|
|
@@ -17511,7 +17852,7 @@ function closeStepReport(phase, attempt2, engagementId) {
|
|
|
17511
17852
|
stdout.push(COULD_NOT_COUNT_LINE);
|
|
17512
17853
|
stdout.push(typedReviewHintLine(answer.typedReviewOffered, phase));
|
|
17513
17854
|
{
|
|
17514
|
-
const viewLine = systemViewReviewLine(answer.typedReviewOffered, engagementId, phase);
|
|
17855
|
+
const viewLine = systemViewReviewLine(answer.typedReviewOffered, engagementId, phase, serviceUrl);
|
|
17515
17856
|
if (viewLine !== void 0)
|
|
17516
17857
|
stdout.push(viewLine);
|
|
17517
17858
|
}
|
|
@@ -17680,7 +18021,7 @@ var PHASE_MODELS_FILENAME = "phase-models.json";
|
|
|
17680
18021
|
var PACING_FOLDER = "pacing";
|
|
17681
18022
|
function pacingFile(engagementId, name, home) {
|
|
17682
18023
|
try {
|
|
17683
|
-
return
|
|
18024
|
+
return join23(engagementStateDir(engagementId, home), PACING_FOLDER, name);
|
|
17684
18025
|
} catch {
|
|
17685
18026
|
return null;
|
|
17686
18027
|
}
|
|
@@ -17691,7 +18032,7 @@ function readPhaseLimitFile(engagementId, phase, home) {
|
|
|
17691
18032
|
return null;
|
|
17692
18033
|
let text;
|
|
17693
18034
|
try {
|
|
17694
|
-
text =
|
|
18035
|
+
text = readFileSync19(path, "utf-8");
|
|
17695
18036
|
} catch (err) {
|
|
17696
18037
|
return err.code === "ENOENT" ? "missing" : null;
|
|
17697
18038
|
}
|
|
@@ -17732,7 +18073,7 @@ function relayedChoiceLines(engagementId, phase, home) {
|
|
|
17732
18073
|
if (path === null)
|
|
17733
18074
|
return [];
|
|
17734
18075
|
try {
|
|
17735
|
-
const parsed = relayedChoicesFileSchema.safeParse(JSON.parse(
|
|
18076
|
+
const parsed = relayedChoicesFileSchema.safeParse(JSON.parse(readFileSync19(path, "utf-8")));
|
|
17736
18077
|
if (!parsed.success || parsed.data.phase !== phase)
|
|
17737
18078
|
return [];
|
|
17738
18079
|
return parsed.data.entries.map((entry) => relayedChoiceLineOf(entry));
|
|
@@ -17745,7 +18086,7 @@ function readPhaseModels(engagementId, phase, openedAt, home) {
|
|
|
17745
18086
|
if (path === null)
|
|
17746
18087
|
return null;
|
|
17747
18088
|
try {
|
|
17748
|
-
const parsed = phaseModelsFileSchema.safeParse(JSON.parse(
|
|
18089
|
+
const parsed = phaseModelsFileSchema.safeParse(JSON.parse(readFileSync19(path, "utf-8")));
|
|
17749
18090
|
if (!parsed.success || parsed.data.phase !== phase)
|
|
17750
18091
|
return null;
|
|
17751
18092
|
if (openedAt !== null && parsed.data.openedAt !== openedAt)
|
|
@@ -17755,8 +18096,8 @@ function readPhaseModels(engagementId, phase, openedAt, home) {
|
|
|
17755
18096
|
return null;
|
|
17756
18097
|
}
|
|
17757
18098
|
}
|
|
17758
|
-
function usageTokensOf(
|
|
17759
|
-
return
|
|
18099
|
+
function usageTokensOf(usage5) {
|
|
18100
|
+
return usage5.inputTokens + usage5.outputTokens + usage5.cacheWriteTokens + usage5.cacheReadTokens;
|
|
17760
18101
|
}
|
|
17761
18102
|
function tierFigureLines(work, expected, summary, models, details = false) {
|
|
17762
18103
|
const lines = [];
|
|
@@ -17848,12 +18189,12 @@ function readRepoCredential(repoRoot, env = process.env, home) {
|
|
|
17848
18189
|
if (pinned !== null)
|
|
17849
18190
|
assertEngagementId(pinned);
|
|
17850
18191
|
const fromStore = pinned === null ? null : readEngagementEnv(pinned, home);
|
|
17851
|
-
const looked = pinned === null ? `${
|
|
18192
|
+
const looked = pinned === null ? `${join24(repoRoot, ".halfcycle", "bundle.json")} (which names no engagement)` : engagementEnvPath(pinned, home);
|
|
17852
18193
|
const values = {};
|
|
17853
18194
|
for (const key of OPEN_PHASE_ENV_KEYS) {
|
|
17854
18195
|
const value = (env[key] ?? fromStore?.[key] ?? "").trim();
|
|
17855
18196
|
if (value === "") {
|
|
17856
|
-
throw new Error(`[halfcycle] ${key} is not set \u2014 it is neither in this shell's environment nor in ${looked}. That file is written by
|
|
18197
|
+
throw new Error(`[halfcycle] ${key} is not set \u2014 it is neither in this shell's environment nor in ${looked}. That file is written by \`${ownBareCommand()}\`; if this repo has no Halfcycle install yet, or this machine has never installed this engagement, run the installer here first.`);
|
|
17857
18198
|
}
|
|
17858
18199
|
values[key] = value;
|
|
17859
18200
|
}
|
|
@@ -17865,7 +18206,7 @@ function readRepoCredential(repoRoot, env = process.env, home) {
|
|
|
17865
18206
|
}
|
|
17866
18207
|
function readPinnedEngagementId(repoRoot) {
|
|
17867
18208
|
try {
|
|
17868
|
-
const pin = JSON.parse(
|
|
18209
|
+
const pin = JSON.parse(readFileSync20(join24(repoRoot, ".halfcycle", "bundle.json"), "utf-8"));
|
|
17869
18210
|
const id = pin[PIN_ENGAGEMENT_ID_FIELD];
|
|
17870
18211
|
return typeof id === "string" && id !== "" ? id : null;
|
|
17871
18212
|
} catch {
|
|
@@ -17887,7 +18228,7 @@ function writePhaseStamp(engagementId, phase, home) {
|
|
|
17887
18228
|
return { path, reason: err instanceof Error ? err.message : String(err) };
|
|
17888
18229
|
}
|
|
17889
18230
|
}
|
|
17890
|
-
async function openPhase(credential, phase, entry, home, repoRoot, sortingId, sizeId) {
|
|
18231
|
+
async function openPhase(credential, phase, entry, home, repoRoot, sortingId, sizeId, regression) {
|
|
17891
18232
|
const parsed = phaseEntryDecisionSchema.safeParse(entry);
|
|
17892
18233
|
if (!parsed.success) {
|
|
17893
18234
|
throw new Error(`[halfcycle] The entry decision is not a decision this platform accepts: ${parsed.error.issues.map((i) => i.message).join("; ")}.`);
|
|
@@ -17909,6 +18250,9 @@ async function openPhase(credential, phase, entry, home, repoRoot, sortingId, si
|
|
|
17909
18250
|
if (phase !== null && sizeId !== void 0) {
|
|
17910
18251
|
requestBody.sizeId = sizeId;
|
|
17911
18252
|
}
|
|
18253
|
+
if (phase !== null && regression === true) {
|
|
18254
|
+
requestBody.regression = true;
|
|
18255
|
+
}
|
|
17912
18256
|
const url = `${credential.serviceUrl}/engagements/${encodeURIComponent(credential.engagementId)}/phase`;
|
|
17913
18257
|
let res;
|
|
17914
18258
|
try {
|
|
@@ -18038,7 +18382,7 @@ async function obtainBuildRecord(credential, repoRoot, phase, io = NODE_RECORD_P
|
|
|
18038
18382
|
};
|
|
18039
18383
|
}
|
|
18040
18384
|
function refetchCommand(phase) {
|
|
18041
|
-
return `${
|
|
18385
|
+
return `${ownCommand()} build-record ${quoteForShell(phase)}`;
|
|
18042
18386
|
}
|
|
18043
18387
|
|
|
18044
18388
|
// dist/phase-work.js
|
|
@@ -18073,7 +18417,7 @@ async function readPhaseWork(credential, phase) {
|
|
|
18073
18417
|
}
|
|
18074
18418
|
|
|
18075
18419
|
// dist/draw/index.js
|
|
18076
|
-
var DRAW_USAGE = `${
|
|
18420
|
+
var DRAW_USAGE = `${ownCommand()} draw (architecture | phase <phase>) [--repo <root>]`;
|
|
18077
18421
|
function parseDrawArgs(argv) {
|
|
18078
18422
|
const positionals = [];
|
|
18079
18423
|
let repoRoot;
|
|
@@ -18349,9 +18693,9 @@ async function writeDrawOutcome(repoRoot, deps, outcome, target, warnings, canno
|
|
|
18349
18693
|
}
|
|
18350
18694
|
|
|
18351
18695
|
// dist/check-tasks.js
|
|
18352
|
-
import { readFileSync as
|
|
18353
|
-
import { join as
|
|
18354
|
-
var CHECK_TASKS_USAGE = `${
|
|
18696
|
+
import { readFileSync as readFileSync21 } from "node:fs";
|
|
18697
|
+
import { join as join25, resolve as resolve6 } from "node:path";
|
|
18698
|
+
var CHECK_TASKS_USAGE = `${ownCommand()} check-tasks <phase> [--with-task-list] [--repo <root>]`;
|
|
18355
18699
|
function parseCheckTasksArgs(argv) {
|
|
18356
18700
|
const positionals = [];
|
|
18357
18701
|
let repoRoot;
|
|
@@ -18391,7 +18735,7 @@ function issuePlace(rel, path) {
|
|
|
18391
18735
|
function readJsonFile(repoRoot, rel, parse) {
|
|
18392
18736
|
let text;
|
|
18393
18737
|
try {
|
|
18394
|
-
text =
|
|
18738
|
+
text = readFileSync21(resolve6(repoRoot, rel), "utf-8");
|
|
18395
18739
|
} catch (err) {
|
|
18396
18740
|
const code = err.code;
|
|
18397
18741
|
if (code === "ENOENT" || code === "ENOTDIR")
|
|
@@ -18449,7 +18793,7 @@ function readPhasePlanParts(repoRoot, phase, description) {
|
|
|
18449
18793
|
const file = planFileRel(phase);
|
|
18450
18794
|
let text;
|
|
18451
18795
|
try {
|
|
18452
|
-
text =
|
|
18796
|
+
text = readFileSync21(join25(repoRoot, file), "utf-8");
|
|
18453
18797
|
} catch (err) {
|
|
18454
18798
|
const code = err.code;
|
|
18455
18799
|
if (code === "ENOENT" || code === "ENOTDIR")
|
|
@@ -18482,7 +18826,7 @@ function readPlanSpecFiles(repoRoot, plan) {
|
|
|
18482
18826
|
const rel = `${SPEC_FOLDER}/${slug}.md`;
|
|
18483
18827
|
let text;
|
|
18484
18828
|
try {
|
|
18485
|
-
text =
|
|
18829
|
+
text = readFileSync21(join25(repoRoot, rel), "utf-8");
|
|
18486
18830
|
} catch (err) {
|
|
18487
18831
|
const code = err.code;
|
|
18488
18832
|
if (code === "ENOENT" || code === "ENOTDIR") {
|
|
@@ -18622,7 +18966,7 @@ async function runCheckTasksSized(repoRoot, phase, withTaskList, deps) {
|
|
|
18622
18966
|
}
|
|
18623
18967
|
|
|
18624
18968
|
// dist/check-criteria.js
|
|
18625
|
-
var CHECK_CRITERIA_USAGE = `${
|
|
18969
|
+
var CHECK_CRITERIA_USAGE = `${ownCommand()} check-criteria [<record file>] [--repo <root>]`;
|
|
18626
18970
|
function parseCheckCriteriaArgs(argv) {
|
|
18627
18971
|
const positionals = [];
|
|
18628
18972
|
let repoRoot;
|
|
@@ -18673,8 +19017,8 @@ function runCheckCriteria(repoRoot, file) {
|
|
|
18673
19017
|
}
|
|
18674
19018
|
|
|
18675
19019
|
// dist/check-report.js
|
|
18676
|
-
import { readFileSync as
|
|
18677
|
-
var CHECK_REPORT_USAGE = `${
|
|
19020
|
+
import { readFileSync as readFileSync22 } from "node:fs";
|
|
19021
|
+
var CHECK_REPORT_USAGE = `${ownCommand()} check-report <file | -> --head <sha>`;
|
|
18678
19022
|
function parseCheckReportArgs(argv) {
|
|
18679
19023
|
const positionals = [];
|
|
18680
19024
|
let head;
|
|
@@ -18709,7 +19053,7 @@ function runCheckReport(source, head, deps = {}) {
|
|
|
18709
19053
|
}
|
|
18710
19054
|
let body;
|
|
18711
19055
|
try {
|
|
18712
|
-
body = source === "-" ? (deps.readStdin ?? (() =>
|
|
19056
|
+
body = source === "-" ? (deps.readStdin ?? (() => readFileSync22(0, "utf-8")))() : readFileSync22(source, "utf-8");
|
|
18713
19057
|
} catch (err) {
|
|
18714
19058
|
const code = err.code;
|
|
18715
19059
|
const what = source === "-" ? "The report could not be read from standard input" : `${source} could not be read`;
|
|
@@ -18727,7 +19071,7 @@ function runCheckReport(source, head, deps = {}) {
|
|
|
18727
19071
|
}
|
|
18728
19072
|
|
|
18729
19073
|
// dist/ready.js
|
|
18730
|
-
var READY_USAGE = `${
|
|
19074
|
+
var READY_USAGE = `${ownCommand()} ready <phase> [--cap <n>] [--repo <root>]`;
|
|
18731
19075
|
var STATES_TEXT = buildTaskStateSchema.options.join(", ");
|
|
18732
19076
|
var READY_LINES = {
|
|
18733
19077
|
start: (id, title) => `Ready to start: ${named(id, title)}`,
|
|
@@ -18876,8 +19220,8 @@ function runReady(repoRoot, phase, cap) {
|
|
|
18876
19220
|
|
|
18877
19221
|
// dist/prove.js
|
|
18878
19222
|
import { spawn as spawn2 } from "node:child_process";
|
|
18879
|
-
var PROVE_USAGE = `${
|
|
18880
|
-
${
|
|
19223
|
+
var PROVE_USAGE = `${ownCommand()} prove <phase> <task> --label "<label>" --before -- <test command>
|
|
19224
|
+
${ownCommand()} prove <phase> <task> --label "<label>" --after [-- <test command>]
|
|
18881
19225
|
(or --change <id> in place of <phase> <task>; add --reason "<why>" to say why) [--repo <root>]`;
|
|
18882
19226
|
function splitProveInvocation(words) {
|
|
18883
19227
|
const dash = words.indexOf("--");
|
|
@@ -18952,9 +19296,9 @@ function runProveChild(binary, credential, repoRoot, words, deps = {}) {
|
|
|
18952
19296
|
}
|
|
18953
19297
|
|
|
18954
19298
|
// dist/uninstall.js
|
|
18955
|
-
import { existsSync as
|
|
19299
|
+
import { existsSync as existsSync15, lstatSync as lstatSync4, readFileSync as readFileSync23, readdirSync, rmSync as rmSync8, rmdirSync, writeFileSync as writeFileSync9 } from "node:fs";
|
|
18956
19300
|
import { homedir as homedir7 } from "node:os";
|
|
18957
|
-
import { dirname as
|
|
19301
|
+
import { dirname as dirname8, join as join26, relative as relative6, sep as sep2 } from "node:path";
|
|
18958
19302
|
var REMOVE_CREDENTIAL_FLAG = "--remove-credential";
|
|
18959
19303
|
function parseUninstallArgs(args2) {
|
|
18960
19304
|
let removeCredential = false;
|
|
@@ -18989,13 +19333,13 @@ function readMergedJson(targetRepo, rule) {
|
|
|
18989
19333
|
const { rel } = rule;
|
|
18990
19334
|
if (symlinkOnPath(targetRepo, rel))
|
|
18991
19335
|
return { state: "symlink" };
|
|
18992
|
-
const path =
|
|
19336
|
+
const path = join26(targetRepo, rel);
|
|
18993
19337
|
if (!lexists(path))
|
|
18994
19338
|
return { state: "absent" };
|
|
18995
19339
|
let text;
|
|
18996
19340
|
let value;
|
|
18997
19341
|
try {
|
|
18998
|
-
text =
|
|
19342
|
+
text = readFileSync23(path, "utf-8");
|
|
18999
19343
|
} catch {
|
|
19000
19344
|
text = "";
|
|
19001
19345
|
}
|
|
@@ -19008,29 +19352,29 @@ function readMergedJson(targetRepo, rule) {
|
|
|
19008
19352
|
return { state: "object", text, value };
|
|
19009
19353
|
if (!rule.holdsOurs(text))
|
|
19010
19354
|
return { state: "not-ours" };
|
|
19011
|
-
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 "${
|
|
19355
|
+
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 "${ownCommand()} uninstall" again.`);
|
|
19012
19356
|
}
|
|
19013
19357
|
function validate(options, rules) {
|
|
19014
19358
|
const { targetRepo, removeCredential, home } = options;
|
|
19015
|
-
const manifestPath =
|
|
19016
|
-
const pinPath =
|
|
19359
|
+
const manifestPath = join26(targetRepo, INSTALL_MANIFEST_REL);
|
|
19360
|
+
const pinPath = join26(targetRepo, BUNDLE_PIN_REL);
|
|
19017
19361
|
if (symlinkOnPath(targetRepo, INSTALL_MANIFEST_REL) || symlinkOnPath(targetRepo, BUNDLE_PIN_REL)) {
|
|
19018
19362
|
throw new Refusal(`.halfcycle, or a file Halfcycle keeps in it, is a symbolic link here, so nothing was followed and nothing was removed.`);
|
|
19019
19363
|
}
|
|
19020
19364
|
let manifest = null;
|
|
19021
19365
|
if (lexists(manifestPath)) {
|
|
19022
19366
|
try {
|
|
19023
|
-
manifest = parseManifest(
|
|
19367
|
+
manifest = parseManifest(readFileSync23(manifestPath, "utf-8"));
|
|
19024
19368
|
} catch (err) {
|
|
19025
19369
|
const why = err instanceof UnreadableManifestError ? err.message : "it could not be read";
|
|
19026
|
-
throw new Refusal(`${INSTALL_MANIFEST_REL} cannot be read (${why}), so nothing was removed. Restore it from git, or run "${
|
|
19370
|
+
throw new Refusal(`${INSTALL_MANIFEST_REL} cannot be read (${why}), so nothing was removed. Restore it from git, or run "${ownCommand()}" once to rewrite it, then run "${ownCommand()} uninstall" again.`);
|
|
19027
19371
|
}
|
|
19028
19372
|
}
|
|
19029
19373
|
let engagementId;
|
|
19030
19374
|
let pinReadable = true;
|
|
19031
19375
|
if (lexists(pinPath)) {
|
|
19032
19376
|
try {
|
|
19033
|
-
const pin = JSON.parse(
|
|
19377
|
+
const pin = JSON.parse(readFileSync23(pinPath, "utf-8"));
|
|
19034
19378
|
const id = isPlainObject4(pin) ? pin[PIN_ENGAGEMENT_ID_FIELD] : void 0;
|
|
19035
19379
|
engagementId = typeof id === "string" && id !== "" ? id : void 0;
|
|
19036
19380
|
} catch {
|
|
@@ -19054,8 +19398,8 @@ function validate(options, rules) {
|
|
|
19054
19398
|
throw new Refusal(`the project id in ${BUNDLE_PIN_REL} is not a valid Halfcycle project id, so it cannot name a folder on this machine. Nothing was removed, here or under ~/.halfcycle.`);
|
|
19055
19399
|
}
|
|
19056
19400
|
if (engagementId !== void 0) {
|
|
19057
|
-
const expectedParent =
|
|
19058
|
-
if (
|
|
19401
|
+
const expectedParent = join26(home ?? homedir7(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME);
|
|
19402
|
+
if (dirname8(engagementStateDir(engagementId, home)) !== expectedParent) {
|
|
19059
19403
|
throw new Refusal(`the project id in ${BUNDLE_PIN_REL} does not name a folder under ~/.halfcycle/engagements. Nothing was removed.`);
|
|
19060
19404
|
}
|
|
19061
19405
|
}
|
|
@@ -19080,7 +19424,7 @@ function mergedFileRules(writeSet) {
|
|
|
19080
19424
|
function writeBack(targetRepo, rel, current, next, lists, name) {
|
|
19081
19425
|
if (next === current)
|
|
19082
19426
|
return;
|
|
19083
|
-
const path =
|
|
19427
|
+
const path = join26(targetRepo, rel);
|
|
19084
19428
|
try {
|
|
19085
19429
|
if (next === null) {
|
|
19086
19430
|
rmSync8(path);
|
|
@@ -19097,7 +19441,7 @@ function statusLineClaimedFromTree(targetRepo, writeSet) {
|
|
|
19097
19441
|
try {
|
|
19098
19442
|
if (symlinkOnPath(targetRepo, SETTINGS_REL))
|
|
19099
19443
|
return {};
|
|
19100
|
-
const value = JSON.parse(
|
|
19444
|
+
const value = JSON.parse(readFileSync23(join26(targetRepo, SETTINGS_REL), "utf-8"));
|
|
19101
19445
|
if (!isPlainObject4(value) || !("statusLine" in value))
|
|
19102
19446
|
return {};
|
|
19103
19447
|
const hash = canonicalSha256(value["statusLine"]);
|
|
@@ -19106,12 +19450,25 @@ function statusLineClaimedFromTree(targetRepo, writeSet) {
|
|
|
19106
19450
|
return {};
|
|
19107
19451
|
}
|
|
19108
19452
|
}
|
|
19453
|
+
function attributionClaimedFromTree(targetRepo, writeSet) {
|
|
19454
|
+
try {
|
|
19455
|
+
if (symlinkOnPath(targetRepo, SETTINGS_REL))
|
|
19456
|
+
return {};
|
|
19457
|
+
const value = JSON.parse(readFileSync23(join26(targetRepo, SETTINGS_REL), "utf-8"));
|
|
19458
|
+
if (!isPlainObject4(value) || !("attribution" in value))
|
|
19459
|
+
return {};
|
|
19460
|
+
const hash = canonicalSha256(value["attribution"]);
|
|
19461
|
+
return writeSet.attributionSha256.has(hash) ? { attributionSha256: hash } : {};
|
|
19462
|
+
} catch {
|
|
19463
|
+
return {};
|
|
19464
|
+
}
|
|
19465
|
+
}
|
|
19109
19466
|
var BEFORE_UNKNOWN_MCP = { created: false, adopted: true, mcpServersCreated: false };
|
|
19110
19467
|
function noRecord(targetRepo, writeSet) {
|
|
19111
19468
|
const claim = (rel, header, lines) => {
|
|
19112
19469
|
let text;
|
|
19113
19470
|
try {
|
|
19114
|
-
text = symlinkOnPath(targetRepo, rel) ? "" :
|
|
19471
|
+
text = symlinkOnPath(targetRepo, rel) ? "" : readFileSync23(join26(targetRepo, rel), "utf-8");
|
|
19115
19472
|
} catch {
|
|
19116
19473
|
text = "";
|
|
19117
19474
|
}
|
|
@@ -19132,7 +19489,8 @@ function noRecord(targetRepo, writeSet) {
|
|
|
19132
19489
|
hooksCreated: false,
|
|
19133
19490
|
permissionsCreated: false,
|
|
19134
19491
|
denyCreated: false,
|
|
19135
|
-
...statusLineClaimedFromTree(targetRepo, writeSet)
|
|
19492
|
+
...statusLineClaimedFromTree(targetRepo, writeSet),
|
|
19493
|
+
...attributionClaimedFromTree(targetRepo, writeSet)
|
|
19136
19494
|
},
|
|
19137
19495
|
mcp: BEFORE_UNKNOWN_MCP,
|
|
19138
19496
|
gitignore: claim(GITIGNORE_REL, GITIGNORE_HEADER, writeSet.gitignoreLines),
|
|
@@ -19195,6 +19553,10 @@ function unmergeSettings(value, rec, writeSet) {
|
|
|
19195
19553
|
delete value["statusLine"];
|
|
19196
19554
|
changed = true;
|
|
19197
19555
|
}
|
|
19556
|
+
if (rec.attributionSha256 !== void 0 && writeSet.attributionSha256.has(rec.attributionSha256) && "attribution" in value && canonicalSha256(value["attribution"]) === rec.attributionSha256) {
|
|
19557
|
+
delete value["attribution"];
|
|
19558
|
+
changed = true;
|
|
19559
|
+
}
|
|
19198
19560
|
if ("schemaBefore" in rec && value["$schema"] === writeSet.settingsSchema) {
|
|
19199
19561
|
if (rec.schemaBefore === null)
|
|
19200
19562
|
delete value["$schema"];
|
|
@@ -19352,7 +19714,7 @@ function lastOccurrence(text, block) {
|
|
|
19352
19714
|
}
|
|
19353
19715
|
function coveredPathExists(targetRepo, line) {
|
|
19354
19716
|
const path = line.trim().replace(/^\//, "").replace(/\/$/, "");
|
|
19355
|
-
return path !== "" && lexists(
|
|
19717
|
+
return path !== "" && lexists(join26(targetRepo, path));
|
|
19356
19718
|
}
|
|
19357
19719
|
function selfBuildMergedFileRules() {
|
|
19358
19720
|
return {
|
|
@@ -19366,7 +19728,7 @@ function removeSelfBuildOwnedFile(targetRepo, rel, lists, name) {
|
|
|
19366
19728
|
name(lists.leftAlone, `${rel} (a symbolic link, or inside one \u2014 not followed)`, rel);
|
|
19367
19729
|
return;
|
|
19368
19730
|
}
|
|
19369
|
-
const abs =
|
|
19731
|
+
const abs = join26(targetRepo, rel);
|
|
19370
19732
|
let isFile;
|
|
19371
19733
|
try {
|
|
19372
19734
|
isFile = lstatSync4(abs).isFile();
|
|
@@ -19390,10 +19752,10 @@ function removeSelfBuildCrewRoster(targetRepo, lists, name) {
|
|
|
19390
19752
|
name(lists.leftAlone, `${rel} (a symbolic link, or inside one \u2014 not followed)`, rel);
|
|
19391
19753
|
return;
|
|
19392
19754
|
}
|
|
19393
|
-
const abs =
|
|
19755
|
+
const abs = join26(targetRepo, rel);
|
|
19394
19756
|
let bytes;
|
|
19395
19757
|
try {
|
|
19396
|
-
bytes =
|
|
19758
|
+
bytes = readFileSync23(abs);
|
|
19397
19759
|
} catch {
|
|
19398
19760
|
return;
|
|
19399
19761
|
}
|
|
@@ -19417,10 +19779,10 @@ function removeSelfBuildCrewProfile(targetRepo, rel, text, lists, name) {
|
|
|
19417
19779
|
name(lists.leftAlone, `${rel} (a symbolic link, or inside one \u2014 not followed)`, rel);
|
|
19418
19780
|
return;
|
|
19419
19781
|
}
|
|
19420
|
-
const abs =
|
|
19782
|
+
const abs = join26(targetRepo, rel);
|
|
19421
19783
|
let bytes;
|
|
19422
19784
|
try {
|
|
19423
|
-
bytes =
|
|
19785
|
+
bytes = readFileSync23(abs);
|
|
19424
19786
|
} catch {
|
|
19425
19787
|
return;
|
|
19426
19788
|
}
|
|
@@ -19442,7 +19804,7 @@ function removeSelfBuildCrewProfile(targetRepo, rel, text, lists, name) {
|
|
|
19442
19804
|
function removeSelfBuildEmptyDir(targetRepo, dir, lists, name) {
|
|
19443
19805
|
if (symlinkOnPath(targetRepo, dir))
|
|
19444
19806
|
return;
|
|
19445
|
-
const abs =
|
|
19807
|
+
const abs = join26(targetRepo, dir);
|
|
19446
19808
|
try {
|
|
19447
19809
|
if (!lstatSync4(abs).isDirectory() || readdirSync(abs).length > 0)
|
|
19448
19810
|
return;
|
|
@@ -19476,7 +19838,7 @@ function uninstallSelfBuild(options) {
|
|
|
19476
19838
|
throw err;
|
|
19477
19839
|
}
|
|
19478
19840
|
const { targetRepo } = options;
|
|
19479
|
-
const pinExisted = lexists(
|
|
19841
|
+
const pinExisted = lexists(join26(targetRepo, BUNDLE_PIN_REL));
|
|
19480
19842
|
const name = (list2, text, _path) => {
|
|
19481
19843
|
list2.push(text);
|
|
19482
19844
|
};
|
|
@@ -19556,14 +19918,18 @@ function uninstall(options) {
|
|
|
19556
19918
|
adopted: false
|
|
19557
19919
|
}));
|
|
19558
19920
|
removeCodexConfigBlock(targetRepo, manifest.codexConfig, deleteCodexWhenEmpty(manifest.codexConfig), result, name);
|
|
19559
|
-
|
|
19921
|
+
const listRemoved = holdsLocalAllowRecord(targetRepo);
|
|
19922
|
+
if (listRemoved) {
|
|
19560
19923
|
removeLocalAllow(targetRepo);
|
|
19561
|
-
|
|
19924
|
+
named2.add(LOCAL_ALLOW_RECORD_REL);
|
|
19925
|
+
}
|
|
19926
|
+
const serverRemoved = removeOwnServerApproval(targetRepo);
|
|
19927
|
+
if (listRemoved || serverRemoved) {
|
|
19928
|
+
if (existsSync15(join26(targetRepo, LOCAL_SETTINGS_REL2))) {
|
|
19562
19929
|
name(result.restored, LOCAL_SETTINGS_REL2, LOCAL_SETTINGS_REL2);
|
|
19563
19930
|
} else {
|
|
19564
19931
|
name(result.removed, LOCAL_SETTINGS_REL2, LOCAL_SETTINGS_REL2);
|
|
19565
19932
|
}
|
|
19566
|
-
named2.add(LOCAL_ALLOW_RECORD_REL);
|
|
19567
19933
|
}
|
|
19568
19934
|
const deferred = /* @__PURE__ */ new Set([BUNDLE_PIN_REL, INSTALL_MANIFEST_REL]);
|
|
19569
19935
|
const recorded = new Map(manifest.files.map((entry) => [entry.path, entry]));
|
|
@@ -19601,7 +19967,7 @@ function uninstall(options) {
|
|
|
19601
19967
|
const gitattributes = manifest.gitattributes ?? { created: false, appended: [] };
|
|
19602
19968
|
trimAppendedFile(targetRepo, gitattributes, deleteWhenEmpty(gitattributes.created), gitattributesRules(writeSet), result, name);
|
|
19603
19969
|
const foundNothing = result.removed.length + result.restored.length + result.kept.length + result.failed.length === 0 && result.keptIgnoreLines.length === 0;
|
|
19604
|
-
if (checked.manifest === null && !lexists(
|
|
19970
|
+
if (checked.manifest === null && !lexists(join26(targetRepo, BUNDLE_PIN_REL)) && foundNothing) {
|
|
19605
19971
|
return { outcome: "not-installed", exitCode: 0, ...result, credential: nothingToSay };
|
|
19606
19972
|
}
|
|
19607
19973
|
const credential = handleCredential(options, checked);
|
|
@@ -19609,7 +19975,7 @@ function uninstall(options) {
|
|
|
19609
19975
|
if (result.failed.length === 0 && !credentialFailed) {
|
|
19610
19976
|
removeInstalledFile(targetRepo, BUNDLE_PIN_REL, recorded.get(BUNDLE_PIN_REL), writeSet, result, name);
|
|
19611
19977
|
try {
|
|
19612
|
-
rmSync8(
|
|
19978
|
+
rmSync8(join26(targetRepo, INSTALL_MANIFEST_REL), { force: true });
|
|
19613
19979
|
named2.add(INSTALL_MANIFEST_REL);
|
|
19614
19980
|
} catch {
|
|
19615
19981
|
name(result.failed, INSTALL_MANIFEST_REL, INSTALL_MANIFEST_REL);
|
|
@@ -19644,13 +20010,13 @@ function applyMergedJson(targetRepo, rule, file, deleteWhenEmpty, lists, name, e
|
|
|
19644
20010
|
return;
|
|
19645
20011
|
const empty = Object.keys(value).length === 0 && deleteWhenEmpty;
|
|
19646
20012
|
writeBack(targetRepo, rel, file.text, empty ? null : inLineEndingOf(file.text, serializeJson(value)), lists, name);
|
|
19647
|
-
if (adopted && lexists(
|
|
20013
|
+
if (adopted && lexists(join26(targetRepo, rel))) {
|
|
19648
20014
|
name(lists.leftAlone, `${rel} (Halfcycle's hooks were taken out. There is no record of this file from before the install, so a permission rule or "$schema" Halfcycle added may still be there \u2014 it cannot be told apart from your own)`, rel);
|
|
19649
20015
|
}
|
|
19650
20016
|
}
|
|
19651
20017
|
function removeCodexConfigBlock(targetRepo, record2, deleteWhenEmpty, lists, name) {
|
|
19652
20018
|
const rel = CODEX_CONFIG_REL2;
|
|
19653
|
-
const path =
|
|
20019
|
+
const path = join26(targetRepo, rel);
|
|
19654
20020
|
if (symlinkOnPath(targetRepo, rel)) {
|
|
19655
20021
|
if (lexists(path))
|
|
19656
20022
|
name(lists.leftAlone, `${rel} (a symbolic link, or inside one \u2014 not followed)`, rel);
|
|
@@ -19660,7 +20026,7 @@ function removeCodexConfigBlock(targetRepo, record2, deleteWhenEmpty, lists, nam
|
|
|
19660
20026
|
try {
|
|
19661
20027
|
if (!lstatSync4(path).isFile())
|
|
19662
20028
|
return;
|
|
19663
|
-
text =
|
|
20029
|
+
text = readFileSync23(path, "utf-8");
|
|
19664
20030
|
} catch {
|
|
19665
20031
|
return;
|
|
19666
20032
|
}
|
|
@@ -19695,7 +20061,7 @@ function removeInstalledFile(targetRepo, path, entry, writeSet, lists, name) {
|
|
|
19695
20061
|
name(lists.leftAlone, `${path} (a symbolic link, or inside one \u2014 not followed)`, path);
|
|
19696
20062
|
return;
|
|
19697
20063
|
}
|
|
19698
|
-
const abs =
|
|
20064
|
+
const abs = join26(targetRepo, path);
|
|
19699
20065
|
let isFile;
|
|
19700
20066
|
try {
|
|
19701
20067
|
isFile = lstatSync4(abs).isFile();
|
|
@@ -19709,7 +20075,7 @@ function removeInstalledFile(targetRepo, path, entry, writeSet, lists, name) {
|
|
|
19709
20075
|
if (!writeSet.ownedDirs.has(path.slice(0, path.lastIndexOf("/")))) {
|
|
19710
20076
|
let bytes;
|
|
19711
20077
|
try {
|
|
19712
|
-
bytes =
|
|
20078
|
+
bytes = readFileSync23(abs);
|
|
19713
20079
|
} catch {
|
|
19714
20080
|
name(lists.failed, path, path);
|
|
19715
20081
|
return;
|
|
@@ -19729,7 +20095,7 @@ function removeInstalledFile(targetRepo, path, entry, writeSet, lists, name) {
|
|
|
19729
20095
|
function removeEmptyDir(targetRepo, dir, writeSet, lists, name) {
|
|
19730
20096
|
if (!writeSet.dirs.has(dir) || symlinkOnPath(targetRepo, dir))
|
|
19731
20097
|
return;
|
|
19732
|
-
const abs =
|
|
20098
|
+
const abs = join26(targetRepo, dir);
|
|
19733
20099
|
try {
|
|
19734
20100
|
if (!lstatSync4(abs).isDirectory() || readdirSync(abs).length > 0)
|
|
19735
20101
|
return;
|
|
@@ -19748,10 +20114,10 @@ function trimAppendedFile(targetRepo, rec, deleteWhenEmpty, rules, lists, name)
|
|
|
19748
20114
|
name(lists.leftAlone, `${rel} (a symbolic link \u2014 not followed)`, rel);
|
|
19749
20115
|
return [];
|
|
19750
20116
|
}
|
|
19751
|
-
const path =
|
|
20117
|
+
const path = join26(targetRepo, rel);
|
|
19752
20118
|
let before;
|
|
19753
20119
|
try {
|
|
19754
|
-
before =
|
|
20120
|
+
before = readFileSync23(path, "utf-8");
|
|
19755
20121
|
} catch {
|
|
19756
20122
|
return [];
|
|
19757
20123
|
}
|
|
@@ -19795,16 +20161,16 @@ function filesUnder(root, rel) {
|
|
|
19795
20161
|
const walk = (dirRel) => {
|
|
19796
20162
|
let names;
|
|
19797
20163
|
try {
|
|
19798
|
-
if (!lstatSync4(
|
|
20164
|
+
if (!lstatSync4(join26(root, dirRel)).isDirectory())
|
|
19799
20165
|
return;
|
|
19800
|
-
names = readdirSync(
|
|
20166
|
+
names = readdirSync(join26(root, dirRel));
|
|
19801
20167
|
} catch {
|
|
19802
20168
|
return;
|
|
19803
20169
|
}
|
|
19804
20170
|
for (const entry of names.sort()) {
|
|
19805
20171
|
const childRel = `${dirRel}/${entry}`;
|
|
19806
20172
|
try {
|
|
19807
|
-
if (lstatSync4(
|
|
20173
|
+
if (lstatSync4(join26(root, childRel)).isDirectory())
|
|
19808
20174
|
walk(childRel);
|
|
19809
20175
|
else
|
|
19810
20176
|
out.push(childRel);
|
|
@@ -19843,13 +20209,13 @@ function renderUninstallReport(result, home) {
|
|
|
19843
20209
|
section("Kept, because it changed after Halfcycle wrote it \u2014 delete it yourself if you do not want it:", result.kept);
|
|
19844
20210
|
section('Kept in .gitignore, because the path each line covers still exists \u2014 without the line, "git add ." would commit it:', result.keptIgnoreLines);
|
|
19845
20211
|
section("Left alone \u2014 Halfcycle did not write these, or cannot tell that it did:", result.leftAlone);
|
|
19846
|
-
section(`Could not remove \u2014 fix the cause, then run "${
|
|
20212
|
+
section(`Could not remove \u2014 fix the cause, then run "${ownCommand()} uninstall" again to finish:`, result.failed);
|
|
19847
20213
|
const c = result.credential;
|
|
19848
20214
|
switch (c.kind) {
|
|
19849
20215
|
case "still-held":
|
|
19850
20216
|
out.push(`[halfcycle] This machine still holds this project's credential, at ${shownPath(c.dir, home)}.`);
|
|
19851
20217
|
out.push(" Every checkout and worktree of this project on this machine uses it.");
|
|
19852
|
-
out.push(` To remove it too: ${
|
|
20218
|
+
out.push(` To remove it too: ${ownCommand()} uninstall ${REMOVE_CREDENTIAL_FLAG}`);
|
|
19853
20219
|
break;
|
|
19854
20220
|
case "invalid-id":
|
|
19855
20221
|
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.`);
|
|
@@ -19857,7 +20223,7 @@ function renderUninstallReport(result, home) {
|
|
|
19857
20223
|
case "removed":
|
|
19858
20224
|
out.push(`[halfcycle] Removed this machine's credential for this project, at ${shownPath(c.dir, home)}.`);
|
|
19859
20225
|
out.push(" Every other checkout and worktree of this project on this machine used it too. Each stops reaching");
|
|
19860
|
-
out.push(` Halfcycle until \`${
|
|
20226
|
+
out.push(` Halfcycle until \`${ownCommand()}\` is run there again.`);
|
|
19861
20227
|
break;
|
|
19862
20228
|
case "not-held":
|
|
19863
20229
|
out.push(`[halfcycle] This machine holds no credential for this project (nothing at ${shownPath(c.dir, home)}).`);
|
|
@@ -19866,7 +20232,7 @@ function renderUninstallReport(result, home) {
|
|
|
19866
20232
|
out.push(`[halfcycle] No credential was removed: ${BUNDLE_PIN_REL} is missing, so there is no project id to find it by.`);
|
|
19867
20233
|
break;
|
|
19868
20234
|
case "failed":
|
|
19869
|
-
out.push(`[halfcycle] Could not remove this machine's credential for this project, at ${shownPath(c.dir, home)}: ${c.reason}. Run "${
|
|
20235
|
+
out.push(`[halfcycle] Could not remove this machine's credential for this project, at ${shownPath(c.dir, home)}: ${c.reason}. Run "${ownCommand()} uninstall ${REMOVE_CREDENTIAL_FLAG}" again to finish.`);
|
|
19870
20236
|
break;
|
|
19871
20237
|
case "nothing-to-say":
|
|
19872
20238
|
break;
|
|
@@ -19876,8 +20242,7 @@ function renderUninstallReport(result, home) {
|
|
|
19876
20242
|
}
|
|
19877
20243
|
|
|
19878
20244
|
// dist/banner-facts.js
|
|
19879
|
-
import {
|
|
19880
|
-
import { basename as basename4, join as join26, resolve as resolve7 } from "node:path";
|
|
20245
|
+
import { basename as basename4, resolve as resolve7 } from "node:path";
|
|
19881
20246
|
var BRAND_URL = "halfcycle.ai";
|
|
19882
20247
|
var TAGLINE = [
|
|
19883
20248
|
"Fewer cycles.",
|
|
@@ -19888,14 +20253,6 @@ var TAGLINE_NARROW = [
|
|
|
19888
20253
|
"The thinking moves earlier.",
|
|
19889
20254
|
"So does the finish."
|
|
19890
20255
|
];
|
|
19891
|
-
function bundleVersion() {
|
|
19892
|
-
try {
|
|
19893
|
-
const pkg = JSON.parse(readFileSync23(join26(BUNDLE_ROOT, "package.json"), "utf-8"));
|
|
19894
|
-
return typeof pkg.version === "string" ? pkg.version : void 0;
|
|
19895
|
-
} catch {
|
|
19896
|
-
return void 0;
|
|
19897
|
-
}
|
|
19898
|
-
}
|
|
19899
20256
|
function claudeCodeVersion(deps = {}) {
|
|
19900
20257
|
return probeClaudeCodeVersion(3e3, deps);
|
|
19901
20258
|
}
|
|
@@ -19984,8 +20341,8 @@ function safeBasename(dir) {
|
|
|
19984
20341
|
}
|
|
19985
20342
|
|
|
19986
20343
|
// dist/size.js
|
|
19987
|
-
var SIZE_USAGE = `${
|
|
19988
|
-
${
|
|
20344
|
+
var SIZE_USAGE = `${ownCommand()} size sort --description "<one line>" [--existing-code] [--sign-in] [--shared-data] [--paid-services] [--more-than-three-parts] [--repo <root>]
|
|
20345
|
+
${ownCommand()} size up <medium|full> [--repo <root>]`;
|
|
19989
20346
|
function usage2(line) {
|
|
19990
20347
|
return { ok: false, line: `${line}
|
|
19991
20348
|
Usage:
|
|
@@ -20114,7 +20471,7 @@ async function runSizeSort(repoRoot, args2, deps) {
|
|
|
20114
20471
|
} catch {
|
|
20115
20472
|
return {
|
|
20116
20473
|
stdout: "",
|
|
20117
|
-
stderr: `halfcycle size: this project is not connected to Halfcycle on this machine. Run ${
|
|
20474
|
+
stderr: `halfcycle size: this project is not connected to Halfcycle on this machine. Run ${ownCommand()} here first, then run this again. Nothing was sent.
|
|
20118
20475
|
`,
|
|
20119
20476
|
exitCode: 1
|
|
20120
20477
|
};
|
|
@@ -20125,7 +20482,7 @@ async function runSizeSort(repoRoot, args2, deps) {
|
|
|
20125
20482
|
`, exitCode: 1 };
|
|
20126
20483
|
const sized = projectSizeSizedSchema.safeParse(answer.raw);
|
|
20127
20484
|
if (sized.success) {
|
|
20128
|
-
const usageLines2 = (sized.data.usage ?? []).map((line) => `[halfcycle] ${line}
|
|
20485
|
+
const usageLines2 = sizeAnswerPrintedLinesOf(sized.data.usage ?? []).map((line) => `[halfcycle] ${line}
|
|
20129
20486
|
`).join("");
|
|
20130
20487
|
return {
|
|
20131
20488
|
stdout: `[halfcycle] ${sized.data.sentence}
|
|
@@ -20162,7 +20519,7 @@ async function runSizeUp(repoRoot, args2, deps) {
|
|
|
20162
20519
|
} catch {
|
|
20163
20520
|
return {
|
|
20164
20521
|
stdout: "",
|
|
20165
|
-
stderr: `halfcycle size: this project is not connected to Halfcycle on this machine. Run ${
|
|
20522
|
+
stderr: `halfcycle size: this project is not connected to Halfcycle on this machine. Run ${ownCommand()} here first, then run this again. Nothing was sent.
|
|
20166
20523
|
`,
|
|
20167
20524
|
exitCode: 1
|
|
20168
20525
|
};
|
|
@@ -20192,11 +20549,134 @@ function runSize(repoRoot, args2, deps) {
|
|
|
20192
20549
|
return args2.form === "sort" ? runSizeSort(repoRoot, args2, deps) : runSizeUp(repoRoot, args2, deps);
|
|
20193
20550
|
}
|
|
20194
20551
|
|
|
20552
|
+
// dist/regression.js
|
|
20553
|
+
var REGRESSION_USAGE = `${ownCommand()} regression --every <1 to 20> [--repo <root>]`;
|
|
20554
|
+
function usage3(line) {
|
|
20555
|
+
return { ok: false, line: `${line}
|
|
20556
|
+
Usage:
|
|
20557
|
+
${REGRESSION_USAGE}
|
|
20558
|
+
`, exitCode: 2 };
|
|
20559
|
+
}
|
|
20560
|
+
function parseRegressionArgs(rest) {
|
|
20561
|
+
let every = null;
|
|
20562
|
+
let repoRoot;
|
|
20563
|
+
for (let i = 0; i < rest.length; i++) {
|
|
20564
|
+
const w = rest[i];
|
|
20565
|
+
if (w === "--every" || w === "--repo") {
|
|
20566
|
+
const v = rest[i + 1];
|
|
20567
|
+
if (v === void 0 || v.trim() === "" || w === "--repo" && v.startsWith("--")) {
|
|
20568
|
+
return usage3(`halfcycle regression: ${w} needs a value after it.`);
|
|
20569
|
+
}
|
|
20570
|
+
if (w === "--every")
|
|
20571
|
+
every = v;
|
|
20572
|
+
else
|
|
20573
|
+
repoRoot = v;
|
|
20574
|
+
i++;
|
|
20575
|
+
} else {
|
|
20576
|
+
return usage3(`halfcycle regression: "${w}" is not something this command takes.`);
|
|
20577
|
+
}
|
|
20578
|
+
}
|
|
20579
|
+
if (every === null)
|
|
20580
|
+
return usage3("halfcycle regression: say how many phases may close between regressions.");
|
|
20581
|
+
const number = /^-?\d+$/.test(every.trim()) ? Number(every) : Number.NaN;
|
|
20582
|
+
const parsed = regressionEveryRequestSchema.safeParse({ every: number });
|
|
20583
|
+
if (!parsed.success) {
|
|
20584
|
+
return { ok: false, line: `${REGRESSION_EVERY_RANGE_REFUSAL}
|
|
20585
|
+
`, exitCode: 2 };
|
|
20586
|
+
}
|
|
20587
|
+
return { ok: true, args: { request: parsed.data, ...repoRoot === void 0 ? {} : { repoRoot } } };
|
|
20588
|
+
}
|
|
20589
|
+
var COULD_NOT_REACH3 = "Halfcycle could not be reached. Nothing was changed; check this machine's connection, then run this again.";
|
|
20590
|
+
async function runRegression(repoRoot, args2, deps) {
|
|
20591
|
+
const p = "halfcycle regression:";
|
|
20592
|
+
let credential;
|
|
20593
|
+
try {
|
|
20594
|
+
credential = deps.readCredential(repoRoot);
|
|
20595
|
+
} catch {
|
|
20596
|
+
return {
|
|
20597
|
+
stdout: "",
|
|
20598
|
+
stderr: `${p} this project is not connected to Halfcycle on this machine. Run ${ownCommand()} here first, then run this again. Nothing was sent.
|
|
20599
|
+
`,
|
|
20600
|
+
exitCode: 1
|
|
20601
|
+
};
|
|
20602
|
+
}
|
|
20603
|
+
const url = `${credential.serviceUrl}/engagements/${encodeURIComponent(credential.engagementId)}/regression-every`;
|
|
20604
|
+
let status;
|
|
20605
|
+
let raw = null;
|
|
20606
|
+
try {
|
|
20607
|
+
const res = await deps.fetch(url, {
|
|
20608
|
+
method: "PUT",
|
|
20609
|
+
headers: { authorization: `Bearer ${credential.token}`, "content-type": "application/json" },
|
|
20610
|
+
body: JSON.stringify(args2.request)
|
|
20611
|
+
});
|
|
20612
|
+
status = res.status;
|
|
20613
|
+
const text = await res.text();
|
|
20614
|
+
try {
|
|
20615
|
+
raw = JSON.parse(text);
|
|
20616
|
+
} catch {
|
|
20617
|
+
raw = null;
|
|
20618
|
+
}
|
|
20619
|
+
} catch {
|
|
20620
|
+
return { stdout: "", stderr: `${p} ${COULD_NOT_REACH3}
|
|
20621
|
+
`, exitCode: 1 };
|
|
20622
|
+
}
|
|
20623
|
+
const answered2 = regressionEveryResponseSchema.safeParse(raw);
|
|
20624
|
+
if (status >= 200 && status < 300 && answered2.success) {
|
|
20625
|
+
const line = answered2.data.line === null ? "" : `[halfcycle] ${answered2.data.line}
|
|
20626
|
+
`;
|
|
20627
|
+
return {
|
|
20628
|
+
stdout: `[halfcycle] The number of phases between regressions is now ${answered2.data.every}.
|
|
20629
|
+
${line}`,
|
|
20630
|
+
stderr: "",
|
|
20631
|
+
exitCode: 0
|
|
20632
|
+
};
|
|
20633
|
+
}
|
|
20634
|
+
const wire = wireErrorSchema.safeParse(raw);
|
|
20635
|
+
const said = wire.success ? wire.data.message : `Halfcycle answered ${status}. Nothing was changed; run this again later.`;
|
|
20636
|
+
return { stdout: "", stderr: `${p} ${said}
|
|
20637
|
+
`, exitCode: 1 };
|
|
20638
|
+
}
|
|
20639
|
+
|
|
20640
|
+
// dist/crew.js
|
|
20641
|
+
var CREW_USAGE = `${ownCommand()} crew [--repo <root>]`;
|
|
20642
|
+
function usage4(line) {
|
|
20643
|
+
return { ok: false, line: `${line}
|
|
20644
|
+
Usage:
|
|
20645
|
+
${CREW_USAGE}
|
|
20646
|
+
`, exitCode: 2 };
|
|
20647
|
+
}
|
|
20648
|
+
function parseCrewArgs(rest) {
|
|
20649
|
+
let repoRoot;
|
|
20650
|
+
for (let i = 0; i < rest.length; i++) {
|
|
20651
|
+
const w = rest[i];
|
|
20652
|
+
if (w === "--repo") {
|
|
20653
|
+
const v = rest[i + 1];
|
|
20654
|
+
if (v === void 0 || v.trim() === "" || v.startsWith("--")) {
|
|
20655
|
+
return usage4("halfcycle crew: --repo needs a value after it.");
|
|
20656
|
+
}
|
|
20657
|
+
repoRoot = v;
|
|
20658
|
+
i++;
|
|
20659
|
+
} else {
|
|
20660
|
+
return usage4(`halfcycle crew: "${w}" is not something this command takes.`);
|
|
20661
|
+
}
|
|
20662
|
+
}
|
|
20663
|
+
return { ok: true, ...repoRoot === void 0 ? {} : { repoRoot } };
|
|
20664
|
+
}
|
|
20665
|
+
async function runCrew(repoRoot, deps) {
|
|
20666
|
+
const size = await readSizeOnce(repoRoot, deps);
|
|
20667
|
+
const members = crewOfProjectSize(size.kind === "small" ? "small" : void 0);
|
|
20668
|
+
const stdout = members.map((m) => `${m.callsign} ${m.role} ${m.tier}
|
|
20669
|
+
`).join("");
|
|
20670
|
+
const stderr = size.kind === "failed" ? `halfcycle crew: ${SIZE_READ_FAILED_LINE}
|
|
20671
|
+
` : "";
|
|
20672
|
+
return { stdout, stderr, exitCode: 0 };
|
|
20673
|
+
}
|
|
20674
|
+
|
|
20195
20675
|
// dist/pace.js
|
|
20196
20676
|
import { spawn as spawn3 } from "node:child_process";
|
|
20197
20677
|
import { readFileSync as readFileSync24 } from "node:fs";
|
|
20198
20678
|
import { join as join27 } from "node:path";
|
|
20199
|
-
var PACE_USAGE = `${
|
|
20679
|
+
var PACE_USAGE = `${ownCommand()} pace [--agent claude-code|codex] [--start <phase> [--agents <n>]] [--repo <root>]`;
|
|
20200
20680
|
function parsePaceArgs(argv) {
|
|
20201
20681
|
let repoRoot;
|
|
20202
20682
|
let phase;
|
|
@@ -20302,7 +20782,7 @@ function readPaceLine(stdout) {
|
|
|
20302
20782
|
return null;
|
|
20303
20783
|
}
|
|
20304
20784
|
}
|
|
20305
|
-
var PACE_NOT_CONNECTED_LINE = `halfcycle pace: this project is not signed in on this computer. ${
|
|
20785
|
+
var PACE_NOT_CONNECTED_LINE = `halfcycle pace: this project is not signed in on this computer. ${hookRemedy(ownVersion())}`;
|
|
20306
20786
|
function notPacedLine(reason) {
|
|
20307
20787
|
return reason === "not-configured" ? `${PACE_NOT_CONNECTED_LINE}
|
|
20308
20788
|
` : "halfcycle pace: Halfcycle could not read this computer's usage records just now. Run this again in a minute.\n";
|
|
@@ -20344,7 +20824,7 @@ async function runPace(repoRoot, start, deps, agent = null) {
|
|
|
20344
20824
|
if (result.suggestion === void 0)
|
|
20345
20825
|
return { stdout: "", stderr: notPacedLine("unreadable"), exitCode: 0 };
|
|
20346
20826
|
const behind = promptScriptBehind((deps.readPromptScript ?? readPromptScript)(repoRoot));
|
|
20347
|
-
return { stdout: `${paceStartLinesOf(result.suggestion, behind).join("\n")}
|
|
20827
|
+
return { stdout: `${paceStartLinesOf(result.suggestion, behind, ownVersion()).join("\n")}
|
|
20348
20828
|
`, stderr: "", exitCode: 0 };
|
|
20349
20829
|
}
|
|
20350
20830
|
const share = result.keepFreePercent ?? KEEP_FREE_DEFAULT;
|
|
@@ -20370,7 +20850,7 @@ async function runPace(repoRoot, start, deps, agent = null) {
|
|
|
20370
20850
|
// dist/route.js
|
|
20371
20851
|
import { readFileSync as readFileSync25 } from "node:fs";
|
|
20372
20852
|
import { join as join28 } from "node:path";
|
|
20373
|
-
var ROUTE_USAGE = `${
|
|
20853
|
+
var ROUTE_USAGE = `${ownCommand()} route <phase> <task> [--agent claude-code|codex] [--repo <root>]`;
|
|
20374
20854
|
var ROUTE_WORK_READ_DEADLINE_MS = 5e3;
|
|
20375
20855
|
function parseRouteArgs(argv) {
|
|
20376
20856
|
const positionals = [];
|
|
@@ -20502,11 +20982,11 @@ async function runRoute(repoRoot, phase, taskId, agent, deps) {
|
|
|
20502
20982
|
}
|
|
20503
20983
|
|
|
20504
20984
|
// dist/restore.js
|
|
20505
|
-
import { spawnSync as
|
|
20985
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
20506
20986
|
import { closeSync as closeSync3, fchmodSync, lstatSync as lstatSync5, mkdirSync as mkdirSync12, openSync as openSync3, readdirSync as readdirSync2, readFileSync as readFileSync26, readlinkSync, realpathSync as realpathSync7, rmSync as rmSync9, symlinkSync, writeSync as writeSync3 } from "node:fs";
|
|
20507
20987
|
import { homedir as homedir8 } from "node:os";
|
|
20508
20988
|
import { join as join29, resolve as resolve8 } from "node:path";
|
|
20509
|
-
var RESTORE_USAGE = `${
|
|
20989
|
+
var RESTORE_USAGE = `${ownCommand()} restore [<id>]`;
|
|
20510
20990
|
function parseRestoreArgs(rest) {
|
|
20511
20991
|
if (rest.length > 1)
|
|
20512
20992
|
return { ok: false };
|
|
@@ -20515,7 +20995,7 @@ function parseRestoreArgs(rest) {
|
|
|
20515
20995
|
return { ok: false };
|
|
20516
20996
|
return { ok: true, id: first };
|
|
20517
20997
|
}
|
|
20518
|
-
var LIST_COMMAND = `${
|
|
20998
|
+
var LIST_COMMAND = `${ownCommand()} restore`;
|
|
20519
20999
|
function runRestore(cwd, id, options = {}) {
|
|
20520
21000
|
const now = options.now ?? Date.now;
|
|
20521
21001
|
const top = git2(cwd, ["rev-parse", "--show-toplevel"]);
|
|
@@ -20525,7 +21005,7 @@ function runRestore(cwd, id, options = {}) {
|
|
|
20525
21005
|
const root = realOrSelf(top.text.trim());
|
|
20526
21006
|
const engagementId = pinnedEngagement(root);
|
|
20527
21007
|
if (engagementId === null) {
|
|
20528
|
-
return refused2(`Halfcycle is not set up in this project, so it has saved nothing here. To set it up, run: ${
|
|
21008
|
+
return refused2(`Halfcycle is not set up in this project, so it has saved nothing here. To set it up, run: ${ownCommand()}`);
|
|
20529
21009
|
}
|
|
20530
21010
|
const folder2 = join29(options.home ?? homedir8(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, SAVED_DIR_NAME);
|
|
20531
21011
|
const refs = savedRefs(root);
|
|
@@ -20752,7 +21232,7 @@ function restoreFromCommit(root, ref, rels, out) {
|
|
|
20752
21232
|
}
|
|
20753
21233
|
}
|
|
20754
21234
|
function readBlobs(root, objects) {
|
|
20755
|
-
const r =
|
|
21235
|
+
const r = spawnSync3("git", ["cat-file", "--batch"], {
|
|
20756
21236
|
cwd: root,
|
|
20757
21237
|
env: gitEnvForNamedRepo(),
|
|
20758
21238
|
input: `${objects.join("\n")}
|
|
@@ -20898,7 +21378,7 @@ function markedRepository(dir) {
|
|
|
20898
21378
|
}
|
|
20899
21379
|
function git2(cwd, args2) {
|
|
20900
21380
|
try {
|
|
20901
|
-
const r =
|
|
21381
|
+
const r = spawnSync3("git", args2, { cwd, encoding: "utf-8", env: gitEnvForNamedRepo(), maxBuffer: 64 * 1024 * 1024 });
|
|
20902
21382
|
return r.status === 0 ? { ok: true, text: r.stdout ?? "" } : { ok: false };
|
|
20903
21383
|
} catch {
|
|
20904
21384
|
return { ok: false };
|
|
@@ -20920,10 +21400,10 @@ function realOrSelf(p) {
|
|
|
20920
21400
|
}
|
|
20921
21401
|
|
|
20922
21402
|
// dist/request-shapes-verb.js
|
|
20923
|
-
import { chmodSync as chmodSync4, existsSync as
|
|
21403
|
+
import { chmodSync as chmodSync4, existsSync as existsSync16, mkdirSync as mkdirSync13, readFileSync as readFileSync27, rmSync as rmSync10, writeFileSync as writeFileSync10 } from "node:fs";
|
|
20924
21404
|
import { homedir as homedir9, platform as platform5 } from "node:os";
|
|
20925
21405
|
import { join as join30 } from "node:path";
|
|
20926
|
-
var REQUEST_SHAPES_USAGE = `${
|
|
21406
|
+
var REQUEST_SHAPES_USAGE = `${ownCommand()} request-shapes on|off|show|clear`;
|
|
20927
21407
|
var REQUEST_SHAPES_FORMS = ["on", "off", "show", "clear"];
|
|
20928
21408
|
var REQUEST_SHAPES_ON_LINE = "Halfcycle will write down the shape of every request this computer sends to it, never what is in them, until you run request-shapes off.";
|
|
20929
21409
|
var REQUEST_SHAPES_OFF_LINE = "Halfcycle has stopped writing down requests. What it wrote is still there: run request-shapes show to read it, or request-shapes clear to remove it.";
|
|
@@ -20967,7 +21447,7 @@ function formatLine(line) {
|
|
|
20967
21447
|
function showLog(logPath2) {
|
|
20968
21448
|
let text = "";
|
|
20969
21449
|
try {
|
|
20970
|
-
text =
|
|
21450
|
+
text = existsSync16(logPath2) ? readFileSync27(logPath2, "utf-8") : "";
|
|
20971
21451
|
} catch {
|
|
20972
21452
|
text = "";
|
|
20973
21453
|
}
|
|
@@ -21004,7 +21484,7 @@ function runRequestShapes(form, home = homedir9()) {
|
|
|
21004
21484
|
case "show":
|
|
21005
21485
|
return { stdout: showLog(logPath2), stderr: "", exitCode: 0 };
|
|
21006
21486
|
case "clear":
|
|
21007
|
-
if (
|
|
21487
|
+
if (existsSync16(logPath2)) {
|
|
21008
21488
|
writeFileSync10(logPath2, "", { mode: 384 });
|
|
21009
21489
|
ownerOnly(logPath2, 384);
|
|
21010
21490
|
}
|
|
@@ -21075,19 +21555,23 @@ function reportCodexVersion(deps = {}) {
|
|
|
21075
21555
|
function shortId(id) {
|
|
21076
21556
|
return id.length > 10 ? `${id.slice(0, 8)}\u2026` : id;
|
|
21077
21557
|
}
|
|
21078
|
-
var USAGE = ` ${
|
|
21558
|
+
var USAGE = ` ${ownCommand()} [install] [target-repo] [engagement-id] [self-build|client]
|
|
21079
21559
|
install into target (default: the current directory)
|
|
21080
21560
|
--yes / -y: use this machine's saved sign-in without being asked
|
|
21081
|
-
${
|
|
21082
|
-
${
|
|
21561
|
+
${ownCommand()} check-drift <target-repo>
|
|
21562
|
+
${ownCommand()} build-record <phase> [--repo <root>]
|
|
21083
21563
|
fetch a closed phase's Build Record and write it into docs/build-records/
|
|
21084
|
-
${
|
|
21564
|
+
${ownCommand()} open-phase <phase|--none> --actor "\u2026" (--evidence "\u2026" | --override --reason "\u2026" | --sorting <id> | --size <id>) [--repo <root>]
|
|
21085
21565
|
${SIZE_USAGE}
|
|
21086
21566
|
size a new project at its first sitting, or move its size up
|
|
21087
|
-
${
|
|
21567
|
+
${REGRESSION_USAGE}
|
|
21568
|
+
set how many phases may close between regression phases
|
|
21569
|
+
${CREW_USAGE}
|
|
21570
|
+
list the crew this project may use
|
|
21571
|
+
${ownCommand()} close-phase <phase> --verdict <clean|defects> --actor "\u2026" [--finding "\u2026"]\u2026 [--details] [--repo <root>]
|
|
21088
21572
|
[--thesis "\u2026"] [--outcome "\u2026"] [--deviation "\u2026"]\u2026
|
|
21089
21573
|
open the owner's review of the phase; it closes when the owner has decided every part
|
|
21090
|
-
${
|
|
21574
|
+
${ownCommand()} system [--repo <root>]
|
|
21091
21575
|
check docs/system.json, the description of your system, and send it to Halfcycle
|
|
21092
21576
|
${DRAW_USAGE}
|
|
21093
21577
|
draw the architecture picture, or one phase's plan, from docs/system.json into docs/diagrams/
|
|
@@ -21113,9 +21597,9 @@ var USAGE = ` ${HALFCYCLE_COMMAND} [install] [target-repo] [engagement-id] [sel
|
|
|
21113
21597
|
many may start right now
|
|
21114
21598
|
${ROUTE_USAGE}
|
|
21115
21599
|
say how to start a task of a phase's plan: which crew member, and on which tier
|
|
21116
|
-
${
|
|
21600
|
+
${ownCommand()} preview [folder]
|
|
21117
21601
|
show what setting up this folder would add, change, remove or leave alone; writes nothing
|
|
21118
|
-
${
|
|
21602
|
+
${ownCommand()} doctor
|
|
21119
21603
|
check this folder: set up, signed in, connected, checks running, up to date; says what to do for each one that is not; changes nothing
|
|
21120
21604
|
${REQUEST_SHAPES_USAGE}
|
|
21121
21605
|
write down the shape of every request this computer sends to Halfcycle, never what is in
|
|
@@ -21123,11 +21607,11 @@ var USAGE = ` ${HALFCYCLE_COMMAND} [install] [target-repo] [engagement-id] [sel
|
|
|
21123
21607
|
${RESTORE_USAGE}
|
|
21124
21608
|
list the copies Halfcycle saved before a change it let through, or bring one back; never
|
|
21125
21609
|
replaces a file or a line of work that exists now
|
|
21126
|
-
${
|
|
21610
|
+
${ownCommand()} allow [--remove]
|
|
21127
21611
|
let your coding agent work without asking on this computer, for tests, builds, installs, local
|
|
21128
21612
|
tools, files and local git; pushing, publishing, deploying, deleting and GitHub still ask;
|
|
21129
21613
|
--remove takes out what this added and keeps everything of yours
|
|
21130
|
-
${
|
|
21614
|
+
${ownCommand()} uninstall [${REMOVE_CREDENTIAL_FLAG}]
|
|
21131
21615
|
take out what the installer added to this project, keeping anything you changed
|
|
21132
21616
|
${REMOVE_CREDENTIAL_FLAG}: also remove this machine's credential for the project, which every
|
|
21133
21617
|
checkout and worktree of that project on this machine shares
|
|
@@ -21140,7 +21624,7 @@ var USAGE = ` ${HALFCYCLE_COMMAND} [install] [target-repo] [engagement-id] [sel
|
|
|
21140
21624
|
var RETIRED_VERBS = /* @__PURE__ */ new Map([
|
|
21141
21625
|
["ci", '"ci" was removed \u2014 CI setup is part of your project, not Halfcycle']
|
|
21142
21626
|
]);
|
|
21143
|
-
var PROJECT_RESET_LINE =
|
|
21627
|
+
var PROJECT_RESET_LINE = `To start your own project here, delete the .halfcycle folder and run "${ownBareCommand()}" again.`;
|
|
21144
21628
|
function selfBuildNotOwnerLine(account) {
|
|
21145
21629
|
return `Halfcycle's own project can only be joined by its owner. This computer is signed in as ${account}. Nothing was written.`;
|
|
21146
21630
|
}
|
|
@@ -21293,7 +21777,7 @@ ${USAGE}`);
|
|
|
21293
21777
|
if (cmd === "allow") {
|
|
21294
21778
|
const extra = rest.filter((word, i) => word !== "--remove" && word !== "--repo" && rest[i - 1] !== "--repo");
|
|
21295
21779
|
if (extra.length > 0) {
|
|
21296
|
-
process.stderr.write(`halfcycle allow: usage: ${
|
|
21780
|
+
process.stderr.write(`halfcycle allow: usage: ${ownCommand()} allow [--remove]
|
|
21297
21781
|
`);
|
|
21298
21782
|
process.exitCode = 2;
|
|
21299
21783
|
return;
|
|
@@ -21309,13 +21793,13 @@ ${USAGE}`);
|
|
|
21309
21793
|
if (installArm) {
|
|
21310
21794
|
const engagementIdArg = positionals[1];
|
|
21311
21795
|
const engagementTypeRaw = positionals[2] ?? "client";
|
|
21312
|
-
if (!
|
|
21796
|
+
if (!existsSync17(targetRepo)) {
|
|
21313
21797
|
process.stderr.write(`${folderMissingRefusal(targetRepo)}
|
|
21314
21798
|
`);
|
|
21315
21799
|
process.exit(1);
|
|
21316
21800
|
return;
|
|
21317
21801
|
}
|
|
21318
|
-
if (!
|
|
21802
|
+
if (!statSync3(targetRepo).isDirectory()) {
|
|
21319
21803
|
process.stderr.write(`${folderIsFileRefusal(targetRepo)}
|
|
21320
21804
|
`);
|
|
21321
21805
|
process.exit(1);
|
|
@@ -21442,7 +21926,11 @@ ${kept}`);
|
|
|
21442
21926
|
} else {
|
|
21443
21927
|
const hadUsableIdBeforeThisRun = readFolderId(targetRepo) !== void 0;
|
|
21444
21928
|
const projectId = runPreCreateChecks(targetRepo);
|
|
21445
|
-
const created = await createOwnedEngagement(serviceUrl, targetRepo, projectId, {
|
|
21929
|
+
const created = await createOwnedEngagement(serviceUrl, targetRepo, projectId, {
|
|
21930
|
+
assumeYes,
|
|
21931
|
+
verbose,
|
|
21932
|
+
folderAlreadyHasProject: hadUsableIdBeforeThisRun
|
|
21933
|
+
});
|
|
21446
21934
|
if (isProjectOwnedElsewhere(created)) {
|
|
21447
21935
|
process.stderr.write(`halfcycle: ${created.message}
|
|
21448
21936
|
${PROJECT_RESET_LINE}
|
|
@@ -21485,8 +21973,10 @@ ${PROJECT_RESET_LINE}
|
|
|
21485
21973
|
engagementId,
|
|
21486
21974
|
engagementType: engagementTypeRaw,
|
|
21487
21975
|
credential,
|
|
21488
|
-
createdByAccountId
|
|
21976
|
+
createdByAccountId,
|
|
21977
|
+
serviceUrl
|
|
21489
21978
|
});
|
|
21979
|
+
const ownServer = approveOwnServer(targetRepo);
|
|
21490
21980
|
if (verbose) {
|
|
21491
21981
|
process.stdout.write(`[halfcycle] Installed v${result.version} into ${targetRepo}
|
|
21492
21982
|
`);
|
|
@@ -21535,18 +22025,18 @@ ${PROJECT_RESET_LINE}
|
|
|
21535
22025
|
`);
|
|
21536
22026
|
}
|
|
21537
22027
|
} else if (probe.slow === true) {
|
|
21538
|
-
process.stdout.write(`${SLOW_SERVICE_LINE}${
|
|
22028
|
+
process.stdout.write(`${SLOW_SERVICE_LINE}${doctorLine(ownVersion())}
|
|
21539
22029
|
`);
|
|
21540
22030
|
} else if (probe.credentialRefused) {
|
|
21541
22031
|
process.stdout.write(`[halfcycle] Method delivery (MCP): ${credential.mcpUrl} \u2014 ${probe.problem}.
|
|
21542
|
-
[halfcycle] The address is right, but Halfcycle commands will not work in this project until it accepts this machine's credential. Re-run "
|
|
21543
|
-
${
|
|
22032
|
+
[halfcycle] The address is right, but Halfcycle commands will not work in this project until it accepts this machine's credential. Re-run "${ownBareCommand()}" in this folder; if it says this again, the refusal is on Halfcycle's side, not in this project.
|
|
22033
|
+
${doctorLine(ownVersion())}
|
|
21544
22034
|
`);
|
|
21545
22035
|
} else {
|
|
21546
22036
|
const otherAddress = controlOriginConfirmed ? `The other address Halfcycle uses answered fine; this is the only one that did not. ` : `This run could not reach the Halfcycle service at all just now, so this may simply be offline. `;
|
|
21547
22037
|
process.stdout.write(`[halfcycle] Method delivery (MCP): ${credential.mcpUrl} \u2014 NOT reachable: ${probe.problem}.
|
|
21548
22038
|
[halfcycle] Halfcycle commands will not work in this project until that address answers. ${otherAddress}Run this installer again once the service is up.
|
|
21549
|
-
${
|
|
22039
|
+
${doctorLine(ownVersion())}
|
|
21550
22040
|
`);
|
|
21551
22041
|
}
|
|
21552
22042
|
}
|
|
@@ -21561,7 +22051,7 @@ ${DOCTOR_LINE}
|
|
|
21561
22051
|
`);
|
|
21562
22052
|
}
|
|
21563
22053
|
if (credential === void 0 && notThisAccountMessage !== void 0) {
|
|
21564
|
-
process.stdout.write(`[halfcycle] ${notThisAccountMessage} Your work carries on exactly as it would without Halfcycle: no edit is evaluated, and nothing you do here is reported as having passed. ${requestAccessSentence(engagementId)} ${identitySwitchRemedy(void 0, isInteractiveRun())}
|
|
22054
|
+
process.stdout.write(`[halfcycle] ${notThisAccountMessage} Your work carries on exactly as it would without Halfcycle: no edit is evaluated, and nothing you do here is reported as having passed. ${requestAccessSentence(engagementId, serviceUrl)} ${identitySwitchRemedy(void 0, isInteractiveRun())}
|
|
21565
22055
|
`);
|
|
21566
22056
|
}
|
|
21567
22057
|
reportClaudeCodeVersion(verbose);
|
|
@@ -21608,8 +22098,10 @@ ${DOCTOR_LINE}
|
|
|
21608
22098
|
if (credential) {
|
|
21609
22099
|
process.stdout.write(`[halfcycle] Next: open this folder in Claude Code \u2014 accept the workspace-trust prompt, it is expected \u2014 then say "set up this project" and answer what it asks about your project
|
|
21610
22100
|
`);
|
|
21611
|
-
|
|
22101
|
+
if (ownServer !== "approved") {
|
|
22102
|
+
process.stdout.write(`[halfcycle] Claude Code will then ask you to approve the new "halfcycle" MCP server. If it is the only one listed, the cursor starts on "Continue without using this MCP server" \u2014 choose "Use this MCP server" instead. If several are listed together, halfcycle is already checked \u2014 leave it that way. Either way, without it Halfcycle will not connect.
|
|
21612
22103
|
`);
|
|
22104
|
+
}
|
|
21613
22105
|
const codexLine2 = codexInstallLine(result.codex);
|
|
21614
22106
|
if (codexLine2 !== void 0)
|
|
21615
22107
|
process.stdout.write(`${codexLine2}
|
|
@@ -21636,7 +22128,7 @@ ${kept}`);
|
|
|
21636
22128
|
}
|
|
21637
22129
|
const fsCode = typeof err === "object" && err !== null && "code" in err ? String(err.code) : void 0;
|
|
21638
22130
|
if (fsCode !== void 0) {
|
|
21639
|
-
stop(`halfcycle: could not write to ${targetRepo} \u2014 check that you can write to this folder and run "
|
|
22131
|
+
stop(`halfcycle: could not write to ${targetRepo} \u2014 check that you can write to this folder and run "${ownBareCommand()}" again.`);
|
|
21640
22132
|
return;
|
|
21641
22133
|
}
|
|
21642
22134
|
stop(`halfcycle install failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -21973,6 +22465,42 @@ halfcycle build-record: until ${obtained.stampFailure.path} can be written, the
|
|
|
21973
22465
|
process.exitCode = run.exitCode;
|
|
21974
22466
|
return;
|
|
21975
22467
|
}
|
|
22468
|
+
if (cmd === "regression") {
|
|
22469
|
+
const parsed = parseRegressionArgs(rest);
|
|
22470
|
+
if (!parsed.ok) {
|
|
22471
|
+
process.stderr.write(parsed.line);
|
|
22472
|
+
process.exitCode = parsed.exitCode;
|
|
22473
|
+
return;
|
|
22474
|
+
}
|
|
22475
|
+
const run = await runRegression(parsed.args.repoRoot ?? process.cwd(), parsed.args, {
|
|
22476
|
+
readCredential: (root) => readRepoCredential(root),
|
|
22477
|
+
fetch: (input, init) => fetch(input, init)
|
|
22478
|
+
});
|
|
22479
|
+
if (run.stdout !== "")
|
|
22480
|
+
process.stdout.write(run.stdout);
|
|
22481
|
+
if (run.stderr !== "")
|
|
22482
|
+
process.stderr.write(run.stderr);
|
|
22483
|
+
process.exitCode = run.exitCode;
|
|
22484
|
+
return;
|
|
22485
|
+
}
|
|
22486
|
+
if (cmd === "crew") {
|
|
22487
|
+
const parsed = parseCrewArgs(rest);
|
|
22488
|
+
if (!parsed.ok) {
|
|
22489
|
+
process.stderr.write(parsed.line);
|
|
22490
|
+
process.exitCode = parsed.exitCode;
|
|
22491
|
+
return;
|
|
22492
|
+
}
|
|
22493
|
+
const run = await runCrew(parsed.repoRoot ?? process.cwd(), {
|
|
22494
|
+
readCredential: (root) => readRepoCredential(root),
|
|
22495
|
+
fetch: (input, init) => fetch(input, init)
|
|
22496
|
+
});
|
|
22497
|
+
if (run.stdout !== "")
|
|
22498
|
+
process.stdout.write(run.stdout);
|
|
22499
|
+
if (run.stderr !== "")
|
|
22500
|
+
process.stderr.write(run.stderr);
|
|
22501
|
+
process.exitCode = run.exitCode;
|
|
22502
|
+
return;
|
|
22503
|
+
}
|
|
21976
22504
|
if (cmd === "size") {
|
|
21977
22505
|
const parsed = parseSizeArgs(rest);
|
|
21978
22506
|
if (!parsed.ok) {
|
|
@@ -21997,8 +22525,8 @@ halfcycle build-record: until ${obtained.stampFailure.path} can be written, the
|
|
|
21997
22525
|
const phaseArg = firstPositional(rest);
|
|
21998
22526
|
if (!toProjectScope && !phaseArg) {
|
|
21999
22527
|
process.stderr.write(`halfcycle open-phase: name the phase to open, or pass --none to return to project scope.
|
|
22000
|
-
${
|
|
22001
|
-
${
|
|
22528
|
+
${ownCommand()} open-phase <phase> --actor "you" --evidence "<what shows this phase is ready to start>"
|
|
22529
|
+
${ownCommand()} open-phase <phase> --actor "you" --override --reason "${REASON_PLACEHOLDER["open-phase"]}"
|
|
22002
22530
|
`);
|
|
22003
22531
|
process.exit(2);
|
|
22004
22532
|
return;
|
|
@@ -22050,9 +22578,20 @@ halfcycle build-record: until ${obtained.stampFailure.path} can be written, the
|
|
|
22050
22578
|
} else {
|
|
22051
22579
|
entry = { decision: "entry-condition-met", actor, evidence: flagValue(rest, "--evidence") ?? "" };
|
|
22052
22580
|
}
|
|
22581
|
+
const regressionArg = rest.includes("--regression");
|
|
22582
|
+
if (regressionArg && toProjectScope) {
|
|
22583
|
+
const refusal3 = openPhaseRequestSchema.safeParse({ phase: null, entry, regression: true });
|
|
22584
|
+
if (!refusal3.success) {
|
|
22585
|
+
const said = refusal3.error.issues.map((issue) => issue.message).join("; ");
|
|
22586
|
+
process.stderr.write(`halfcycle open-phase: --regression ${said}. Nothing was sent.
|
|
22587
|
+
`);
|
|
22588
|
+
process.exit(2);
|
|
22589
|
+
return;
|
|
22590
|
+
}
|
|
22591
|
+
}
|
|
22053
22592
|
try {
|
|
22054
22593
|
const credential = readRepoCredential(repoRoot);
|
|
22055
|
-
const opened = await openPhase(credential, toProjectScope ? null : phaseArg, entry, void 0, repoRoot, sortingArg, sizeArg);
|
|
22594
|
+
const opened = await openPhase(credential, toProjectScope ? null : phaseArg, entry, void 0, repoRoot, sortingArg, sizeArg, regressionArg);
|
|
22056
22595
|
process.stdout.write(`[halfcycle] ${describeOpen(opened)}
|
|
22057
22596
|
`);
|
|
22058
22597
|
const startCommitLine = describeStartCommit(opened.phase, opened.startCommitSent, opened.startCommit);
|
|
@@ -22118,8 +22657,8 @@ halfcycle open-phase: until ${opened.stampFailure.path} can be written, guard ev
|
|
|
22118
22657
|
const phaseArg = firstPositional(rest);
|
|
22119
22658
|
if (!phaseArg) {
|
|
22120
22659
|
process.stderr.write(`halfcycle close-phase: name the phase whose review you are opening.
|
|
22121
|
-
${
|
|
22122
|
-
${
|
|
22660
|
+
${ownCommand()} close-phase <phase> --verdict clean --actor "you"
|
|
22661
|
+
${ownCommand()} close-phase <phase> --verdict defects --actor "you" --finding "<what is still wrong>"
|
|
22123
22662
|
`);
|
|
22124
22663
|
process.exit(2);
|
|
22125
22664
|
return;
|
|
@@ -22191,7 +22730,7 @@ halfcycle open-phase: until ${opened.stampFailure.path} can be written, guard ev
|
|
|
22191
22730
|
acceptanceOutcome: { verdict: verdict2, findings: findingsDetail.length, findingsDetail },
|
|
22192
22731
|
stated
|
|
22193
22732
|
}, () => runObserveChild(runner, credential, repoRoot), repoRoot, () => runTimeChild(runner, credential, repoRoot));
|
|
22194
|
-
const report = closeStepReport(phaseArg, attempt2, credential.engagementId);
|
|
22733
|
+
const report = closeStepReport(phaseArg, attempt2, credential.engagementId, credential.serviceUrl);
|
|
22195
22734
|
for (const line of report.stdout)
|
|
22196
22735
|
process.stdout.write(`${line}
|
|
22197
22736
|
`);
|
|
@@ -22329,7 +22868,7 @@ function firstPositional(argv) {
|
|
|
22329
22868
|
const arg = argv[i];
|
|
22330
22869
|
if (arg.startsWith("--")) {
|
|
22331
22870
|
const next = argv[i + 1];
|
|
22332
|
-
if (next !== void 0 && !next.startsWith("--"))
|
|
22871
|
+
if (arg !== "--regression" && next !== void 0 && !next.startsWith("--"))
|
|
22333
22872
|
i++;
|
|
22334
22873
|
continue;
|
|
22335
22874
|
}
|