halfcycle 0.3.26 → 0.3.27

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/dist/install.d.ts CHANGED
@@ -237,6 +237,53 @@ export declare const ZONE_B_DIR = ".workbench";
237
237
  * depending on build mode — see the comment above `findBundleRoot`).
238
238
  */
239
239
  export declare const BUNDLE_ROOT: string;
240
+ /** The committed pin. */
241
+ export declare const BUNDLE_PIN_REL = ".halfcycle/bundle.json";
242
+ /** The three files the installer merges into rather than owns. */
243
+ export declare const SETTINGS_REL = ".claude/settings.json";
244
+ export declare const GITIGNORE_REL = ".gitignore";
245
+ export interface ClosedWriteSet {
246
+ /** Files the installer writes whole. */
247
+ readonly files: ReadonlySet<string>;
248
+ /** Files the installer merges its entries into. */
249
+ readonly merged: ReadonlySet<string>;
250
+ /** Every directory any of the above can create. */
251
+ readonly dirs: ReadonlySet<string>;
252
+ /** The directories owned by path: `.halfcycle/` and those under it. */
253
+ readonly ownedDirs: ReadonlySet<string>;
254
+ /** Every hook command string the installer generates into `.claude/settings.json`. */
255
+ readonly hookCommands: ReadonlySet<string>;
256
+ /** The SHA-256 of every deny rule the installer adds. */
257
+ readonly denyRuleSha256: ReadonlySet<string>;
258
+ /** Every line the installer appends to `.gitignore`. */
259
+ readonly gitignoreLines: ReadonlySet<string>;
260
+ }
261
+ /**
262
+ * THE CLOSED WRITE SET, derived from the same constants the installer writes with
263
+ * (`client-uninstall.md`, INV-023's two-key removal).
264
+ *
265
+ * THE SECOND KEY REACHES INSIDE THE MERGED FILES TOO (R-21, F1). A hook command, a
266
+ * deny rule or a `.gitignore` line is taken out only if the manifest records it AND
267
+ * it is one the installer generates: the commands `generateSettingsJson` emits, the
268
+ * hashes of `DENY_PATTERNS`, and the `.gitignore` header plus
269
+ * `REQUIRED_GITIGNORE_ENTRIES` plus the `.env.local` line older versions wrote.
270
+ * Without it a crafted manifest could name a developer's own hook, deny rule or
271
+ * ignore line and have uninstall delete it.
272
+ *
273
+ * A path in the install manifest never decides a removal on its own: uninstall
274
+ * removes or edits a path only if the manifest lists it AND it is a member here. The
275
+ * manifest is a committed file anyone who can land a commit can edit; this set is
276
+ * code, shipped in the package. So a manifest naming `src/index.ts` names a path
277
+ * that is not removed, whatever else it says.
278
+ *
279
+ * DERIVED, NOT LISTED. The command stubs come from `plugin.json`'s `commands[]`, the
280
+ * same array `copyManifestCommands` copies from; the hook scripts and the headers
281
+ * helper from `OWNED_GENERATED_HEADERS`, the table the owned-generated writes key
282
+ * on; the rest from the named constants their writers use. The directories are the
283
+ * ancestors of every member, so a new file under a new directory brings its
284
+ * directory with it. The writer and the remover therefore cannot hold two lists.
285
+ */
286
+ export declare function closedWriteSet(): ClosedWriteSet;
240
287
  /**
241
288
  * The write outcome for one target path under the copy set.
242
289
  * - `written` — the file did not exist and was created.
@@ -333,6 +380,87 @@ export declare const WRITE_CAPABLE_TOOLS: readonly string[];
333
380
  * across lines, this hook has one line of context budget to spend per prompt.
334
381
  */
335
382
  export declare const USER_PROMPT_REMINDER_SENTENCE: string;
