halfcycle 0.4.3 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/bin/bin.bundle.mjs +6 -1
- package/dist/cli.js +59 -37
- package/dist/cli.js.map +2 -2
- package/dist/confirm-identity.d.ts +28 -18
- package/dist/confirm-identity.d.ts.map +1 -1
- package/dist/index.js +28 -17
- package/dist/index.js.map +1 -1
- package/dist/install-manifest.d.ts +27 -2
- package/dist/install-manifest.d.ts.map +1 -1
- package/dist/install.d.ts +20 -10
- package/dist/install.d.ts.map +1 -1
- package/dist/uninstall.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -235,27 +235,37 @@ export declare function describeAccount(serviceUrl: string, credential: string,
|
|
|
235
235
|
* scrollback — and `--verbose` puts this run's id back on screen for the run that
|
|
236
236
|
* wants it.
|
|
237
237
|
*
|
|
238
|
-
* WITH NO EMAIL the id is all there is, and it is printed
|
|
239
|
-
*
|
|
238
|
+
* WITH NO EMAIL BUT AN ANSWERED ACCOUNT the id is all there is, and it is printed —
|
|
239
|
+
* ONLY when `identity` itself is defined: an unnamed account named by its row is better
|
|
240
|
+
* than an unnamed account, but that is a claim about what `GET /account` just said, not
|
|
241
|
+
* about a credential this run merely holds. `(no email on record for it)` is the
|
|
242
|
+
* accepted exception to the "no internal words" scan precisely because it is scoped to
|
|
243
|
+
* this one case.
|
|
240
244
|
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
245
|
+
* **`identity === undefined` MEANS THE SAME THING EVERYWHERE THIS FUNCTION IS CALLED,
|
|
246
|
+
* AND IT MEANS "SAY NOTHING THAT CLAIMS AN ANSWER" (onboarding fix wave 2, F-01, the
|
|
247
|
+
* review's ruling on RW-02/RW-03 F-2, 2026-10-18).** `storedAccountId` is a credential
|
|
248
|
+
* this machine holds, not a fact the service stated — printing it, or printing
|
|
249
|
+
* "(no email on record for it)" beside it, asserts an answer this run never got.
|
|
250
|
+
* EVERY CALLER funnels through this one branch rather than deciding per-caller: `bin.ts`'s
|
|
251
|
+
* final report never reaches it (guarded by `identity !== undefined` before the call), and
|
|
252
|
+
* `confirm-identity.ts`'s own preamble (`accountLine`, gated the same way) and its stop
|
|
253
|
+
* remedy both go through it. The first cut of this fix gated only the preamble and left the
|
|
254
|
+
* stop remedy printing `account <id> (no email on record for it)` offline — the review's
|
|
255
|
+
* finding that fixing one caller and not the function itself reproduces the defect at the
|
|
256
|
+
* next call site.
|
|
244
257
|
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
* `
|
|
254
|
-
* through `confirm-identity.ts`'s OWN prompt line, a different surface with a
|
|
255
|
-
* different, still-true claim: a stored or environment credential's cached id,
|
|
256
|
-
* named while this run's own fresh identity read is still pending.
|
|
258
|
+
* **RETURNS `undefined` FOR THAT CASE RATHER THAN A SPLICED-IN NOUN PHRASE (round 2 of the
|
|
259
|
+
* same review, 2026-10-18).** A first attempt here returned a generic phrase ("the account
|
|
260
|
+
* this machine's saved sign-in belongs to" / "this credential's account") for every caller
|
|
261
|
+
* to interpolate — which reads naturally nowhere it is actually interpolated ("This
|
|
262
|
+
* machine's saved sign-in is for the account this machine's saved sign-in belongs to" / "is
|
|
263
|
+
* the credential for this credential's account"). Pushing the UNKNOWN case out to
|
|
264
|
+
* `undefined` keeps the one rule here (no id, no email claim, when `identity` is absent)
|
|
265
|
+
* while leaving each caller free to write its own grammatically whole sentence for that
|
|
266
|
+
* case — `armScript` below is the one caller that needs to, and does.
|
|
257
267
|
*/
|
|
258
|
-
export declare function accountLabel(identity: AccountIdentity | undefined, storedAccountId: string | undefined, verbose?: boolean): string;
|
|
268
|
+
export declare function accountLabel(identity: AccountIdentity | undefined, storedAccountId: string | undefined, verbose?: boolean): string | undefined;
|
|
259
269
|
/**
|
|
260
270
|
* Read one line from the terminal. Kept out of the decision so the decision is testable.
|
|
261
271
|
* Also the Git for Windows offer's prompt (`pre-create-checks.ts`), so the terminal is
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"confirm-identity.d.ts","sourceRoot":"","sources":["../src/confirm-identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoGG;AAGH,OAAO,EAAmD,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAI1G,OAAO,EAAiB,KAAK,cAAc,EAAE,KAAK,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAKtG,uFAAuF;AACvF,eAAO,MAAM,iBAAiB,sBAAsB,CAAC;AAErD,6CAA6C;AAC7C,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,gBAAgB,GAAG,MAAM,CAAC;AAErE,6FAA6F;AAC7F,MAAM,MAAM,UAAU,GAClB;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACxD;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1F,MAAM,WAAW,mBAAoB,SAAQ,cAAc;IACzD,8DAA8D;IAC9D,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,gFAAgF;IAChF,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,iEAAiE;IACjE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,oFAAoF;IACpF,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAeD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,eAAe,GAAG,SAAS,CAAC;IAC/C;;;;;;;;OAQG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,UAAU,CAC9B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,SAAS,GAAE,OAAO,KAAa,GAC9B,OAAO,CAAC,aAAa,CAAC,CAiBxB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,eAAe,CACnC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,SAAS,GAAE,OAAO,KAAa,GAC9B,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAEtC;AAED
|
|
1
|
+
{"version":3,"file":"confirm-identity.d.ts","sourceRoot":"","sources":["../src/confirm-identity.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1356,12 +1356,20 @@ function createProjectIdentityFile(targetRepoRoot) {
|
|
|
1356
1356
|
}
|
|
1357
1357
|
|
|
1358
1358
|
// dist/install-manifest.js
|
|
1359
|
+
import { Buffer } from "node:buffer";
|
|
1359
1360
|
import { createHash } from "node:crypto";
|
|
1360
1361
|
var INSTALL_MANIFEST_REL = ".halfcycle/install-manifest.json";
|
|
1361
1362
|
var INSTALL_MANIFEST_FORMAT = "halfcycle-install-manifest/v1";
|
|
1362
1363
|
function sha256Hex(bytes) {
|
|
1363
1364
|
return createHash("sha256").update(bytes).digest("hex");
|
|
1364
1365
|
}
|
|
1366
|
+
function lineKey(line) {
|
|
1367
|
+
return line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
1368
|
+
}
|
|
1369
|
+
function fileSha256(bytes) {
|
|
1370
|
+
const text = typeof bytes === "string" ? bytes : Buffer.from(bytes).toString("utf-8");
|
|
1371
|
+
return sha256Hex(text.split("\n").map(lineKey).join("\n"));
|
|
1372
|
+
}
|
|
1365
1373
|
function canonicalJson(value) {
|
|
1366
1374
|
if (Array.isArray(value))
|
|
1367
1375
|
return value.map(canonicalJson);
|
|
@@ -1544,7 +1552,7 @@ function startManifest(previous, isMember, isMemberDir) {
|
|
|
1544
1552
|
};
|
|
1545
1553
|
}
|
|
1546
1554
|
function recordInstalledFile(draft, path, bytes) {
|
|
1547
|
-
draft.files.set(path, { path, sha256:
|
|
1555
|
+
draft.files.set(path, { path, sha256: fileSha256(bytes) });
|
|
1548
1556
|
}
|
|
1549
1557
|
function recordRemovedFile(draft, path) {
|
|
1550
1558
|
draft.files.delete(path);
|
|
@@ -1871,17 +1879,17 @@ function sha256OfWhatThisVersionWrites(commands) {
|
|
|
1871
1879
|
const out = /* @__PURE__ */ new Map();
|
|
1872
1880
|
const fromFile = (rel, src) => {
|
|
1873
1881
|
if (existsSync3(src))
|
|
1874
|
-
out.set(rel,
|
|
1882
|
+
out.set(rel, fileSha256(readFileSync4(src)));
|
|
1875
1883
|
};
|
|
1876
1884
|
for (const cmd of commands)
|
|
1877
1885
|
fromFile(commandStubRel(cmd.name), join5(BUNDLE_ROOT, ".claude-plugin", cmd.path));
|
|
1878
1886
|
for (const [rel, content] of hookScriptsThisVersionWrites())
|
|
1879
|
-
out.set(rel,
|
|
1887
|
+
out.set(rel, fileSha256(content));
|
|
1880
1888
|
fromFile(CAPTURED_INDEX_REL, capturedIndexScaffold());
|
|
1881
1889
|
return out;
|
|
1882
1890
|
}
|
|
1883
1891
|
function bytesAreOurs(path, bytes, recorded, writeSet) {
|
|
1884
|
-
const hash =
|
|
1892
|
+
const hash = fileSha256(bytes);
|
|
1885
1893
|
const recordedHash = recorded !== void 0 && "sha256" in recorded ? recorded.sha256 : void 0;
|
|
1886
1894
|
return hash === recordedHash || hash === writeSet.generatedSha256.get(path);
|
|
1887
1895
|
}
|
|
@@ -2069,16 +2077,17 @@ function writeCollisionSafe(targetAbsPath, targetRepoRoot, content, isOurs) {
|
|
|
2069
2077
|
throw new Error(`[halfcycle] Write-allowlist violation: attempted to write "${rel}". Only the method surface and scaffolding paths are writable.`);
|
|
2070
2078
|
}
|
|
2071
2079
|
let replacedExisting = false;
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2080
|
+
const current = existsSync3(targetAbsPath) ? readFileSync4(targetAbsPath) : null;
|
|
2081
|
+
const text = inLineEndingOf(current?.toString("utf-8") ?? null, content);
|
|
2082
|
+
if (current !== null) {
|
|
2083
|
+
if (current.toString("utf-8") === text)
|
|
2075
2084
|
return { outcome: "skipped", replacedExisting: false };
|
|
2076
2085
|
if (!isOurs(current))
|
|
2077
2086
|
return { outcome: "collided", replacedExisting: false };
|
|
2078
2087
|
replacedExisting = true;
|
|
2079
2088
|
}
|
|
2080
2089
|
mkdirSync4(dirname3(targetAbsPath), { recursive: true });
|
|
2081
|
-
writeFileSync4(targetAbsPath,
|
|
2090
|
+
writeFileSync4(targetAbsPath, text, "utf-8");
|
|
2082
2091
|
return { outcome: "written", replacedExisting };
|
|
2083
2092
|
}
|
|
2084
2093
|
var INSTALLER_OWNED_DIR = ".halfcycle/";
|
|
@@ -2090,11 +2099,12 @@ function writeOwned(targetAbsPath, targetRepoRoot, content) {
|
|
|
2090
2099
|
if (!rel.startsWith(INSTALLER_OWNED_DIR)) {
|
|
2091
2100
|
throw new Error(`[halfcycle] Path-ownership violation: attempted to claim "${rel}" by path. Only ${INSTALLER_OWNED_DIR} is owned outright by this installer; everywhere else a differing file may be a developer's, so ownership must be proved from the file's own contents (writeOwnedGenerated) rather than assumed from where it sits.`);
|
|
2092
2101
|
}
|
|
2093
|
-
|
|
2102
|
+
const current = readFileOrNull(targetAbsPath);
|
|
2103
|
+
const text = inLineEndingOf(current, content);
|
|
2104
|
+
if (current === text)
|
|
2094
2105
|
return "skipped";
|
|
2095
|
-
}
|
|
2096
2106
|
mkdirSync4(dirname3(targetAbsPath), { recursive: true });
|
|
2097
|
-
writeFileSync4(targetAbsPath,
|
|
2107
|
+
writeFileSync4(targetAbsPath, text, "utf-8");
|
|
2098
2108
|
return "written";
|
|
2099
2109
|
}
|
|
2100
2110
|
var GENERATED_HEADER_LINE_INDEX = 1;
|
|
@@ -2496,13 +2506,13 @@ function adoptBlockUnderHeader(before, header, claimable) {
|
|
|
2496
2506
|
}
|
|
2497
2507
|
return claimed.length > 0 ? claimed.join("\n") + "\n" : "";
|
|
2498
2508
|
}
|
|
2499
|
-
function lineKey(line) {
|
|
2500
|
-
return line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
2501
|
-
}
|
|
2502
2509
|
function lineEndingOf(text) {
|
|
2503
2510
|
const at = text.indexOf("\n");
|
|
2504
2511
|
return at > 0 && text[at - 1] === "\r" ? "\r\n" : "\n";
|
|
2505
2512
|
}
|
|
2513
|
+
function inLineEndingOf(existing, generated) {
|
|
2514
|
+
return existing === null ? generated : generated.split("\n").join(lineEndingOf(existing));
|
|
2515
|
+
}
|
|
2506
2516
|
function gitignoreCovers(content) {
|
|
2507
2517
|
const lines = new Set(content.split("\n").map((l) => l.trim()));
|
|
2508
2518
|
return REQUIRED_GITIGNORE_ENTRIES.every((entry) => lines.has(entry));
|
|
@@ -2595,7 +2605,8 @@ function writeBundlePin(targetRepoRoot, version, engagementId, engagementType, c
|
|
|
2595
2605
|
...projectPage !== void 0 ? { projectPage } : {}
|
|
2596
2606
|
};
|
|
2597
2607
|
const pinPath = join5(targetRepoRoot, BUNDLE_PIN_REL);
|
|
2598
|
-
|
|
2608
|
+
const pinText = inLineEndingOf(readFileOrNull(pinPath), JSON.stringify(pin, null, 2) + "\n");
|
|
2609
|
+
writeAllowlisted(pinPath, targetRepoRoot, pinText, writtenPaths);
|
|
2599
2610
|
}
|
|
2600
2611
|
function writeCrewRoster(targetRepoRoot, report) {
|
|
2601
2612
|
const doc = {
|
|
@@ -2743,7 +2754,7 @@ async function install(options) {
|
|
|
2743
2754
|
}
|
|
2744
2755
|
recordSettingsMerge(draft, existingSettings, generatedSettings, settingsPreexisted);
|
|
2745
2756
|
const mergedSettings = mergeSettings(existingSettings, generatedSettings);
|
|
2746
|
-
const mergedSettingsText = JSON.stringify(mergedSettings, null, 2) + "\n";
|
|
2757
|
+
const mergedSettingsText = inLineEndingOf(readFileOrNull(settingsPath), JSON.stringify(mergedSettings, null, 2) + "\n");
|
|
2747
2758
|
mkdirSync4(dirname3(settingsPath), { recursive: true });
|
|
2748
2759
|
writeFileSync4(settingsPath, mergedSettingsText, "utf-8");
|
|
2749
2760
|
(settingsPreexisted ? report.mergedPaths : report.writtenPaths).push(".claude/settings.json");
|
|
@@ -2771,7 +2782,7 @@ async function install(options) {
|
|
|
2771
2782
|
if (credential) {
|
|
2772
2783
|
const mcpPath = join5(targetRepo, MCP_REGISTRATION_REL);
|
|
2773
2784
|
const existingMcp = existsSync3(mcpPath) ? readFileSync4(mcpPath, "utf-8") : null;
|
|
2774
|
-
const mcpContent = generateMcpRegistration(existingMcp, credential.mcpUrl);
|
|
2785
|
+
const mcpContent = inLineEndingOf(existingMcp, generateMcpRegistration(existingMcp, credential.mcpUrl));
|
|
2775
2786
|
recordMcpMerge(draft, existingMcp, mcpContent);
|
|
2776
2787
|
if (existingMcp === null) {
|
|
2777
2788
|
writeAllowlisted(mcpPath, targetRepo, mcpContent, report.writtenPaths);
|