halfcycle 0.3.40 → 0.4.1

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.
@@ -0,0 +1,54 @@
1
+ /**
2
+ * THE ONE HOME of the oldest Node Halfcycle runs on, and of the one-line check that
3
+ * says so (#1561).
4
+ *
5
+ * **Why a check at all.** Everything Halfcycle runs on a developer's machine is
6
+ * built for a modern Node. On an older one it does not fail with a reason: it fails
7
+ * while PARSING, before a line of it runs, with `Unexpected token {`, `Unexpected
8
+ * token .` or a missing built-in, and a stack trace. A GUI-launched editor on macOS
9
+ * can find an old Node first on its path, so a developer who installed a current
10
+ * one still meets this.
11
+ *
12
+ * **Why it is a program TEXT and not a function.** The check has to run before
13
+ * anything that would not parse, so it cannot live inside the code it protects.
14
+ * It is spliced, as source, in front of each thing that starts Node on the
15
+ * developer's machine:
16
+ *
17
+ * - the installer's entry file (`scripts/bundle-entries.mjs` writes `dist/bin.js`
18
+ * from it, in front of a dynamic import of the real CLI);
19
+ * - the MCP connection-headers command (`MCP_HEADERS_HELPER_COMMAND`, install.ts);
20
+ * - the guard hook wrapper, before it hands over to the vendored runner
21
+ * (`generateGuardRunnerWrapper`, install.ts).
22
+ *
23
+ * The session-marker scripts' own short `node -e` lines are NOT gated: they already
24
+ * swallow their errors and fall back, so an old Node there costs a marker, never a
25
+ * stack trace, and gating them would print the same sentence twice at session start
26
+ * (the guard wrapper also runs then).
27
+ *
28
+ * **What the program text must hold to.**
29
+ * - It PARSES ON NODE 10: no `?.`, no `??`, no `??=`, no private class fields,
30
+ * nothing newer. The guarded tail is the caller's business; on an old Node it is
31
+ * parsed but never run, so it may use any syntax Node 10 can parse (a dynamic
32
+ * `import()` parses there; measured).
33
+ * - It needs no `require` and no `import`: it runs as a script (Node 10, and the
34
+ * `node -e` programs) and as a module (`dist/bin.js` on Node 12+), and only
35
+ * `process` and `console` exist in both.
36
+ * - It holds none of `"`, `$`, `%`, a backtick, `\` or `!`, because the headers
37
+ * command sits inside one pair of double quotes under both a POSIX shell and
38
+ * `cmd.exe` (see `MCP_HEADERS_HELPER_COMMAND`), and the hook wrapper quotes it
39
+ * the same way.
40
+ * - It SETS the exit code rather than calling `process.exit`, so the sentence on
41
+ * stderr is never cut short when stderr is a pipe, which it is under a hook.
42
+ *
43
+ * **Exit 1 with the sentence on stderr** is how Claude Code shows a hook's
44
+ * non-blocking error: the first stderr line, in the transcript. It is the channel
45
+ * the runner already uses when it has never been configured.
46
+ */
47
+ export declare const NODE_FLOOR_MAJOR = 18;
48
+ /**
49
+ * Wrap `guarded` (program text) so it runs only on a Node at or above the floor.
50
+ * Below it, one line goes to stderr and the exit code is 1. Pass `''` for a
51
+ * check-only program.
52
+ */
53
+ export declare function nodeFloorProgram(guarded: string): string;
54
+ //# sourceMappingURL=node-floor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-floor.d.ts","sourceRoot":"","sources":["../src/node-floor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAKnC;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAOxD"}
@@ -101,6 +101,24 @@ export interface OutcomeBRefusal {
101
101
  }
102
102
  /** Discriminate `joinPinnedEngagement`'s result without inspecting an error. */
103
103
  export declare function isOutcomeBRefusal(result: OwnedEngagement | OutcomeBRefusal): result is OutcomeBRefusal;