383
+ /** What `removeLegacyCiStanza` found on disk and did about it. */
384
+ export type CiStanzaRemoval = 'removed' | 'kept-foreign' | 'kept-uncommitted' | 'absent' | 'failed';
385
+ /**
386
+ * Delete `.halfcycle/ci-stanza.yml` when, and only when, BOTH hold (R-25 fix
387
+ * wave, F1): its opening line hashes to exactly one generation this installer
388
+ * ever shipped, AND the file is a clean, committed copy in git. Nothing writes
389
+ * this path any more; this only retires one an earlier version left on an
390
+ * already-installed tree.
391
+ *
392
+ * **Either check failing means KEEP, with a message — never delete.** A header
393
+ * hash matching none of `CI_STANZA_KNOWN_HEADER_HASHES` might be a developer's
394
+ * own file at this name, or a generation of ours this enumeration has not been
395
+ * kept in step with. A header that matches but a file that is uncommitted, or
396
+ * committed with local edits, might be a hand-edited stanza whose header line a
397
+ * developer left untouched while changing everything below it — the exact
398
+ * defect this rule now refuses, walked before this fix landed: a truncated
399
+ * prefix matched that file and deleted it. Either way, deleting on uncertainty
400
+ * is the wrong side of it. This is the same posture criterion 5 established for
401
+ * the generated hook scripts — evidence before disturbing a file this installer
402
+ * did not just write — applied to a delete instead of an overwrite.
403
+ *
404
+ * BEST-EFFORT, mirroring `migrateLegacyEnvLocal`: a read or unlink failure is
405
+ * reported, never thrown. Cleaning up a file this task retires must not fail an
406
+ * otherwise-successful install.
407
+ */
408
+ export declare function removeLegacyCiStanza(targetRepo: string): CiStanzaRemoval;
409
+ /**
410
+ * **THE INSTALLER WRITES NO `.env.example`, AND THIS IS WHERE THAT GENERATOR WAS.**
411
+ *
412
+ * It survived the removal of the four common addresses earlier the same day, still
413
+ * declaring three names — the engagement's token, its id, and the same credential
414
+ * under the name every other surface reads. The operator then read the file it
415
+ * produced and asked whether the remaining three needed to be there at all. They did
416
+ * not, and the block was **a second home**:
417
+ *
418
+ * - The token and the engagement id were declared in the generated CI stanza,
419
+ * **at the point they were used**, with their own correct explanation. The
420
+ * scaffold repeated that for a job the stanza already owned. *(Overtaken
421
+ * 2026-09-16, T-15: the stanza declared neither by then — the CI job
422
+ * authenticated with an identity token and no stored secret. Overtaken again
423
+ * at T-25: the stanza itself is gone, CI setup being project scope rather than
424
+ * a product requirement, so there is no generated file left for either name to
425
+ * have a home in.)*
426
+ * - `HALFCYCLE_TOKEN` is not in the stanza at all. It is the escape hatch for
427
+ * running as a specific identity — ours, not something a client sets — which is
428
+ * the same category as the control origin, and the same reason that one went.
429
+ * - **The file's own text was the argument against it.** It spent twenty lines
430
+ * explaining that you must not do the thing a `.env.example` exists for. The
431
+ * convention of that filename is *copy this and fill it in*, and no comment
432
+ * outranks a convention.
433
+ *
434
+ * So the decision is not "declare fewer names" but "declare none, and place no such
435
+ * file": one fewer file in someone else's repository, and each of these facts keeps
436
+ * exactly one home. The address paragraph this file used to carry did not die with
437
+ * it — it moved into the CI stanza's comment block, where a reader looking for
438
+ * something to configure will actually meet it.
439
+ *
440
+ * **AN EXISTING `.env.example` IS NOT TOUCHED, and that is a decision rather than an
441
+ * oversight.** A tree an older install wrote still carries the block it appended; a
442
+ * re-install neither strips nor deletes it. The alternative is a pass with DELETE
443
+ * authority over a file the developer owns, identifying our old block by a header
444
+ * string that has itself changed twice — guesswork, on a population that is close to
445
+ * empty, since the CLI reaches a developer only on a republish. The credential
446
+ * migration that does have delete authority earned it by operating on keys it wrote
447
+ * itself in a file this product created; neither is true here. The stale block names
448
+ * three variables that are genuinely the client's and instructs nobody to set an
449
+ * address, so leaving it is inert.
450
+ *
451
+ * **WHAT MUST NOT MOVE WITH IT.** `ENGAGEMENT_ENV_KEYS` and `CREDENTIAL_REQUIRED_KEYS`
452
+ * are untouched: every name is still WRITTEN into the machine-level credential store.
453
+ * Declaring and writing were decoupled when the addresses came out, and dropping a
454
+ * key from the store makes every already-installed repository read as credential-less
455
+ * on its next run and silently join a fresh credential.
456
+ */
457
+ /** The project-scoped MCP registration file — `.mcp.json` at the repo root, the
458
+ * Claude Code convention. Root-level, so it is a distinct allowlist entry. */
459
+ export declare const MCP_REGISTRATION_REL = ".mcp.json";
460
+ /** The key the Halfcycle server is registered under inside `.mcp.json`'s
461
+ * `mcpServers` — one home, because two generated shell scripts now look for it and
462
+ * a literal typed twice is a literal that can disagree with itself. */
463
+ export declare const MCP_SERVER_KEY = "halfcycle";
336
464
  /**
337
465
  * THE ONE HOME of *which generated files this installer claims as its own, and by
338
466
  * what evidence* (#1103). Every entry is a client-tree path this package
@@ -360,10 +488,12 @@ export declare const USER_PROMPT_REMINDER_SENTENCE: string;
360
488
  * change a marker. `test/install-stop-hook.test.ts` DID declare its own copy and
361
489
  * now imports this table instead.
362
490
  *
363
- * `.halfcycle/ci-stanza.yml` is deliberately ABSENT: its opening line has taken
364
- * four distinct forms, so no prefix long enough to be evidence covers every
365
- * generation. It stays collision-safe, the write site records why, and the gap is
366
- * filed as `#1112`.
491
+ * `.halfcycle/ci-stanza.yml` is deliberately ABSENT — it was never a member of
492
+ * this table even while it was generated, because its opening line took four
493
+ * distinct forms and no prefix long enough to be evidence covered every
494
+ * generation (`#1112`). T-25 removed the generator itself; the four forms' hashes
495
+ * now live in `CI_STANZA_KNOWN_HEADER_HASHES`, evidence for a REMOVAL rather than
496
+ * a write.
367
497
  */
