@starci/hfs 4.7.47 → 4.9.0

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.
Files changed (153) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +4 -9
  3. package/emit/operation-schemas.mjs +3 -0
  4. package/lint/bound-sys.cjs +2 -2
  5. package/package.json +11 -5
  6. package/runtime/engine/admission.mjs +1 -1
  7. package/runtime/engine/config.mjs +2 -2
  8. package/runtime/engine/db/machine.mjs +2 -3
  9. package/runtime/engine/invalid-config.mjs +1 -1
  10. package/runtime/knowledge/grammars/{starci → core}/playbook.yaml +3 -3
  11. package/runtime/knowledge/grammars/index.yaml +14 -23
  12. package/runtime/knowledge/grammars/offset-pop/playbook.yaml +2 -2
  13. package/runtime/knowledge/hfs/canon-pins.yaml +23 -23
  14. package/runtime/knowledge/hfs/fe-stack.yaml +1 -1
  15. package/runtime/knowledge/hfs/rules.yaml +83 -60
  16. package/runtime/modules/kernel/failure-codes.yaml +23 -13
  17. package/runtime/scripts/guards/git-policy.mjs +2 -3
  18. package/runtime/scripts/hfs/rule-catalog.mjs +2 -2
  19. package/runtime/scripts/hfs/rules/pipeline.mjs +5 -5
  20. package/runtime/scripts/lib/example-refs.mjs +10 -3
  21. package/runtime/scripts/lib/grammar-lint.mjs +16 -3
  22. package/runtime/scripts/lib/package-at.mjs +5 -6
  23. package/runtime/scripts/lib/roots.mjs +2 -2
  24. package/runtime/scripts/work/draw/draw-dna-findings.mjs +23 -12
  25. package/runtime/scripts/work/draw/draw-dna.mjs +14 -8
  26. package/scaffold/app.mjs +6 -6
  27. package/src/main.mjs +3 -3
  28. package/sync/hygiene.mjs +1 -1
  29. package/sync/index.mjs +15 -2
  30. package/sync/managed.mjs +3 -2
  31. package/templates/app/codegen-graphql.mjs.partial +4 -4
  32. package/templates/app/quality-config/sonar-project.properties +4 -2
  33. package/runtime/engine/canonical-json.mjs +0 -14
  34. package/runtime/engine/db/ledger-open.mjs +0 -38
  35. package/runtime/engine/db/ledger-paths.mjs +0 -87
  36. package/runtime/engine/db/ledger.mjs +0 -1068
  37. package/runtime/engine/db/provision-authority.mjs +0 -76
  38. package/runtime/engine/db/provision-operations.mjs +0 -146
  39. package/runtime/packages/grammar/scripts/build-stamp.mjs +0 -98
  40. package/runtime/scripts/agent/codex-app-server.mjs +0 -79
  41. package/runtime/scripts/agent/codex-mirror-source.mjs +0 -34
  42. package/runtime/scripts/agent/host-agents.mjs +0 -80
  43. package/runtime/scripts/agent/launch-trust-policy.mjs +0 -53
  44. package/runtime/scripts/agent/trust-launch.mjs +0 -191
  45. package/runtime/scripts/agent/trust.mjs +0 -523
  46. package/runtime/scripts/api/fs/temp-path.mjs +0 -8
  47. package/runtime/scripts/api/git/diff-names.mjs +0 -8
  48. package/runtime/scripts/api/git/head-time.mjs +0 -10
  49. package/runtime/scripts/api/git/index-lock.mjs +0 -78
  50. package/runtime/scripts/api/git/log.mjs +0 -5
  51. package/runtime/scripts/api/git/merge-tree.mjs +0 -5
  52. package/runtime/scripts/api/git/rev-list.mjs +0 -5
  53. package/runtime/scripts/api/git/rev-parse.mjs +0 -13
  54. package/runtime/scripts/api/git/status-query.mjs +0 -5
  55. package/runtime/scripts/api/git/worktree-list-query.mjs +0 -5
  56. package/runtime/scripts/api/node/spawn-node.mjs +0 -6
  57. package/runtime/scripts/api/npm/dist-tag.mjs +0 -8
  58. package/runtime/scripts/api/npm/lib.mjs +0 -32
  59. package/runtime/scripts/api/npm/pack-dry-run.mjs +0 -9
  60. package/runtime/scripts/api/npm/run-npm.mjs +0 -6
  61. package/runtime/scripts/api/npm/view.mjs +0 -30
  62. package/runtime/scripts/api/npm/whoami.mjs +0 -8
  63. package/runtime/scripts/api/process/hide-child-windows.mjs +0 -31
  64. package/runtime/scripts/api/process/kill-tree.mjs +0 -22
  65. package/runtime/scripts/api/process/lib.mjs +0 -125
  66. package/runtime/scripts/api/process/probe-guard-command.mjs +0 -74
  67. package/runtime/scripts/api/process/process-list.mjs +0 -28
  68. package/runtime/scripts/api/process/system-tool.mjs +0 -37
  69. package/runtime/scripts/gates/canon-digest.mjs +0 -81
  70. package/runtime/scripts/gates/canon-pins.mjs +0 -119
  71. package/runtime/scripts/gates/gate-input.mjs +0 -98
  72. package/runtime/scripts/gates/gate.mjs +0 -636
  73. package/runtime/scripts/gates/grammar-dist.mjs +0 -206
  74. package/runtime/scripts/gates/output.mjs +0 -40
  75. package/runtime/scripts/gates/runtime-package-selection.mjs +0 -27
  76. package/runtime/scripts/gates/test-world-run.mjs +0 -252
  77. package/runtime/scripts/gates/type-impact.mjs +0 -277
  78. package/runtime/scripts/gates/type-program.mjs +0 -113
  79. package/runtime/scripts/guards/assigned-command.mjs +0 -12
  80. package/runtime/scripts/guards/call-rights.mjs +0 -47
  81. package/runtime/scripts/guards/command-guard.mjs +0 -468
  82. package/runtime/scripts/guards/command-policy.mjs +0 -342
  83. package/runtime/scripts/guards/critic-reach.mjs +0 -82
  84. package/runtime/scripts/guards/deps-guard.mjs +0 -150
  85. package/runtime/scripts/guards/env-dump-verdict.mjs +0 -89
  86. package/runtime/scripts/guards/guards-root.mjs +0 -27
  87. package/runtime/scripts/guards/hook-install.mjs +0 -343
  88. package/runtime/scripts/guards/hook-verdict.mjs +0 -12
  89. package/runtime/scripts/guards/install-verdict.mjs +0 -41
  90. package/runtime/scripts/guards/kernel-seat.mjs +0 -63
  91. package/runtime/scripts/guards/launch-verdict.mjs +0 -74
  92. package/runtime/scripts/guards/op-starci.mjs +0 -16
  93. package/runtime/scripts/guards/op-write-scope.mjs +0 -27
  94. package/runtime/scripts/guards/orca-self-lifecycle.mjs +0 -39
  95. package/runtime/scripts/guards/process-kill-verdict.mjs +0 -106
  96. package/runtime/scripts/guards/protected-zone.mjs +0 -56
  97. package/runtime/scripts/guards/refusals.mjs +0 -27
  98. package/runtime/scripts/guards/rights.mjs +0 -268
  99. package/runtime/scripts/guards/seat-tools.mjs +0 -47
  100. package/runtime/scripts/guards/shell-commands.mjs +0 -328
  101. package/runtime/scripts/guards/supervisor-seat.mjs +0 -80
  102. package/runtime/scripts/guards/verdict-process.mjs +0 -81
  103. package/runtime/scripts/lib/app-data.mjs +0 -11
  104. package/runtime/scripts/lib/canon-binding-text.mjs +0 -129
  105. package/runtime/scripts/lib/check-format.mjs +0 -7
  106. package/runtime/scripts/lib/failure-code-reads.mjs +0 -100
  107. package/runtime/scripts/lib/failure-code-template-errors.mjs +0 -161
  108. package/runtime/scripts/lib/git-dir.mjs +0 -120
  109. package/runtime/scripts/lib/guard-command.mjs +0 -24
  110. package/runtime/scripts/lib/interrupt-cleanup.mjs +0 -38
  111. package/runtime/scripts/lib/json-formats.mjs +0 -34
  112. package/runtime/scripts/lib/managed-json.mjs +0 -51
  113. package/runtime/scripts/lib/menu-parts.mjs +0 -11
  114. package/runtime/scripts/lib/op-gate.mjs +0 -15
  115. package/runtime/scripts/lib/provision-control-failure.mjs +0 -32
  116. package/runtime/scripts/lib/release-version.mjs +0 -134
  117. package/runtime/scripts/lib/runtime-location.mjs +0 -21
  118. package/runtime/scripts/lib/runtime-package-inventory.mjs +0 -21
  119. package/runtime/scripts/lib/runtime-package-manifests.mjs +0 -84
  120. package/runtime/scripts/lib/runtime-publication-tags.mjs +0 -56
  121. package/runtime/scripts/lib/seat-env.mjs +0 -43
  122. package/runtime/scripts/lib/sonar-new-code.mjs +0 -58
  123. package/runtime/scripts/lib/source-package-imports.mjs +0 -111
  124. package/runtime/scripts/lib/tar-files.mjs +0 -39
  125. package/runtime/scripts/lib/uat-run-folder.mjs +0 -118
  126. package/runtime/scripts/lib/worktree-record.mjs +0 -14
  127. package/runtime/scripts/machine/ask-receipts.mjs +0 -113
  128. package/runtime/scripts/machine/evidence-store.mjs +0 -223
  129. package/runtime/scripts/machine/home.mjs +0 -216
  130. package/runtime/scripts/machine/host-lock.mjs +0 -184
  131. package/runtime/scripts/machine/lock-recovery.mjs +0 -55
  132. package/runtime/scripts/machine/roles-contract.mjs +0 -90
  133. package/runtime/scripts/machine/runtime-change.mjs +0 -20
  134. package/runtime/scripts/machine/workflow-tree.mjs +0 -122
  135. package/runtime/scripts/reconciler/checklist-items.mjs +0 -7
  136. package/runtime/scripts/reconciler/guard-row.mjs +0 -25
  137. package/runtime/scripts/work/brand/brand-colour.mjs +0 -125
  138. package/runtime/scripts/work/brand/brand-tokens.mjs +0 -167
  139. package/runtime/scripts/work/brand/brand-value-source.mjs +0 -106
  140. package/runtime/scripts/work/brand/brand.mjs +0 -892
  141. package/runtime/scripts/work/draw/draw-layer.mjs +0 -306
  142. package/runtime/scripts/work/draw/draw-rationale-page.mjs +0 -237
  143. package/runtime/scripts/work/draw/draw-rationale-ref.mjs +0 -14
  144. package/runtime/scripts/work/draw/draw-rationale.mjs +0 -353
  145. package/runtime/scripts/work/draw/draw-source.mjs +0 -412
  146. package/runtime/scripts/work/draw/draw-taste.mjs +0 -278
  147. package/runtime/scripts/work/draw/draw-typecheck.mjs +0 -72
  148. package/runtime/scripts/work/draw/rationale-findings.mjs +0 -205
  149. package/runtime/scripts/work/draw-grammar.mjs +0 -175
  150. package/runtime/scripts/work/draw-render-page.mjs +0 -188
  151. package/runtime/scripts/work/draw-render.mjs +0 -626
  152. package/runtime/scripts/work/png.mjs +0 -237
  153. package/runtime/scripts/work/render-tools.mjs +0 -64