104
+ /**
105
+ * What `createOwnedEngagement` RETURNS, never throws, when the folder's id names a
106
+ * project another account owns (`project-identity-reuse` AC4, #1555) — someone else's
107
+ * committed `.halfcycle/project.json`, with no pin beside it.
108
+ *
109
+ * A return rather than a throw for the same reason `OutcomeBRefusal` is one: the caller
110
+ * must tell this apart from every other refusal without reading an error message. It is
111
+ * NOT `OutcomeBRefusal`, because the two outcomes are opposite: the join arm installs
112
+ * uninstrumented against an engagement id it already has, while this arm has no
113
+ * project to pin the folder to and stops (`bin.ts` exits 1 with the reset line).
114
+ */
115
+ export interface ProjectOwnedElsewhere {
116
+ readonly outcome: 'project-owned-elsewhere';
117
+ /** The service's own sentence, forwarded verbatim. */
118
+ readonly message: string;
119
+ }
120
+ /** Discriminate `createOwnedEngagement`'s result without inspecting an error. */
121
+ export declare function isProjectOwnedElsewhere(result: OwnedEngagement | ProjectOwnedElsewhere): result is ProjectOwnedElsewhere;
104
122
  /**
105
123
  * Sign in if needed, then create an engagement owned by whoever signed in (T-08).
106
124
  *
@@ -126,9 +144,11 @@ export declare function isOutcomeBRefusal(result: OwnedEngagement | OutcomeBRefu
126
144
  * attempt's credential has source `browser-handoff`, for which
127
145
  * `replaceRefusedCredential` always returns `null`.
128
146
  *
129
- * NOTHING HAS BEEN WRITTEN when any of this fails. Every throw here happens before
130
- * `install()`, and `createEngagement` itself refuses a half-formed 201 rather than
131
- * returning one.
147
+ * NOTHING MORE HAS BEEN WRITTEN when any of this fails. Every throw here happens before
148
+ * `install()`, and the request itself refuses a half-formed answer rather than returning
149
+ * one. The one file already in the folder is `.halfcycle/project.json`, written before
150
+ * this function by `runPreCreateChecks` so that `projectId` exists before any request
151
+ * (#1555): whatever fails here, a re-run asks for the same project.
132
152
  *
133
153
  * ---------------------------------------------------------------------------
134
154
  * **THE CONFIRMATION IS BETWEEN THE TWO STATEMENTS, AND ITS POSITION IS THE FIX**
@@ -144,7 +164,7 @@ export declare function isOutcomeBRefusal(result: OwnedEngagement | OutcomeBRefu
144
164
  * ignored the return would print a browser handoff and then create the project under
145
165
  * the account the person had just rejected.
146
166
  */