368
498
  export declare const OWNED_GENERATED_HEADERS: {
369
499
  readonly '.claude/hooks/guard-runner.sh': "# Governance hook wrapper — generated by";
@@ -372,6 +502,12 @@ export declare const OWNED_GENERATED_HEADERS: {
372
502
  readonly '.claude/hooks/session-start-marker.sh': "# Session-start marker — generated by";
373
503
  readonly '.halfcycle/mcp-headers.sh': "# Halfcycle MCP connection headers — generated by";
374
504
  };
505
+ /**
506
+ * The header line every `.gitignore` block this installer appends opens with. One
507
+ * form in every published version (`client-uninstall.md`, *Grounded claims*), which
508
+ * is what lets a first-manifest run claim the blocks an older version appended.
509
+ */
510
+ export declare const GITIGNORE_HEADER = "# Halfcycle \u2014 machine-local secrets/state and Zone-B (never push to client remote)";
375
511
  /**
376
512
  * Read the pinned bundle.json from a target repo and compare its version
377
513
  * against the current bundle version. Returns the installed pin if present.
@@ -491,15 +627,18 @@ export declare function checkDrift(targetRepoRoot: string): {
491
627
  * 4. vendored guard binary → .halfcycle/bin/bin.bundle.mjs (self-contained)
492
628
  * 5. Governance-hook wiring (.claude/settings.json, guard-runner.sh,
493
629
  * session-start-marker.sh, user-prompt-reminder.sh)
494
- * 6. CI stanza file + the MCP registration and the connection-headers helper it
495
- * names (credentialed installs only — T-24). NO env scaffold: the installer
496
- * writes no `.env.example` at all (operator decision, 2026-08-30)
630
+ * 6. (nothing) — no CI stanza (removed at T-25); the MCP registration and the
631
+ * connection-headers helper it names (credentialed installs only — T-24).
632
+ * NO env scaffold either: the installer writes no `.env.example` at all
633
+ * (operator decision, 2026-08-30)
497
634
  * 7. .gitignore + project identity + the engagement credential (OUTSIDE the tree,
498
635
  * at machine level — T-09) + migration of any pre-T-09 in-tree credential
499
636
  * 8. .halfcycle/bundle.json pin
500
637
  * 8b. .halfcycle/crew.json — CREW_ROSTER, serialised verbatim (crew-roster-and-dispatch)
501
638
  * 9. Bootstrap scan → .halfcycle/state.json (INV-021 — once, onboarding-time,
502
639
  * a no-op if the file already exists; never re-run on a re-install)
640
+ * 10. .halfcycle/install-manifest.json — what this and every earlier install put
641
+ * here, for `halfcycle uninstall` (T-21, `install-manifest.ts`)
503
642
  */
504
643
  export declare function install(options: InstallOptions): Promise<InstallResult>;
505
644
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"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"}
@@ -60,6 +60,18 @@ export interface ClaudeSettings {
60
60
  permissions?: ClaudePermissions;
61
61
  [key: string]: unknown;
62
62
  }
