halfcycle 0.4.4 → 0.4.6

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,91 @@
1
+ /**
2
+ * The client half of `npx halfcycle@latest draw architecture` (System view · E1,
3
+ * feature `diagram-files-for-docs`, AC1, AC4, AC5, AC6 and AC8's missing route).
4
+ *
5
+ * THE ORDER IS THE SPEC'S. (1) read and check `docs/system.json`; (2) report it and
6
+ * get its version — both through `system`'s own first stage,
7
+ * `checkAndReportDescription` (`../system-report.ts`), never a copy of it; (3) ask the
8
+ * service to draw that version; (4) check all five files, every look's picture and the
9
+ * Mermaid, then write them; (5) print the Markdown, whose image line is the embedded
10
+ * look's PNG (`EMBED_IMAGE_REL`, `./write.ts`, composed from the same table as that
11
+ * file's path). The place and the files already there are checked on the machine
12
+ * before anything is sent, and again just before writing, so a refusal this command
13
+ * can make locally never costs a request, and a change made while it waited is still
14
+ * seen.
15
+ *
16
+ * ONE RESPONSE-HANDLING RULE, the Build Record path's. A body that parses under
17
+ * `systemDrawResultSchema` (`@halfcycle/events`, the one home of the shape) is
18
+ * authoritative, whatever its status. Otherwise the service's missing-route answer to
19
+ * this POST means it predates drawing — asked of `isMissingRoute`
20
+ * (`../missing-route.ts`), the one predicate, never a copy of its expression; a
21
+ * missing report route says the same, because either way this service cannot draw.
22
+ * Anything else is reported by its status and the service's own sentence.
23
+ *
24
+ * NO BYTE OF THE RESPONSE DECIDES A PATH (INV-023): the response gives contents only;
25
+ * where they go is `./write.ts`'s constants.
26
+ *
27
+ * WHAT LEAVES THE MACHINE. The checked description (through the shared stage), then
28
+ * only `{ version, picture: 'architecture' }`.
29
+ *
30
+ * INV-001: Node builtins, the global `fetch`, `@halfcycle/events` and `@halfcycle/core`
31
+ * only. SHIPS TO CLIENT DISK inside the CLI: every string here is a sentence a person
32
+ * reads, so none names an internal register, route or setting; no comment sits beside
33
+ * an object member.
34
+ */
35
+ import { type SystemDrawDrawn } from '@halfcycle/events';
36
+ import type { RepoCredential } from '../open-phase.js';
37
+ import { type SystemDeps, type SystemRun } from '../system-report.js';
38
+ import { type DiagramSet, type DiagramWriteResult } from './write.js';
39
+ export { ARCHITECTURE_NAME, DIAGRAM_DIR_REL, DIAGRAM_FILES, DIAGRAM_MARKER_LINE, EMBED_IMAGE_REL, EMBEDDED_LOOK, PICTURE_NAMES, checkPictures, diagramRel, pngOf, svgOf, diagramPaths, writeDiagramSet, } from './write.js';
40
+ /** The usage line, printed for any argument but `architecture`. */
41
+ export declare const DRAW_USAGE = "npx halfcycle@latest draw architecture [--repo <root>]";
42
+ /**
43
+ * What the arguments after `draw` came to: the repository root to work in, or usage.
44
+ * Exactly one positional, `architecture`, and at most one `--repo <root>`; anything
45
+ * else, including no argument, is usage. A phase name never reaches here in E1.
46
+ */
47
+ export type DrawArgs = {
48
+ ok: true;
49
+ repoRoot: string | undefined;
50
+ } | {
51
+ ok: false;
52
+ };
53
+ export declare function parseDrawArgs(argv: readonly string[]): DrawArgs;
54
+ /**
55
+ * What came of asking for the drawing. `drawn`: the Mermaid and every look's picture,
56
+ * parsed under the strict result schema (so no look is missing and none is extra) but
57
+ * not yet checked. `refused`: the service's reason. `no-route`: this service cannot draw yet.
58
+ * `unavailable`: it could not be drawn, and `why` says so.
59
+ */
60
+ export type DrawOutcome = {
61
+ kind: 'drawn';
62
+ drawn: SystemDrawDrawn;
63
+ } | {
64
+ kind: 'refused';
65
+ reason: string;
66
+ } | {
67
+ kind: 'no-route';
68
+ } | {
69
+ kind: 'unavailable';
70
+ why: string;
71
+ };
72
+ /** The address the drawing is asked of: the engagement is one path segment, encoded once. */
73
+ export declare function systemDrawUrl(credential: RepoCredential): string;
74
+ /**
75
+ * Ask the service to draw `version`. Never throws: the network, the service and an
76
+ * answer this CLI cannot read are each an outcome the caller prints.
77
+ */
78
+ export declare function requestDrawing(credential: RepoCredential, version: number): Promise<DrawOutcome>;
79
+ /** The pieces `runDraw` reaches outside itself through; replaced in tests. */
80
+ export interface DrawDeps extends SystemDeps {
81
+ readonly draw: (credential: RepoCredential, version: number) => Promise<DrawOutcome>;
82
+ readonly write: (repoRoot: string, set: DiagramSet) => DiagramWriteResult;
83
+ }
84
+ /** The Markdown to paste into a document directly under `docs/`, and the line before it. */
85
+ export declare function pasteLines(mermaid: string): string;
86
+ /**
87
+ * The whole verb, in the order the module header gives. Exit 0 only when the five
88
+ * files are written or already up to date and the Markdown is printed.
89
+ */
90
+ export declare function runDraw(repoRoot: string, deps: DrawDeps): Promise<SystemRun>;
91
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/draw/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAAkD,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAGzG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAA6B,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACjG,OAAO,EAWL,KAAK,UAAU,EACf,KAAK,kBAAkB,EACxB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,aAAa,EACb,mBAAmB,EACnB,eAAe,EACf,aAAa,EACb,aAAa,EACb,aAAa,EACb,UAAU,EACV,KAAK,EACL,KAAK,EACL,YAAY,EACZ,eAAe,GAChB,MAAM,YAAY,CAAC;AAEpB,mEAAmE;AACnE,eAAO,MAAM,UAAU,2DAA2D,CAAC;AAEnF;;;;GAIG;AACH,MAAM,MAAM,QAAQ,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAA;CAAE,CAAC;AAElF,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,QAAQ,CAiB/D;AAED;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,eAAe,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GACpB;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzC,6FAA6F;AAC7F,wBAAgB,aAAa,CAAC,UAAU,EAAE,cAAc,GAAG,MAAM,CAEhE;AAUD;;;GAGG;AACH,wBAAsB,cAAc,CAAC,UAAU,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAmDtG;AAED,8EAA8E;AAC9E,MAAM,WAAW,QAAS,SAAQ,UAAU;IAC1C,QAAQ,CAAC,IAAI,EAAE,CAAC,UAAU,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;IACrF,QAAQ,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,KAAK,kBAAkB,CAAC;CAC3E;AAMD,4FAA4F;AAC5F,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CASlD;AAYD;;;GAGG;AACH,wBAAsB,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC,CAiElF"}
@@ -0,0 +1,232 @@
1
+ /**
2
+ * The architecture pictures writer — what `draw architecture` writes into the person's
3
+ * repository (System view · E1, feature `diagram-files-for-docs`, AC4, AC5, AC6 and
4
+ * the "someone else's file in the way" behaviour).
5
+ *
6
+ * NO BYTE OF THE RESPONSE DECIDES A PATH (INV-023). Every path here is composed from
7
+ * `repoRoot` (the caller's `--repo` or the working directory), the constant directory
8
+ * `docs/diagrams` and the constant name `architecture` with a constant extension. The
9
+ * response contributes file CONTENTS only; nothing in it is ever joined, resolved or
10
+ * compared as a path. `DiagramPaths` is built by `diagramPaths(repoRoot)` alone.
11
+ *
12
+ * FIVE FILES, ONE TABLE. Each look in `SYSTEM_PICTURE_THEMES` (`@halfcycle/events`, the
13
+ * one list of looks) has a PNG and an SVG; the Mermaid is one file, because Mermaid has
14
+ * no look. A look selects its file name from `PICTURE_NAMES`, a constant typed against
15
+ * `SystemPictureTheme`, so a look the list does not hold has no name and the compiler
16
+ * refuses a look the table forgets. The looks are iterated from the list, never from
17
+ * the response's keys: the strict result schema already refused any other key, and
18
+ * this module would not read one if it came. Which look documents embed is
19
+ * `EMBEDDED_LOOK`, and only there: the printed image line (`EMBED_IMAGE_REL`) is
20
+ * composed from the same file-name table as that look's path, so the line and the file
21
+ * cannot name different pictures.
22
+ *
23
+ * THE REALPATH CHECK (AC5). Before anything is made or written, the realpath of the
24
+ * nearest existing ancestor of `docs/diagrams` must lie inside the realpath of the
25
+ * repository. "Exists" is asked with `lstat`, so a dangling link is the ancestor and
26
+ * its failed realpath refuses, rather than being stepped over as absent. A planted
27
+ * `docs` link pointing out of the repository therefore refuses before `mkdir`. The
28
+ * same check runs again on `docs/diagrams` itself after `mkdir`, before the first file
29
+ * is created, so a directory made through a link cannot be written into. RESIDUAL: a
30
+ * process racing this one, swapping a directory for a link between that second check
31
+ * and a rename, is not closed by a userspace check; the window is a few syscalls.
32
+ *
33
+ * IN THE WAY (the spec's ownership rule). The command writes only files it recognises
34
+ * as its own: an SVG whose first line is the marker. Any SVG without the marker is
35
+ * named and nothing is written. Each PNG is ours only beside ITS OWN marked SVG
36
+ * (`architecture.png` with `architecture.svg`, `architecture.flightdeck.png` with
37
+ * `architecture.flightdeck.svg`), never another look's. The Mermaid file is ours only
38
+ * beside the embedded look's marked SVG, `architecture.svg`, the one whose name it
39
+ * shares. Anything at one of the five paths that is not a regular file (a link, a
40
+ * folder) is in the way too: replacing a link would silently turn it into a file, and
41
+ * following one would write somewhere this check never looked.
42
+ *
43
+ * LEFTOVER TEMPORARIES, BY THE SAME RULE. A run that dies (killed, power cut) between
44
+ * staging and its last rename leaves `<name>.tmp` files with no chance to clear them.
45
+ * Each one is judged by the rule its final file is judged by, one rule for both: an
46
+ * SVG temporary is ours if its first line is the marker; a PNG temporary is ours only
47
+ * beside its own look's marked SVG, at the final path or as that SVG's own marked
48
+ * temporary; the Mermaid temporary likewise only beside the embedded look's marked SVG
49
+ * or its marked temporary. Anything else at a temporary path (a person's own file,
50
+ * unmarked or unpaired, or anything that is not a regular file) is in the way, named,
51
+ * and never deleted. Ours are removed just before staging, so the exclusive create
52
+ * below never meets one and never has to delete a file it did not create. They are
53
+ * not removed on an up-to-date run, which touches no file (AC6); a dead run's
54
+ * temporaries always sit beside a final file that differs from them, so the same
55
+ * description run again is never up to date while they remain. RESIDUAL: a death
56
+ * between the create and the first byte written leaves an empty SVG temporary, which
57
+ * carries no marker and so is named as in the way; the window is one syscall.
58
+ *
59
+ * ALL OR NOTHING (AC4). Every check on the five files runs before any file is touched.
60
+ * Then each changed file is written to `<name>.tmp` beside it, every temporary first,
61
+ * and renamed into place one by one. If a rename fails, every file already renamed
62
+ * gets its previous bytes back, or is removed if it did not exist before; a file that
63
+ * cannot be put back is removed and named. The SVGs are renamed FIRST on purpose, then
64
+ * the PNGs, then the Mermaid (`DIAGRAM_FILES`'s order), and every SVG temporary is
65
+ * staged before any PNG or Mermaid one: if the process dies at any point, every PNG
66
+ * and Mermaid file or temporary it left has its owning marked SVG (final or temporary)
67
+ * already there, so the next run recognises all of it as its own, clears the
68
+ * temporaries and writes.
69
+ *
70
+ * UP TO DATE (AC6). Each of the five files' bytes is compared with the new bytes first,
71
+ * and an equal file is not written at all, so an unchanged description leaves every
72
+ * modification time as it was.
73
+ *
74
+ * ONE SEAM. The file operations go through `RecordPairIo`, the Build Record writer's
75
+ * seam (`../build-record/write.ts`), widened to take bytes, so the exclusive create
76
+ * has one implementation and a test can fail any one rename.
77
+ *
78
+ * INV-001: Node builtins, `@halfcycle/events` and this package only. SHIPS TO CLIENT
79
+ * DISK inside the CLI: every string here is a sentence a person reads, and no comment
80
+ * sits beside an object member.
81
+ */
82
+ import { type SystemDrawDrawn, type SystemPictureTheme } from '@halfcycle/events';
83
+ import { type RecordPairIo } from '../build-record/write.js';
84
+ /** The one directory the pictures go in, relative to the repository root, `/`-separated. */
85
+ export declare const DIAGRAM_DIR_REL = "docs/diagrams";
86
+ /** The one picture name E1 draws; also the Mermaid file's name. */
87
+ export declare const ARCHITECTURE_NAME = "architecture";
88
+ /**
89
+ * Each look's file name, before its extension: Broadsheet takes the bare name, Flight
90
+ * Deck the name with its look after it. Typed against the one list of looks, so the
91
+ * table cannot hold a look the list does not, or miss one it does.
92
+ */
93
+ export declare const PICTURE_NAMES: Readonly<Record<SystemPictureTheme, string>>;
94
+ /**
95
+ * The look documents embed, and the only place that choice is made. Its PNG is the
96
+ * printed image line, and its SVG is the one the Mermaid file sits beside.
97
+ */
98
+ export declare const EMBEDDED_LOOK: SystemPictureTheme;
99
+ /**
100
+ * The first line of every SVG this command writes, and the only thing that makes an
101
+ * existing SVG ours. The service prepends it; the CLI checks it before writing and
102
+ * reads it on the next run. Its one home is the description contract.
103
+ */
104
+ export declare const DIAGRAM_MARKER_LINE = "<!-- drawn by Halfcycle from docs/system.json -->";
105
+ /** One look's SVG or PNG. */
106
+ export type PictureFile = `${SystemPictureTheme}.svg` | `${SystemPictureTheme}.png`;
107
+ /** Which of the five files: a look's SVG or PNG, or the one Mermaid file. */
108
+ export type DiagramFile = PictureFile | 'mmd';
109
+ /** A look's SVG. */
110
+ export declare function svgOf(look: SystemPictureTheme): PictureFile;
111
+ /** A look's PNG. */
112
+ export declare function pngOf(look: SystemPictureTheme): PictureFile;
113
+ /**
114
+ * The five files, in the order they are renamed: every SVG, then every PNG, then the
115
+ * Mermaid, as the module header says why.
116
+ */
117
+ export declare const DIAGRAM_FILES: readonly DiagramFile[];
118
+ /**
119
+ * The embedded picture as a document directly under `docs/` names it:
120
+ * `diagrams/architecture.png`. Composed from the same table as that file's path.
121
+ */
122
+ export declare const EMBED_IMAGE_REL: string;
123
+ /** The fixed paths, composed from `repoRoot` and constants only. */
124
+ export interface DiagramPaths {
125
+ readonly repoRoot: string;
126
+ readonly dir: string;
127
+ readonly file: Readonly<Record<DiagramFile, string>>;
128
+ }
129
+ export declare function diagramPaths(repoRoot: string): DiagramPaths;
130
+ /** A file's place as a person finds it: `docs/diagrams/architecture.svg`. */
131
+ export declare function diagramRel(which: DiagramFile): string;
132
+ /**
133
+ * The five files in the order a person reads them: each look's PNG then its SVG, then
134
+ * the Mermaid file.
135
+ */
136
+ export declare const DIAGRAM_FILES_AS_LISTED: readonly DiagramFile[];
137
+ /** `a`, `a and b`, `a, b and c`: a list as a sentence says it. */
138
+ export declare function spokenList(items: readonly string[]): string;
139
+ /**
140
+ * What the place the pictures go came to. `ok`: safe to write there. Otherwise the
141
+ * sentence to print, already naming the remedy.
142
+ */
143
+ export type PlaceCheck = {
144
+ ok: true;
145
+ } | {
146
+ ok: false;
147
+ why: string;
148
+ };
149
+ /**
150
+ * THE REALPATH CHECK (AC5), as the module header states it: the nearest ancestor of
151
+ * `docs/diagrams` that `lstat` finds must resolve inside the repository and be a
152
+ * folder. Run before anything is sent and again before anything is made.
153
+ */
154
+ export declare function checkDiagramPlace(paths: DiagramPaths): PlaceCheck;
155
+ /** What is at the five paths now. `null`: nothing. Bytes: a regular file's contents. */
156
+ export type ExistingSet = Readonly<Record<DiagramFile, Uint8Array | null>>;
157
+ /**
158
+ * What reading the existing set came to: the set and which leftover temporaries are
159
+ * ours to clear, or the first file in the way (`temp`: the temporary beside it).
160
+ */
161
+ export type ExistingRead = {
162
+ ok: true;
163
+ existing: ExistingSet;
164
+ ourTemps: readonly DiagramFile[];
165
+ } | {
166
+ ok: false;
167
+ inTheWay: DiagramFile;
168
+ temp: boolean;
169
+ };
170
+ /**
171
+ * Read what is at the five paths and at their five temporaries, and apply the one
172
+ * ownership rule to both, as the module header states it. The final files are judged
173
+ * first, in the order that names the deciding file: every SVG, then each PNG against
174
+ * its own look's SVG, then the Mermaid file against the embedded look's SVG. Then the
175
+ * temporaries in the same order, where a PNG or Mermaid temporary's owner may be the
176
+ * marked SVG at its final path or that SVG's own marked temporary. Anything that is not
177
+ * a regular file, final or temporary, is in the way.
178
+ */
179
+ export declare function readExistingSet(paths: DiagramPaths): ExistingRead;
180
+ /** The sentence for a file in the way, or for the temporary file beside it when `temp`. */
181
+ export declare function inTheWayLine(which: DiagramFile, temp?: boolean): string;
182
+ /** The five files as bytes, checked and ready to write. */
183
+ export type DiagramSet = Readonly<Record<DiagramFile, Uint8Array>>;
184
+ /** What checking the drawn pictures came to: the bytes, or what was wrong, in plain words. */
185
+ export type PictureCheck = {
186
+ ok: true;
187
+ set: DiagramSet;
188
+ } | {
189
+ ok: false;
190
+ why: string;
191
+ };
192
+ /**
193
+ * AC4's checks on what the service drew, all before anything is written, for each look
194
+ * in the list: the PNG's signature and decoded size; the SVG's marker line, its `<svg`
195
+ * start and `</svg>` end (one newline allowed after), and its size. Then the Mermaid's
196
+ * `flowchart` start and its size, once. The sizes are the events package's bounds,
197
+ * checked again on the bytes about to be written rather than trusted from the parse.
198
+ * Each sentence names the file the bad bytes were for, so a person reading it knows
199
+ * which picture was wrong.
200
+ */
201
+ export declare function checkPictures(drawn: SystemDrawDrawn): PictureCheck;
202
+ /**
203
+ * What writing came to. `up-to-date`: every file already held these bytes and none
204
+ * was touched. `written`: the files in `changed` were replaced or created.
205
+ * `in-the-way` / `unsafe`: refused before anything was made, with the sentence.
206
+ * `failed`: a write failed; every file is as it was, except those in `removed`, which
207
+ * could not be put back and were removed.
208
+ */
209
+ export type DiagramWriteResult = {
210
+ kind: 'up-to-date';
211
+ } | {
212
+ kind: 'written';
213
+ changed: DiagramFile[];
214
+ } | {
215
+ kind: 'in-the-way';
216
+ file: DiagramFile;
217
+ temp: boolean;
218
+ } | {
219
+ kind: 'unsafe';
220
+ why: string;
221
+ } | {
222
+ kind: 'failed';
223
+ file: DiagramFile;
224
+ removed: DiagramFile[];
225
+ };
226
+ /**
227
+ * Write the checked pictures into `docs/diagrams`, all or nothing, as the module
228
+ * header states. Never throws for a file-system failure: it is a `failed` result,
229
+ * and the set on disk is the old one.
230
+ */
231
+ export declare function writeDiagramSet(repoRoot: string, set: DiagramSet, io?: RecordPairIo): DiagramWriteResult;
232
+ //# sourceMappingURL=write.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../src/draw/write.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1356,7 +1356,7 @@ function createProjectIdentityFile(targetRepoRoot) {
1356
1356
  }