@@ -1,237 +0,0 @@
1
- // png.mjs — the PNG reader and writer the layout-tree tooling composes with.
2
- //
3
- // No dependency: `node:zlib` inflates and deflates, and `zlib.crc32` (Node >= 22.2) stamps the chunks. It
4
- // exists so a composite direction is a deterministic function of its inputs on every host - a consumer
5
- // install has no sharp, and a native image library would make the same inputs produce different bytes on
6
- // two machines. What it reads: bit depths 1/2/4/8/16, colour types 0 (greyscale), 2 (truecolour), 3
7
- // (palette, with tRNS), 4 (greyscale + alpha) and 6 (truecolour + alpha), non-interlaced. Everything else
8
- // throws `unsupported png: <why>` rather than guessing. Every image is handed back as 8-bit RGBA.
9
- import zlib from 'node:zlib';
10
-
11
- const SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
12
- const CHANNELS = { 0: 1, 2: 3, 3: 1, 4: 2, 6: 4 };
13
-
14
- const paeth = (a, b, c) => {
15
- const p = a + b - c, pa = Math.abs(p - a), pb = Math.abs(p - b), pc = Math.abs(p - c);
16
- if (pa <= pb && pa <= pc) return a;
17
- return pb <= pc ? b : c;
18
- };
19
-
20
- function pngChunks(buf) {
21
- let header = null, palette = null, trns = null;
22
- const idat = [];
23
- for (let at = 8; at + 8 <= buf.length;) {
24
- const length = buf.readUInt32BE(at), type = buf.toString('latin1', at + 4, at + 8), start = at + 8;
25
- if (start + length + 4 > buf.length) throw new Error(`unsupported png: chunk ${type} runs past the end of the file`);
26
- const body = buf.subarray(start, start + length);
27
- if (type === 'IHDR') header = { width: body.readUInt32BE(0), height: body.readUInt32BE(4), depth: body[8], colour: body[9], interlace: body[12] };
28
- else if (type === 'PLTE') palette = body;
29
- else if (type === 'tRNS') trns = body;
30
- else if (type === 'IDAT') idat.push(body);
31
- else if (type === 'IEND') break;
32
- at = start + length + 4;
33
- }
34
- return { header, palette, trns, idat };
35
- }
36
-
37
- function sampleAt(line, depth, index) {
38
- if (depth === 8) return line[index];
39
- if (depth === 16) return line[index * 2];
40
- const bit = index * depth, byte = line[bit >> 3], shift = 8 - depth - (bit & 7);
41
- return (byte >> shift) & ((1 << depth) - 1);
42
- }
43
-
44
- function unfilterRow(raw, at, line, prior, filter, bpp) {
45
- for (let i = 0; i < line.length; i += 1) {
46
- const x = raw[at + 1 + i], a = i >= bpp ? line[i - bpp] : 0, b = prior[i], c = i >= bpp ? prior[i - bpp] : 0;
47
- let value = x;
48
- if (filter === 1) value += a;
49
- else if (filter === 2) value += b;
50
- else if (filter === 3) value += (a + b) >> 1;
51
- else if (filter === 4) value += paeth(a, b, c);
52
- line[i] = value & 0xff;
53
- }
54
- }
55
-
56
- function writePixel(out, offset, sample, colour, scale, palette, trns) {
57
- if (colour === 3) {
58
- const index = sample(0);
59
- out[offset] = palette[index * 3] ?? 0; out[offset + 1] = palette[index * 3 + 1] ?? 0; out[offset + 2] = palette[index * 3 + 2] ?? 0;
60
- out[offset + 3] = trns && index < trns.length ? trns[index] : 255;
61
- } else if (colour === 0 || colour === 4) {
62
- const g = Math.round(sample(0) * scale);
63
- out[offset] = g; out[offset + 1] = g; out[offset + 2] = g; out[offset + 3] = colour === 4 ? sample(1) : 255;
64
- } else {
65
- out[offset] = sample(0); out[offset + 1] = sample(1); out[offset + 2] = sample(2); out[offset + 3] = colour === 6 ? sample(3) : 255;
66
- }
67
- }
68
-
69
- function decodeRows(raw, header, palette, trns, channels, stride, bpp) {
70
- const { width, height, depth, colour } = header;
71
- if (raw.length < (stride + 1) * height) throw new Error('unsupported png: image data shorter than the header declares');
72
- const out = new Uint8Array(width * height * 4);
73
- let prior = new Uint8Array(stride);
74
- const line = new Uint8Array(stride);
75
- const scale = depth < 8 && colour !== 3 ? 255 / ((1 << depth) - 1) : 1;
76
- for (let y = 0; y < height; y += 1) {
77
- const at = y * (stride + 1), filter = raw[at];
78
- if (filter > 4) throw new Error(`unsupported png: filter type ${filter}`);
79
- unfilterRow(raw, at, line, prior, filter, bpp);
80
- const sample = (index) => sampleAt(line, depth, index);
81
- for (let x = 0; x < width; x += 1) writePixel(out, (y * width + x) * 4, (index) => sample(x * channels + index), colour, scale, palette, trns);
82
- prior = Uint8Array.from(line);
83
- }
84
- return out;
85
- }
86
-
87
- /** Decode PNG bytes into {width, height, data: Uint8Array RGBA}. Throws `unsupported png: ...`. */
88
- export function decodePng(bytes) {
89
- const buf = Buffer.isBuffer(bytes) ? bytes : Buffer.from(bytes ?? []);
90
- if (buf.length < 8 || !buf.subarray(0, 8).equals(SIGNATURE)) throw new Error('unsupported png: not a PNG signature');
91
- const { header, palette, trns, idat } = pngChunks(buf);
92
- if (!header?.width || !header?.height) throw new Error('unsupported png: no usable IHDR');
93
- const { width, height, depth, colour } = header;
94
- if (!(colour in CHANNELS)) throw new Error(`unsupported png: colour type ${colour}`);
95
- if (![1, 2, 4, 8, 16].includes(depth) || ((colour === 2 || colour === 4 || colour === 6) && depth < 8) || (colour === 3 && depth > 8)) throw new Error(`unsupported png: bit depth ${depth} for colour type ${colour}`);
96
- if (header.interlace !== 0) throw new Error('unsupported png: interlaced (Adam7) images are not read');
97
- if (colour === 3 && !palette) throw new Error('unsupported png: palette image without PLTE');
98
- let raw;
99
- try { raw = zlib.inflateSync(Buffer.concat(idat)); } catch (error) { throw new Error(`unsupported png: image data does not inflate (${error.message})`); }
100
- const channels = CHANNELS[colour];
101
- const bitsPerPixel = channels * depth;
102
- const stride = Math.ceil((width * bitsPerPixel) / 8);
103
- const bpp = Math.max(1, bitsPerPixel >> 3);
104
- return { width, height, data: decodeRows(raw, header, palette, trns, channels, stride, bpp) };
105
- }
106
-
107
- const chunk = (type, body) => {
108
- const head = Buffer.alloc(8);
109
- head.writeUInt32BE(body.length, 0);
110
- head.write(type, 4, 'latin1');
111
- const crc = Buffer.alloc(4);
112
- crc.writeUInt32BE(zlib.crc32(Buffer.concat([Buffer.from(type, 'latin1'), body])) >>> 0, 0);
113
- return Buffer.concat([head, body, crc]);
114
- };
115
-
116
- /** Encode {width, height, data RGBA} as PNG: truecolour when fully opaque, else truecolour + alpha. Filter 0, fixed deflate level. */
117
- export function encodePng({ width, height, data }) {
118
- let opaque = true;
119
- for (let i = 3; i < data.length; i += 4) if (data[i] !== 255) { opaque = false; break; }
120
- const channels = opaque ? 3 : 4, stride = width * channels;
121
- const raw = Buffer.alloc((stride + 1) * height);
122
- for (let y = 0; y < height; y += 1) {
123
- const row = y * (stride + 1);
124
- raw[row] = 0;
125
- for (let x = 0; x < width; x += 1) {
126
- const s = (y * width + x) * 4, d = row + 1 + x * channels;
127
- raw[d] = data[s]; raw[d + 1] = data[s + 1]; raw[d + 2] = data[s + 2];
128
- if (!opaque) raw[d + 3] = data[s + 3];
129
- }
130
- }
131
- const ihdr = Buffer.alloc(13);
132
- ihdr.writeUInt32BE(width, 0); ihdr.writeUInt32BE(height, 4);
133
- ihdr[8] = 8; ihdr[9] = opaque ? 2 : 6; ihdr[10] = 0; ihdr[11] = 0; ihdr[12] = 0;
134
- return Buffer.concat([SIGNATURE, chunk('IHDR', ihdr), chunk('IDAT', zlib.deflateSync(raw, { level: 9 })), chunk('IEND', Buffer.alloc(0))]);
135
- }
136
-
137
- /** A blank RGBA image filled with one colour [r, g, b, a]. */
138
- export function blankImage(width, height, rgba = [255, 255, 255, 255]) {
139
- const data = new Uint8Array(width * height * 4);
140
- for (let i = 0; i < data.length; i += 4) { data[i] = rgba[0]; data[i + 1] = rgba[1]; data[i + 2] = rgba[2]; data[i + 3] = rgba[3]; }
141
- return { width, height, data };
142
- }
143
-
144
- /**
145
- * Resample `image` to width x height. Downscaling by more than half averages the covered source area (a box
146
- * filter), everything else is bilinear. Pure arithmetic, so the same inputs give the same pixels anywhere.
147
- */
148
- function resizeBox(image, width, height, sx, sy, src, out) {
149
- for (let y = 0; y < height; y += 1) {
150
- const y0 = Math.floor(y * sy), y1 = Math.max(y0 + 1, Math.floor((y + 1) * sy));
151
- for (let x = 0; x < width; x += 1) {
152
- const x0 = Math.floor(x * sx), x1 = Math.max(x0 + 1, Math.floor((x + 1) * sx));
153
- const sum = [0, 0, 0, 0];
154
- for (let yy = y0; yy < y1; yy += 1) for (let xx = x0; xx < x1; xx += 1) { const s = (yy * image.width + xx) * 4; sum[0] += src[s]; sum[1] += src[s + 1]; sum[2] += src[s + 2]; sum[3] += src[s + 3]; }
155
- const n = (y1 - y0) * (x1 - x0), d = (y * width + x) * 4;
156
- for (let c = 0; c < 4; c += 1) out[d + c] = Math.round(sum[c] / n);
157
- }
158
- }
159
- return { width, height, data: out };
160
- }
161
-
162
- function resizeBilinear(image, width, height, sx, sy, src, out) {
163
- for (let y = 0; y < height; y += 1) {
164
- const fy = Math.min(image.height - 1, Math.max(0, (y + 0.5) * sy - 0.5)), y0 = Math.floor(fy), y1 = Math.min(image.height - 1, y0 + 1), wy = fy - y0;
165
- for (let x = 0; x < width; x += 1) {
166
- const fx = Math.min(image.width - 1, Math.max(0, (x + 0.5) * sx - 0.5)), x0 = Math.floor(fx), x1 = Math.min(image.width - 1, x0 + 1), wx = fx - x0;
167
- const a = (y0 * image.width + x0) * 4, b = (y0 * image.width + x1) * 4, c = (y1 * image.width + x0) * 4, e = (y1 * image.width + x1) * 4, d = (y * width + x) * 4;
168
- for (let k = 0; k < 4; k += 1) out[d + k] = Math.round((src[a + k] * (1 - wx) + src[b + k] * wx) * (1 - wy) + (src[c + k] * (1 - wx) + src[e + k] * wx) * wy);
169
- }
170
- }
171
- return { width, height, data: out };
172
- }
173
-
174
- export function resizeImage(image, width, height) {
175
- if (image.width === width && image.height === height) return { width, height, data: Uint8Array.from(image.data) };
176
- const out = new Uint8Array(width * height * 4), sx = image.width / width, sy = image.height / height, src = image.data;
177
- if (sx > 2 || sy > 2) return resizeBox(image, width, height, sx, sy, src, out);
178
- return resizeBilinear(image, width, height, sx, sy, src, out);
179
- }
180
-
181
- /** Crop a rectangle out of `image`. */
182
- export function cropImage(image, { x, y, width, height }) {
183
- const out = new Uint8Array(width * height * 4);
184
- for (let row = 0; row < height; row += 1) {
185
- const s = ((y + row) * image.width + x) * 4;
186
- out.set(image.data.subarray(s, s + width * 4), row * width * 4);
187
- }
188
- return { width, height, data: out };
189
- }
190
-
191
- function compositePixel(base, top, source, target, alphaByte) {
192
- if (alphaByte === 255) { base.data[target] = top.data[source]; base.data[target + 1] = top.data[source + 1]; base.data[target + 2] = top.data[source + 2]; base.data[target + 3] = 255; return; }
193
- if (alphaByte === 0) return;
194
- const alpha = alphaByte / 255, beneath = base.data[target + 3] / 255, outA = alpha + beneath * (1 - alpha);
195
- for (let c = 0; c < 3; c += 1) base.data[target + c] = Math.round((top.data[source + c] * alpha + base.data[target + c] * beneath * (1 - alpha)) / (outA || 1));
196
- base.data[target + 3] = Math.round(outA * 255);
197
- }
198
-
199
- /** Alpha-composite `top` over `base` (mutated) with its top-left corner at (x, y); pixels outside `base` are dropped. */
200
- export function drawOver(base, top, x, y) {
201
- for (let row = 0; row < top.height; row += 1) {
202
- const by = y + row;
203
- if (by < 0 || by >= base.height) continue;
204
- for (let col = 0; col < top.width; col += 1) {
205
- const bx = x + col;
206
- if (bx < 0 || bx >= base.width) continue;
207
- const s = (row * top.width + col) * 4, d = (by * base.width + bx) * 4;
208
- compositePixel(base, top, s, d, top.data[s + 3]);
209
- }
210
- }
211
- return base;
212
- }
213
-
214
- /** Darken every pixel of `image` (mutated) by a black scrim of opacity `amount` in [0, 1]. */
215
- export function dimImage(image, amount) {
216
- const keep = 1 - amount;
217
- for (let i = 0; i < image.data.length; i += 4) for (let c = 0; c < 3; c += 1) image.data[i + c] = Math.round(image.data[i + c] * keep);
218
- return image;
219
- }
220
-
221
- /**
222
- * The bounding rectangle of the pixels matching `key` ([r, g, b], within `tolerance` per channel), and how much
223
- * of that rectangle they fill. A capture or a drawn layout marks its page slot with one flat key colour, so the
224
- * slot is measured from the image rather than typed in.
225
- */
226
- export function keyRect(image, key = [255, 0, 255], tolerance = 8) {
227
- let minX = Infinity, minY = Infinity, maxX = -1, maxY = -1, count = 0;
228
- const hits = (i) => Math.abs(image.data[i] - key[0]) <= tolerance && Math.abs(image.data[i + 1] - key[1]) <= tolerance && Math.abs(image.data[i + 2] - key[2]) <= tolerance && image.data[i + 3] > 200;
229
- for (let y = 0; y < image.height; y += 1) for (let x = 0; x < image.width; x += 1) {
230
- if (!hits((y * image.width + x) * 4)) continue;
231
- count += 1;
232
- minX = Math.min(minX, x); maxX = Math.max(maxX, x); minY = Math.min(minY, y); maxY = Math.max(maxY, y);
233
- }
234
- if (!count) return null;
235
- const rect = { x: minX, y: minY, width: maxX - minX + 1, height: maxY - minY + 1 };
236
- return { rect, fill: count / (rect.width * rect.height) };
237
- }
@@ -1,64 +0,0 @@
1
- // render-tools.mjs - the tools a capture needs (Playwright with a browser, esbuild) and where they come from.
2
- //
3
- // A product declares neither: a drawing is captured by the runtime, so the runtime pins and installs both (package.json devDependencies,
4
- // the browser download Playwright keeps in its own cache). The product's own install, when it has one, comes first (its tailwindcss,
5
- // React and tsconfig paths resolve from the product regardless); the runtime's install is always the last candidate. This is the same
6
- // order layout-render.mjs uses for its capture. A host with neither a project install nor the runtime's, or with Playwright and no browser,
7
- // is RENDER_TOOL_UNAVAILABLE: one typed host fact the dispatch of an op that needs a capture checks before it spends an attempt.
8
- import fs from 'node:fs';
9
- import { skillRoot } from '../../engine/runtime-root.mjs';
10
- import { findPackage, requirePackage } from '../lib/package-at.mjs';
11
- import { toolSearchDirs } from '../lib/roots.mjs';
12
-
13
- /** The catalogued code of a host that cannot take a capture (modules/kernel/failure-codes.yaml). */
14
- export const RENDER_TOOL_UNAVAILABLE = 'RENDER_TOOL_UNAVAILABLE';
15
- /** The hint an op manifest carries in route.riskHints when its steps capture a render. */
16
- export const RENDER_CAPABILITY_HINT = 'host-capability-required:render';
17
- const PLAYWRIGHT_PACKAGES = Object.freeze(['playwright', '@playwright/test', 'playwright-core']);
18
-
19
- /** The first install of `names` resolvable from each of `dirs` in turn, one per distinct package root. */
20
- function installsOf(dirs, names) {
21
- const seen = new Set();
22
- const out = [];
23
- for (const dir of dirs) {
24
- const found = findPackage([dir], names);
25
- if (found && !seen.has(found.root)) { seen.add(found.root); out.push(found); }
26
- }
27
- return out;
28
- }
29
-
30
- /** Whether the browser a Playwright install would launch is on the disk. */
31
- function browserOf(found) {
32
- try { return fs.existsSync(requirePackage(found).chromium.executablePath()); } catch { return false; }
33
- }
34
-
35
- /**
36
- * The Playwright install a capture launches: the first of the project's and the runtime's whose browser exists, else the first install
37
- * (its launch then names the missing browser). Null when no directory resolves one.
38
- */
39
- export function playwrightInstall(dirs, { runtime = skillRoot } = {}) {
40
- const installs = installsOf(toolSearchDirs(dirs, runtime), PLAYWRIGHT_PACKAGES);
41
- return installs.find(browserOf) ?? installs[0] ?? null;
42
- }
43
-
44
- /** The esbuild install a capture bundles with: the project's, else the runtime's; null when neither resolves one. */
45
- export const esbuildInstall = (dirs, { runtime = skillRoot } = {}) => findPackage(toolSearchDirs(dirs, runtime), ['esbuild']);
46
-
47
- /**
48
- * What this host can capture with, from `dirs` (the tree the op runs in) and the runtime: {ok, missing: [{tool, why}], playwright, esbuild}.
49
- * `missing` names each absent tool with the one-line reason a Kernel and the owner read.
50
- */
51
- export function renderToolStatus(dirs = [], { runtime = skillRoot } = {}) {
52
- const playwright = playwrightInstall(dirs, { runtime });
53
- const esbuild = esbuildInstall(dirs, { runtime });
54
- const missing = [];
55
- if (!playwright) missing.push({ tool: 'playwright', why: 'no Playwright install resolves from the project or the runtime' });
56
- else if (!browserOf(playwright)) missing.push({ tool: 'chromium', why: `Playwright ${playwright.version} resolves at ${playwright.root.split(/[\\/]/).slice(-2).join('/')} and its Chromium download is absent from the browser cache` });
57
- if (!esbuild) missing.push({ tool: 'esbuild', why: 'no esbuild install resolves from the project or the runtime' });
58
- return { ok: missing.length === 0, missing, playwright, esbuild };
59
- }
60
-
61
- /** The one-line instruction for a missing tool: what provisions it. */
62
- export const renderToolFix = (missing) => missing.map((item) => (item.tool === 'chromium'
63
- ? 'run `npx playwright install chromium` once on the host (the browser cache is the owner\'s to fill)'
64
- : `install the runtime's dependencies (\`npm install\` in the runtime checkout) so ${item.tool} resolves`)).join('; ');