63
+ /**
64
+ * True iff `entry` is Halfcycle-authored — i.e. it carries a hook command that
65
+ * matches one of ours. This is how a re-install replaces our entry in place
66
+ * rather than appending a duplicate. A developer's own entry (any other command
67
+ * string) is never matched, so it is never touched.
68
+ *
69
+ * Exported because it is THE rule for recognising a Halfcycle hook entry, and the
70
+ * installer's manifest (deciding whether a pre-manifest settings file is adopted)
71
+ * and the uninstaller (deciding which entries to take apart) ask the same question.
72
+ * A second predicate beside either of them would be a second answer to it.
73
+ */
74
+ export declare function isHalfcycleEntry(entry: HookEntry, halfcycleCommands: Set<string>): boolean;
63
75
  /**
64
76
  * Merge Halfcycle's generated hook entries into an existing settings object.
65
77
  *
@@ -1 +1 @@
1
- {"version":3,"file":"merge-settings.d.ts","sourceRoot":"","sources":["../src/merge-settings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,yEAAyE;AACzE,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,6DAA6D;AAC7D,MAAM,WAAW,SAAS;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,WAAW,EAAE,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,sEAAsE;AACtE,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;IACpC,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAYD;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,cAAc,EAAE,SAAS,EAAE,cAAc,GAAG,cAAc,CAwCjG"}
1
+ {"version":3,"file":"merge-settings.d.ts","sourceRoot":"","sources":["../src/merge-settings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,yEAAyE;AACzE,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,6DAA6D;AAC7D,MAAM,WAAW,SAAS;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,WAAW,EAAE,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,sEAAsE;AACtE,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;IACpC,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,EAAE,iBAAiB,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,OAAO,CAE1F;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,cAAc,EAAE,SAAS,EAAE,cAAc,GAAG,cAAc,CAwCjG"}
@@ -1 +1 @@
1
- {"version":3,"file":"open-phase.d.ts","sourceRoot":"","sources":["../src/open-phase.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAIH,OAAO,EAIL,KAAK,kBAAkB,EACxB,MAAM,mBAAmB,CAAC;AAS3B;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAChC,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,sFAAsF;AACtF,MAAM,WAAW,WAAW;IAC1B,iDAAiD;IACjD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,qDAAqD;IACrD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B;;;;OAIG;IACH,YAAY,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACvC,QAAQ,EAAE;QACR,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,aAAa,EAAE,MAAM,CAAC;QACtB,yEAAyE;QACzE,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;QAC/B,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED;;;;;;GAMG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;aAEvB,MAAM,EAAE,MAAM;aAEd,OAAO,EAAE,MAAM,GAAG,IAAI;gBAFtB,MAAM,EAAE,MAAM,EAC9B,OAAO,EAAE,MAAM,EACC,OAAO,EAAE,MAAM,GAAG,IAAI;CAKzC;AAED;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,kFAItB,CAAC;AAEX,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,IAAI,CAAC,EAAE,MAAM,GACZ,cAAc,CA0BhB;AAuBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,eAAe,CAC7B,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,IAAI,CAAC,EAAE,MAAM,GACZ,iBAAiB,GAAG,IAAI,CAc1B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,SAAS,CAC7B,UAAU,EAAE,cAAc,EAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,KAAK,EAAE,kBAAkB,EACzB,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,WAAW,CAAC,CAyEtB"}
1
+ {"version":3,"file":"open-phase.d.ts","sourceRoot":"","sources":["../src/open-phase.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAIH,OAAO,EAIL,KAAK,kBAAkB,EACxB,MAAM,mBAAmB,CAAC;AAY3B;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAChC,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,sFAAsF;AACtF,MAAM,WAAW,WAAW;IAC1B,iDAAiD;IACjD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,qDAAqD;IACrD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B;;;;OAIG;IACH,YAAY,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACvC,QAAQ,EAAE;QACR,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,aAAa,EAAE,MAAM,CAAC;QACtB,yEAAyE;QACzE,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;QAC/B,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED;;;;;;GAMG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;aAEvB,MAAM,EAAE,MAAM;aAEd,OAAO,EAAE,MAAM,GAAG,IAAI;gBAFtB,MAAM,EAAE,MAAM,EAC9B,OAAO,EAAE,MAAM,EACC,OAAO,EAAE,MAAM,GAAG,IAAI;CAKzC;AAED;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,kFAItB,CAAC;AAEX,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,IAAI,CAAC,EAAE,MAAM,GACZ,cAAc,CA4BhB;AAuBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,eAAe,CAC7B,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,IAAI,CAAC,EAAE,MAAM,GACZ,iBAAiB,GAAG,IAAI,CAmB1B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,SAAS,CAC7B,UAAU,EAAE,cAAc,EAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,KAAK,EAAE,kBAAkB,EACzB,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,WAAW,CAAC,CAyEtB"}
package/dist/scan.d.ts CHANGED
@@ -101,6 +101,13 @@ export interface HalfcycleStateFile {
101
101
  export declare const LAYER_ARTEFACT_PATHS: Partial<Record<MethodLayer, string[]>>;
102
102
  /** Scan `targetRepo` for each layer's artefact convention. Pure filesystem read. */