1357
1357
 
1358
1358
  // dist/install-manifest.js
1359
- import { Buffer } from "node:buffer";
1359
+ import { Buffer as Buffer2 } from "node:buffer";
1360
1360
  import { createHash } from "node:crypto";
1361
1361
  var INSTALL_MANIFEST_REL = ".halfcycle/install-manifest.json";
1362
1362
  var INSTALL_MANIFEST_FORMAT = "halfcycle-install-manifest/v1";
@@ -1367,7 +1367,7 @@ function lineKey(line) {
1367
1367
  return line.endsWith("\r") ? line.slice(0, -1) : line;
1368
1368
  }
1369
1369
  function fileSha256(bytes) {
1370
- const text = typeof bytes === "string" ? bytes : Buffer.from(bytes).toString("utf-8");
1370
+ const text = typeof bytes === "string" ? bytes : Buffer2.from(bytes).toString("utf-8");
1371
1371
  return sha256Hex(text.split("\n").map(lineKey).join("\n"));
1372
1372
  }
1373
1373
  function canonicalJson(value) {
@@ -1813,6 +1813,8 @@ var WRITE_ALLOWLIST = [
1813
1813
  BUNDLE_PIN_REL,
1814
1814
  ".halfcycle/bin",
1815
1815
  "docs/build-records",
1816
+ // Architecture pictures the draw command writes from Halfcycle's answer. The installer never writes here; keep the entry so this path stays declared as one Halfcycle writes.
1817
+ "docs/diagrams",
1816
1818
  "test/fixtures/captured",
1817
1819
  MCP_REGISTRATION_REL,
1818
1820
  ".gitattributes",
@@ -3889,10 +3891,14 @@ function assertInsideOutDir(outDir, candidate, identity) {
3889
3891
  throw new InvalidPhaseIdentityError(identity, BUILD_RECORD_DIR);
3890
3892
  }
3891
3893
  }
3892
- function createExclusiveOnDisk(path, text) {
3894
+ function createExclusiveOnDisk(path, data) {
3895
+ const bytes = typeof data === "string" ? Buffer.from(data, "utf-8") : data;
3893
3896
  const fd = openSync(path, "wx");
3894
3897
  try {
3895
- writeSync(fd, text);
3898
+ let written = 0;
3899
+ while (written < bytes.length) {
3900
+ written += writeSync(fd, bytes, written, bytes.length - written);
3901
+ }
3896
3902
  } catch (err) {
3897
3903
  closeSync(fd);
3898
3904
  rmSync3(path, { force: true });
@@ -4009,6 +4015,22 @@ function replaceOne(io, tmp, finalPath, text) {
4009
4015
 
4010
4016
  // dist/build-record/obtain.js
4011
4017
  import { relative as relative3 } from "node:path";
4018
+
4019
+ // dist/missing-route.js
4020
+ var MESSAGE = {
4021
+ GET: /^Route GET .* not found\.$/s,
4022
+ POST: /^Route POST .* not found\.$/s
4023
+ };
4024
+ function isMissingRoute(method, status, body) {
4025
+ if (status !== 404 || typeof body !== "object" || body === null)
4026
+ return false;
4027
+ const { error, message } = body;
4028
+ if (error !== "Not Found" || typeof message !== "string")
4029
+ return false;
4030
+ return MESSAGE[method].test(message);
4031
+ }
4032
+
4033
+ // dist/build-record/obtain.js
4012
4034
  var REFUSAL_FOLLOW_UP = {
4013
4035
  "still-open": "Nothing was written. The record is produced when the phase closes.",
4014
4036
  "reopened": "Nothing was written. A new record is produced when the phase closes again.",
@@ -4020,12 +4042,6 @@ var REFUSAL_REASONS = phaseRecordRefusalReasonSchema.options;
4020
4042
  function buildRecordUrl(credential, phase) {
4021
4043
  return `${credential.serviceUrl}/engagements/${encodeURIComponent(credential.engagementId)}/phases/${encodeURIComponent(phase)}/build-record`;
4022
4044
  }
4023
- function isMissingRoute(status, body) {
4024
- if (status !== 404 || typeof body !== "object" || body === null)
4025
- return false;
4026
- const { error, message } = body;
4027
- return error === "Not Found" && typeof message === "string" && /^Route GET .* not found\.$/s.test(message);
4028
- }
4029
4045
  async function obtainBuildRecord(credential, repoRoot, phase, io = NODE_RECORD_PAIR_IO) {
4030
4046
  const url = buildRecordUrl(credential, phase);
4031
4047
  let res;
@@ -4082,7 +4098,7 @@ async function obtainBuildRecord(credential, repoRoot, phase, io = NODE_RECORD_P
4082
4098
  headline: latestCloseHeadline(answer.record)
4083
4099
  };
4084
4100
  }
4085
- if (isMissingRoute(res.status, raw)) {
4101
+ if (isMissingRoute("GET", res.status, raw)) {
4086
4102
  return { kind: "unavailable", why: "this Halfcycle service does not provide Build Records yet." };
4087
4103
  }
4088
4104
  const message = typeof raw === "object" && raw !== null && typeof raw.message === "string" ? raw.message : null;