147
- export declare function createOwnedEngagement(serviceUrl: string, targetRepo: string, deps?: OwnEngagementDeps): Promise<OwnedEngagement>;
167
+ export declare function createOwnedEngagement(serviceUrl: string, targetRepo: string, projectId: string, deps?: OwnEngagementDeps): Promise<OwnedEngagement | ProjectOwnedElsewhere>;
148
168
  /**
149
169
  * Sign in if needed, then obtain THIS caller's own credential for the engagement the
150
170
  * repository is already pinned to (T-24, #610).
@@ -209,7 +229,7 @@ export declare function createOwnedEngagement(serviceUrl: string, targetRepo: st
209
229
  * this client knows something about that the plane does not.
210
230
  *
211
231
  * A rethrown `CreateEngagementRefused` already carries a client-safe composed message
212
- * (the plane's sentence inside `[bundle install] Join-engagement failed: …`), so the
232
+ * (the plane's sentence inside `[halfcycle] Join-engagement failed: …`), so the
213
233
  * caller prints the fact and adds nothing. **Nothing is retried and nothing is
214
234
  * written** either way; the difference is only whether this client speaks.
215
235
  *
@@ -1 +1 @@
1
- {"version":3,"file":"own-engagement.d.ts","sourceRoot":"","sources":["../src/own-engagement.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAA2B,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAClF,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,wBAAwB,CAAC;AAOhC,OAAO,EAAyB,KAAK,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAGxF;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,mBAAmB,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,eAAgB,SAAQ,iBAAiB;IACxD,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,eAAe,CAAC;CAC3C;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;IACrC,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,eAAe,CAAC;CAC3C;AAED,gFAAgF;AAChF,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,eAAe,GAAG,eAAe,GACxC,MAAM,IAAI,eAAe,CAE3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAsB,qBAAqB,CACzC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE,iBAAsB,GAC3B,OAAO,CAAC,eAAe,CAAC,CAgD1B;AAmDD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2EG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE,iBAAsB,GAC3B,OAAO,CAAC,eAAe,GAAG,eAAe,CAAC,CAoF5C"}
1
+ {"version":3,"file":"own-engagement.d.ts","sourceRoot":"","sources":["../src/own-engagement.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAA2B,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAClF,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,wBAAwB,CAAC;AAOhC,OAAO,EAAyB,KAAK,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAGxF;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,mBAAmB,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,eAAgB,SAAQ,iBAAiB;IACxD,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,eAAe,CAAC;CAC3C;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;IACrC,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,eAAe,CAAC;CAC3C;AAED,gFAAgF;AAChF,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,eAAe,GAAG,eAAe,GACxC,MAAM,IAAI,eAAe,CAE3B;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,OAAO,EAAE,yBAAyB,CAAC;IAC5C,sDAAsD;IACtD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,iFAAiF;AACjF,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,eAAe,GAAG,qBAAqB,GAC9C,MAAM,IAAI,qBAAqB,CAEjC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAsB,qBAAqB,CACzC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE,iBAAsB,GAC3B,OAAO,CAAC,eAAe,GAAG,qBAAqB,CAAC,CAwDlD;AAmDD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2EG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE,iBAAsB,GAC3B,OAAO,CAAC,eAAe,GAAG,eAAe,CAAC,CAoF5C"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The checks the installer runs BEFORE it sends anything (`project-identity-reuse`
3
+ * AC1 and AC2, #1555 decision D1; #1567's settings-file and read-only messages; #1563's
4
+ * system folders and nested projects; #1557's Git for Windows offer, decision D2).
5
+ * Two halves of one sequence: `runLocalChecks` (writes nothing) runs on every arm before
6
+ * the first request; `runPreCreateChecks` (claims the folder id) runs on the create arm
7
+ * before the create.
8
+ *
9
+ * An install that is going to fail must fail before a project exists on the server.
10
+ * Until this module it failed inside `install()`, after the create: a read-only folder
11
+ * or a `.claude/settings.json` with a comment in it left an orphan project behind on
12
+ * every attempt, and each re-run made another.
13
+ *
14
+ * ONE STRAIGHT SEQUENCE, IN ORDER, EACH STEP A REFUSAL OR NOTHING. `runPreCreateChecks`
15
+ * is a list of statements, not a decision tree: a later check that belongs before the
16
+ * create is one more line in it, in the position its cost earns. A check that must not
17
+ * write into the folder at all (a system folder, a folder inside another project) goes
18
+ * ABOVE `claimFolderId`, which is the first step that writes.
19
+ *
20
+ * Every refusal is a `PreCreateRefused` carrying one client-safe sentence that names
21
+ * the folder or the file and the one thing to do. `bin.ts` prints it and exits 1.
22
+ *
23
+ * INV-001: Node builtins, `@halfcycle/events`, and bundle-internal modules. The folder
24
+ * id is judged by `projectIdSchema` — the same schema control uses to answer `400` —
25
+ * so this package holds no second idea of what a usable id looks like.
26
+ */
27
+ /** A local check refused the install. `message` is the sentence to print. */
28
+ export declare class PreCreateRefused extends Error {
29
+ constructor(message: string);
30
+ }
31
+ /**
32
+ * THE FIRST HALF OF THE SEQUENCE, ON EVERY ARM: the checks that write nothing. `bin.ts`
33
+ * runs it before the first request any arm sends (a re-run's `askAccount` comes before
34
+ * the arms decide), so a system folder, a folder inside another project and a Windows
35
+ * machine without Git Bash are refused whether the run would create, join or reuse.
36
+ * Returns whether Git for Windows was installed by this run. Sends no request; throws
37
+ * `PreCreateRefused`.
38
+ */
39
+ export declare function runLocalChecks(targetRepo: string): Promise<{
40
+ readonly gitInstalled: boolean;
41
+ }>;
42
+ /**
43
+ * THE SECOND HALF, ON THE CREATE ARM ONLY: claim the folder id — the first step that
44
+ * writes — then the settings file. Returns the folder id the create request must carry.
45
+ * Not run on the join and reuse arms, which never write before their request (a refused
46
+ * join leaves the tree exactly as it was — `project-identity-reuse` AC7). Sends no
47
+ * request; throws `PreCreateRefused`.
48
+ */
49
+ export declare function runPreCreateChecks(targetRepo: string): string;
50
+ export declare function refuseSystemFolder(targetRepo: string, platform?: NodeJS.Platform): void;
51
+ /**
52
+ * A folder inside a folder that is already a Halfcycle project (#1563): some PARENT
53
+ * holds the committed pin. Refused, naming that parent, and never reused or created
54
+ * here — a second project nested in the first is what 0.4.0 made. The target's own
55
+ * pin is not looked at (that is a re-run, decided by the arms in `bin.ts`). Edge cases
56
+ * such as monorepos are deliberately not special-cased here (#1570).
57
+ */
58
+ export declare function refuseInsideProject(targetRepo: string): void;
59
+ /** The one install command the offer runs and the refusal names (#1557, decision D2). */
60
+ export declare const GIT_INSTALL_ARGS: readonly string[];
61
+ //# sourceMappingURL=pre-create-checks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pre-create-checks.d.ts","sourceRoot":"","sources":["../src/pre-create-checks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAUH,6EAA6E;AAC7E,qBAAa,gBAAiB,SAAQ,KAAK;gBAC7B,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAA;CAAE,CAAC,CAKpG;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAI7D;AA0BD,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAAG,IAAI,CAWzG;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAU5D;AAUD,yFAAyF;AACzF,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAA+D,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"resolve-credential.d.ts","sourceRoot":"","sources":["../src/resolve-credential.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAMzD,OAAO,EAAU,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAE7D,wDAAwD;AACxD,MAAM,MAAM,gBAAgB;AAC1B,+EAA+E;AAC7E,aAAa;AACf,uEAAuE;GACrE,QAAQ;AACV,oEAAoE;GAClE,iBAAiB,CAAC;AAEtB,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,gBAAgB,CAAC;IACzB;;;;;;;;;;OAUG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAe,SAAQ,UAAU;IAChD,uEAAuE;IACvE,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,qFAAqF;IACrF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC,kBAAkB,CAAC,CAiB7B;AAqCD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC,kBAAkB,CAAC,CAG7B;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,wBAAwB,CAC5C,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,kBAAkB,EAC3B,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAWpC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAqBxE"}
1
+ {"version":3,"file":"resolve-credential.d.ts","sourceRoot":"","sources":["../src/resolve-credential.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAMzD,OAAO,EAAU,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAE7D,wDAAwD;AACxD,MAAM,MAAM,gBAAgB;AAC1B,+EAA+E;AAC7E,aAAa;AACf,uEAAuE;GACrE,QAAQ;AACV,oEAAoE;GAClE,iBAAiB,CAAC;AAEtB,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,gBAAgB,CAAC;IACzB;;;;;;;;;;OAUG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAe,SAAQ,UAAU;IAChD,uEAAuE;IACvE,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,qFAAqF;IACrF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC,kBAAkB,CAAC,CAiB7B;AAqCD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC,kBAAkB,CAAC,CAG7B;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,wBAAwB,CAC5C,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,kBAAkB,EAC3B,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAWpC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAyBxE"}
@@ -2,18 +2,40 @@
2
2
  * `halfcycle uninstall` — takes back exactly what the installer put in this
3
3
  * repository, and nothing else (`client-uninstall.md`, T-21).
4
4
  *
5
- * WHAT DECIDES THAT A PATH IS OURS: two keys, both required (INV-023). The committed
6
- * install manifest (`install-manifest.ts`) must list it, AND it must be a member of
7
- * the closed write set held in code (`install.ts`, `closedWriteSet`). The manifest is
8
- * a file anyone who can land a commit can edit, so on its own it decides nothing.
9
- * A file is then removed only while its bytes still hash to what the installer last
10
- * wrote; one that changed is kept and named. A symbolic link, or a path under one, is
11
- * never followed.
5
+ * WHAT DECIDES THAT SOMETHING IS OURS: WHAT IT IS, NEVER WHETHER A RECORD LISTS IT
6
+ * (#1568). The committed install manifest (`install-manifest.ts`) is a file anyone
7
+ * can edit, delete with `.halfcycle/`, or never get, when an install stops part-way.
8
+ * So nothing is removed because the manifest names it, and nothing is kept because
9
+ * the manifest is missing. Every path acted on is a member of the closed write set
10
+ * held in code (`install.ts`, `closedWriteSet`), and within it:
11
+ * - an ENTRY that runs Halfcycle goes: every hook command in
12
+ * `HALFCYCLE_HOOK_COMMANDS` (current and retired, `@halfcycle/core`) and the
13
+ * `halfcycle` MCP server — the same entries a re-install replaces;
14
+ * - a FILE goes when it sits in `.halfcycle/`, the directory the installer owns by
15
+ * path, or when its bytes are bytes Halfcycle wrote there: the hash the record
16
+ * holds, or what this version writes (`generatedSha256`). Any other file is kept
17
+ * and named;
18
+ * - a MERGED FILE (`.claude/settings.json`, `.mcp.json`, `.gitignore`,
19
+ * `.gitattributes`) loses our entries and nothing else, always: the same
20
+ * predicate decides them on every path (`ourHookEntries`, `ourMcpServers`, the
21
+ * block under our header). Nothing is written that the developer did not have —
22
+ * no copy from git, no earlier version of the file — so their content always
23
+ * stays; a merged JSON file comes back in the installer's formatting, and one
24
+ * left empty goes (T-17c: putting git's copy back lost uncommitted edits);
25
+ * - a write-set directory goes, once empty, only when an install recorded creating
26
+ * it or the installer owns it by path — never one that may be the developer's.
27
+ * The record supplies only what nothing else can show: the deny rules and `$schema`
28
+ * an install added, the blocks it appended to `.gitignore` and `.gitattributes`, and
29
+ * which keys it created. With no record those come from the tree by the installer's
30
+ * own first-run rule (`noRecord`), which claims nothing it cannot see — so our deny
31
+ * rules and `$schema` stay. A symbolic link, or a path under one, is never
32
+ * followed.
12
33
  *
13
34
  * THE ORDER OF WORK IS THE SPEC'S, and it is load-bearing:
14
35
  * 1. validate everything and change nothing on any failure — the arguments (in
15
- * `bin.ts`), the manifest, the pin, the project id when the credential is to go,
16
- * and that each merged JSON file present parses as a JSON object;
36
+ * `bin.ts`), the manifest when there is one, the pin, the project id when the
37
+ * credential is to go, and that each merged JSON file present either parses as
38
+ * a JSON object or holds nothing of Halfcycle's;
17
39
  * 2. unmerge `.claude/settings.json` first, so no hook points at a deleted script,
18
40
  * then `.mcp.json`, so no registration points at a deleted helper;
19
41
  * 3. remove the recorded files, except the pin and the manifest, and the CI
@@ -1 +1 @@
1
- {"version":3,"file":"uninstall.d.ts","sourceRoot":"","sources":["../src/uninstall.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AA0CH,2BAA2B;AAC3B,eAAO,MAAM,sBAAsB,wBAAwB,CAAC;AAE5D,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC,iFAAiF;IACjF,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,kEAAkE;AAClE,MAAM,MAAM,iBAAiB,GACzB;IAAE,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAA;CAAE,GACnC;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACrD;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAA;CAAE,GAC/B;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACnD;IAAE,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAA;CAAE,GAClC;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,SAAS,GAAG,aAAa,CAAC;IAC9D,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC;IACzB,yDAAyD;IACzD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;IAC5B,mEAAmE;IACnE,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;IACxB,wEAAwE;IACxE,QAAQ,CAAC,eAAe,EAAE,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAC;CACxC;AAED,kFAAkF;AAClF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG;IAAE,gBAAgB,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAc7G;AA6YD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,eAAe,CAoIpE;AA+OD,yFAAyF;AACzF,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAmEhH"}
1
+ {"version":3,"file":"uninstall.d.ts","sourceRoot":"","sources":["../src/uninstall.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AA2CH,2BAA2B;AAC3B,eAAO,MAAM,sBAAsB,wBAAwB,CAAC;AAE5D,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC,iFAAiF;IACjF,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,kEAAkE;AAClE,MAAM,MAAM,iBAAiB,GACzB;IAAE,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAA;CAAE,GACnC;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACrD;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAA;CAAE,GAC/B;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACnD;IAAE,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAA;CAAE,GAClC;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,SAAS,GAAG,aAAa,CAAC;IAC9D,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC;IACzB,yDAAyD;IACzD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;IAC5B,mEAAmE;IACnE,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;IACxB,wEAAwE;IACxE,QAAQ,CAAC,eAAe,EAAE,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAC;CACxC;AAED,kFAAkF;AAClF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG;IAAE,gBAAgB,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAc7G;AA4hBD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,eAAe,CAiJpE;AA8OD,yFAAyF;AACzF,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAmEhH"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "halfcycle",
3
- "version": "0.3.40",
3
+ "version": "0.4.1",
4
4
  "description": "Your coding agents are the engineers. Halfcycle is the rest of the team: product owner, architect, planner and independent reviewer for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",
@@ -24,6 +24,9 @@
24
24
  "license": "MIT",
25
25
  "type": "module",
26
26
  "main": "./dist/index.js",
27
+ "engines": {
28
+ "node": ">=18"
29
+ },
27
30
  "types": "./dist/index.d.ts",
28
31
  "files": [
29
32
  "dist",
@@ -44,7 +47,8 @@
44
47
  "scripts": {
45
48
  "build": "tsc -p tsconfig.json && node scripts/bundle-entries.mjs && node scripts/vendor-runner-bin.mjs",
46
49
  "typecheck": "tsc --noEmit -p tsconfig.test.json",
47
- "test": "tsc -p tsconfig.test.json && node --test \"dist-test/test/\"*.test.js"
50
+ "build:test": "tsc -p tsconfig.test.json",
51
+ "test": "node -e \"const{existsSync,readdirSync}=require('fs');const d='dist-test/test';if(!existsSync(d)||!readdirSync(d).some(f=>f.endsWith('.test.js'))){console.error('dist-test/test has no compiled *.test.js files — run pnpm run build:test first');process.exit(1)}\" && node --test \"dist-test/test/\"*.test.js"
48
52
  },
49
53
  "dependencies": {
50
54
  "zod": "^4.4.3"