103
103
  export declare function scanLayers(targetRepo: string, scannedAt?: string): HalfcycleStateFile;
104
+ /**
105
+ * Where the scan writes, relative to the repository root. Named once because the
106
+ * installer's closed write set (`install.ts`, `closedWriteSet`) is derived from the
107
+ * constants the writers use, so the path uninstall may remove is the path this
108
+ * writes, and not a second spelling of it.
109
+ */
110
+ export declare const BOOTSTRAP_STATE_REL = ".halfcycle/state.json";
104
111
  /**
105
112
  * Run the bootstrap scan ONCE. If `.halfcycle/state.json` already exists this
106
113
  * is a no-op (returns the existing file unchanged) — the "never again" half of
@@ -1 +1 @@
1
- {"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAIH,OAAO,EAAE,iBAAiB,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAMxE,4EAA4E;AAC5E,eAAO,MAAM,sBAAsB,EAAG,yBAAkC,CAAC;AAEzE,kFAAkF;AAClF,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAE7B;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GAAG,UAAU,CAAC;AAE3C,0DAA0D;AAC1D,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,iBAAiB,CAAC;IACzB,oEAAoE;IACpE,MAAM,EAAE,gBAAgB,CAAC;IACzB,2EAA2E;IAC3E,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,oBAAoB,QAStB,CAAC;AAEZ,4CAA4C;AAC5C,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,OAAO,sBAAsB,CAAC;IACtC;;;OAGG;IACH,IAAI,EAAE,OAAO,oBAAoB,CAAC;IAClC,kEAAkE;IAClE,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,iBAAiB,CAAC,CAAC,CAAC;CACzD;AAMD;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,oBAAoB,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,CAAC,CAOvE,CAAC;AAMF,oFAAoF;AACpF,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,GAAE,MAAiC,GAAG,kBAAkB,CAc/G;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,kBAAkB,CAAC;IAAC,GAAG,EAAE,OAAO,CAAA;CAAE,CAUhG;AAMD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,kBAAkB,EACzB,qBAAqB,EAAE,WAAW,CAAC,WAAW,CAAC,GAC9C,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,UAAU,GAAG,UAAU,CAAC,CAAC,CAcvD"}
1
+ {"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAIH,OAAO,EAAE,iBAAiB,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAMxE,4EAA4E;AAC5E,eAAO,MAAM,sBAAsB,EAAG,yBAAkC,CAAC;AAEzE,kFAAkF;AAClF,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAE7B;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GAAG,UAAU,CAAC;AAE3C,0DAA0D;AAC1D,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,iBAAiB,CAAC;IACzB,oEAAoE;IACpE,MAAM,EAAE,gBAAgB,CAAC;IACzB,2EAA2E;IAC3E,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,oBAAoB,QAStB,CAAC;AAEZ,4CAA4C;AAC5C,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,OAAO,sBAAsB,CAAC;IACtC;;;OAGG;IACH,IAAI,EAAE,OAAO,oBAAoB,CAAC;IAClC,kEAAkE;IAClE,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,iBAAiB,CAAC,CAAC,CAAC;CACzD;AAMD;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,oBAAoB,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,CAAC,CAOvE,CAAC;AAMF,oFAAoF;AACpF,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,GAAE,MAAiC,GAAG,kBAAkB,CAc/G;AAED;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,0BAA0B,CAAC;AAE3D;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,kBAAkB,CAAC;IAAC,GAAG,EAAE,OAAO,CAAA;CAAE,CAUhG;AAMD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,kBAAkB,EACzB,qBAAqB,EAAE,WAAW,CAAC,WAAW,CAAC,GAC9C,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,UAAU,GAAG,UAAU,CAAC,CAAC,CAcvD"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * `halfcycle uninstall` — takes back exactly what the installer put in this
3
+ * repository, and nothing else (`client-uninstall.md`, T-21).
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.
12
+ *
13
+ * THE ORDER OF WORK IS THE SPEC'S, and it is load-bearing:
14
+ * 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;
17
+ * 2. unmerge `.claude/settings.json` first, so no hook points at a deleted script,
18
+ * then `.mcp.json`, so no registration points at a deleted helper;
19
+ * 3. remove the recorded files, except the pin and the manifest, and the CI
20
+ * template an earlier version wrote (`removeLegacyCiStanza`, which carries its
21
+ * own two keys: an exact known header and a clean committed copy);
22
+ * 4. remove the directories an install created, deepest first, when empty;
23
+ * 5. trim `.gitignore`;
24
+ * 6. remove this machine's credential for the project, if asked;
25
+ * 7. only when nothing so far failed: the pin, the manifest, then `.halfcycle/`.
26
+ * A failure at 3–6 therefore leaves the pin and the manifest in place, and a rerun
27
+ * finds everything else already absent — absent counts as removed — and retries.
28
+ *
29
+ * WHAT IT NEVER TOUCHES: any path outside the closed write set (Build Records, what
30
+ * setup wrote, `.workbench/`, captured fixtures, `.claude/agents/`), the project on
31
+ * Halfcycle, this machine's sign-in (`~/.halfcycle/account.json`) and the shared
32
+ * local log directory. It makes no network call and asks nothing.
33
+ *
34
+ * INV-001: imports only Node built-ins and this package's own modules.
35
+ */
36
+ /** The verb's one flag. */
37
+ export declare const REMOVE_CREDENTIAL_FLAG = "--remove-credential";
38
+ export interface UninstallOptions {
39
+ readonly targetRepo: string;
40
+ readonly removeCredential: boolean;
41
+ /** The home directory `~/.halfcycle` lives under; defaults to `os.homedir()`. */
42
+ readonly home?: string;
43
+ }
44
+ /** What happened to this machine's credential for the project. */
45
+ export type CredentialOutcome = {
46
+ readonly kind: 'nothing-to-say';
47
+ } | {
48
+ readonly kind: 'still-held';
49
+ readonly dir: string;
50
+ } | {
51
+ readonly kind: 'invalid-id';
52
+ } | {
53
+ readonly kind: 'removed';
54
+ readonly dir: string;
55
+ } | {
56
+ readonly kind: 'not-held';
57
+ readonly dir: string;
58
+ } | {
59
+ readonly kind: 'no-project-id';
60
+ } | {
61
+ readonly kind: 'failed';
62
+ readonly dir: string;
63
+ readonly reason: string;
64
+ };
65
+ export interface UninstallResult {
66
+ readonly outcome: 'not-installed' | 'refused' | 'uninstalled';
67
+ readonly exitCode: 0 | 1;
68
+ /** Why a refused run changed nothing, and what to do. */
69
+ readonly refusal?: string;
70
+ readonly removed: string[];
71
+ readonly restored: string[];
72
+ /** Installed files that changed after the installer wrote them. */
73
+ readonly kept: string[];
74
+ /** `.gitignore` lines kept because the path they cover still exists. */
75
+ readonly keptIgnoreLines: string[];
76
+ readonly leftAlone: string[];
77
+ readonly failed: string[];
78
+ readonly credential: CredentialOutcome;
79
+ }
80
+ /** Parse the verb's arguments (global flags are already stripped by `bin.ts`). */
81
+ export declare function parseUninstallArgs(args: readonly string[]): {
82
+ removeCredential: boolean;
83
+ } | {
84
+ error: string;
85
+ };
86
+ /**
87
+ * Run the uninstall. Never throws for a condition the spec names: a refusal comes
88
+ * back as `outcome: 'refused'` with nothing changed.
89
+ */
90
+ export declare function uninstall(options: UninstallOptions): UninstallResult;
91
+ /** The report, as the CLI prints it: stdout for a finished run, stderr for a refusal. */
92
+ export declare function renderUninstallReport(result: UninstallResult, home?: string): {
93
+ stdout: string;
94
+ stderr: string;
95
+ };
96
+ //# sourceMappingURL=uninstall.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uninstall.d.ts","sourceRoot":"","sources":["../src/uninstall.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAsCH,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;AAwWD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,eAAe,CA6HpE;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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "halfcycle",
3
- "version": "0.3.26",
3
+ "version": "0.3.27",
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",
@@ -19,7 +19,7 @@
19
19
  "homepage": "https://halfcycle.ai",
20
20
  "repository": {
21
21
  "type": "git",
22
- "url": "git+https://github.com/Halfcycle-AI/halfcycle-cli.git"
22
+ "url": "git+https://github.com/Halfcycle-AI/halfcycle.git"
23
23
  },
24
24
  "license": "MIT",
25
25
  "type": "module",
package/dist/ci-bind.d.ts DELETED
@@ -1,116 +0,0 @@
1
- /**
2
- * THE `ci bind` / `ci unbind` VERBS' WIRING — the client half of T-05's
3
- * account-authenticated trust binding (`ci-oidc-token-exchange`, T-11, AC2 client
4
- * half).
5
- *
6
- * `PUT|DELETE {baseUrl}/engagements/:engagementId/ci-bindings/:owner/:repo` says
7
- * which repository's GitHub Actions OIDC tokens an engagement trusts — a signed-in
8
- * developer states it once, from their own machine, and a client's CI afterwards
9
- * needs no secret of ours to authenticate (the exchange itself, on the control
10
- * plane, is the other half).
11
- *
12
- * WIRING, NOT MACHINERY — the same posture as `open-phase.ts` and `close-phase.ts`.
13
- * Every rule this route enforces (ownership, the one-project-at-a-time conflict,
14
- * what a repository string may be) is the route's; this module builds the request,
15
- * sends it, and reports what came back.
16
- *
17
- * ---------------------------------------------------------------------------
18
- * **THE ACCOUNT CREDENTIAL, NOT THE ENGAGEMENT TOKEN.** The route refuses a browser
19
- * session by construction and refuses EVERY engagement token as a class — whether or
20
- * not it carries an account. So this module resolves its credential exactly the way
21
- * `own-engagement.ts` resolves the create/join credential — `resolve-credential.ts`'s
22
- * `obtainCredential`, never `open-phase.ts`'s `readRepoCredential`, which reads the
23
- * OTHER credential class (the engagement token) out of a repository's pinned env.
24
- *
25
- * **THIS PARAGRAPH USED TO REST ON "an engagement token that carries no account", AND
26
- * THAT WAS THE HOLE** (T-05f, 2026-09-16): that phrasing describes a column, not a
27
- * credential class, and once those rows began carrying an account the route admitted
28
- * them — measured, at 204, with the binding surviving the token's revocation. There is
29
- * a consequence for the caller here, and it is intended: `obtainCredential` gives
30
- * `HALFCYCLE_TOKEN` precedence over a saved sign-in, so a developer with that variable
31
- * set is now refused with a `403` whose sentence names unsetting it as the step. Before
32
- * the fix that caller silently bound with the wrong credential class.
33
- *
34
- * **NO IDENTITY-CONFIRMATION CEREMONY, AND THAT IS A DELIBERATE NARROWING FROM
35
- * `own-engagement.ts`, NOT AN OMISSION.** `createOwnedEngagement`/`joinPinnedEngagement`
36
- * run `confirmActingIdentity` first because a create or a join under a stale saved
37
- * sign-in SUCCEEDS silently, under the wrong account — a durable resource now exists
38
- * that the person at the keyboard did not ask for. A bind under the wrong account
39
- * cannot do that: T-05's `isOwnedBy` gate refuses it with `CiBindRefused` (403)
40
- * before anything changes, so the failure mode this ceremony exists to interrupt is
41
- * not reachable here — the route answers it, every time, without this client having
42
- * to ask first.
43
- *
44
- * **THE RETRY IS ON `401` ALONE**, mirroring `joinPinnedEngagement`'s reasoning in
45
- * `own-engagement.ts` exactly: this route's other refusals — `403` (not an account
46
- * credential, or an engagement the caller does not own) and `409` (bound elsewhere)
47
- * — are not a stale credential, and retrying either would discard a perfectly good
48
- * sign-in to arrive at the identical refusal a moment later.
49
- *
50
- * INV-001: Node builtins, `@halfcycle/events` (transitively, via `resolve-credential`),
51
- * and bundle-internal modules — never the control service. The route declares no
52
- * wire shape (T-05: path segments in, `204` out — deliberately, so a `POST … {
53
- * repository, engagementId }` body never needed a second `packages/events` home),
54
- * so there is nothing here for this file to validate a body against; its only job
55
- * is the path and the header.
56
- */
57
- import { type ResolveOptions } from './resolve-credential.js';
58
- /** The repository a `ci bind`/`ci unbind` call names. */
59
- export interface CiRepository {
60
- readonly owner: string;
61
- readonly repo: string;
62
- }
63
- /**
64
- * Read `owner/repo` off the command line. `null` for anything that is not exactly
65
- * two non-blank segments — the same shape the route's own repository normaliser
66
- * refuses, checked here only so a malformed argument fails before a round trip
67
- * rather than after one to a route this CLI could have refused itself.
68
- */
69
- export declare function parseCiRepositoryArg(arg: string): CiRepository | null;
70
- /**
71
- * A refusal from the control plane, carried whole so the CLI can print it.
72
- *
73
- * `403` covers two causes this file cannot and must not tell apart — the caller's
74
- * credential is not an account credential at all, or it names an engagement the
75
- * caller does not own — the same one-sentence-for-both posture T-05's own
76
- * `CI_BIND_REFUSED_MESSAGE` is written with, so nothing here would have a second
77
- * sentence to pick between anyway. `409` is the repository being bound elsewhere,
78
- * client-safely worded by the route to name no other project.
79
- */
80
- export declare class CiBindRefused extends Error {
81
- readonly status: number;
82
- constructor(status: number, message: string);
83
- /** Is this the ONE arm signing in again can fix — see this file's header. */
84
- get authRefused(): boolean;
85
- /**
86
- * Is this "ours to fix, try again", not "yours to fix"? The question is not
87
- * mechanical (did a handler run) — it is what a developer reading the message
88
- * does next. `502`/`504` are the reverse proxy in front of the published origin
89
- * answering for a control plane that is down or slow, with an HTML body this
90
- * file's own `requestBinding` cannot parse, so the message would otherwise
91
- * degrade to a bare "the URL returned 502". `503` is `resolveAccount`'s own
92
- * fail-closed refusal when it could not reach the token store — the caller's
93
- * credential was never actually checked, and its posture is emphatic this is NOT
94
- * a "no". **`500` belongs beside them, not with the 4xx arms.** It is an
95
- * unhandled throw: nothing decided the request's merits, and the bind may even
96
- * have half-happened if the throw landed after a commit. On this route the
97
- * body is the same degraded "the URL returned 500" a 502/504 produces — an
98
- * internal path echoed back with no advice — so `refused (500)` would send a
99
- * developer to re-check ownership, the repository name and whether it is bound
100
- * elsewhere: every 4xx question, none of them this status's actual cause.
101
- *
102
- * So this is every `5xx`, not an enumerated set of three — the boundary is
103
- * "did any handler decide yes or no", and a 4xx is the only family that ever did.
104
- */
105
- get unavailable(): boolean;
106
- }
107
- /** Trust this engagement's CI with tokens from `repository` (T-05's `PUT` route). */
108
- export declare function bindCiRepository(serviceUrl: string, engagementId: string, repository: CiRepository, deps?: ResolveOptions): Promise<void>;
109
- /**
110
- * Withdraw that trust (T-05's `DELETE` route) — reversible, and a success whether or
111
- * not the repository was bound to this engagement in the first place (T-05's own
112
- * docblock: `204` either way, so this route is never an oracle for whether a
113
- * repository the caller does not own is bound to something else).
114
- */
115
- export declare function unbindCiRepository(serviceUrl: string, engagementId: string, repository: CiRepository, deps?: ResolveOptions): Promise<void>;
116
- //# sourceMappingURL=ci-bind.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ci-bind.d.ts","sourceRoot":"","sources":["../src/ci-bind.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAEH,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,yBAAyB,CAAC;AAGjC,yDAAyD;AACzD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAOrE;AAED;;;;;;;;;GASG;AACH,qBAAa,aAAc,SAAQ,KAAK;aAEpB,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM,EAC9B,OAAO,EAAE,MAAM;IAMjB,6EAA6E;IAC7E,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACH,IAAI,WAAW,IAAI,OAAO,CAEzB;CACF;AAiFD,qFAAqF;AACrF,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,YAAY,EACxB,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,YAAY,EACxB,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC,IAAI,CAAC,CAEf"}