cherry-works 0.1.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.
package/dist/main.js ADDED
@@ -0,0 +1,4086 @@
1
+ #!/usr/bin/env node
2
+
3
+ // main.ts
4
+ import { pathToFileURL as pathToFileURL2 } from "url";
5
+
6
+ // package.json
7
+ var package_default = {
8
+ name: "cherry-works",
9
+ version: "0.1.0",
10
+ description: "The charter engine: resolves a charter and compiles it into what a coding agent reads.",
11
+ keywords: [
12
+ "claude-code",
13
+ "coding-agent",
14
+ "charter",
15
+ "ai-agents",
16
+ "cli",
17
+ "guardrails"
18
+ ],
19
+ homepage: "https://github.com/tamnguyenvt/cherry-works#readme",
20
+ bugs: {
21
+ url: "https://github.com/tamnguyenvt/cherry-works/issues"
22
+ },
23
+ repository: {
24
+ type: "git",
25
+ url: "git+https://github.com/tamnguyenvt/cherry-works.git"
26
+ },
27
+ license: "MIT",
28
+ author: "Tam Nguyen",
29
+ type: "module",
30
+ engines: {
31
+ node: ">=22",
32
+ pnpm: ">=10"
33
+ },
34
+ packageManager: "pnpm@10.2.1",
35
+ bin: {
36
+ cw: "./dist/cw.js"
37
+ },
38
+ files: [
39
+ "dist"
40
+ ],
41
+ imports: {
42
+ "#hexagon/*": "./src/hexagon/*",
43
+ "#portal/*": "./dist/portal/*"
44
+ },
45
+ scripts: {
46
+ build: "tsup && tailwindcss -i src/driver/portal/page/styles.css -o dist/portal/styles.css --minify",
47
+ typecheck: "tsc --noEmit && tsc --noEmit -p src/driver/portal/page",
48
+ test: 'pnpm run lint:deps && pnpm run build && node --import tsx --test "test/**/*.test.ts"',
49
+ cw: "node --import tsx main.ts",
50
+ "lint:deps": "depcruise src main.ts",
51
+ "specs:link": "node --import tsx scripts/link-specs.ts",
52
+ "specs:check": "node --import tsx scripts/link-specs.ts --check",
53
+ release: "node --import tsx scripts/release.ts",
54
+ prepublishOnly: `node -e "console.error('Publish with \\"pnpm release <version>\\", which checks the package first.'); process.exit(1)"`
55
+ },
56
+ devDependencies: {
57
+ "@codemirror/commands": "^6.11.1",
58
+ "@codemirror/lang-markdown": "^6.5.2",
59
+ "@codemirror/language": "^6.12.4",
60
+ "@codemirror/state": "^6.7.4",
61
+ "@codemirror/view": "^6.43.11",
62
+ "@tailwindcss/cli": "^4.3.3",
63
+ "@tanstack/react-query": "^5.103.2",
64
+ "@types/node": "^22.10.2",
65
+ "@types/picomatch": "^4.0.3",
66
+ "@types/prompts": "^2.4.9",
67
+ "@types/react": "^19.3.0",
68
+ "@types/react-dom": "^19.3.0",
69
+ "@types/yargs": "^17.0.35",
70
+ "class-variance-authority": "^0.7.1",
71
+ clsx: "^2.1.1",
72
+ cmdk: "^1.1.1",
73
+ "dependency-cruiser": "^18.2.0",
74
+ "lucide-react": "^1.47.0",
75
+ marked: "^18.0.13",
76
+ playwright: "^1.63.0",
77
+ "radix-ui": "^1.6.7",
78
+ react: "^19.3.0",
79
+ "react-dom": "^19.3.0",
80
+ sonner: "^2.0.8",
81
+ "tailwind-merge": "^3.7.0",
82
+ tailwindcss: "^4.3.3",
83
+ tsup: "^8.3.5",
84
+ tsx: "^4.19.2",
85
+ "tw-animate-css": "^1.4.0",
86
+ typescript: "^5.7.2"
87
+ },
88
+ pnpm: {
89
+ onlyBuiltDependencies: [
90
+ "esbuild"
91
+ ]
92
+ },
93
+ dependencies: {
94
+ "@hono/node-server": "^2.1.1",
95
+ "@hono/zod-openapi": "^1.6.3",
96
+ hono: "^4.13.7",
97
+ picomatch: "^4.0.7",
98
+ prompts: "^2.4.2",
99
+ yaml: "^2.9.0",
100
+ yargs: "^18.1.0",
101
+ zod: "^4.6.1"
102
+ }
103
+ };
104
+
105
+ // src/driver/cli/Commander.ts
106
+ import yargs from "yargs";
107
+
108
+ // src/hexagon/domain/models/Fault.ts
109
+ var Fault = class extends Error {
110
+ constructor(message, fix, severity = "error") {
111
+ super(message);
112
+ this.fix = fix;
113
+ this.severity = severity;
114
+ this.name = new.target.name;
115
+ }
116
+ fix;
117
+ severity;
118
+ };
119
+ var CharterRootFault = class extends Fault {
120
+ };
121
+ var SettingsFault = class extends Fault {
122
+ };
123
+ var CharterPrimitiveFault = class extends Fault {
124
+ };
125
+ var TestSuiteFault = class extends Fault {
126
+ };
127
+ var TestCaseFault = class extends Fault {
128
+ };
129
+ var VendorFault = class extends Fault {
130
+ };
131
+ function throwAggregateError(faults2) {
132
+ throw new AggregateError(faults2, faults2.map((one) => one.message).join(" "));
133
+ }
134
+ var FaultsByFile = class _FaultsByFile {
135
+ /** Nothing wrong with any file. */
136
+ static none = new _FaultsByFile({});
137
+ #files;
138
+ constructor(files) {
139
+ this.#files = files;
140
+ }
141
+ /** Every fault, under the file it is wrong with. */
142
+ get files() {
143
+ return this.#files;
144
+ }
145
+ /** Is no file wrong at all? */
146
+ get isEmpty() {
147
+ return Object.keys(this.#files).length === 0;
148
+ }
149
+ /** These and the other's together, a file both name holding the faults of
150
+ * both. */
151
+ with(other) {
152
+ const files = { ...this.#files };
153
+ for (const [file, faults2] of Object.entries(other.#files)) files[file] = [...files[file] ?? [], ...faults2];
154
+ return new _FaultsByFile(files);
155
+ }
156
+ /** What is wrong badly enough to stop a build, under the file that has to
157
+ * change. A file whose every fault is a warning is not in here at all
158
+ * (FR-005). */
159
+ errors() {
160
+ return new _FaultsByFile(
161
+ Object.fromEntries(
162
+ Object.entries(this.#files).map(([file, faults2]) => [file, faults2.filter((one) => one.severity === "error")]).filter(([, faults2]) => faults2.length > 0)
163
+ )
164
+ );
165
+ }
166
+ /** The same faults, each file named the way an author would type it: where
167
+ * it sits under the repository they ran in, where the file is the `href` it
168
+ * was read from. */
169
+ namedFrom(repo) {
170
+ return new _FaultsByFile(Object.fromEntries(Object.entries(this.#files).map(([file, faults2]) => [nameOf(file, repo), faults2])));
171
+ }
172
+ };
173
+ var Faults = class {
174
+ constructor(faults2) {
175
+ this.faults = faults2;
176
+ }
177
+ faults;
178
+ };
179
+ function nameOf(file, repo) {
180
+ const at = `${repo.href.replace(/\/$/, "")}/`;
181
+ return decodeURIComponent(file.startsWith(at) ? file.slice(at.length) : file);
182
+ }
183
+
184
+ // src/hexagon/domain/models/AgentProvider.ts
185
+ var AGENT_PROVIDERS = ["claude"];
186
+ function isAgentProvider(name) {
187
+ return typeof name === "string" && AGENT_PROVIDERS.includes(name);
188
+ }
189
+
190
+ // src/hexagon/utils/globs.ts
191
+ import picomatch from "picomatch";
192
+ function covers(outer, inner) {
193
+ return outer === inner || matches(outer, inner);
194
+ }
195
+ function matches(glob, path) {
196
+ return picomatch(glob, { dot: true })(path);
197
+ }
198
+
199
+ // src/hexagon/domain/models/charter/primitive/Primitive.ts
200
+ import { z as z4 } from "zod";
201
+
202
+ // src/hexagon/domain/models/charter/primitive/BasePrimitive.ts
203
+ import { z } from "zod";
204
+
205
+ // src/hexagon/domain/models/helper.ts
206
+ function formatFrontmatterValue(value) {
207
+ return Array.isArray(value) ? `[${value.map((one) => JSON.stringify(one)).join(", ")}]` : String(value);
208
+ }
209
+
210
+ // src/hexagon/domain/models/charter/primitive/BasePrimitive.ts
211
+ var DELIMITER = "---";
212
+ var goodLine = z.string().regex(/\S/);
213
+ var goodArray = z.array(goodLine).min(1).readonly();
214
+ var CommonHeaders = z.object({
215
+ id: z.string().regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/),
216
+ /** All an agent reads of a primitive before opening its body. */
217
+ description: goodLine,
218
+ tags: z.array(goodLine).readonly().optional(),
219
+ /** The files this primitive speaks about, where it speaks about any. A guide's
220
+ * are what decide when its body is loaded; a mixin may name them to say how
221
+ * far what it lends reaches (FR-006). */
222
+ globs: z.array(goodLine).readonly().optional(),
223
+ /** `corpus:<id>`, the reasoning this primitive cites (FR-005). */
224
+ rationale: goodLine.optional(),
225
+ mixins: z.array(goodLine).readonly().optional()
226
+ });
227
+ var BasePrimitive = class {
228
+ constructor(headers, body) {
229
+ this.headers = headers;
230
+ this.body = body;
231
+ }
232
+ headers;
233
+ body;
234
+ /** What every kind requires of its author, whatever else its own contract
235
+ * asks for. A kind's own `requires` stands beside this rather than adding to
236
+ * it, so whoever asks for a kind's headers asks for both (FR-039). `id` is
237
+ * not among them: a primitive is named where it is asked for rather than
238
+ * answered for, so what is left to ask about is what it is for. */
239
+ static requires = { description: "line" };
240
+ /** The schema every kind's headers are read by, its own standing in place of
241
+ * this. What a file is refused against, and the one place saying what shape
242
+ * each header holds — including the ones a kind takes without requiring, so
243
+ * whoever is writing a header reads its shape off the same contract a file
244
+ * is refused against (FR-004). */
245
+ static schema = CommonHeaders;
246
+ /**
247
+ * This primitive as the file it is authored in: its headers between the
248
+ * delimiters, and its body under them.
249
+ *
250
+ * The other direction of the reading `primitiveOf` does, and the same
251
+ * frontmatter, so a file written here is a file that reads back as this
252
+ * primitive. Said here rather than by whoever writes, for the reason
253
+ * `description` is: what a primitive holds is the primitive's own, and a
254
+ * caller holding one already has everything the file says.
255
+ *
256
+ * The kind leads, since it is what decides how the rest is read (FR-003). A
257
+ * list is written the way the parser gives it back, so an author opening the
258
+ * file finds what they declared rather than a second spelling of it.
259
+ */
260
+ toMarkdown() {
261
+ return [
262
+ DELIMITER,
263
+ ...Object.entries({ kind: this.kind, ...this.headers }).map(([field, value]) => `${field}: ${formatFrontmatterValue(value)}`),
264
+ DELIMITER,
265
+ "",
266
+ this.body
267
+ ].join("\n").replace(/\n*$/, "\n");
268
+ }
269
+ /**
270
+ * The one line this primitive is found by, composed: what its author wrote it
271
+ * is for, and — where its kind decides when it applies — what it applies to.
272
+ *
273
+ * Said here rather than by whoever compiles, because what makes a kind
274
+ * activate is the kind's own contract (FR-001, FR-004): a skill's triggers are
275
+ * the author's words, which only a skill knows how to say in a line. A kind
276
+ * that nothing activates on its description is the description it was
277
+ * written with.
278
+ */
279
+ description() {
280
+ return this.headers.description;
281
+ }
282
+ };
283
+ function headersOf(sampledKind, headerSchema, record) {
284
+ const parsed = headerSchema.safeParse(record);
285
+ if (parsed.success) return parsed.data;
286
+ const { kind, sample } = sampledKind;
287
+ const sampleLines = Object.entries({ kind, ...sample }).map(([field, value]) => `${field}: ${formatFrontmatterValue(value)}`);
288
+ return throwAggregateError([
289
+ new CharterPrimitiveFault(
290
+ `These headers are not what a ${kind} holds.`,
291
+ `Write them the way this ${kind} does:
292
+ ${sampleLines.join("\n")}`
293
+ )
294
+ ]);
295
+ }
296
+
297
+ // src/hexagon/domain/models/charter/primitive/AgentPrimitive.ts
298
+ var AgentHeaders = CommonHeaders.extend({
299
+ tools: goodArray
300
+ });
301
+ var AgentPrimitive = class _AgentPrimitive extends BasePrimitive {
302
+ static kind = "agent";
303
+ kind = _AgentPrimitive.kind;
304
+ /** What this kind requires beyond the common headers, declared where its
305
+ * contract is: it is read both where a file is checked and where one is
306
+ * scaffolded (FR-004, FR-039). */
307
+ static requires = { tools: "list" };
308
+ /** The schema its headers are read by: what `headersOf` refuses a file
309
+ * against, and what says the shape of every header this kind takes, required
310
+ * or not (FR-004). */
311
+ static schema = AgentHeaders;
312
+ /** When a reader of this charter is to open this kind at all, said
313
+ * where the kind's contract is: the neutral surface lists one
314
+ * line per kind and none of them is written down twice (FR-002). */
315
+ static activatesWhen = "it is spawned by identity, holding the `tools` it lists and nothing else";
316
+ /** An agent as an author writes one: what a refused header is fixed with. */
317
+ static sample = {
318
+ id: "reviewer",
319
+ description: "Review a diff and name what is wrong with it.",
320
+ tools: ["Read", "Grep"]
321
+ };
322
+ /** One agent, or every fault its headers have (FR-004). */
323
+ static of(record, body) {
324
+ return new _AgentPrimitive(headersOf(_AgentPrimitive, AgentHeaders, record), body);
325
+ }
326
+ };
327
+
328
+ // src/hexagon/domain/models/charter/primitive/CommandPrimitive.ts
329
+ var CommandPrimitive = class _CommandPrimitive extends BasePrimitive {
330
+ static kind = "command";
331
+ kind = _CommandPrimitive.kind;
332
+ /** What this kind requires beyond the common headers: nothing. Declared all
333
+ * the same, since what a file is scaffolded with is read off this (FR-039). */
334
+ static requires = {};
335
+ /** The schema its headers are read by: what `headersOf` refuses a file
336
+ * against, and what says the shape of every header this kind takes, required
337
+ * or not (FR-004). */
338
+ static schema = CommonHeaders;
339
+ /** When a reader of this charter is to open this kind at all, said
340
+ * where the kind's contract is: the neutral surface lists one
341
+ * line per kind and none of them is written down twice (FR-002). */
342
+ static activatesWhen = "it is asked for by name, or a request matches what it describes itself as being for";
343
+ /** A command as an author writes one: what a refused header is fixed with. */
344
+ static sample = {
345
+ id: "release",
346
+ description: "Cut a release from the main branch."
347
+ };
348
+ /** One command, or every fault its headers have. Nothing beyond what every
349
+ * primitive declares: a command is asked for by name, and the one line it
350
+ * describes itself with is what decides that it is the one being asked for. */
351
+ static of(record, body) {
352
+ return new _CommandPrimitive(headersOf(_CommandPrimitive, CommonHeaders, record), body);
353
+ }
354
+ };
355
+
356
+ // src/hexagon/domain/models/charter/primitive/CorpusPrimitive.ts
357
+ var CorpusHeaders = CommonHeaders.extend({});
358
+ var CorpusPrimitive = class _CorpusPrimitive extends BasePrimitive {
359
+ static kind = "corpus";
360
+ kind = _CorpusPrimitive.kind;
361
+ /** What this kind requires beyond the common headers: nothing. Declared all
362
+ * the same, since what a file is scaffolded with is read off this (FR-039). */
363
+ static requires = {};
364
+ /** The schema its headers are read by: what `headersOf` refuses a file
365
+ * against, and what says the shape of every header this kind takes, required
366
+ * or not (FR-004). */
367
+ static schema = CorpusHeaders;
368
+ /** When a reader of this charter is to open this kind at all, said
369
+ * where the kind's contract is: the neutral surface lists one
370
+ * line per kind and none of them is written down twice (FR-002). */
371
+ static activatesWhen = "a primitive's `rationale` cites it \u2014 the reasoning, read when someone asks why";
372
+ /** A corpus as an author writes one: what a refused header is fixed with. */
373
+ static sample = {
374
+ id: "type-safety",
375
+ description: "Why the charter insists on types."
376
+ };
377
+ /** One corpus, or every fault its headers have (FR-004). */
378
+ static of(record, body) {
379
+ return new _CorpusPrimitive(headersOf(_CorpusPrimitive, CorpusHeaders, record), body);
380
+ }
381
+ };
382
+
383
+ // src/hexagon/domain/models/charter/primitive/GuidePrimitive.ts
384
+ var GuideHeaders = CommonHeaders.extend({});
385
+ var GuidePrimitive = class _GuidePrimitive extends BasePrimitive {
386
+ static kind = "guide";
387
+ kind = _GuidePrimitive.kind;
388
+ /** What this kind requires beyond the common headers: nothing. Declared all
389
+ * the same, since what a file is scaffolded with is read off this (FR-039). */
390
+ static requires = {};
391
+ /** The schema its headers are read by: what `headersOf` refuses a file
392
+ * against, and what says the shape of every header this kind takes, required
393
+ * or not (FR-004). */
394
+ static schema = GuideHeaders;
395
+ /** When a reader of this charter is to open this kind at all, said
396
+ * where the kind's contract is: the neutral surface lists one
397
+ * line per kind and none of them is written down twice (FR-002). */
398
+ static activatesWhen = "a touched file matches one of its `globs`, or every turn where globs are not specified";
399
+ /** A guide as an author writes one: what a refused header is fixed with. */
400
+ static sample = {
401
+ id: "no-any",
402
+ description: "Reject the any type in application code.",
403
+ globs: ["src/**/*.ts"],
404
+ tags: ["typescript"],
405
+ rationale: "corpus:type-safety"
406
+ };
407
+ /** One guide, or every fault its headers have (FR-004). */
408
+ static of(record, body) {
409
+ return new _GuidePrimitive(headersOf(_GuidePrimitive, GuideHeaders, record), body);
410
+ }
411
+ };
412
+
413
+ // src/hexagon/domain/models/charter/primitive/MixinPrimitive.ts
414
+ import { z as z2 } from "zod";
415
+ var MixinHeaders = CommonHeaders.extend({
416
+ /** Never: reading refuses a mixin that declares one. */
417
+ mixins: z2.undefined().optional()
418
+ });
419
+ var MixinPrimitive = class _MixinPrimitive extends BasePrimitive {
420
+ static kind = "mixin";
421
+ kind = _MixinPrimitive.kind;
422
+ /** What this kind requires beyond the common headers: nothing. Declared all
423
+ * the same, since what a file is scaffolded with is read off this (FR-039). */
424
+ static requires = {};
425
+ /** The schema its headers are read by: what `headersOf` refuses a file
426
+ * against, and what says the shape of every header this kind takes, required
427
+ * or not (FR-004). */
428
+ static schema = MixinHeaders;
429
+ /** When a reader of this charter is to open this kind at all, said
430
+ * where the kind's contract is: the neutral surface lists one
431
+ * line per kind and none of them is written down twice (FR-002). */
432
+ static activatesWhen = "never on its own: its body is lent to the primitives that pull it in";
433
+ /** A mixin as an author writes one: what a refused header is fixed with. */
434
+ static sample = {
435
+ id: "typescript-scope",
436
+ description: "What counts as TypeScript application code.",
437
+ globs: ["src/**/*.ts"]
438
+ };
439
+ /** One mixin, or every fault its headers have (FR-004, FR-006). */
440
+ static of(record, body) {
441
+ return new _MixinPrimitive(headersOf(_MixinPrimitive, MixinHeaders, record), body);
442
+ }
443
+ };
444
+
445
+ // src/hexagon/domain/models/charter/primitive/PlaybookPrimitive.ts
446
+ var PlaybookHeaders = CommonHeaders.extend({
447
+ triggers: goodArray
448
+ });
449
+ var PlaybookPrimitive = class _PlaybookPrimitive extends BasePrimitive {
450
+ static kind = "playbook";
451
+ kind = _PlaybookPrimitive.kind;
452
+ /** What this kind requires beyond the common headers, declared where its
453
+ * contract is: it is read both where a file is checked and where one is
454
+ * scaffolded (FR-004, FR-039). */
455
+ static requires = { triggers: "list" };
456
+ /** The schema its headers are read by: what `headersOf` refuses a file
457
+ * against, and what says the shape of every header this kind takes, required
458
+ * or not (FR-004). */
459
+ static schema = PlaybookHeaders;
460
+ /** When a reader of this charter is to open this kind at all, said
461
+ * where the kind's contract is: the neutral surface lists one
462
+ * line per kind and none of them is written down twice (FR-002). */
463
+ static activatesWhen = "one of its `triggers` matches; the body is a sequence of commands";
464
+ /** A playbook as an author writes one: what a refused header is fixed with. */
465
+ static sample = {
466
+ id: "ship-a-feature",
467
+ description: "Take a finished branch from review to release.",
468
+ triggers: ["ship this", "release this feature"]
469
+ };
470
+ /** What this is found by: what it is for, and the requests its author said
471
+ * bring it up. */
472
+ description() {
473
+ return `${this.headers.description} Use when: ${this.headers.triggers.join("; ")}.`;
474
+ }
475
+ /** One playbook, or every fault its headers have (FR-004). */
476
+ static of(record, body) {
477
+ return new _PlaybookPrimitive(headersOf(_PlaybookPrimitive, PlaybookHeaders, record), body);
478
+ }
479
+ };
480
+
481
+ // src/hexagon/domain/models/charter/primitive/PosturePrimitive.ts
482
+ var PostureHeaders = CommonHeaders.extend({
483
+ allow: goodArray,
484
+ deny: goodArray
485
+ });
486
+ var PosturePrimitive = class _PosturePrimitive extends BasePrimitive {
487
+ static kind = "posture";
488
+ kind = _PosturePrimitive.kind;
489
+ /** What this kind requires beyond the common headers, declared where its
490
+ * contract is: it is read both where a file is checked and where one is
491
+ * scaffolded (FR-004, FR-039). */
492
+ static requires = { allow: "list", deny: "list" };
493
+ /** The schema its headers are read by: what `headersOf` refuses a file
494
+ * against, and what says the shape of every header this kind takes, required
495
+ * or not (FR-004). */
496
+ static schema = PostureHeaders;
497
+ /** When a reader of this charter is to open this kind at all, said
498
+ * where the kind's contract is: the neutral surface lists one
499
+ * line per kind and none of them is written down twice (FR-002). */
500
+ static activatesWhen = "always, wherever the host can be told what to `allow` and what to `deny`";
501
+ /** A posture as an author writes one: what a refused header is fixed with. */
502
+ static sample = {
503
+ id: "no-force-push",
504
+ description: "Never rewrite history someone else may have pulled.",
505
+ allow: ["Bash(git push:*)"],
506
+ deny: ["Bash(git push --force:*)"]
507
+ };
508
+ /** One posture, or every fault its headers have (FR-004). */
509
+ static of(record, body) {
510
+ return new _PosturePrimitive(headersOf(_PosturePrimitive, PostureHeaders, record), body);
511
+ }
512
+ };
513
+
514
+ // src/hexagon/domain/models/charter/primitive/SensorPrimitive.ts
515
+ import { z as z3 } from "zod";
516
+ var SIGNALS = [
517
+ "PreToolUse",
518
+ "PostToolUse",
519
+ "UserPromptSubmit",
520
+ "Notification",
521
+ "Stop",
522
+ "SubagentStop",
523
+ "PreCompact",
524
+ "SessionStart",
525
+ "SessionEnd"
526
+ ];
527
+ var SensorHeaders = CommonHeaders.extend({
528
+ /** What raises it: one of the events every host of this engine speaks in. */
529
+ signal: z3.enum(SIGNALS),
530
+ /** What is run when it does. A sensor that names no command is a sensor
531
+ * nothing can fire, so this is required. */
532
+ run: goodLine
533
+ });
534
+ var SensorPrimitive = class _SensorPrimitive extends BasePrimitive {
535
+ static kind = "sensor";
536
+ kind = _SensorPrimitive.kind;
537
+ /** What this kind requires beyond the common headers, declared where its
538
+ * contract is: it is read both where a file is checked and where one is
539
+ * scaffolded (FR-004, FR-039). */
540
+ static requires = { signal: "line", run: "line" };
541
+ /** The schema its headers are read by: what `headersOf` refuses a file
542
+ * against, and what says the shape of every header this kind takes, required
543
+ * or not (FR-004). */
544
+ static schema = SensorHeaders;
545
+ /** When a reader of this charter is to open this kind at all, said
546
+ * where the kind's contract is: the neutral surface lists one
547
+ * line per kind and none of them is written down twice (FR-002). */
548
+ static activatesWhen = "the `signal` it names is raised, and the harness runs what it says to `run`";
549
+ /** A sensor as an author writes one: what a refused header is fixed with. */
550
+ static sample = {
551
+ id: "test-on-stop",
552
+ description: "Run the tests when the agent stops.",
553
+ signal: "Stop",
554
+ run: "pnpm test"
555
+ };
556
+ /** One sensor, or every fault its headers have (FR-004). */
557
+ static of(record, body) {
558
+ return new _SensorPrimitive(headersOf(_SensorPrimitive, SensorHeaders, record), body);
559
+ }
560
+ };
561
+
562
+ // src/hexagon/domain/models/charter/primitive/SkillPrimitive.ts
563
+ var SkillHeaders = CommonHeaders.extend({
564
+ triggers: goodArray
565
+ });
566
+ var SkillPrimitive = class _SkillPrimitive extends BasePrimitive {
567
+ static kind = "skill";
568
+ kind = _SkillPrimitive.kind;
569
+ /** What this kind requires beyond the common headers, declared where its
570
+ * contract is: it is read both where a file is checked and where one is
571
+ * scaffolded (FR-004, FR-039). */
572
+ static requires = { triggers: "list" };
573
+ /** The schema its headers are read by: what `headersOf` refuses a file
574
+ * against, and what says the shape of every header this kind takes, required
575
+ * or not (FR-004). */
576
+ static schema = SkillHeaders;
577
+ /** When a reader of this charter is to open this kind at all, said
578
+ * where the kind's contract is: the neutral surface lists one
579
+ * line per kind and none of them is written down twice (FR-002). */
580
+ static activatesWhen = "one of its `triggers` matches what is being asked";
581
+ /** A skill as an author writes one: what a refused header is fixed with. */
582
+ static sample = {
583
+ id: "refactoring",
584
+ description: "Change how code is structured without changing what it does.",
585
+ triggers: ["refactor this", "clean this up"]
586
+ };
587
+ /** What this is found by: what it is for, and the requests its author said
588
+ * bring it up. */
589
+ description() {
590
+ return `${this.headers.description} Use when: ${this.headers.triggers.join("; ")}.`;
591
+ }
592
+ /** One skill, or every fault its headers have (FR-004). */
593
+ static of(record, body) {
594
+ return new _SkillPrimitive(headersOf(_SkillPrimitive, SkillHeaders, record), body);
595
+ }
596
+ };
597
+
598
+ // src/hexagon/domain/models/charter/primitive/Primitive.ts
599
+ var PRIMITIVE_CLASSES = [
600
+ GuidePrimitive,
601
+ SensorPrimitive,
602
+ CommandPrimitive,
603
+ SkillPrimitive,
604
+ PlaybookPrimitive,
605
+ AgentPrimitive,
606
+ PosturePrimitive,
607
+ CorpusPrimitive,
608
+ MixinPrimitive
609
+ ];
610
+ var KINDS = Object.freeze(PRIMITIVE_CLASSES.map((one) => one.kind));
611
+ var PRIMITIVE_IDENTITY_SCHEMA = z4.string().regex(/^[a-z0-9-]+:[a-z0-9-]+$/).transform((identity) => identity);
612
+ function identityOf(kindAndId) {
613
+ if (typeof kindAndId !== "string") return `${kindAndId.kind}:${kindAndId.id}`;
614
+ const parsed = PRIMITIVE_IDENTITY_SCHEMA.safeParse(kindAndId);
615
+ if (!parsed.success)
616
+ throw new CharterPrimitiveFault(
617
+ `"${kindAndId}" is not an identity: one is a kind and an id with a colon between them.`,
618
+ 'Write it as <kind>:<id>, as in "guide:no-any".'
619
+ );
620
+ return parsed.data;
621
+ }
622
+ function isKind(value) {
623
+ return typeof value === "string" && KINDS.includes(value);
624
+ }
625
+ var CLASS_OF = new Map(
626
+ PRIMITIVE_CLASSES.map((one) => [one.kind, one])
627
+ );
628
+ var PrimitiveHeader = class {
629
+ constructor(field, shape, required, allowedValues) {
630
+ this.field = field;
631
+ this.shape = shape;
632
+ this.required = required;
633
+ this.allowedValues = allowedValues;
634
+ }
635
+ field;
636
+ shape;
637
+ required;
638
+ allowedValues;
639
+ };
640
+ var PrimitiveRequirements = class {
641
+ constructor(headers, sample) {
642
+ this.headers = headers;
643
+ this.sample = sample;
644
+ }
645
+ headers;
646
+ sample;
647
+ };
648
+ function primitiveHeadersOf(kind) {
649
+ const kindClass = CLASS_OF.get(kind);
650
+ const required = { ...BasePrimitive.requires, ...kindClass.requires };
651
+ return Object.entries(kindClass.schema.shape).filter(([field]) => field !== "id").map(([field, type]) => {
652
+ let heldType = type;
653
+ while (!(heldType instanceof z4.ZodArray) && typeof heldType?.unwrap === "function")
654
+ heldType = heldType.unwrap();
655
+ return new PrimitiveHeader(
656
+ field,
657
+ heldType instanceof z4.ZodArray ? "list" : "line",
658
+ field in required,
659
+ heldType instanceof z4.ZodEnum ? heldType.options : void 0
660
+ );
661
+ });
662
+ }
663
+ function primitiveSampleOf(kind) {
664
+ return Object.entries({ kind, ...CLASS_OF.get(kind).sample }).map(([field, value]) => `${field}: ${formatFrontmatterValue(value)}`).join("\n");
665
+ }
666
+ function primitiveOf(input, parser) {
667
+ let headers;
668
+ let body;
669
+ if (typeof input === "string") {
670
+ const lines3 = input.replace(/^/, "").split(/\r?\n/);
671
+ if (lines3[0]?.trim() !== DELIMITER)
672
+ throwAggregateError([
673
+ new CharterPrimitiveFault(
674
+ `This file opens with no "${DELIMITER}" frontmatter block.`,
675
+ `Add one at the top declaring at least "id" and "description".`
676
+ )
677
+ ]);
678
+ const end = lines3.findIndex((line, index) => index > 0 && line.trim() === DELIMITER);
679
+ if (end === -1)
680
+ throwAggregateError([
681
+ new CharterPrimitiveFault(
682
+ `The frontmatter block is never closed.`,
683
+ `Add a "${DELIMITER}" line after the headers.`
684
+ )
685
+ ]);
686
+ headers = parser.parse(lines3.slice(1, end).join("\n"));
687
+ body = lines3.slice(end + 1).join("\n").trim();
688
+ } else {
689
+ headers = input.headers;
690
+ body = input.body;
691
+ }
692
+ if (!isKind(headers.kind))
693
+ throwAggregateError([
694
+ headers.kind === void 0 ? new CharterPrimitiveFault(
695
+ `This file declares no "kind", so nothing knows what to read it as.`,
696
+ `Add "kind: <one the charter knows>" to the frontmatter.`
697
+ ) : new CharterPrimitiveFault(
698
+ `"${String(headers.kind)}" is not a kind the charter knows.`,
699
+ `Declare a kind the charter knows: ${KINDS.join(", ")}.`
700
+ )
701
+ ]);
702
+ const kind = headers.kind;
703
+ if (typeof input === "string") return CLASS_OF.get(kind).of(headers, body);
704
+ const headerFields = primitiveHeadersOf(kind).map((header) => header.field);
705
+ const unknownHeaderFaults = Object.keys(headers).filter((field) => field !== "kind" && field !== "id" && !headerFields.includes(field)).map(
706
+ (field) => new CharterPrimitiveFault(
707
+ `A ${kind} holds no "${field}".`,
708
+ `Answer one of what a ${kind} holds: ${headerFields.join(", ")}.`
709
+ )
710
+ );
711
+ let primitive;
712
+ let schemaFaults = [];
713
+ try {
714
+ primitive = CLASS_OF.get(kind).of(headers, body);
715
+ } catch (raised) {
716
+ if (!(raised instanceof AggregateError)) throw raised;
717
+ schemaFaults = raised.errors;
718
+ }
719
+ if (primitive === void 0 || unknownHeaderFaults.length > 0) throwAggregateError([...unknownHeaderFaults, ...schemaFaults]);
720
+ return primitive;
721
+ }
722
+
723
+ // src/hexagon/domain/models/charter/CharterRoot.ts
724
+ var REPO_SCOPE = "repo";
725
+ var VENDOR_SCOPE = "vendor";
726
+ var BUILTIN_SCOPE = "builtin";
727
+ var BUILTIN_PATH_PREFIX = "(built into cw)";
728
+ var ScopedPrimitive = class {
729
+ constructor(identity, scope, file, primitive) {
730
+ this.identity = identity;
731
+ this.scope = scope;
732
+ this.file = file;
733
+ this.primitive = primitive;
734
+ }
735
+ identity;
736
+ scope;
737
+ file;
738
+ primitive;
739
+ /** Whether a word someone searched for is anywhere a reader would look for
740
+ * it: the identity, the kind, what it is for, when its kind comes up, its
741
+ * file, or any header it declared — ignoring case, since whoever typed it
742
+ * did not know how it was written (FR-114). */
743
+ mentions(word) {
744
+ const activatesWhen = PRIMITIVE_CLASSES.find((one) => one.kind === this.primitive.kind).activatesWhen;
745
+ const headerValues = Object.values(this.primitive.headers).flatMap(
746
+ (value) => value === void 0 ? [] : Array.isArray(value) ? value : [String(value)]
747
+ );
748
+ const loweredWord = word.toLowerCase();
749
+ return [this.identity, this.primitive.kind, activatesWhen, this.file, ...headerValues].some(
750
+ (one) => one.toLowerCase().includes(loweredWord)
751
+ );
752
+ }
753
+ };
754
+ var CharterRoot = class {
755
+ constructor(primitives, faultsByFiles) {
756
+ this.primitives = primitives;
757
+ this.faultsByFiles = faultsByFiles;
758
+ }
759
+ primitives;
760
+ faultsByFiles;
761
+ /** Everything wrong with this charter, under the file that has to change: the
762
+ * files that could not be read as a primitive at all, and what the ones that
763
+ * were read get wrong together. The two never name one file — a file reading
764
+ * refused holds no primitive to get anything else wrong — so one asking is
765
+ * the whole answer (FR-009). */
766
+ get allFaultsByFiles() {
767
+ return this.faultsByFiles.with(this.compositeFaultsByFiles);
768
+ }
769
+ /** Every mixin, under the id a host names it by, wherever it was authored. */
770
+ get mixins() {
771
+ return new Map(
772
+ this.primitives.filter(({ primitive }) => primitive.kind === "mixin").map((one) => [one.primitive.headers.id, one.primitive])
773
+ );
774
+ }
775
+ /**
776
+ * What one primitive says once its mixins are in it: the body of every mixin
777
+ * it pulls in, in the order it named them, and then its own (FR-006).
778
+ *
779
+ * The one place a mixin is applied at all. Nothing is merged and no header
780
+ * moves — a mixin lends text, so what comes back is text, and the host's own
781
+ * words come last so they read as the point and the lent ones as the setting.
782
+ *
783
+ * Asked of the charter because the charter is what already read the mixins
784
+ * and knows which identity a name means across layers. A mixin nothing
785
+ * answers to is left out rather than raised: `allFaultsByFiles` already names
786
+ * that file, and a build refused for it never reaches here (FR-009).
787
+ */
788
+ bodyOf(one) {
789
+ const mixins = this.mixins;
790
+ return [
791
+ ...(one.primitive.headers.mixins ?? []).map((named) => mixins.get(named)?.body),
792
+ one.primitive.body
793
+ ].filter((body) => body !== void 0 && body.trim() !== "").join("\n\n");
794
+ }
795
+ /**
796
+ * Every primitive under the identity the whole charter names it by (FR-017).
797
+ *
798
+ * Held here for the reason `mixins` is: one identity space covers every
799
+ * layer, so which file declares a name is a question about the whole charter
800
+ * and not about either layer on its own (FR-014).
801
+ *
802
+ * The first claim on an identity is the one in here where two files claim it.
803
+ * A collision is what `compositeFaultsByFiles` is for, and it names both files
804
+ * there; nothing is asked to explain a charter that has one (SC-006).
805
+ */
806
+ get primitiveById() {
807
+ const byIdentity = /* @__PURE__ */ new Map();
808
+ for (const one of this.primitives) if (!byIdentity.has(one.identity)) byIdentity.set(one.identity, one);
809
+ return byIdentity;
810
+ }
811
+ /** Every corpus, under the identity a primitive cites it by, wherever it was
812
+ * authored: `corpus:<id>`. */
813
+ get corpora() {
814
+ return new Set(
815
+ this.primitives.filter(({ primitive }) => primitive.kind === "corpus").map((one) => one.identity)
816
+ );
817
+ }
818
+ /** Every mixin one primitive uses, in the order it named them (FR-014). A
819
+ * name nothing answers to is left out: `compositeFaultsByFiles` already
820
+ * names that file as an error. */
821
+ mixinsOf(one) {
822
+ const byIdentity = this.primitiveById;
823
+ return (one.primitive.headers.mixins ?? []).flatMap((id) => byIdentity.get(`mixin:${id}`) ?? []);
824
+ }
825
+ /** The corpus one primitive cites as its rationale, or nothing where it cites
826
+ * none or cites one this charter does not hold — the second is a warning
827
+ * `compositeFaultsByFiles` already names (FR-005, FR-014). */
828
+ rationaleOf(one) {
829
+ const { rationale } = one.primitive.headers;
830
+ return rationale !== void 0 && this.corpora.has(rationale) ? this.primitiveById.get(rationale) : void 0;
831
+ }
832
+ /** Every primitive using one mixin; nothing for any other kind (FR-014). */
833
+ hostsOf(mixin) {
834
+ if (mixin.primitive.kind !== "mixin") return [];
835
+ return this.primitives.filter((host) => (host.primitive.headers.mixins ?? []).includes(mixin.primitive.headers.id));
836
+ }
837
+ /** Every primitive citing one corpus as its rationale; nothing for any other
838
+ * kind (FR-014). */
839
+ citersOf(corpus) {
840
+ if (corpus.primitive.kind !== "corpus") return [];
841
+ return this.primitives.filter((citer) => citer.primitive.headers.rationale === corpus.identity);
842
+ }
843
+ /** Does what a mixin lends reach as far as the host pulling it in, or the host
844
+ * no further than the mixin? A side that names no files is asking about
845
+ * nothing in particular, and is left alone (FR-006). */
846
+ reaches(host, mixin) {
847
+ const hostGlobs = host.headers.globs ?? [];
848
+ const mixinGlobs = mixin.headers.globs ?? [];
849
+ if (hostGlobs.length === 0 || mixinGlobs.length === 0) return true;
850
+ const coversAll = (outer, inner) => inner.every((narrower) => outer.some((wider) => covers(wider, narrower)));
851
+ return coversAll(mixinGlobs, hostGlobs) || coversAll(hostGlobs, mixinGlobs);
852
+ }
853
+ /**
854
+ * What is wrong with this charter that is wrong with no one file on its own,
855
+ * under the file that has to change (FR-009).
856
+ *
857
+ * Reading refused the files whose own headers do not hold, and said so where
858
+ * it read them; what is left are the questions no single file answers. Every
859
+ * layer is read into one identity space, the way one registry holds one name
860
+ * per package: two files claim one identity; one names a mixin nothing
861
+ * answers to; one pulls in a mixin that speaks about other files than it does
862
+ * — a mixin lends its body at projection time, so what makes the two belong
863
+ * together is the files they both apply to (FR-006, FR-014). One cites a
864
+ * rationale no corpus answers to, which is a warning rather than an error:
865
+ * the rule holds without its reasoning, and the author is told the reasoning
866
+ * is gone (FR-005). A corpus nobody cites and a mixin nobody pulls in are
867
+ * warnings too: nothing breaks, and nothing reads them either (FR-014).
868
+ */
869
+ get compositeFaultsByFiles() {
870
+ const everyPrimitive = this.primitives;
871
+ const mixinsByIdentity = this.mixins;
872
+ const corpusIdentities = this.corpora;
873
+ const fileByIdentity = /* @__PURE__ */ new Map();
874
+ const faultsByFiles = {};
875
+ const addFault = (file, found) => {
876
+ faultsByFiles[file] = [...faultsByFiles[file] ?? [], found];
877
+ };
878
+ for (const one of everyPrimitive) {
879
+ const { file, primitive } = one;
880
+ const { identity } = one;
881
+ const declaredIn = fileByIdentity.get(identity);
882
+ if (declaredIn === void 0) fileByIdentity.set(identity, file);
883
+ else
884
+ addFault(
885
+ file,
886
+ new CharterRootFault(
887
+ `"${identity}" is already declared by ${declaredIn}. One identity names one primitive in a charter, whichever layer it was authored in.`,
888
+ `Give this one an id of its own, or delete it if the other says the same thing. A vendor claiming an id you authored is one to raise with whoever publishes it. An id ${BUILTIN_PATH_PREFIX} claims is the engine's own and nobody can change it, so the one to rename is yours.`
889
+ )
890
+ );
891
+ for (const namedMixin of primitive.headers.mixins ?? []) {
892
+ const mixin = mixinsByIdentity.get(namedMixin);
893
+ if (mixin === void 0) {
894
+ addFault(
895
+ file,
896
+ new CharterRootFault(
897
+ `This pulls in the mixin "${namedMixin}", and this charter holds no mixin of that id.`,
898
+ `Author it, or drop "${namedMixin}" from "mixins". A mixin is named by its id, whichever layer authored it.`
899
+ )
900
+ );
901
+ continue;
902
+ }
903
+ if (!this.reaches(primitive, mixin)) {
904
+ addFault(
905
+ file,
906
+ new CharterRootFault(
907
+ `This pulls in the mixin "${namedMixin}", and neither one's "globs" covers the other's. The mixin's body would be written into a primitive that applies to different files.`,
908
+ `Widen one side's "globs" until it covers the other's, or move the shared text into a corpus and cite it with "rationale".`
909
+ )
910
+ );
911
+ }
912
+ }
913
+ const cited = primitive.headers.rationale;
914
+ if (cited !== void 0 && !corpusIdentities.has(cited)) {
915
+ addFault(
916
+ file,
917
+ new CharterRootFault(
918
+ `This cites "${cited}" as its rationale, and this charter holds no corpus of that identity.`,
919
+ `Author that corpus, correct the reference, or drop "rationale". A corpus is cited as "corpus:<id>", whichever layer authored it.`,
920
+ "warn"
921
+ )
922
+ );
923
+ }
924
+ if (primitive.kind === "corpus" && this.citersOf(one).length === 0)
925
+ addFault(
926
+ file,
927
+ new CharterRootFault(
928
+ `No primitive cites "${identity}" as its rationale, so nothing an agent reads leads to it.`,
929
+ `Cite it from the rules it explains with "rationale: ${identity}", or delete it.`,
930
+ "warn"
931
+ )
932
+ );
933
+ if (primitive.kind === "mixin" && this.hostsOf(one).length === 0)
934
+ addFault(
935
+ file,
936
+ new CharterRootFault(
937
+ `No primitive pulls in the mixin "${primitive.headers.id}", so its body is never written anywhere.`,
938
+ `Name "${primitive.headers.id}" under "mixins" of the primitives it was written for, or delete it.`,
939
+ "warn"
940
+ )
941
+ );
942
+ }
943
+ return new FaultsByFile(faultsByFiles);
944
+ }
945
+ };
946
+ function charterRootOf({
947
+ repo,
948
+ vendor,
949
+ builtin
950
+ }, yamlParser2) {
951
+ const primitives = [];
952
+ const faultsByFiles = {};
953
+ const read = (one, scope) => {
954
+ try {
955
+ const primitive = primitiveOf(one.contents, yamlParser2);
956
+ primitives.push(new ScopedPrimitive(identityOf({ kind: primitive.kind, id: primitive.headers.id }), scope, one.path, primitive));
957
+ } catch (raised) {
958
+ if (!(raised instanceof AggregateError)) throw raised;
959
+ faultsByFiles[one.path] = raised.errors;
960
+ }
961
+ };
962
+ const sorted = (files) => [...files].sort((one, another) => one.path.localeCompare(another.path));
963
+ for (const one of sorted(builtin)) read(one, BUILTIN_SCOPE);
964
+ for (const one of sorted(repo)) read(one, REPO_SCOPE);
965
+ for (const one of sorted(vendor)) read(one, VENDOR_SCOPE);
966
+ return new CharterRoot(primitives, new FaultsByFile(faultsByFiles));
967
+ }
968
+
969
+ // src/hexagon/domain/path.ts
970
+ function folderURL(at) {
971
+ const url = new URL(at);
972
+ if (!url.pathname.endsWith("/"))
973
+ throw new Fault(
974
+ `"${url.href}" names a file, and a folder is what is wanted here.`,
975
+ `Write it with the separator a folder ends in, as in "${url.href}/".`
976
+ );
977
+ return url;
978
+ }
979
+ var WORKSPACE_DIRECTORY = ".cw";
980
+ var CHARTER_DIRECTORY = `${WORKSPACE_DIRECTORY}/charter`;
981
+ var VENDOR_DIRECTORY = `${WORKSPACE_DIRECTORY}/vendor`;
982
+ var TEST_DIRECTORY = `${WORKSPACE_DIRECTORY}/test`;
983
+ var OUT_DIRECTORY = `${WORKSPACE_DIRECTORY}/out`;
984
+ var SETTINGS_FILE = `${WORKSPACE_DIRECTORY}/settings.json`;
985
+ function charterFolderIn(repo) {
986
+ return folderIn(repo, CHARTER_DIRECTORY);
987
+ }
988
+ function outFolderIn(repo) {
989
+ return folderIn(repo, OUT_DIRECTORY);
990
+ }
991
+ var CLAUDE_DIRECTORY = ".claude";
992
+ var CLAUDE_ENTRY_FILE = "CLAUDE.md";
993
+ function agentProviderFolderIn(repo, provider) {
994
+ switch (provider) {
995
+ case "claude":
996
+ return folderIn(repo, CLAUDE_DIRECTORY);
997
+ }
998
+ }
999
+ function testFolderIn(repo) {
1000
+ return folderIn(repo, TEST_DIRECTORY);
1001
+ }
1002
+ function vendorFolderIn(repo) {
1003
+ return folderIn(repo, VENDOR_DIRECTORY);
1004
+ }
1005
+ function folderIn(repo, directory) {
1006
+ return folderURL(new URL(`${directory}/`, repo.href.endsWith("/") ? repo : new URL(`${repo.href}/`)));
1007
+ }
1008
+ function settingsFileIn(repo) {
1009
+ return new URL(SETTINGS_FILE, repo.href.endsWith("/") ? repo : new URL(`${repo.href}/`));
1010
+ }
1011
+
1012
+ // src/driver/cli/commands/AddCommand.ts
1013
+ import prompts from "prompts";
1014
+
1015
+ // src/driver/cli/commands/Command.ts
1016
+ import { pathToFileURL } from "url";
1017
+ var EXIT_OK = 0;
1018
+ var EXIT_FAILURE = 1;
1019
+ var EXIT_USAGE = 2;
1020
+
1021
+ // src/driver/cli/commands/helper.ts
1022
+ import { spawn } from "child_process";
1023
+ function parseKVParams(typed, paramName) {
1024
+ const params = /* @__PURE__ */ Object.create(null);
1025
+ for (const one of typed) {
1026
+ const at = one.indexOf("=");
1027
+ if (at <= 0)
1028
+ throw new Fault(
1029
+ `"--${paramName} ${one}" names nothing to put a value under.`,
1030
+ `Type each one as --${paramName} name=value, once per name.`
1031
+ );
1032
+ (params[one.slice(0, at)] ??= []).push(one.slice(at + 1));
1033
+ }
1034
+ return params;
1035
+ }
1036
+ function toText({ data: { files } }) {
1037
+ return Object.keys(files).sort().map((file) => [file, ...(files[file] ?? []).flatMap(lines), ""].join("\n")).join("");
1038
+ }
1039
+ function lines({ data: { severity, message, fix } }) {
1040
+ return [` ${severity}: ${message}`, ...fix ? [` ${fix}`] : []];
1041
+ }
1042
+ async function editInEditor(cwd, file) {
1043
+ const editorCommand = process.env.VISUAL || process.env.EDITOR;
1044
+ if (!editorCommand)
1045
+ throw new Fault("Neither $VISUAL nor $EDITOR is set, so there is no editor to open it in.", 'Set one, as in "export EDITOR=vim".');
1046
+ const exitCode = await new Promise(
1047
+ (resolve, reject) => spawn("sh", ["-c", `${editorCommand} "$1"`, editorCommand, `${cwd}/${file}`], { stdio: "inherit" }).once("error", reject).once("exit", resolve)
1048
+ );
1049
+ return exitCode === 0 ? void 0 : `${editorCommand} exited with ${exitCode} on ${file}.
1050
+ `;
1051
+ }
1052
+
1053
+ // src/driver/cli/commands/AddCommand.ts
1054
+ var OPTIONS = {
1055
+ kind: {
1056
+ type: "string",
1057
+ describe: "Which kind of primitive to write; a word that is none is answered with every kind there is"
1058
+ },
1059
+ id: {
1060
+ type: "string",
1061
+ describe: 'What it is named by across the whole charter: "no-any"'
1062
+ },
1063
+ header: {
1064
+ type: "string",
1065
+ isArray: true,
1066
+ describe: 'One header the kind requires, as name=value; typed once per header, and what "cw kinds <kind>" names'
1067
+ }
1068
+ };
1069
+ var AddCommand = class {
1070
+ name = "add <kind> <id>";
1071
+ summary = "Write a new primitive of one kind, holding what that kind requires";
1072
+ options = OPTIONS;
1073
+ async run({ charterAuthoringApp: charterAuthoringApp2 }, { kind, id, header }) {
1074
+ const taken = (await charterAuthoringApp2.listPrimitiveRequirements(kind ?? "")).data.headers.map((one) => one.data);
1075
+ const lists = new Set(taken.filter((one) => one.shape === "list").map((one) => one.field));
1076
+ const answers = header.length > 0 ? Object.fromEntries(
1077
+ Object.entries(parseKVParams(header, "header")).map(([field, values]) => {
1078
+ if (lists.has(field)) return [field, values];
1079
+ if (values.length > 1)
1080
+ throw new Fault(
1081
+ `"${field}" holds one line, and was typed ${values.length} times.`,
1082
+ `Type "--header ${field}=..." once.`
1083
+ );
1084
+ return [field, values[0]];
1085
+ })
1086
+ ) : await this.#answered(
1087
+ taken.filter((one) => one.required),
1088
+ kind ?? ""
1089
+ );
1090
+ const scopedPrimitiveDTO2 = await charterAuthoringApp2.add(kind ?? "", id ?? "", answers);
1091
+ if (scopedPrimitiveDTO2.type === "Faults")
1092
+ return {
1093
+ code: EXIT_FAILURE,
1094
+ problem: scopedPrimitiveDTO2.data.faults.map(({ data: { message, fix } }) => `${message}
1095
+ ${fix}
1096
+ `).join("")
1097
+ };
1098
+ return {
1099
+ code: EXIT_OK,
1100
+ result: [`Wrote ${scopedPrimitiveDTO2.data.file}.`, 'Write what it has to say in the body, then run "cw build".', ""].join("\n")
1101
+ };
1102
+ }
1103
+ /**
1104
+ * Every header this kind requires, answered by whoever is at the terminal.
1105
+ *
1106
+ * One question per header, in the order the engine named them, each asked as
1107
+ * what it holds: one line of text, or a list of names typed with commas between
1108
+ * them.
1109
+ *
1110
+ * Nobody at the terminal is refused rather than answered with blanks: every
1111
+ * header here is one its kind requires, so a file written without them is a
1112
+ * file the next build refuses, and writing it would leave the author to find
1113
+ * that out later (FR-035 is setup's, and this asks for what nothing can default).
1114
+ */
1115
+ async #answered(required, kind) {
1116
+ if (!process.stdin.isTTY)
1117
+ throw new Fault(
1118
+ `A ${kind} requires ${required.map((one) => `"${one.field}"`).join(", ")}, and nobody is at the terminal to answer.`,
1119
+ `Run "cw add" where you can answer, or author the file under the charter yourself.`
1120
+ );
1121
+ const answers = await prompts(
1122
+ required.map(
1123
+ ({ field, shape }) => shape === "line" ? { type: "text", name: field, message: `${field}?` } : {
1124
+ type: "list",
1125
+ name: field,
1126
+ message: `${field}?`,
1127
+ // `prompts` hands an answer left empty back as [""].
1128
+ format: (entries) => entries.map((one) => one.trim()).filter((one) => one !== "")
1129
+ }
1130
+ )
1131
+ );
1132
+ if (required.some((one) => answers[one.field] === void 0))
1133
+ throw new Fault("This was stopped before it had its answers, so nothing was written.", 'Run "cw add" again.');
1134
+ return answers;
1135
+ }
1136
+ };
1137
+
1138
+ // src/driver/cli/commands/BuildCommand.ts
1139
+ var BUILDING = {
1140
+ preview: {
1141
+ type: "boolean",
1142
+ describe: "Say what a build would do to each file, and write nothing",
1143
+ default: false
1144
+ }
1145
+ };
1146
+ var BuildCommand = class {
1147
+ name = "build";
1148
+ summary = "Compile the charter into the files each agent reads";
1149
+ options = BUILDING;
1150
+ async run({ charterAuthoringApp: charterAuthoringApp2 }, { preview }) {
1151
+ if (preview) {
1152
+ const planSummaryDTO3 = await charterAuthoringApp2.preview();
1153
+ if (planSummaryDTO3.type === "FaultsByFile") {
1154
+ const noOfFiles = Object.keys(planSummaryDTO3.data.files).length;
1155
+ return {
1156
+ code: EXIT_FAILURE,
1157
+ problem: `Nothing was previewed: ${noOfFiles} file${noOfFiles === 1 ? " has" : "s have"} errors.
1158
+ Run "cw doctor" to see what is wrong with them.
1159
+ `
1160
+ };
1161
+ }
1162
+ const { added: added2, edited: edited2, deleted: deleted2, unchanged } = planSummaryDTO3.data;
1163
+ const changes = [
1164
+ ...added2.map((path) => ["create", path]),
1165
+ ...edited2.map((path) => ["update", path]),
1166
+ ...deleted2.map((path) => ["delete", path]),
1167
+ ...unchanged.map((path) => ["unchanged", path])
1168
+ ];
1169
+ return {
1170
+ code: EXIT_OK,
1171
+ result: [
1172
+ ...changes.map(([change, path]) => ` ${change.padEnd("unchanged".length)} ${path}`),
1173
+ `${changes.length} file${changes.length === 1 ? "" : "s"}: ${added2.length} create, ${edited2.length} update, ${deleted2.length} delete, ${unchanged.length} unchanged.`,
1174
+ ""
1175
+ ].join("\n")
1176
+ };
1177
+ }
1178
+ const planSummaryDTO2 = await charterAuthoringApp2.build();
1179
+ if (planSummaryDTO2.type === "FaultsByFile") {
1180
+ const noOfFiles = Object.keys(planSummaryDTO2.data.files).length;
1181
+ return {
1182
+ code: EXIT_FAILURE,
1183
+ problem: `Nothing was built: ${noOfFiles} file${noOfFiles === 1 ? " has" : "s have"} errors.
1184
+ Run "cw doctor" to see what is wrong with them.
1185
+ `
1186
+ };
1187
+ }
1188
+ const { added, edited, deleted } = planSummaryDTO2.data;
1189
+ const wrote = added.length + edited.length;
1190
+ return {
1191
+ code: EXIT_OK,
1192
+ result: [
1193
+ `Built ${wrote} file${wrote === 1 ? "" : "s"}: ${added.length} added, ${edited.length} changed.`,
1194
+ ...deleted.map((path) => ` deleted ${path}, whose primitive is gone`),
1195
+ ""
1196
+ ].join("\n")
1197
+ };
1198
+ }
1199
+ };
1200
+
1201
+ // src/driver/cli/commands/DoctorCommand.ts
1202
+ var DoctorCommand = class {
1203
+ name = "doctor";
1204
+ summary = "Check this repository: agents, charter, vendors, and what is built";
1205
+ async run({ version, charterAuthoringApp: charterAuthoringApp2 }, _options) {
1206
+ const {
1207
+ agents,
1208
+ errorCount: errors,
1209
+ warnCount: warnings,
1210
+ pendingCount: pending,
1211
+ faultsByFile: faultsByFile2,
1212
+ driftedVendors: driftedVendors2,
1213
+ problemCount
1214
+ } = (await charterAuthoringApp2.doctor()).data;
1215
+ return {
1216
+ code: problemCount === 0 ? EXIT_OK : EXIT_FAILURE,
1217
+ result: [
1218
+ // Which cw wrote the report, so one pasted into a bug report names
1219
+ // the release it came from (FR-132).
1220
+ `cw ${version}`,
1221
+ // A repository that compiles for none is set up and building: the
1222
+ // neutral surface is compiled for everybody (FR-019).
1223
+ agents.length === 0 ? "Agents: none chosen, so only the neutral surface is compiled." : `Agents: ${agents.join(", ")}.`,
1224
+ errors > 0 ? `Charter: ${errors} error${errors === 1 ? "" : "s"}.` : warnings === 0 ? "Charter: holds." : `Charter: holds, with ${warnings} warning${warnings === 1 ? "" : "s"}.`,
1225
+ driftedVendors2.length === 0 ? "Vendors: none edited here." : `Vendors: edited here: ${driftedVendors2.join(", ")}. Run "git checkout" under .cw/vendor/ to undo, or commit what you meant.`,
1226
+ pending === null ? "Built: not known, since the charter does not hold." : pending === 0 ? "Built: up to date." : `Built: ${pending} file${pending === 1 ? "" : "s"} out of date. Run "cw build".`,
1227
+ // Every fault under the file that has to change, below the four lines
1228
+ // rather than inside them: a line says how many, and this says which
1229
+ // (FR-009).
1230
+ ...Object.keys(faultsByFile2.data.files).length === 0 ? [] : ["", toText(faultsByFile2)],
1231
+ problemCount === 0 ? "Nothing to fix." : `${problemCount} thing${problemCount === 1 ? "" : "s"} to fix.`,
1232
+ ""
1233
+ ].join("\n")
1234
+ };
1235
+ }
1236
+ };
1237
+
1238
+ // src/driver/cli/commands/EditCommand.ts
1239
+ var OPTIONS2 = {
1240
+ identity: {
1241
+ type: "string",
1242
+ describe: 'What the primitive is named by across the whole charter: "guide:no-any"'
1243
+ }
1244
+ };
1245
+ var EditCommand = class {
1246
+ name = "edit <identity>";
1247
+ summary = "Open the file of one primitive in your editor";
1248
+ options = OPTIONS2;
1249
+ async run({ cwd, charterAuthoringApp: charterAuthoringApp2 }, { identity }) {
1250
+ const { scopedPrimitive: scopedPrimitive2 } = (await charterAuthoringApp2.open(identity ?? "")).data;
1251
+ if (scopedPrimitive2.data.scope === BUILTIN_SCOPE)
1252
+ throw new Fault(
1253
+ `${scopedPrimitive2.data.identity} is built into cw, and there is no file of it in this repository to edit.`,
1254
+ `To differ from it, run "cw add" for a primitive of your own under an identity of its own.`
1255
+ );
1256
+ const problem = await editInEditor(cwd, scopedPrimitive2.data.file);
1257
+ if (problem !== void 0) return { code: EXIT_FAILURE, problem };
1258
+ return {
1259
+ code: EXIT_OK,
1260
+ result: [`Closed ${scopedPrimitive2.data.file}.`, 'Run "cw doctor" to see that the charter still holds, then "cw build".', ""].join("\n")
1261
+ };
1262
+ }
1263
+ };
1264
+
1265
+ // src/driver/cli/commands/ExplainCommand.ts
1266
+ var OPTIONS3 = {
1267
+ identity: {
1268
+ type: "string",
1269
+ describe: 'What the primitive is named by across the whole charter: "guide:no-any"'
1270
+ }
1271
+ };
1272
+ var ExplainCommand = class {
1273
+ name = "explain <identity>";
1274
+ summary = "Say which file declares an identity, and which layer it came from";
1275
+ options = OPTIONS3;
1276
+ async run({ charterAuthoringApp: charterAuthoringApp2 }, { identity }) {
1277
+ const explanationOutcomeDTO2 = await charterAuthoringApp2.explain(identity ?? "");
1278
+ if (explanationOutcomeDTO2.type === "FaultsByFile") {
1279
+ const noOfFiles = Object.keys(explanationOutcomeDTO2.data.files).length;
1280
+ return {
1281
+ code: EXIT_FAILURE,
1282
+ problem: `Nothing was explained: ${noOfFiles} file${noOfFiles === 1 ? " has" : "s have"} errors.
1283
+ Run "cw doctor" to see what is wrong with them.
1284
+ `
1285
+ };
1286
+ }
1287
+ const { scopedPrimitive: scopedPrimitive2, activatesWhen, useMixins, rationale, hosts, citers, testCasesByFile: testCasesByFile2 } = explanationOutcomeDTO2.data;
1288
+ const citedRationale = scopedPrimitive2.data.headers.rationale;
1289
+ const declaredHeaders = Object.entries(scopedPrimitive2.data.headers).filter(([name]) => name !== "id" && name !== "description");
1290
+ return {
1291
+ code: EXIT_OK,
1292
+ result: [
1293
+ `${scopedPrimitive2.data.identity} ${scopedPrimitive2.data.description}`,
1294
+ ` ${scopedPrimitive2.data.file}`,
1295
+ ` ${layer(scopedPrimitive2.data.scope)}`,
1296
+ ` comes up when ${activatesWhen}`,
1297
+ ...declaredHeaders.map(([name, value]) => ` declares ${name}: ${typeof value === "string" ? value : value.join(", ")}`),
1298
+ ...useMixins.map(({ data }) => ` uses mixin ${data.identity}`),
1299
+ ...rationale !== void 0 ? [` rationale ${rationale.data.identity}`] : citedRationale !== void 0 ? [` rationale ${String(citedRationale)} (does not resolve)`] : [],
1300
+ ...hosts.map(({ data }) => ` mixin of ${data.identity}`),
1301
+ ...citers.map(({ data }) => ` rationale of ${data.identity}`),
1302
+ ...Object.entries(testCasesByFile2.data).flatMap(
1303
+ ([file, situations]) => situations.map((situation) => ` pinned down by ${file}: ${situation}`)
1304
+ ),
1305
+ ""
1306
+ ].join("\n")
1307
+ };
1308
+ }
1309
+ };
1310
+ function layer(scope) {
1311
+ const saidByScope = {
1312
+ [REPO_SCOPE]: "authored in this repository",
1313
+ [VENDOR_SCOPE]: "installed from a vendor",
1314
+ [BUILTIN_SCOPE]: "built into cw, and not authored in this repository"
1315
+ };
1316
+ return saidByScope[scope];
1317
+ }
1318
+
1319
+ // src/driver/cli/commands/InitCommand.ts
1320
+ import prompts2 from "prompts";
1321
+ var OPTIONS4 = {
1322
+ agent: {
1323
+ type: "string",
1324
+ describe: `Which agent this charter compiles for: ${AGENT_PROVIDERS.join(", ")}`
1325
+ }
1326
+ };
1327
+ var InitCommand = class {
1328
+ name = "init";
1329
+ summary = "Set this repository up under a charter";
1330
+ options = OPTIONS4;
1331
+ async run({ charterAuthoringApp: charterAuthoringApp2 }, { agent }) {
1332
+ const agents = await whichAgents(charterAuthoringApp2, agent);
1333
+ await charterAuthoringApp2.init({ agents });
1334
+ return {
1335
+ code: EXIT_OK,
1336
+ result: [
1337
+ "Set up this repository under a charter.",
1338
+ `Compiling for: ${agents.join(", ")}.`,
1339
+ 'Author your first rule under .cw/charter/guide/, then run "cw build".',
1340
+ ""
1341
+ ].join("\n")
1342
+ };
1343
+ }
1344
+ };
1345
+ async function whichAgents(charterAuthoringApp2, typed) {
1346
+ if (typed !== void 0) {
1347
+ if (!isAgentProvider(typed))
1348
+ throw new Fault(
1349
+ `This engine compiles for no agent called "${typed}".`,
1350
+ `Name any of: ${AGENT_PROVIDERS.join(", ")}.`
1351
+ );
1352
+ return [typed];
1353
+ }
1354
+ const agentsInSettings = (await charterAuthoringApp2.settings()).data.agents.filter(isAgentProvider);
1355
+ if (!process.stdin.isTTY) {
1356
+ if (agentsInSettings.length === 0)
1357
+ throw new Fault(
1358
+ "Nobody is at the terminal to choose an agent, and this repository has none chosen yet.",
1359
+ `Run "cw init --agent <name>", naming one of: ${AGENT_PROVIDERS.join(", ")}.`
1360
+ );
1361
+ return agentsInSettings;
1362
+ }
1363
+ const { chosen } = await prompts2({
1364
+ type: "multiselect",
1365
+ name: "chosen",
1366
+ message: "Which agents does this charter compile for?",
1367
+ // What the settings already name arrives ticked, so a return keeps what this
1368
+ // repository is already running under and a second run is rechoosing rather
1369
+ // than starting over (FR-038). Nothing named yet — a repository being set up
1370
+ // for the first time — arrives with nothing ticked.
1371
+ choices: AGENT_PROVIDERS.map((one) => ({ title: one, value: one, selected: agentsInSettings.includes(one) })),
1372
+ // Ticking none of them is not an answer: a charter compiles for at least one
1373
+ // agent, so the prompt holds the submit until one is ticked (FR-033).
1374
+ min: 1
1375
+ });
1376
+ if (chosen === void 0)
1377
+ throw new Fault("Setup was stopped before it had its answers.", 'Run "cw init" again.');
1378
+ return chosen;
1379
+ }
1380
+
1381
+ // src/driver/cli/commands/KindsCommand.ts
1382
+ var OPTIONS5 = {
1383
+ kind: {
1384
+ type: "string",
1385
+ describe: "Which kind to be told what it requires; left off, every kind and when each comes up"
1386
+ }
1387
+ };
1388
+ var KindsCommand = class {
1389
+ name = "kinds [kind]";
1390
+ summary = "Say which kinds a charter is written in, and what one of them requires";
1391
+ options = OPTIONS5;
1392
+ async run({ charterAuthoringApp: charterAuthoringApp2 }, { kind }) {
1393
+ if (kind === void 0) {
1394
+ const { data: data2 } = await charterAuthoringApp2.kinds();
1395
+ return {
1396
+ code: EXIT_OK,
1397
+ result: `${Object.entries(data2).map(([named, activatesWhen]) => `${named} ${activatesWhen}`).join("\n")}
1398
+ `
1399
+ };
1400
+ }
1401
+ const { data } = await charterAuthoringApp2.listPrimitiveRequirements(kind);
1402
+ const required = data.headers.filter((one) => one.data.required);
1403
+ const widestField = Math.max(...required.map((one) => one.data.field.length));
1404
+ return {
1405
+ code: EXIT_OK,
1406
+ result: [
1407
+ `A ${kind} requires:`,
1408
+ // A header read from a closed set is one of those values or refused, so
1409
+ // the values are part of what it requires (FR-118).
1410
+ ...required.map(
1411
+ ({ data: { field, shape, allowedValues } }) => ` ${field.padEnd(widestField)} ${shape}${allowedValues === void 0 ? "" : `, one of ${allowedValues.join(", ")}`}`
1412
+ ),
1413
+ "",
1414
+ `Write one the way this ${kind} is written:`,
1415
+ data.sample,
1416
+ ""
1417
+ ].join("\n")
1418
+ };
1419
+ }
1420
+ };
1421
+
1422
+ // src/driver/cli/commands/ListCommand.ts
1423
+ var LISTING = {
1424
+ kind: { type: "string", describe: "Only the primitives of this kind" },
1425
+ min: {
1426
+ type: "boolean",
1427
+ describe: "What an agent surveys by: identity and description, nothing else",
1428
+ default: false
1429
+ }
1430
+ };
1431
+ var ListCommand = class {
1432
+ name = "list";
1433
+ summary = "Say what the charter holds";
1434
+ options = LISTING;
1435
+ async run({ charterAuthoringApp: charterAuthoringApp2 }, { kind, min }) {
1436
+ const catalogueDTO2 = await charterAuthoringApp2.list(kind);
1437
+ if (catalogueDTO2.type === "FaultsByFile") {
1438
+ const noOfFiles = Object.keys(catalogueDTO2.data.files).length;
1439
+ return {
1440
+ code: EXIT_FAILURE,
1441
+ problem: `Nothing was listed: ${noOfFiles} file${noOfFiles === 1 ? " has" : "s have"} errors.
1442
+ Run "cw doctor" to see what is wrong with them.
1443
+ `
1444
+ };
1445
+ }
1446
+ const { entries } = catalogueDTO2.data;
1447
+ if (entries.length === 0)
1448
+ return {
1449
+ code: EXIT_OK,
1450
+ result: kind === void 0 ? "This charter holds nothing yet.\n" : `This charter holds no ${kind}.
1451
+ `
1452
+ };
1453
+ return {
1454
+ code: EXIT_OK,
1455
+ result: min ? `${entries.map(surveyed).join("\n")}
1456
+ ` : `${entries.map(inFull).join("\n")}
1457
+ `
1458
+ };
1459
+ }
1460
+ };
1461
+ function surveyed({ data: { identity, description } }) {
1462
+ return `${identity} ${description}`;
1463
+ }
1464
+ function inFull(entry) {
1465
+ const { file, tags, globs, rationale, mixins } = entry.data;
1466
+ const declared = [
1467
+ ...globs === void 0 ? [] : [`globs: ${globs.join(", ")}`],
1468
+ ...tags === void 0 ? [] : [`tags: ${tags.join(", ")}`],
1469
+ ...mixins === void 0 ? [] : [`mixins: ${mixins.join(", ")}`],
1470
+ ...rationale === void 0 ? [] : [`rationale: ${rationale}`]
1471
+ ];
1472
+ return [surveyed(entry), ` ${file}`, ...declared.length === 0 ? [] : [` ${declared.join(", ")}`]].join("\n");
1473
+ }
1474
+
1475
+ // src/driver/portal/server.ts
1476
+ import { randomBytes } from "crypto";
1477
+ import { access } from "fs/promises";
1478
+ import { fileURLToPath } from "url";
1479
+ import { Hono } from "hono";
1480
+ import { bearerAuth } from "hono/bearer-auth";
1481
+ import { serve } from "@hono/node-server";
1482
+ import { serveStatic } from "@hono/node-server/serve-static";
1483
+
1484
+ // src/driver/portal/routes.ts
1485
+ import { createRoute, OpenAPIHono } from "@hono/zod-openapi";
1486
+ import { HTTPException } from "hono/http-exception";
1487
+ import { z as z10 } from "zod";
1488
+
1489
+ // src/hexagon/port/driver/dtos/dto.ts
1490
+ import { z as z5 } from "zod";
1491
+ var strings = z5.array(z5.string()).readonly();
1492
+ var dto = (type, data) => z5.object({ type: z5.literal(type), data });
1493
+ function byType(...schemas) {
1494
+ return Object.fromEntries(schemas.map((one) => [one.shape.type.value, one]));
1495
+ }
1496
+
1497
+ // src/hexagon/port/driver/dtos/data.ts
1498
+ import { z as z6 } from "zod";
1499
+ var fault = dto(
1500
+ "Fault",
1501
+ z6.object({
1502
+ message: z6.string(),
1503
+ fix: z6.string(),
1504
+ severity: z6.enum(["error", "warn"])
1505
+ })
1506
+ );
1507
+ var faultsByFile = dto(
1508
+ "FaultsByFile",
1509
+ z6.object({
1510
+ files: z6.record(z6.string(), z6.array(fault).readonly())
1511
+ })
1512
+ );
1513
+ var faults = dto(
1514
+ "Faults",
1515
+ z6.object({
1516
+ faults: z6.array(fault).readonly()
1517
+ })
1518
+ );
1519
+ var primitiveKinds = dto("PrimitiveKinds", z6.record(z6.string(), z6.string()));
1520
+ var catalogueEntry = dto(
1521
+ "CatalogueEntry",
1522
+ z6.object({
1523
+ identity: z6.string(),
1524
+ kind: z6.string(),
1525
+ id: z6.string(),
1526
+ description: z6.string(),
1527
+ file: z6.string(),
1528
+ tags: strings.optional(),
1529
+ globs: strings.optional(),
1530
+ rationale: z6.string().optional(),
1531
+ mixins: strings.optional()
1532
+ })
1533
+ );
1534
+ var catalogue = dto(
1535
+ "Catalogue",
1536
+ z6.object({
1537
+ entries: z6.array(catalogueEntry).readonly()
1538
+ })
1539
+ );
1540
+ var scopedPrimitive = dto(
1541
+ "ScopedPrimitive",
1542
+ z6.object({
1543
+ identity: z6.string(),
1544
+ kind: z6.string(),
1545
+ description: z6.string(),
1546
+ file: z6.string(),
1547
+ scope: z6.enum(["repo", "vendor", "builtin"]),
1548
+ headers: z6.record(z6.string(), z6.union([z6.string(), strings]))
1549
+ })
1550
+ );
1551
+ var scopedPrimitives = dto(
1552
+ "ScopedPrimitives",
1553
+ z6.object({
1554
+ primitives: z6.array(scopedPrimitive).readonly()
1555
+ })
1556
+ );
1557
+ var primitiveSnapshot = dto(
1558
+ "PrimitiveSnapshot",
1559
+ z6.object({
1560
+ scopedPrimitive,
1561
+ body: z6.string(),
1562
+ revision: z6.string()
1563
+ })
1564
+ );
1565
+ var planSummary = dto(
1566
+ "PlanSummary",
1567
+ z6.object({
1568
+ added: strings,
1569
+ edited: strings,
1570
+ deleted: strings,
1571
+ unchanged: strings
1572
+ })
1573
+ );
1574
+ var primitiveHeader = dto(
1575
+ "PrimitiveHeader",
1576
+ z6.object({
1577
+ field: z6.string(),
1578
+ shape: z6.enum(["line", "list"]),
1579
+ required: z6.boolean(),
1580
+ allowedValues: strings.optional()
1581
+ })
1582
+ );
1583
+ var primitiveRequirements = dto(
1584
+ "PrimitiveRequirements",
1585
+ z6.object({
1586
+ headers: z6.array(primitiveHeader).readonly(),
1587
+ sample: z6.string()
1588
+ })
1589
+ );
1590
+ var testCasesByFile = dto("TestCasesByFile", z6.record(z6.string(), strings));
1591
+ var testCaseReport = dto(
1592
+ "TestCaseReport",
1593
+ z6.object({
1594
+ suiteName: z6.string(),
1595
+ situation: z6.string(),
1596
+ passed: z6.boolean(),
1597
+ unmet: fault.optional()
1598
+ })
1599
+ );
1600
+ var testRunReport = dto(
1601
+ "TestRunReport",
1602
+ z6.object({
1603
+ testCaseReports: z6.array(testCaseReport).readonly()
1604
+ })
1605
+ );
1606
+ var testCase = dto(
1607
+ "TestCase",
1608
+ z6.object({
1609
+ situation: z6.string(),
1610
+ expectation: z6.string(),
1611
+ identity: z6.string().optional()
1612
+ })
1613
+ );
1614
+ var testSuite = dto(
1615
+ "TestSuite",
1616
+ z6.object({
1617
+ name: z6.string(),
1618
+ text: z6.string().optional(),
1619
+ description: z6.string().optional(),
1620
+ cases: z6.array(testCase).readonly(),
1621
+ fault: fault.optional()
1622
+ })
1623
+ );
1624
+ var testSuites = dto(
1625
+ "TestSuites",
1626
+ z6.object({
1627
+ testSuites: z6.array(testSuite).readonly()
1628
+ })
1629
+ );
1630
+ var workspaceSettings = dto(
1631
+ "WorkspaceSettings",
1632
+ z6.object({
1633
+ agents: strings
1634
+ })
1635
+ );
1636
+ var DataDTOs = byType(
1637
+ catalogue,
1638
+ catalogueEntry,
1639
+ fault,
1640
+ faults,
1641
+ faultsByFile,
1642
+ planSummary,
1643
+ primitiveHeader,
1644
+ primitiveKinds,
1645
+ primitiveRequirements,
1646
+ primitiveSnapshot,
1647
+ scopedPrimitive,
1648
+ scopedPrimitives,
1649
+ testCase,
1650
+ testCaseReport,
1651
+ testCasesByFile,
1652
+ testRunReport,
1653
+ testSuite,
1654
+ testSuites,
1655
+ workspaceSettings
1656
+ );
1657
+
1658
+ // src/hexagon/port/driver/dtos/outcome.ts
1659
+ import { z as z7 } from "zod";
1660
+ var doctorOutcome = dto(
1661
+ "DoctorOutcome",
1662
+ z7.object({
1663
+ agents: strings,
1664
+ errorCount: z7.number(),
1665
+ warnCount: z7.number(),
1666
+ pendingCount: z7.number().nullable(),
1667
+ faultsByFile: DataDTOs.FaultsByFile,
1668
+ driftedVendors: strings,
1669
+ problemCount: z7.number()
1670
+ })
1671
+ );
1672
+ var explanationOutcome = dto(
1673
+ "ExplanationOutcome",
1674
+ z7.object({
1675
+ scopedPrimitive: DataDTOs.ScopedPrimitive,
1676
+ activatesWhen: z7.string(),
1677
+ useMixins: z7.array(DataDTOs.ScopedPrimitive).readonly(),
1678
+ rationale: DataDTOs.ScopedPrimitive.optional(),
1679
+ hosts: z7.array(DataDTOs.ScopedPrimitive).readonly(),
1680
+ citers: z7.array(DataDTOs.ScopedPrimitive).readonly(),
1681
+ testCasesByFile: DataDTOs.TestCasesByFile
1682
+ })
1683
+ );
1684
+ var OutcomeDTOs = byType(doctorOutcome, explanationOutcome);
1685
+
1686
+ // src/hexagon/domain/models/test/TestSuite.ts
1687
+ import { z as z9 } from "zod";
1688
+
1689
+ // src/hexagon/domain/models/test/TestCase.ts
1690
+ import { z as z8 } from "zod";
1691
+ var TEST_CASE_SCHEMA = z8.union([
1692
+ z8.strictObject({
1693
+ do: z8.strictObject({ touchFile: z8.string().min(1) }),
1694
+ expect: z8.union([
1695
+ z8.strictObject({ activate: PRIMITIVE_IDENTITY_SCHEMA }),
1696
+ z8.strictObject({ allow: z8.boolean() })
1697
+ ])
1698
+ }),
1699
+ z8.strictObject({
1700
+ when: z8.enum(SIGNALS),
1701
+ expect: z8.strictObject({ run: PRIMITIVE_IDENTITY_SCHEMA })
1702
+ })
1703
+ ]);
1704
+ var TestCase = class {
1705
+ constructor(written) {
1706
+ this.written = written;
1707
+ }
1708
+ written;
1709
+ /** The situation it puts, said as a line, and what it expects of it: what a
1710
+ * run reports it under, and what an explanation names it by (FR-048,
1711
+ * FR-014). */
1712
+ describe() {
1713
+ return `${this.situation} ${this.expectation}`;
1714
+ }
1715
+ /** The situation it puts: the file it touches, or the event it raises. */
1716
+ get situation() {
1717
+ return "when" in this.written ? `firing event ${this.written.when}` : `touching ${this.written.do.touchFile}`;
1718
+ }
1719
+ /** What it expects the charter to make of its situation. */
1720
+ get expectation() {
1721
+ const written = this.written;
1722
+ if ("when" in written) return `runs ${written.expect.run}`;
1723
+ if ("activate" in written.expect) return `activates ${written.expect.activate}`;
1724
+ return `is ${written.expect.allow ? "allowed" : "denied"}`;
1725
+ }
1726
+ /**
1727
+ * Which primitive this case expects to be activated: the sensor a raised event
1728
+ * is expected to run, or the guide a touched file is expected to bring up
1729
+ * (FR-014).
1730
+ *
1731
+ * Nothing for the third shape. That one expects a touched file to be allowed
1732
+ * or denied, which every posture in the charter answers between them, so it
1733
+ * activates no one primitive by name.
1734
+ */
1735
+ get activatedIdentity() {
1736
+ if ("when" in this.written) return this.written.expect.run;
1737
+ if ("activate" in this.written.expect) return this.written.expect.activate;
1738
+ return void 0;
1739
+ }
1740
+ };
1741
+
1742
+ // src/hexagon/domain/models/test/TestSuite.ts
1743
+ var TEST_SUITE_SCHEMA = z9.strictObject({
1744
+ description: z9.string().min(1).optional(),
1745
+ cases: z9.array(TEST_CASE_SCHEMA.transform((written) => new TestCase(written))).min(1)
1746
+ });
1747
+ var TestSuite = class {
1748
+ constructor(description, cases, body) {
1749
+ this.description = description;
1750
+ this.cases = cases;
1751
+ this.body = body;
1752
+ }
1753
+ description;
1754
+ cases;
1755
+ body;
1756
+ };
1757
+ var SAMPLE_CASES = [
1758
+ { do: { touchFile: "src/one.ts" }, expect: { activate: "guide:no-any" } },
1759
+ { do: { touchFile: ".env" }, expect: { allow: false } },
1760
+ { when: "PreToolUse", expect: { run: "sensor:no-secrets" } }
1761
+ ];
1762
+ var SAMPLE = JSON.stringify({ cases: SAMPLE_CASES }, void 0, 1).replace(/\n\s*/g, " ");
1763
+ var DEFAULT_TEST_SUITE_BODY = `${JSON.stringify({ description: "What these cases pin down.", cases: [SAMPLE_CASES[0]] }, null, 2)}
1764
+ `;
1765
+ function testSuiteOf(text) {
1766
+ let written;
1767
+ try {
1768
+ written = JSON.parse(text);
1769
+ } catch {
1770
+ throw new TestSuiteFault(
1771
+ `This file is not JSON, so nothing can read the cases it was meant to hold.`,
1772
+ `Write it as ${SAMPLE}`
1773
+ );
1774
+ }
1775
+ const read = TEST_SUITE_SCHEMA.safeParse(written);
1776
+ if (!read.success) throw new TestSuiteFault(`This file is not written as a suite of cases.`, `Write it as ${SAMPLE}`);
1777
+ return new TestSuite(read.data.description, read.data.cases, text);
1778
+ }
1779
+
1780
+ // src/hexagon/domain/models/test/TestRoot.ts
1781
+ function testSuiteNameOf(path) {
1782
+ return path.slice(`${TEST_DIRECTORY}/`.length);
1783
+ }
1784
+ var TestRoot = class {
1785
+ constructor(suitesByFile, faultsByFiles) {
1786
+ this.suitesByFile = suitesByFile;
1787
+ this.faultsByFiles = faultsByFiles;
1788
+ }
1789
+ suitesByFile;
1790
+ faultsByFiles;
1791
+ /** Whether a test file is under this name, whether it reads or not. A name
1792
+ * reaching outside `.cw/test/` is no name of one. */
1793
+ hasTestSuiteNamed(name) {
1794
+ const path = `${TEST_DIRECTORY}/${name}`;
1795
+ return path in this.suitesByFile || path in this.faultsByFiles.files;
1796
+ }
1797
+ /** What a new test file is named: `untitled-<n>.json` for the first `n` no
1798
+ * test file has (FR-091). */
1799
+ get untitledName() {
1800
+ let untitledNumber = 1;
1801
+ while (this.hasTestSuiteNamed(`untitled-${untitledNumber}.json`)) untitledNumber++;
1802
+ return `untitled-${untitledNumber}.json`;
1803
+ }
1804
+ };
1805
+ function testRootOf(testSuiteFiles) {
1806
+ const suitesByFile = {};
1807
+ const faultsByFiles = {};
1808
+ for (const { path, contents } of [...testSuiteFiles].sort((one, another) => one.path.localeCompare(another.path))) {
1809
+ try {
1810
+ suitesByFile[path] = testSuiteOf(contents);
1811
+ } catch (raised) {
1812
+ if (!(raised instanceof Fault)) throw raised;
1813
+ faultsByFiles[path] = [raised];
1814
+ }
1815
+ }
1816
+ return new TestRoot(suitesByFile, new FaultsByFile(faultsByFiles));
1817
+ }
1818
+
1819
+ // src/hexagon/application/dtos.ts
1820
+ function catalogueDTO(catalogue2) {
1821
+ return { type: "Catalogue", data: { entries: catalogue2.full.map((one) => ({ type: "CatalogueEntry", data: one })) } };
1822
+ }
1823
+ function doctorOutcomeDTO({ agents }, faultsByFile2, planSummary2, driftedVendors2, repo) {
1824
+ const faults2 = Object.values(faultsByFile2.files).flat();
1825
+ const errorCount = faults2.filter((one) => one.severity === "error").length;
1826
+ const pendingCount = planSummary2 === void 0 ? null : planSummary2.added.length + planSummary2.edited.length + planSummary2.deleted.length;
1827
+ const unwell = [errorCount > 0, driftedVendors2.length > 0, pendingCount !== 0];
1828
+ return {
1829
+ type: "DoctorOutcome",
1830
+ data: {
1831
+ agents,
1832
+ errorCount,
1833
+ warnCount: faults2.filter((one) => one.severity === "warn").length,
1834
+ pendingCount,
1835
+ faultsByFile: faultsByFileDTO(faultsByFile2, repo),
1836
+ driftedVendors: driftedVendors2,
1837
+ problemCount: unwell.filter(Boolean).length
1838
+ }
1839
+ };
1840
+ }
1841
+ function explanationOutcomeDTO(scopedPrimitive2, activatesWhen, useMixins, rationale, hosts, citers, testCasesByFile2) {
1842
+ return {
1843
+ type: "ExplanationOutcome",
1844
+ data: {
1845
+ scopedPrimitive: scopedPrimitiveDTO(scopedPrimitive2),
1846
+ activatesWhen,
1847
+ useMixins: useMixins.map(scopedPrimitiveDTO),
1848
+ ...rationale === void 0 ? {} : { rationale: scopedPrimitiveDTO(rationale) },
1849
+ hosts: hosts.map(scopedPrimitiveDTO),
1850
+ citers: citers.map(scopedPrimitiveDTO),
1851
+ testCasesByFile: { type: "TestCasesByFile", data: testCasesByFile2 }
1852
+ }
1853
+ };
1854
+ }
1855
+ function faultDTO({ message, fix, severity }) {
1856
+ return { type: "Fault", data: { message, fix, severity } };
1857
+ }
1858
+ function faultsDTO(faults2) {
1859
+ return { type: "Faults", data: { faults: faults2.faults.map(faultDTO) } };
1860
+ }
1861
+ function faultsByFileDTO(faultsByFile2, repo) {
1862
+ return {
1863
+ type: "FaultsByFile",
1864
+ data: {
1865
+ files: Object.fromEntries(
1866
+ Object.entries(faultsByFile2.namedFrom(repo).files).map(([file, faults2]) => [file, faults2.map(faultDTO)])
1867
+ )
1868
+ }
1869
+ };
1870
+ }
1871
+ function primitiveSnapshotDTO(scopedPrimitive2, revision) {
1872
+ return {
1873
+ type: "PrimitiveSnapshot",
1874
+ data: { scopedPrimitive: scopedPrimitiveDTO(scopedPrimitive2), body: scopedPrimitive2.primitive.body, revision }
1875
+ };
1876
+ }
1877
+ function planSummaryDTO({ added, edited, deleted, unchanged }) {
1878
+ return { type: "PlanSummary", data: { added, edited, deleted, unchanged } };
1879
+ }
1880
+ function primitiveHeaderDTO({ field, shape, required, allowedValues }) {
1881
+ return {
1882
+ type: "PrimitiveHeader",
1883
+ data: { field, shape, required, ...allowedValues === void 0 ? {} : { allowedValues } }
1884
+ };
1885
+ }
1886
+ function primitiveRequirementsDTO({ headers, sample }) {
1887
+ return { type: "PrimitiveRequirements", data: { headers: headers.map(primitiveHeaderDTO), sample } };
1888
+ }
1889
+ function primitiveKindsDTO(kinds) {
1890
+ return { type: "PrimitiveKinds", data: Object.fromEntries(kinds.map((one) => [one.kind, one.activatesWhen])) };
1891
+ }
1892
+ function scopedPrimitiveDTO({ identity, scope, file, primitive }) {
1893
+ return {
1894
+ type: "ScopedPrimitive",
1895
+ data: {
1896
+ identity,
1897
+ kind: primitive.kind,
1898
+ description: primitive.headers.description,
1899
+ file,
1900
+ scope,
1901
+ // A header nobody wrote is left out rather than carried as undefined: this
1902
+ // says what the file declared.
1903
+ headers: Object.fromEntries(
1904
+ Object.entries(primitive.headers).filter(
1905
+ (entry) => entry[1] !== void 0
1906
+ )
1907
+ )
1908
+ }
1909
+ };
1910
+ }
1911
+ function scopedPrimitivesDTO(scopedPrimitives2) {
1912
+ return {
1913
+ type: "ScopedPrimitives",
1914
+ data: {
1915
+ primitives: [...scopedPrimitives2].sort((one, another) => one.identity < another.identity ? -1 : one.identity > another.identity ? 1 : 0).map(scopedPrimitiveDTO)
1916
+ }
1917
+ };
1918
+ }
1919
+ function testCaseReportDTO({ suiteName, situation, passed, unmet }) {
1920
+ return {
1921
+ type: "TestCaseReport",
1922
+ data: { suiteName, situation, passed, ...unmet === void 0 ? {} : { unmet: faultDTO(unmet) } }
1923
+ };
1924
+ }
1925
+ function testRunReportDTO({ testCaseReports }) {
1926
+ return { type: "TestRunReport", data: { testCaseReports: testCaseReports.map(testCaseReportDTO) } };
1927
+ }
1928
+ function testSuitesDTO({ suitesByFile, faultsByFiles }) {
1929
+ const paths = [...Object.keys(suitesByFile), ...Object.keys(faultsByFiles.files)].sort((one, another) => one.localeCompare(another));
1930
+ return {
1931
+ type: "TestSuites",
1932
+ data: {
1933
+ testSuites: paths.map((path) => {
1934
+ const suite = suitesByFile[path];
1935
+ const [fault2] = faultsByFiles.files[path] ?? [];
1936
+ return {
1937
+ type: "TestSuite",
1938
+ data: {
1939
+ name: testSuiteNameOf(path),
1940
+ ...suite === void 0 ? {} : { text: suite.body },
1941
+ ...suite?.description === void 0 ? {} : { description: suite.description },
1942
+ cases: (suite?.cases ?? []).map(({ situation, expectation, activatedIdentity }) => ({
1943
+ type: "TestCase",
1944
+ data: { situation, expectation, ...activatedIdentity === void 0 ? {} : { identity: activatedIdentity } }
1945
+ })),
1946
+ ...fault2 === void 0 ? {} : { fault: faultDTO(fault2) }
1947
+ }
1948
+ };
1949
+ })
1950
+ }
1951
+ };
1952
+ }
1953
+ function workspaceSettingsDTO({ agents }) {
1954
+ return { type: "WorkspaceSettings", data: { agents } };
1955
+ }
1956
+
1957
+ // src/driver/portal/routes.ts
1958
+ var json = (schema, description) => ({
1959
+ content: { "application/json": { schema } },
1960
+ description
1961
+ });
1962
+ function api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2) {
1963
+ const app = new OpenAPIHono();
1964
+ app.onError((raised, c) => {
1965
+ if (raised instanceof Fault) return c.json(faultDTO(raised), 422);
1966
+ if (raised instanceof HTTPException) return raised.getResponse();
1967
+ console.error(raised);
1968
+ return c.text(raised.message, 500);
1969
+ });
1970
+ return app.openapi(
1971
+ createRoute({
1972
+ method: "get",
1973
+ path: "/charter/root/faults",
1974
+ responses: { 200: json(DataDTOs.FaultsByFile, "Everything wrong with the charter, under each file") }
1975
+ }),
1976
+ async (c) => c.json((await charterAuthoringApp2.doctor()).data.faultsByFile, 200)
1977
+ ).openapi(
1978
+ createRoute({
1979
+ method: "get",
1980
+ path: "/charter/root/primitives",
1981
+ request: { query: z10.object({ matching: z10.string().optional() }) },
1982
+ responses: {
1983
+ 200: json(DataDTOs.ScopedPrimitives, "Every primitive of every layer, or those mentioning the word"),
1984
+ 422: json(z10.union([DataDTOs.FaultsByFile, DataDTOs.Fault]), "The charter does not hold")
1985
+ }
1986
+ }),
1987
+ async (c) => {
1988
+ const scopedPrimitivesDTO2 = await charterAuthoringApp2.fullList(c.req.valid("query").matching || void 0);
1989
+ return scopedPrimitivesDTO2.type === "FaultsByFile" ? c.json(scopedPrimitivesDTO2, 422) : c.json(scopedPrimitivesDTO2, 200);
1990
+ }
1991
+ ).openapi(
1992
+ createRoute({
1993
+ method: "get",
1994
+ path: "/charter/root/primitives/{identity}/explanation",
1995
+ request: { params: z10.object({ identity: z10.string() }) },
1996
+ responses: {
1997
+ 200: json(OutcomeDTOs.ExplanationOutcome, "What the engine says of one primitive"),
1998
+ 422: json(z10.union([DataDTOs.FaultsByFile, DataDTOs.Fault]), "The charter does not hold, or holds nothing of it")
1999
+ }
2000
+ }),
2001
+ async (c) => {
2002
+ const { identity } = c.req.valid("param");
2003
+ const explanationOutcomeDTO2 = await charterAuthoringApp2.explain(identity);
2004
+ return explanationOutcomeDTO2.type === "FaultsByFile" ? c.json(explanationOutcomeDTO2, 422) : c.json(explanationOutcomeDTO2, 200);
2005
+ }
2006
+ ).openapi(
2007
+ createRoute({
2008
+ method: "post",
2009
+ path: "/charter/root/primitives",
2010
+ request: {
2011
+ body: json(
2012
+ z10.object({
2013
+ kind: z10.string(),
2014
+ id: z10.string(),
2015
+ headers: z10.record(z10.string(), z10.union([z10.string(), z10.array(z10.string())])),
2016
+ body: z10.string()
2017
+ }),
2018
+ "A new primitive: its kind and id, a line or a list under each header, and its body"
2019
+ )
2020
+ },
2021
+ responses: {
2022
+ 201: {
2023
+ ...json(DataDTOs.ScopedPrimitive, "The primitive written"),
2024
+ headers: z10.object({ Location: z10.string() })
2025
+ },
2026
+ 422: json(z10.union([DataDTOs.Faults, DataDTOs.Fault]), "The answers the kind refused, or an identity already claimed")
2027
+ }
2028
+ }),
2029
+ async (c) => {
2030
+ const { kind, id, headers, body } = c.req.valid("json");
2031
+ const scopedPrimitiveDTO2 = await charterAuthoringApp2.add(kind, id, headers, body);
2032
+ if (scopedPrimitiveDTO2.type === "Faults") return c.json(scopedPrimitiveDTO2, 422);
2033
+ return c.json(scopedPrimitiveDTO2, 201, { Location: `/api/charter/root/primitives/${scopedPrimitiveDTO2.data.identity}` });
2034
+ }
2035
+ ).openapi(
2036
+ createRoute({
2037
+ method: "get",
2038
+ path: "/charter/root/primitives/{identity}",
2039
+ request: { params: z10.object({ identity: z10.string() }) },
2040
+ responses: {
2041
+ 200: {
2042
+ ...json(DataDTOs.PrimitiveSnapshot, "One primitive as the charter read it"),
2043
+ headers: z10.object({ ETag: z10.string() })
2044
+ },
2045
+ 422: json(DataDTOs.Fault, "The charter holds nothing of it")
2046
+ }
2047
+ }),
2048
+ async (c) => {
2049
+ const { identity } = c.req.valid("param");
2050
+ const primitiveSnapshotDTO2 = await charterAuthoringApp2.open(identity);
2051
+ return c.json(primitiveSnapshotDTO2, 200, { ETag: `"${primitiveSnapshotDTO2.data.revision}"` });
2052
+ }
2053
+ ).openapi(
2054
+ createRoute({
2055
+ method: "put",
2056
+ path: "/charter/root/primitives/{identity}",
2057
+ request: {
2058
+ params: z10.object({ identity: z10.string() }),
2059
+ headers: z10.object({ "if-match": z10.string().optional() }),
2060
+ body: json(
2061
+ z10.object({
2062
+ headers: z10.record(z10.string(), z10.union([z10.string(), z10.array(z10.string())])),
2063
+ body: z10.string()
2064
+ }),
2065
+ "A line or a list under each header, and the body, written over the primitive's own"
2066
+ )
2067
+ },
2068
+ responses: {
2069
+ 200: json(DataDTOs.ScopedPrimitive, "The primitive written"),
2070
+ 412: json(DataDTOs.Fault, "The file changed on disk after it was opened"),
2071
+ 422: json(z10.union([DataDTOs.Faults, DataDTOs.Fault]), "The answers the kind refused, or a primitive this repository did not author")
2072
+ }
2073
+ }),
2074
+ async (c) => {
2075
+ const { identity } = c.req.valid("param");
2076
+ const { revision, scopedPrimitive: scopedPrimitive2 } = (await charterAuthoringApp2.open(identity)).data;
2077
+ if (c.req.valid("header")["if-match"] !== `"${revision}"`)
2078
+ return c.json(
2079
+ faultDTO(
2080
+ new Fault(
2081
+ `${scopedPrimitive2.data.file} changed on disk after it was opened, and saving would write over that change.`,
2082
+ "Open it again to see what changed, then make your edit there."
2083
+ )
2084
+ ),
2085
+ 412
2086
+ );
2087
+ const { headers, body } = c.req.valid("json");
2088
+ const scopedPrimitiveDTO2 = await charterAuthoringApp2.rewrite(identity, headers, body, revision);
2089
+ return scopedPrimitiveDTO2.type === "Faults" ? c.json(scopedPrimitiveDTO2, 422) : c.json(scopedPrimitiveDTO2, 200);
2090
+ }
2091
+ ).openapi(
2092
+ createRoute({
2093
+ method: "delete",
2094
+ path: "/charter/root/primitives/{identity}",
2095
+ request: { params: z10.object({ identity: z10.string() }) },
2096
+ responses: {
2097
+ 204: { description: "The file is gone" },
2098
+ 422: json(DataDTOs.Fault, "A primitive this repository did not author, or none at all")
2099
+ }
2100
+ }),
2101
+ async (c) => {
2102
+ const { identity } = c.req.valid("param");
2103
+ await charterAuthoringApp2.remove(identity);
2104
+ return c.body(null, 204);
2105
+ }
2106
+ ).openapi(
2107
+ createRoute({
2108
+ method: "get",
2109
+ path: "/definitions/kinds",
2110
+ responses: { 200: json(DataDTOs.PrimitiveKinds, "Every kind, and when a primitive of it comes up") }
2111
+ }),
2112
+ async (c) => c.json(await charterAuthoringApp2.kinds(), 200)
2113
+ ).openapi(
2114
+ createRoute({
2115
+ method: "get",
2116
+ path: "/definitions/kinds/{kind}/requirements",
2117
+ request: { params: z10.object({ kind: z10.string() }) },
2118
+ responses: {
2119
+ 200: json(DataDTOs.PrimitiveRequirements, "Every header the kind takes, and its sample"),
2120
+ 422: json(DataDTOs.Fault, "A word that is no kind")
2121
+ }
2122
+ }),
2123
+ async (c) => c.json(await charterAuthoringApp2.listPrimitiveRequirements(c.req.valid("param").kind), 200)
2124
+ ).openapi(
2125
+ createRoute({
2126
+ method: "get",
2127
+ path: "/charter/root/build",
2128
+ responses: {
2129
+ 200: json(DataDTOs.PlanSummary, "What a build would do to every file, nothing written"),
2130
+ 422: json(DataDTOs.FaultsByFile, "The charter does not hold")
2131
+ }
2132
+ }),
2133
+ async (c) => {
2134
+ const planSummaryDTO2 = await charterAuthoringApp2.preview();
2135
+ return planSummaryDTO2.type === "FaultsByFile" ? c.json(planSummaryDTO2, 422) : c.json(planSummaryDTO2, 200);
2136
+ }
2137
+ ).openapi(
2138
+ createRoute({
2139
+ method: "post",
2140
+ path: "/charter/root/build",
2141
+ responses: {
2142
+ 200: json(DataDTOs.PlanSummary, "What the build wrote and deleted"),
2143
+ 422: json(DataDTOs.FaultsByFile, "The charter does not hold, and nothing was written")
2144
+ }
2145
+ }),
2146
+ async (c) => {
2147
+ const planSummaryDTO2 = await charterAuthoringApp2.build();
2148
+ return planSummaryDTO2.type === "FaultsByFile" ? c.json(planSummaryDTO2, 422) : c.json(planSummaryDTO2, 200);
2149
+ }
2150
+ ).openapi(
2151
+ createRoute({
2152
+ method: "get",
2153
+ path: "/charter/root/health",
2154
+ responses: { 200: json(OutcomeDTOs.DoctorOutcome, "The four answers cw doctor gives, and every fault") }
2155
+ }),
2156
+ async (c) => c.json(await charterAuthoringApp2.doctor(), 200)
2157
+ ).openapi(
2158
+ createRoute({
2159
+ method: "get",
2160
+ path: "/test-suites",
2161
+ responses: { 200: json(DataDTOs.TestSuites, "Every test file, its text, and its cases or why it does not read") }
2162
+ }),
2163
+ async (c) => c.json(await testAuthoringApp2.suites(), 200)
2164
+ ).openapi(
2165
+ createRoute({
2166
+ method: "post",
2167
+ path: "/test-suites",
2168
+ responses: {
2169
+ 201: {
2170
+ ...json(z10.string(), "The name of the test file written"),
2171
+ headers: z10.object({ Location: z10.string() })
2172
+ }
2173
+ }
2174
+ }),
2175
+ async (c) => {
2176
+ const name = await testAuthoringApp2.addSuite();
2177
+ return c.json(name, 201, { Location: `/api/test-suites/${name}` });
2178
+ }
2179
+ ).openapi(
2180
+ createRoute({
2181
+ method: "put",
2182
+ path: "/test-suites/{name}",
2183
+ request: {
2184
+ params: z10.object({ name: z10.string() }),
2185
+ body: json(z10.object({ text: z10.string() }), "The whole text the test file is written over with")
2186
+ },
2187
+ responses: {
2188
+ 200: json(z10.string(), "The name of the test file written"),
2189
+ 422: json(DataDTOs.Fault, "Text that is not a suite, with its sample, or a name that is no test file")
2190
+ }
2191
+ }),
2192
+ async (c) => c.json(await testAuthoringApp2.writeSuite(c.req.valid("param").name, c.req.valid("json").text), 200)
2193
+ ).openapi(
2194
+ createRoute({
2195
+ method: "delete",
2196
+ path: "/test-suites/{name}",
2197
+ request: { params: z10.object({ name: z10.string() }) },
2198
+ responses: {
2199
+ 204: { description: "The test file is gone" },
2200
+ 422: json(DataDTOs.Fault, "A name that is no test file")
2201
+ }
2202
+ }),
2203
+ async (c) => {
2204
+ await testAuthoringApp2.removeSuite(c.req.valid("param").name);
2205
+ return c.body(null, 204);
2206
+ }
2207
+ ).openapi(
2208
+ createRoute({
2209
+ method: "get",
2210
+ path: "/test-suites/outcome",
2211
+ responses: {
2212
+ 200: json(DataDTOs.TestRunReport, "How every case came out"),
2213
+ 422: json(DataDTOs.FaultsByFile, "The charter does not hold, or a test file does not read")
2214
+ }
2215
+ }),
2216
+ async (c) => {
2217
+ const testRunReportDTO2 = await charterAuthoringApp2.test();
2218
+ return testRunReportDTO2.type === "FaultsByFile" ? c.json(testRunReportDTO2, 422) : c.json(testRunReportDTO2, 200);
2219
+ }
2220
+ ).openapi(
2221
+ createRoute({
2222
+ method: "get",
2223
+ path: "/vendors",
2224
+ responses: { 200: json(z10.array(z10.string()).readonly(), "Every folder a vendor source was installed as") }
2225
+ }),
2226
+ async (c) => c.json(await charterVendoringApp2.installed(), 200)
2227
+ ).openapi(
2228
+ createRoute({
2229
+ method: "post",
2230
+ path: "/vendors",
2231
+ request: {
2232
+ body: json(
2233
+ z10.object({ source: z10.string(), version: z10.string().optional() }),
2234
+ "A source as git fetches it, and the version to pin it to where one is named"
2235
+ )
2236
+ },
2237
+ responses: {
2238
+ 204: { description: "The source is installed, and committed" },
2239
+ 422: json(DataDTOs.Fault, "No repository, work in hand, or what git refused")
2240
+ }
2241
+ }),
2242
+ async (c) => {
2243
+ const { source, version } = c.req.valid("json");
2244
+ await charterVendoringApp2.add(source, version);
2245
+ return c.body(null, 204);
2246
+ }
2247
+ ).openapi(
2248
+ createRoute({
2249
+ method: "delete",
2250
+ path: "/vendors/{name}",
2251
+ request: { params: z10.object({ name: z10.string() }) },
2252
+ responses: {
2253
+ 204: { description: "The folder is gone, and that it is gone is committed" },
2254
+ 422: json(DataDTOs.Fault, "No repository, work in hand, or what git refused")
2255
+ }
2256
+ }),
2257
+ async (c) => {
2258
+ await charterVendoringApp2.remove(c.req.valid("param").name);
2259
+ return c.body(null, 204);
2260
+ }
2261
+ );
2262
+ }
2263
+
2264
+ // src/driver/portal/server.ts
2265
+ var DEFAULT_PORT = 9927;
2266
+ async function startPortal(page, charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2, port = DEFAULT_PORT) {
2267
+ await access(new URL("index.html", page)).catch(() => {
2268
+ throw new Fault("The portal's page is not built.", 'Run "pnpm build", then start the portal again.');
2269
+ });
2270
+ const token = randomBytes(32).toString("base64url");
2271
+ for (let next = port; ; next++) {
2272
+ try {
2273
+ const address = new URL(`http://127.0.0.1:${next}/?t=${token}`);
2274
+ return { address, server: await listen(portal(page, charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2, token, next), next) };
2275
+ } catch (raised) {
2276
+ if (raised.code !== "EADDRINUSE" || next === 65535) throw raised;
2277
+ }
2278
+ }
2279
+ }
2280
+ function portal(page, charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2, token, port) {
2281
+ const hosts = [`127.0.0.1:${port}`, `localhost:${port}`];
2282
+ return new Hono().use(async (c, next) => {
2283
+ if (!hosts.includes(c.req.header("host") ?? "")) {
2284
+ return c.text(`The portal answers only at ${hosts.join(" and ")}.`, 403);
2285
+ }
2286
+ if (c.req.method !== "GET" && c.req.header("content-type")?.split(";")[0]?.trim() !== "application/json") {
2287
+ return c.text("Every request but a GET is sent with Content-Type: application/json.", 415);
2288
+ }
2289
+ await next();
2290
+ }).use("/", async (c, next) => {
2291
+ if (c.req.query("t") !== token) return c.text('Open the address "cw portal" printed: this one has no token.', 401);
2292
+ await next();
2293
+ }).use("/api/*", bearerAuth({ token })).route("/api", api(charterAuthoringApp2, charterVendoringApp2, testAuthoringApp2)).use("/*", serveStatic({ root: fileURLToPath(page) }));
2294
+ }
2295
+ function listen(app, port) {
2296
+ return new Promise((resolve, reject) => {
2297
+ const server = serve({ fetch: app.fetch, hostname: "127.0.0.1", port }, () => resolve(server));
2298
+ server.once("error", reject);
2299
+ });
2300
+ }
2301
+
2302
+ // src/driver/cli/commands/PortalCommand.ts
2303
+ var OPTIONS6 = {
2304
+ port: {
2305
+ type: "number",
2306
+ describe: "Which port to serve on, the next free one when it is taken",
2307
+ default: DEFAULT_PORT
2308
+ }
2309
+ };
2310
+ var PortalCommand = class {
2311
+ name = "portal";
2312
+ summary = "Serve this repository's charter to a browser on this machine";
2313
+ options = OPTIONS6;
2314
+ async run({ charterAuthoringApp: charterAuthoringApp2, charterVendoringApp: charterVendoringApp2, testAuthoringApp: testAuthoringApp2 }, { port }) {
2315
+ await charterAuthoringApp2.ensureRepoReady();
2316
+ const { address } = await startPortal(
2317
+ new URL("./", import.meta.resolve("#portal/index.html")),
2318
+ charterAuthoringApp2,
2319
+ charterVendoringApp2,
2320
+ testAuthoringApp2,
2321
+ port
2322
+ );
2323
+ return { code: EXIT_OK, result: `The portal is at ${address}
2324
+ Press Ctrl+C to stop it.
2325
+ ` };
2326
+ }
2327
+ };
2328
+
2329
+ // src/driver/cli/commands/RemoveCommand.ts
2330
+ import prompts3 from "prompts";
2331
+ var OPTIONS7 = {
2332
+ identity: {
2333
+ type: "string",
2334
+ describe: 'What the primitive is named by across the whole charter: "guide:no-any"'
2335
+ },
2336
+ yes: {
2337
+ type: "boolean",
2338
+ alias: "y",
2339
+ default: false,
2340
+ describe: "Remove it without asking first, as a run with nobody at the terminal must"
2341
+ }
2342
+ };
2343
+ var RemoveCommand = class {
2344
+ name = "remove <identity>";
2345
+ summary = "Delete the file of one primitive this repository authored";
2346
+ options = OPTIONS7;
2347
+ async run({ charterAuthoringApp: charterAuthoringApp2 }, { identity, yes }) {
2348
+ const { scopedPrimitive: scopedPrimitive2 } = (await charterAuthoringApp2.open(identity ?? "")).data;
2349
+ if (!yes && scopedPrimitive2.data.scope === REPO_SCOPE) {
2350
+ if (!process.stdin.isTTY)
2351
+ throw new Fault(
2352
+ `Removing ${scopedPrimitive2.data.file} is asked first, and nobody is at the terminal to answer.`,
2353
+ `Run "cw remove ${scopedPrimitive2.data.identity} --yes" to remove it without asking.`
2354
+ );
2355
+ const { confirmed } = await prompts3({
2356
+ type: "confirm",
2357
+ name: "confirmed",
2358
+ message: `Remove ${scopedPrimitive2.data.file}?`,
2359
+ initial: false
2360
+ });
2361
+ if (confirmed !== true) return { code: EXIT_OK, result: "Nothing was removed.\n" };
2362
+ }
2363
+ const scopedPrimitiveDTO2 = await charterAuthoringApp2.remove(scopedPrimitive2.data.identity);
2364
+ return {
2365
+ code: EXIT_OK,
2366
+ result: [
2367
+ `Removed ${scopedPrimitiveDTO2.data.file}.`,
2368
+ 'Run "cw doctor" to see what still names it, then "cw build".',
2369
+ ""
2370
+ ].join("\n")
2371
+ };
2372
+ }
2373
+ };
2374
+
2375
+ // src/driver/cli/commands/SuiteAddCommand.ts
2376
+ var SuiteAddCommand = class {
2377
+ name = "suite add";
2378
+ summary = `Write a new test file under ${TEST_DIRECTORY}/, holding one sample case`;
2379
+ async run({ testAuthoringApp: testAuthoringApp2 }) {
2380
+ const name = await testAuthoringApp2.addSuite();
2381
+ return {
2382
+ code: EXIT_OK,
2383
+ result: [`Wrote ${TEST_DIRECTORY}/${name}.`, `Run "cw suite edit ${name}" to write its cases, then "cw test".`, ""].join("\n")
2384
+ };
2385
+ }
2386
+ };
2387
+
2388
+ // src/driver/cli/commands/SuiteEditCommand.ts
2389
+ var OPTIONS8 = {
2390
+ name: {
2391
+ type: "string",
2392
+ describe: `The test file's name under ${TEST_DIRECTORY}/: "untitled-1.json"`
2393
+ }
2394
+ };
2395
+ var SuiteEditCommand = class {
2396
+ name = "suite edit <name>";
2397
+ summary = "Open one test file in your editor";
2398
+ options = OPTIONS8;
2399
+ async run({ cwd, testAuthoringApp: testAuthoringApp2 }, { name }) {
2400
+ const { testSuites: testSuites2 } = (await testAuthoringApp2.suites()).data;
2401
+ if (!testSuites2.some((one) => one.data.name === name))
2402
+ throw new Fault(`${TEST_DIRECTORY}/ holds no test file called "${name ?? ""}".`, 'Run "cw suite add" to write a new one.');
2403
+ const file = `${TEST_DIRECTORY}/${name}`;
2404
+ const problem = await editInEditor(cwd, file);
2405
+ if (problem !== void 0) return { code: EXIT_FAILURE, problem };
2406
+ return { code: EXIT_OK, result: [`Closed ${file}.`, 'Run "cw test" to see how its cases come out.', ""].join("\n") };
2407
+ }
2408
+ };
2409
+
2410
+ // src/driver/cli/commands/SuiteRemoveCommand.ts
2411
+ var OPTIONS9 = {
2412
+ name: {
2413
+ type: "string",
2414
+ describe: `The test file's name under ${TEST_DIRECTORY}/: "untitled-1.json"`
2415
+ }
2416
+ };
2417
+ var SuiteRemoveCommand = class {
2418
+ name = "suite remove <name>";
2419
+ summary = "Delete one test file";
2420
+ options = OPTIONS9;
2421
+ async run({ testAuthoringApp: testAuthoringApp2 }, { name }) {
2422
+ const removedName = await testAuthoringApp2.removeSuite(name ?? "");
2423
+ return { code: EXIT_OK, result: `Removed ${TEST_DIRECTORY}/${removedName}.
2424
+ ` };
2425
+ }
2426
+ };
2427
+
2428
+ // src/driver/cli/commands/TestCommand.ts
2429
+ var TestCommand = class {
2430
+ name = "test";
2431
+ summary = `Resolve every situation in ${TEST_DIRECTORY}/ against the charter`;
2432
+ async run({ charterAuthoringApp: charterAuthoringApp2 }, _options) {
2433
+ const testRunReportDTO2 = await charterAuthoringApp2.test();
2434
+ if (testRunReportDTO2.type === "FaultsByFile")
2435
+ return { code: EXIT_FAILURE, problem: `Nothing was resolved.
2436
+
2437
+ ${toText(testRunReportDTO2)}` };
2438
+ const cases = testRunReportDTO2.data.testCaseReports;
2439
+ if (cases.length === 0) {
2440
+ return {
2441
+ code: EXIT_OK,
2442
+ result: `No tests: ${TEST_DIRECTORY}/ holds no case.
2443
+ Write one as ${TEST_DIRECTORY}/<name>.json to pin a situation down.
2444
+ `
2445
+ };
2446
+ }
2447
+ const failed = cases.filter((one) => !one.data.passed);
2448
+ return {
2449
+ code: failed.length === 0 ? EXIT_OK : EXIT_FAILURE,
2450
+ result: [
2451
+ ...cases.flatMap(lines2),
2452
+ "",
2453
+ failed.length === 0 ? `${cases.length} case${cases.length === 1 ? "" : "s"} passed.` : `${failed.length} of ${cases.length} case${cases.length === 1 ? "" : "s"} failed.`,
2454
+ ""
2455
+ ].join("\n")
2456
+ };
2457
+ }
2458
+ };
2459
+ function lines2(one) {
2460
+ return [
2461
+ ` ${one.data.passed ? "pass" : "FAIL"} ${one.data.situation}`,
2462
+ ...one.data.unmet === void 0 ? [] : [` ${one.data.unmet.data.message}`, ` ${one.data.unmet.data.fix}`]
2463
+ ];
2464
+ }
2465
+
2466
+ // src/driver/cli/commands/VendorAddCommand.ts
2467
+ var OPTIONS10 = {
2468
+ source: {
2469
+ type: "string",
2470
+ describe: "What git clones the charter from: an address over any transport, or a path on disk"
2471
+ },
2472
+ ref: {
2473
+ type: "string",
2474
+ describe: "The tag, branch or commit to pin to; the source's own default where none is named"
2475
+ }
2476
+ };
2477
+ var VendorAddCommand = class {
2478
+ name = "vendor add <source>";
2479
+ summary = "Install a charter from a git source, or bring an installed one up to date";
2480
+ options = OPTIONS10;
2481
+ async run({ charterVendoringApp: charterVendoringApp2 }, { source, ref }) {
2482
+ const folder = await charterVendoringApp2.add(source ?? "", ref);
2483
+ return {
2484
+ code: EXIT_OK,
2485
+ result: [
2486
+ `Installed ${source} under ${folder}${ref === void 0 ? "" : `, pinned to ${ref}`}.`,
2487
+ 'Run "cw build" to compile what it adds to this charter.',
2488
+ ""
2489
+ ].join("\n")
2490
+ };
2491
+ }
2492
+ };
2493
+
2494
+ // src/driver/cli/commands/VendorListCommand.ts
2495
+ var VendorListCommand = class {
2496
+ name = "vendor list";
2497
+ summary = "List the folders this repository installed charters as";
2498
+ async run({ charterVendoringApp: charterVendoringApp2 }) {
2499
+ const folders = await charterVendoringApp2.installed();
2500
+ if (folders.length === 0)
2501
+ return { code: EXIT_OK, result: 'No vendor source is installed. Run "cw vendor add <source>" to install one.\n' };
2502
+ return { code: EXIT_OK, result: `${folders.join("\n")}
2503
+ ` };
2504
+ }
2505
+ };
2506
+
2507
+ // src/driver/cli/commands/VendorRemoveCommand.ts
2508
+ var OPTIONS11 = {
2509
+ name: {
2510
+ type: "string",
2511
+ describe: "The folder this charter was installed as, as it reads under .cw/vendor/"
2512
+ }
2513
+ };
2514
+ var VendorRemoveCommand = class {
2515
+ name = "vendor remove <name>";
2516
+ summary = "Take an installed charter away";
2517
+ options = OPTIONS11;
2518
+ async run({ charterVendoringApp: charterVendoringApp2 }, { name }) {
2519
+ const folder = await charterVendoringApp2.remove(name ?? "");
2520
+ return {
2521
+ code: EXIT_OK,
2522
+ result: [
2523
+ `Removed ${folder}.`,
2524
+ 'Run "cw build" to compile this charter without it.',
2525
+ ""
2526
+ ].join("\n")
2527
+ };
2528
+ }
2529
+ };
2530
+
2531
+ // src/driver/cli/commands/index.ts
2532
+ var COMMANDS = [
2533
+ new InitCommand(),
2534
+ new BuildCommand(),
2535
+ new ListCommand(),
2536
+ new KindsCommand(),
2537
+ new AddCommand(),
2538
+ new EditCommand(),
2539
+ new RemoveCommand(),
2540
+ new ExplainCommand(),
2541
+ new DoctorCommand(),
2542
+ new TestCommand(),
2543
+ new SuiteAddCommand(),
2544
+ new SuiteEditCommand(),
2545
+ new SuiteRemoveCommand(),
2546
+ new VendorAddCommand(),
2547
+ new VendorRemoveCommand(),
2548
+ new VendorListCommand(),
2549
+ new PortalCommand()
2550
+ ];
2551
+
2552
+ // src/driver/cli/Commander.ts
2553
+ var Commander = class {
2554
+ #context;
2555
+ #commands;
2556
+ constructor(context, commands = COMMANDS) {
2557
+ this.#context = context;
2558
+ this.#commands = commands;
2559
+ }
2560
+ async run(argv) {
2561
+ let code = EXIT_OK;
2562
+ let failed = false;
2563
+ await this.#parser((outcome) => {
2564
+ code = outcome.code;
2565
+ if (outcome.result) process.stdout.write(outcome.result);
2566
+ if (outcome.problem) process.stderr.write(outcome.problem);
2567
+ }).parseAsync([...argv], {}, (error, _parsed, output) => {
2568
+ failed = error !== null && error !== void 0;
2569
+ if (output) (failed ? process.stderr : process.stdout).write(`${output}
2570
+ `);
2571
+ });
2572
+ return failed ? EXIT_USAGE : code;
2573
+ }
2574
+ #parser(collect) {
2575
+ let cli2 = yargs().scriptName("cw").usage("Usage: $0 <command> [options]").demandCommand(1, "").strict().recommendCommands().help("help").alias("h", "help").version(this.#context.version).wrap(80).exitProcess(false);
2576
+ const groups = /* @__PURE__ */ new Map();
2577
+ for (const command of this.#commands) {
2578
+ const head = wordsOf(command.name)[0];
2579
+ groups.set(head, [...groups.get(head) ?? [], command]);
2580
+ }
2581
+ for (const [head, commands] of groups) {
2582
+ const [only] = commands;
2583
+ cli2 = commands.length === 1 && only !== void 0 && wordsOf(only.name).length === 1 ? this.#declaringCommand(cli2, only, only.name, collect) : cli2.command({
2584
+ command: head,
2585
+ // What the group is for is what its commands are: saying it any
2586
+ // other way would be a second description to keep in step.
2587
+ describe: commands.map((one) => wordsOf(one.name)[1]).join(", "),
2588
+ builder: (child) => commands.reduce((parser, command) => this.#declaringCommand(parser, command, command.name.slice(head.length + 1), collect), child).demandCommand(1, ""),
2589
+ handler: () => void 0
2590
+ });
2591
+ }
2592
+ return cli2;
2593
+ }
2594
+ /** One command, said to yargs under the name it is typed by — the whole of it
2595
+ * for a command that stands alone, what follows the group for one inside
2596
+ * it. */
2597
+ #declaringCommand(cli2, command, name, collect) {
2598
+ return cli2.command({
2599
+ command: name,
2600
+ describe: command.summary,
2601
+ builder: (child) => declaringArguments(child, command.options ?? {}, name),
2602
+ handler: async (options) => {
2603
+ try {
2604
+ collect(await command.run(this.#context, options));
2605
+ } catch (raised) {
2606
+ if (!(raised instanceof Fault)) throw raised;
2607
+ collect({ code: EXIT_FAILURE, problem: `${raised.message}
2608
+ ${raised.fix}
2609
+ ` });
2610
+ }
2611
+ }
2612
+ });
2613
+ }
2614
+ };
2615
+ function wordsOf(name) {
2616
+ return name.split(" ").filter((word) => !word.startsWith("<") && !word.startsWith("["));
2617
+ }
2618
+ function declaringArguments(cli2, options, name) {
2619
+ return Object.entries(options).reduce((parser, [argument, option]) => {
2620
+ const declared = {
2621
+ type: option.type,
2622
+ describe: option.describe,
2623
+ ...option.alias === void 0 ? {} : { alias: option.alias },
2624
+ ...option.default === void 0 ? {} : { default: option.default },
2625
+ // Typed once per value: yargs collects them, and a run that typed none
2626
+ // reads an empty list rather than nothing, so the command asks how many
2627
+ // were typed instead of whether any were.
2628
+ ...option.isArray === void 0 ? {} : { array: true, default: [] }
2629
+ };
2630
+ return name.includes(`<${argument}>`) || name.includes(`[${argument}]`) ? parser.positional(argument, declared) : parser.option(argument, declared);
2631
+ }, cli2);
2632
+ }
2633
+
2634
+ // src/hexagon/domain/models/charter/builtin/CwAuthorSkill.ts
2635
+ var CwAuthorSkill = class extends SkillPrimitive {
2636
+ constructor() {
2637
+ super(
2638
+ {
2639
+ id: "cw-author",
2640
+ description: "Author a new primitive of this repository's charter, asking cw what its kind requires.",
2641
+ triggers: [
2642
+ "write a rule",
2643
+ "add a standard",
2644
+ "write a skill",
2645
+ "add a command",
2646
+ "author a primitive of the charter"
2647
+ ]
2648
+ },
2649
+ `# Authoring a primitive of this charter
2650
+
2651
+ This skill is cw's own. It comes with the engine, is not authored in this
2652
+ repository, and cannot be changed here. A repository that wants another way of
2653
+ authoring writes a primitive of its own, under an identity of its own.
2654
+
2655
+ It covers writing a new primitive. Changing or removing one that already exists
2656
+ is not what it covers.
2657
+
2658
+ Do not write a primitive from memory, or copy the headers of another one. What a
2659
+ kind requires is asked of cw, which answers from the same contract a file is
2660
+ refused against.
2661
+
2662
+ 1. If the kind is not settled, run \`cw kinds\`. It names every kind and when a
2663
+ primitive of each comes up; take the one whose line matches what is being
2664
+ asked for.
2665
+ 2. Run \`cw kinds <kind>\`. It names every header that kind requires, the shape
2666
+ each takes \u2014 one line, or a list \u2014 the values one drawn from a closed set
2667
+ may hold, and a sample written in full.
2668
+ 3. Run \`cw add <kind> <id> --header <name>=<value>\`, one \`--header\` for each
2669
+ header step 2 named, and for any other the sample shows this primitive
2670
+ needs. A header that takes a list is given once per entry, in order; one that
2671
+ takes a line is given once. Everything after the first \`=\` is the value. If
2672
+ cw refuses, it says why and shows the kind's sample: answer again with that
2673
+ in hand. Nothing is written until the headers hold.
2674
+ 4. Write the body into the file cw named. The headers decide when the primitive
2675
+ comes up; the body is what an agent reads once it has.
2676
+ 5. Run \`cw build\`. If it refuses, \`cw doctor\` names each file with a problem
2677
+ and how to fix it.`
2678
+ );
2679
+ }
2680
+ };
2681
+
2682
+ // src/hexagon/domain/models/charter/builtin/index.ts
2683
+ var BUILTIN_PRIMITIVES = [new CwAuthorSkill()];
2684
+
2685
+ // src/hexagon/service/charterRepo.ts
2686
+ async function loadCharterRoot(repo, fileReaders, yamlParser2) {
2687
+ const repoHref = repo.href.endsWith("/") ? repo.href : `${repo.href}/`;
2688
+ const read = async (layer2) => (await fileReaders.readFilesRecursively(layer2)).filter(
2689
+ ({ file }) => file.href.endsWith(".md") && KINDS.some((kind) => file.href.startsWith(`${layer2.href}${kind}/`))
2690
+ ).map(({ file, contents }) => ({ path: decodeURIComponent(file.href.slice(repoHref.length)), contents }));
2691
+ const authoredFiles = await read(charterFolderIn(repo));
2692
+ const vendors = await fileReaders.listFolders(vendorFolderIn(repo));
2693
+ const vendorFiles = await Promise.all(
2694
+ vendors.map(async (folder) => {
2695
+ const vendor = decodeURIComponent(folder.href.split("/").at(-2) ?? "");
2696
+ return (await read(folder)).map((one) => ({ ...one, vendor }));
2697
+ })
2698
+ );
2699
+ const builtinFiles = BUILTIN_PRIMITIVES.map((primitive) => ({
2700
+ path: `${BUILTIN_PATH_PREFIX}/${primitive.kind}/${primitive.headers.id}.md`,
2701
+ contents: primitive.toMarkdown()
2702
+ }));
2703
+ return charterRootOf({ repo: authoredFiles, vendor: vendorFiles.flat(), builtin: builtinFiles }, yamlParser2);
2704
+ }
2705
+ async function writeCharter(repo, primitive, fileReader2, fileWriter2) {
2706
+ const under2 = `${primitive.kind}/${primitive.headers.id}.md`;
2707
+ const path = `${CHARTER_DIRECTORY}/${under2}`;
2708
+ const file = new URL(under2, charterFolderIn(repo));
2709
+ if (await fileReader2.readIfThere(file) !== void 0)
2710
+ throw new Fault(
2711
+ `${path} is already there, and writing this one would write over what it holds.`,
2712
+ `Open it, or run this again with an identity this charter has not got.`
2713
+ );
2714
+ await fileWriter2.write(file, primitive.toMarkdown());
2715
+ return new ScopedPrimitive(identityOf({ kind: primitive.kind, id: primitive.headers.id }), REPO_SCOPE, path, primitive);
2716
+ }
2717
+
2718
+ // src/hexagon/domain/models/Settings.ts
2719
+ import { z as z11 } from "zod";
2720
+ var SETTINGS = z11.object({
2721
+ agents: z11.array(z11.enum(AGENT_PROVIDERS))
2722
+ });
2723
+ var WorkspaceSettings = class {
2724
+ constructor(agents) {
2725
+ this.agents = agents;
2726
+ }
2727
+ agents;
2728
+ };
2729
+
2730
+ // src/hexagon/service/settingsRepo.ts
2731
+ async function loadSettings(repo, fileReaders) {
2732
+ const text = await fileReaders.readIfThere(settingsFileIn(repo));
2733
+ if (text === void 0) return new WorkspaceSettings([]);
2734
+ let written;
2735
+ try {
2736
+ written = JSON.parse(text);
2737
+ } catch {
2738
+ throw new SettingsFault(
2739
+ `These are this repository's own settings and this is not JSON.`,
2740
+ `Correct it, or delete it and run "cw init" to write it again.`
2741
+ );
2742
+ }
2743
+ const read = SETTINGS.safeParse(written);
2744
+ if (!read.success) throw whatIsWrongWith(written);
2745
+ return new WorkspaceSettings(read.data.agents);
2746
+ }
2747
+ function whatIsWrongWith(written) {
2748
+ if (typeof written !== "object" || written === null || Array.isArray(written))
2749
+ return new SettingsFault(
2750
+ `These are this repository's own settings and this holds something other than a set of fields.`,
2751
+ `Write it as an object, as in { "agents": ["claude"] }.`
2752
+ );
2753
+ const agents = written.agents;
2754
+ if (!Array.isArray(agents))
2755
+ return new SettingsFault(
2756
+ `"agents" is what this repository compiles its charter for, and it is not a list of names here.`,
2757
+ `Write it as a list of names, as in { "agents": ["claude"] }.`
2758
+ );
2759
+ return new SettingsFault(
2760
+ `This says it compiles for ${agents.filter((one) => !isAgentProvider(one)).map((one) => `"${String(one)}"`).join(", ")}, and this engine compiles for no such agent.`,
2761
+ `Name any of ${AGENT_PROVIDERS.join(", ")}, or drop what is left.`
2762
+ );
2763
+ }
2764
+
2765
+ // src/hexagon/service/testSuitesRepo.ts
2766
+ async function loadTestRoot(repo, fileReaders) {
2767
+ const repoHref = repo.href.endsWith("/") ? repo.href : `${repo.href}/`;
2768
+ return testRootOf(
2769
+ (await fileReaders.readFilesRecursively(testFolderIn(repo))).filter(({ file }) => file.href.endsWith(".json")).map(({ file, contents }) => ({ path: decodeURIComponent(file.href.slice(repoHref.length)), contents }))
2770
+ );
2771
+ }
2772
+ async function addTestSuite(repo, fileReaders, fileWriter2) {
2773
+ const name = (await loadTestRoot(repo, fileReaders)).untitledName;
2774
+ await fileWriter2.write(new URL(name, testFolderIn(repo)), DEFAULT_TEST_SUITE_BODY);
2775
+ return name;
2776
+ }
2777
+ async function writeTestSuite(repo, fileReaders, fileWriter2, name, text) {
2778
+ await ensureTestSuiteNamed(repo, fileReaders, name);
2779
+ testSuiteOf(text);
2780
+ await fileWriter2.write(new URL(name, testFolderIn(repo)), text);
2781
+ return name;
2782
+ }
2783
+ async function removeTestSuite(repo, fileReaders, fileWriter2, name) {
2784
+ await ensureTestSuiteNamed(repo, fileReaders, name);
2785
+ await fileWriter2.delete(new URL(name, testFolderIn(repo)));
2786
+ return name;
2787
+ }
2788
+ async function ensureTestSuiteNamed(repo, fileReaders, name) {
2789
+ if (!(await loadTestRoot(repo, fileReaders)).hasTestSuiteNamed(name))
2790
+ throw new TestSuiteFault(`${TEST_DIRECTORY}/ holds no test file called "${name}".`, 'Run "cw suite add" to write a new one.');
2791
+ }
2792
+
2793
+ // src/hexagon/domain/models/vendor/VendorSource.ts
2794
+ var VendorSource = class {
2795
+ constructor(source, name) {
2796
+ this.source = source;
2797
+ this.name = name;
2798
+ }
2799
+ source;
2800
+ name;
2801
+ };
2802
+ function vendorSourceOf(source) {
2803
+ const name = source.replace(/\.git$/, "").split(/[/:\\]/).filter(Boolean).at(-1) ?? "";
2804
+ if (name === "." || name === ".." || name === "") throw new VendorFault(`Invalid path "${source}".`, "Use correct git path.");
2805
+ return new VendorSource(source, name);
2806
+ }
2807
+
2808
+ // src/hexagon/service/vendorRepo.ts
2809
+ async function loadVendorNames(repo, fileReaders) {
2810
+ return (await fileReaders.listFolders(vendorFolderIn(repo))).map((folder) => `${VENDOR_DIRECTORY}/${decodeURIComponent(folder.href.split("/").at(-2) ?? "")}`).sort();
2811
+ }
2812
+ async function ensureVCSReady(repo, vcs2, because) {
2813
+ if (!await vcs2.isInstalled(repo))
2814
+ throw new Fault(
2815
+ "This folder is not inside a git repository, and a vendor lives in one.",
2816
+ 'Run "git init" here, or run this again where your repository is.'
2817
+ );
2818
+ if (!await vcs2.isClean(repo))
2819
+ throw new Fault(
2820
+ `This repository has work in hand, and ${because}.`,
2821
+ "Commit or stash what you are holding, then run this again."
2822
+ );
2823
+ }
2824
+ async function addVendor(repo, vcs2, source, version) {
2825
+ await ensureVCSReady(repo, vcs2, "installing a vendor commits what it installs");
2826
+ const folder = `${VENDOR_DIRECTORY}/${vendorSourceOf(source).name}`;
2827
+ await vcs2.subtreeAdd(source, repo, folder, version);
2828
+ return folder;
2829
+ }
2830
+ async function removeVendor(repo, vcs2, name) {
2831
+ await ensureVCSReady(repo, vcs2, "removing a vendor commits that it is gone");
2832
+ const folder = `${VENDOR_DIRECTORY}/${vendorSourceOf(name).name}`;
2833
+ await vcs2.removeSubFolder(repo, folder);
2834
+ return folder;
2835
+ }
2836
+ async function driftedVendors(repo, vcs2) {
2837
+ const changed = await vcs2.changedUnder(repo, VENDOR_DIRECTORY);
2838
+ const names = changed.map((path) => path.slice(`${VENDOR_DIRECTORY}/`.length).split("/")[0]).filter((name) => name !== void 0 && name !== "");
2839
+ return [...new Set(names)].sort();
2840
+ }
2841
+
2842
+ // src/hexagon/domain/services/testService.ts
2843
+ var TestCaseReport = class {
2844
+ constructor(suiteName, situation, unmet) {
2845
+ this.suiteName = suiteName;
2846
+ this.situation = situation;
2847
+ this.unmet = unmet;
2848
+ }
2849
+ suiteName;
2850
+ situation;
2851
+ unmet;
2852
+ get passed() {
2853
+ return this.unmet === void 0;
2854
+ }
2855
+ };
2856
+ var TestRunReport = class {
2857
+ constructor(testCaseReports) {
2858
+ this.testCaseReports = testCaseReports;
2859
+ }
2860
+ testCaseReports;
2861
+ };
2862
+ function runSuite(charter, suiteName, suite) {
2863
+ return suite.cases.map((one) => runCase(charter, suiteName, one));
2864
+ }
2865
+ function findUntestedPrimitives(charter, testSuites2) {
2866
+ const testedIdentities = new Set(testSuites2.flatMap((testSuite2) => testSuite2.cases.map((one) => one.activatedIdentity)));
2867
+ const faultsByFiles = {};
2868
+ for (const { identity, file, primitive } of charter.primitives) {
2869
+ if (primitive.kind !== "guide" && primitive.kind !== "sensor" || testedIdentities.has(identity)) continue;
2870
+ faultsByFiles[file] = [
2871
+ new TestCaseFault(
2872
+ `No test case names "${identity}", so nothing notices when it stops coming up where it should.`,
2873
+ primitive.kind === "guide" ? `Add a case to a file under .cw/test/: { "do": { "touchFile": "<a file it speaks about>" }, "expect": { "activate": "${identity}" } }.` : `Add a case to a file under .cw/test/: { "when": "${primitive.headers.signal}", "expect": { "run": "${identity}" } }.`,
2874
+ "warn"
2875
+ )
2876
+ ];
2877
+ }
2878
+ return new FaultsByFile(faultsByFiles);
2879
+ }
2880
+ function runCase(charter, suiteName, one) {
2881
+ const written = one.written;
2882
+ if ("when" in written) {
2883
+ return new TestCaseReport(suiteName, one.describe(), assertScriptRun(charter, written.when, written.expect.run));
2884
+ }
2885
+ if ("do" in written) {
2886
+ const { touchFile } = written.do;
2887
+ if ("activate" in written.expect) {
2888
+ return new TestCaseReport(suiteName, one.describe(), assertPrimitiveActivated(charter, touchFile, written.expect.activate));
2889
+ }
2890
+ if ("allow" in written.expect) {
2891
+ return new TestCaseReport(suiteName, one.describe(), assertAllowed(charter, touchFile, written.expect.allow));
2892
+ }
2893
+ }
2894
+ throw new Error(`Never happen`);
2895
+ }
2896
+ function assertPrimitiveActivated(charter, file, identity) {
2897
+ const declared = charter.primitiveById.get(identity);
2898
+ if (declared === void 0) return primitiveNotFound(identity);
2899
+ if (declared.primitive.kind !== "guide")
2900
+ return new TestCaseFault(
2901
+ `Coming up when a file is touched is a guide's rule, and "${identity}" is a ${declared.primitive.kind}.`,
2902
+ `Expect a guide here, or put the situation that kind answers.`
2903
+ );
2904
+ const { globs = [] } = declared.primitive.headers;
2905
+ if (globs.length === 0)
2906
+ return new TestCaseFault(
2907
+ `"${identity}" names no files, so touching one never brings it up.`,
2908
+ `Give that guide "globs", or expect nothing of it here.`
2909
+ );
2910
+ return globs.some((glob) => matches(glob, file)) ? void 0 : new TestCaseFault(
2911
+ `This file matches none of the globs "${identity}" speaks about: ${globs.join(", ")}.`,
2912
+ `Widen that guide's "globs", or touch a file it already speaks about.`
2913
+ );
2914
+ }
2915
+ function assertAllowed(charter, file, expected) {
2916
+ const denying = charter.primitives.filter(
2917
+ ({ primitive }) => primitive.kind === "posture" && primitive.headers.deny.some((glob) => matches(glob, file))
2918
+ );
2919
+ if (expected)
2920
+ return denying.length === 0 ? void 0 : new TestCaseFault(
2921
+ `${denying.map((one) => `"${one.identity}"`).join(", ")} denies this file.`,
2922
+ `Drop it from that posture's "deny", or expect "allow": false.`
2923
+ );
2924
+ const postures = charter.primitives.filter(({ primitive }) => primitive.kind === "posture");
2925
+ if (postures.length === 0)
2926
+ return new TestCaseFault(
2927
+ `This charter holds no posture, so it refuses nothing.`,
2928
+ `Author a posture denying this file, or expect "allow": true.`
2929
+ );
2930
+ return denying.length > 0 ? void 0 : new TestCaseFault(
2931
+ `No posture denies this file.`,
2932
+ `Add it to a posture's "deny", or expect "allow": true.`
2933
+ );
2934
+ }
2935
+ function assertScriptRun(charter, event, identity) {
2936
+ const declared = charter.primitiveById.get(identity);
2937
+ if (declared === void 0) return primitiveNotFound(identity);
2938
+ if (declared.primitive.kind !== "sensor")
2939
+ return new TestCaseFault(
2940
+ `Running when an event is raised is a sensor's rule, and "${identity}" is a ${declared.primitive.kind}.`,
2941
+ `Expect a sensor here, or put the situation that kind answers.`
2942
+ );
2943
+ const { signal } = declared.primitive.headers;
2944
+ return signal === event ? void 0 : new TestCaseFault(
2945
+ `"${identity}" fires on ${signal}, not on ${event}.`,
2946
+ `Raise the event it names, or declare the "signal" this case raises.`
2947
+ );
2948
+ }
2949
+ function primitiveNotFound(identity) {
2950
+ return new TestCaseFault(
2951
+ `This charter holds no primitive called "${identity}".`,
2952
+ `Author it, or correct the name. "cw list --min" says every identity this charter does hold.`
2953
+ );
2954
+ }
2955
+
2956
+ // src/hexagon/domain/specifications/RepoScopedPrimitive.ts
2957
+ var RepoScopedPrimitive = class {
2958
+ static isSatisfiedBy(scopedPrimitive2) {
2959
+ return scopedPrimitive2.scope === REPO_SCOPE;
2960
+ }
2961
+ };
2962
+
2963
+ // src/hexagon/application/helper.ts
2964
+ async function contentHashOf(content) {
2965
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(content));
2966
+ return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
2967
+ }
2968
+
2969
+ // src/hexagon/domain/models/output/StampedDocument.ts
2970
+ var GENERATED_STAMP = "Generated by cherry-works";
2971
+ function stamp(document) {
2972
+ return `${document.replace(/\n+$/, "")}
2973
+
2974
+ <!-- ${GENERATED_STAMP}. Do not edit; edit the charter and build again. -->
2975
+ `;
2976
+ }
2977
+ function isStamped(contents) {
2978
+ return contents.includes(GENERATED_STAMP);
2979
+ }
2980
+
2981
+ // src/hexagon/domain/models/output/CharterOutput.ts
2982
+ var Catalogue = class _Catalogue {
2983
+ constructor(entries) {
2984
+ this.entries = entries;
2985
+ }
2986
+ entries;
2987
+ static of(entries) {
2988
+ return new _Catalogue(
2989
+ [...entries].sort((one, another) => one.identity < another.identity ? -1 : one.identity > another.identity ? 1 : 0)
2990
+ );
2991
+ }
2992
+ /** This listing is the charter's own file: it says what the charter holds now,
2993
+ * and nothing else has anything to say in it. */
2994
+ projection = "replace";
2995
+ /**
2996
+ * The same listing, narrowed to the primitives of one kind (FR-011).
2997
+ *
2998
+ * A narrowed listing is a listing: what comes back answers `full` and
2999
+ * `compact` the way this does, so whoever reads it never has to ask whether it
3000
+ * was narrowed. Nothing is re-sorted — the entries keep the order they were
3001
+ * put in, which is the order by identity `of` gave them.
3002
+ */
3003
+ filterByKind(kind) {
3004
+ return new _Catalogue(this.entries.filter((one) => one.kind === kind));
3005
+ }
3006
+ /** Everything every primitive's headers declare about it, and the file its body
3007
+ * is in (FR-011). */
3008
+ get full() {
3009
+ return this.entries;
3010
+ }
3011
+ /**
3012
+ * The same primitives as an agent surveys them: what each is, what it is
3013
+ * called, and the one line saying what it is for (FR-012).
3014
+ *
3015
+ * This is what an agent reads first, so it holds nothing an agent surveying the
3016
+ * charter would not use — no globs, no tags, and no file. Having found what it
3017
+ * wants here, it asks `full` where that identity lives.
3018
+ */
3019
+ get compact() {
3020
+ return this.entries.map(({ identity, kind, id, description }) => ({ identity, kind, id, description }));
3021
+ }
3022
+ };
3023
+ var CharterMd = class _CharterMd {
3024
+ constructor(body) {
3025
+ this.body = body;
3026
+ }
3027
+ body;
3028
+ static type = "charter-md";
3029
+ type = _CharterMd.type;
3030
+ static of(kinds) {
3031
+ return new _CharterMd(`${[
3032
+ "# Charter",
3033
+ `This repository is governed by a charter \u2014 the standards it authored under \`${CHARTER_DIRECTORY}/\`, which constrain whatever coding agent runs here. Every surface an agent reads, this file included, is generated from that charter: do not edit them, edit the primitive behind them.`,
3034
+ "## Read first",
3035
+ `[catalog.min.json](./catalog.min.json) \u2014 the kind, identity and description of every primitive this charter holds. Survey it to find what you need; open [catalog.json](./catalog.json) for that primitive's file, globs and mixins, and open a body only once its activation condition below is met.`,
3036
+ "## When each kind applies",
3037
+ kinds.map((one) => `- **${one.kind}** \u2014 ${one.activatesWhen}.`).join("\n"),
3038
+ "## One identity, one primitive",
3039
+ `An identity names one primitive in the whole charter: \`kind:id\`, whether this repository authored it, installed it from a vendor, or the engine brought it. Nothing overrides anything \u2014 two files claiming one identity is an error the charter refuses to build with.`
3040
+ ].join("\n\n")}
3041
+ `);
3042
+ }
3043
+ /** This file is the charter's own: it is written whole over whatever is
3044
+ * there. */
3045
+ projection = "replace";
3046
+ /** This file as a reader opens it: the stamp saying this engine wrote it, and
3047
+ * the orientation itself. */
3048
+ toStampedDocument() {
3049
+ return stamp(this.body);
3050
+ }
3051
+ };
3052
+
3053
+ // src/hexagon/domain/models/output/providers/claude/ClaudeSettingsComponent.ts
3054
+ var CLAUDE_HOOK_EVENTS = [
3055
+ "PreToolUse",
3056
+ "PostToolUse",
3057
+ "UserPromptSubmit",
3058
+ "Notification",
3059
+ "Stop",
3060
+ "SubagentStop",
3061
+ "PreCompact",
3062
+ "SessionStart",
3063
+ "SessionEnd"
3064
+ ];
3065
+ function isClaudeHookEvent(name) {
3066
+ return CLAUDE_HOOK_EVENTS.includes(name);
3067
+ }
3068
+ var ClaudeSettingsComponent = class _ClaudeSettingsComponent {
3069
+ constructor(settings) {
3070
+ this.settings = settings;
3071
+ }
3072
+ settings;
3073
+ static kind = "settings";
3074
+ kind = _ClaudeSettingsComponent.kind;
3075
+ /** The settings file this host reads is shared with whatever the repository set
3076
+ * for itself, so what is put down there is written into what is there rather
3077
+ * than in place of it (FR-018). */
3078
+ projection = "mergeJSON";
3079
+ static of(settings) {
3080
+ return new _ClaudeSettingsComponent(settings);
3081
+ }
3082
+ };
3083
+
3084
+ // src/hexagon/domain/models/output/providers/claude/document-based-components/DocumentBasedComponent.ts
3085
+ var DocumentBasedComponent = class {
3086
+ constructor(name, headers, body) {
3087
+ this.name = name;
3088
+ this.headers = headers;
3089
+ this.body = body;
3090
+ }
3091
+ name;
3092
+ headers;
3093
+ body;
3094
+ /** A document is this primitive's alone, so putting it down replaces whatever
3095
+ * is there (FR-020). */
3096
+ projection = "replace";
3097
+ /**
3098
+ * This document as the file this host reads: its frontmatter, its body, and
3099
+ * the stamp saying this engine wrote it.
3100
+ *
3101
+ * Called where a projection is put on disk. A kind that declares no frontmatter
3102
+ * gets none.
3103
+ *
3104
+ * Every one of them carries the stamp, which is what a reader is told and what
3105
+ * a build reads back before taking a file away — a document without it is
3106
+ * someone's own and is left alone (FR-017, FR-020).
3107
+ */
3108
+ toStampedDocument() {
3109
+ const headers = Object.entries(this.headers);
3110
+ return stamp(
3111
+ [
3112
+ // A kind whose file this host reads whole declares no frontmatter, and
3113
+ // gets none: an empty block would be this engine writing a field for
3114
+ // this host to read, and there is none to write.
3115
+ ...headers.length === 0 ? [] : [["---", ...headers.map(([field, value]) => `${field}: ${formatFrontmatterValue(value)}`), "---"].join("\n")],
3116
+ this.body
3117
+ ].filter((block) => block !== "").join("\n\n")
3118
+ );
3119
+ }
3120
+ };
3121
+
3122
+ // src/hexagon/domain/models/output/providers/claude/document-based-components/ClaudeAgentComponent.ts
3123
+ var ClaudeAgentComponent = class _ClaudeAgentComponent extends DocumentBasedComponent {
3124
+ static kind = "agent";
3125
+ kind = _ClaudeAgentComponent.kind;
3126
+ static of(name, declared, body) {
3127
+ return new _ClaudeAgentComponent(name, { name, ...declared }, body);
3128
+ }
3129
+ };
3130
+
3131
+ // src/hexagon/domain/models/output/providers/claude/document-based-components/ClaudeCommandComponent.ts
3132
+ var ClaudeCommandComponent = class _ClaudeCommandComponent extends DocumentBasedComponent {
3133
+ static kind = "command";
3134
+ kind = _ClaudeCommandComponent.kind;
3135
+ static of(name, headers, body) {
3136
+ return new _ClaudeCommandComponent(name, headers, body);
3137
+ }
3138
+ };
3139
+
3140
+ // src/hexagon/domain/models/output/providers/claude/document-based-components/ClaudeRuleComponent.ts
3141
+ var ClaudeRuleComponent = class _ClaudeRuleComponent extends DocumentBasedComponent {
3142
+ static kind = "rule";
3143
+ kind = _ClaudeRuleComponent.kind;
3144
+ static of(name, headers, body) {
3145
+ return new _ClaudeRuleComponent(name, headers, body);
3146
+ }
3147
+ };
3148
+
3149
+ // src/hexagon/domain/models/output/providers/claude/document-based-components/ClaudeSkillComponent.ts
3150
+ var ClaudeSkillComponent = class _ClaudeSkillComponent extends DocumentBasedComponent {
3151
+ static kind = "skill";
3152
+ kind = _ClaudeSkillComponent.kind;
3153
+ static of(name, declared, body) {
3154
+ return new _ClaudeSkillComponent(name, { name, ...declared }, body);
3155
+ }
3156
+ };
3157
+
3158
+ // src/hexagon/domain/services/compileService.ts
3159
+ function compile(charter, agents) {
3160
+ return {
3161
+ // A header nobody wrote is left out rather than listed as nothing: a
3162
+ // listing says what the author declared. `file` is where the body sits
3163
+ // inside the workspace — a vendored primitive reads as
3164
+ // `.cw/vendor/<name>/...`, which is where it is.
3165
+ catalogue: Catalogue.of(
3166
+ charter.primitives.map((one) => {
3167
+ const { id, description, tags, globs, rationale, mixins } = one.primitive.headers;
3168
+ return {
3169
+ identity: one.identity,
3170
+ kind: one.primitive.kind,
3171
+ id,
3172
+ description,
3173
+ file: one.file,
3174
+ ...tags === void 0 ? {} : { tags },
3175
+ ...globs === void 0 ? {} : { globs },
3176
+ ...rationale === void 0 ? {} : { rationale },
3177
+ ...mixins === void 0 ? {} : { mixins }
3178
+ };
3179
+ })
3180
+ ),
3181
+ charterMd: CharterMd.of(PRIMITIVE_CLASSES),
3182
+ providerComponents: agents.flatMap((agent) => forAgent(agent, charter))
3183
+ };
3184
+ }
3185
+ function forAgent(agent, charter) {
3186
+ switch (agent) {
3187
+ case "claude":
3188
+ return compileClaude(charter);
3189
+ }
3190
+ }
3191
+ function compileClaude(charter) {
3192
+ const asSettings = charter.primitives.filter(isSettingComponent);
3193
+ const asDocuments = charter.primitives.filter((one) => !isSettingComponent(one));
3194
+ return [
3195
+ ...asSettings.length === 0 ? [] : [ClaudeSettingsComponent.of(claudeSettingsOf(asSettings))],
3196
+ ...asDocuments.flatMap((one) => claudeDocumentComponentOf(charter, one) ?? [])
3197
+ ];
3198
+ }
3199
+ function isSettingComponent(one) {
3200
+ return one.primitive.kind === PosturePrimitive.kind || one.primitive.kind === SensorPrimitive.kind;
3201
+ }
3202
+ function claudeSettingsOf(ones) {
3203
+ const allow = /* @__PURE__ */ new Set();
3204
+ const deny = /* @__PURE__ */ new Set();
3205
+ const hooks = {};
3206
+ for (const one of ones) {
3207
+ if (one.primitive.kind === PosturePrimitive.kind) {
3208
+ const headers = one.primitive.headers;
3209
+ for (const permission of headers.allow) allow.add(permission);
3210
+ for (const permission of headers.deny) deny.add(permission);
3211
+ }
3212
+ if (one.primitive.kind === SensorPrimitive.kind) {
3213
+ const { signal, run: run2 } = one.primitive.headers;
3214
+ if (isClaudeHookEvent(signal))
3215
+ hooks[signal] = [...hooks[signal] ?? [], { hooks: [{ type: "command", command: run2 }] }];
3216
+ }
3217
+ }
3218
+ return {
3219
+ ...allow.size === 0 && deny.size === 0 ? {} : { permissions: { allow: [...allow], deny: [...deny] } },
3220
+ ...Object.keys(hooks).length === 0 ? {} : { hooks }
3221
+ };
3222
+ }
3223
+ function claudeDocumentComponentOf(charter, sc) {
3224
+ const name = claudeNameOf(sc);
3225
+ const body = charter.bodyOf(sc);
3226
+ switch (sc.primitive.kind) {
3227
+ // A guide is a rule this host loads into context whole: when a file it names
3228
+ // is touched, which is what `paths` on a rule does, and at the start of every
3229
+ // session where it names none (FR-013). Under the identity it is changed by,
3230
+ // so a reader who wants it changed is sent to the primitive rather than
3231
+ // editing what the next build overwrites (FR-017, FR-020).
3232
+ case GuidePrimitive.kind: {
3233
+ const { globs = [] } = sc.primitive.headers;
3234
+ return ClaudeRuleComponent.of(
3235
+ name,
3236
+ globs.length === 0 ? {} : { paths: globs },
3237
+ `${[`## ${sc.identity}`, body].join("\n\n")}
3238
+ `
3239
+ );
3240
+ }
3241
+ // Named by the identifier alone, because the filename is what someone types
3242
+ // after the slash: this host keeps commands in a directory of their own, so
3243
+ // no other kind can land beside one and the kind would only be typed twice.
3244
+ case CommandPrimitive.kind:
3245
+ return ClaudeCommandComponent.of(sc.primitive.headers.id, { description: sc.primitive.description() }, body);
3246
+ case AgentPrimitive.kind: {
3247
+ const { tools } = sc.primitive.headers;
3248
+ return ClaudeAgentComponent.of(name, { description: sc.primitive.description(), tools: tools.join(", ") }, body);
3249
+ }
3250
+ // One kind of this host for the two the charter loads when the request calls
3251
+ // for them: what decides that the body is worth opening is the description,
3252
+ // because that line is all this host reads before deciding (FR-013), and
3253
+ // composing it is the primitive's.
3254
+ case SkillPrimitive.kind:
3255
+ case PlaybookPrimitive.kind:
3256
+ return ClaudeSkillComponent.of(name, { description: sc.primitive.description() }, body);
3257
+ default:
3258
+ return void 0;
3259
+ }
3260
+ }
3261
+ function claudeNameOf(one) {
3262
+ return one.identity.replace(/[:/]/g, "-");
3263
+ }
3264
+
3265
+ // src/hexagon/domain/services/projectionService.ts
3266
+ function charterOutputProjection(output, agentProviders) {
3267
+ return [
3268
+ // Indented: this one is committed and read in diffs, and a listing whose
3269
+ // entries land one per line says what changed in the charter rather than
3270
+ // that the listing changed.
3271
+ {
3272
+ path: `${OUT_DIRECTORY}/catalog.json`,
3273
+ contents: `${JSON.stringify(output.catalogue.full, void 0, 2)}
3274
+ `,
3275
+ projectionPolicy: output.catalogue.projection
3276
+ },
3277
+ // Not indented: this one is read to be surveyed cheaply, and the whitespace
3278
+ // is bytes an agent pays for and no one reads (SC-005).
3279
+ {
3280
+ path: `${OUT_DIRECTORY}/catalog.min.json`,
3281
+ contents: `${JSON.stringify(output.catalogue.compact)}
3282
+ `,
3283
+ projectionPolicy: output.catalogue.projection
3284
+ },
3285
+ {
3286
+ path: `${OUT_DIRECTORY}/CHARTER.md`,
3287
+ contents: output.charterMd.toStampedDocument(),
3288
+ projectionPolicy: output.charterMd.projection
3289
+ },
3290
+ ...agentProviders.map(entryFileProjection),
3291
+ ...output.providerComponents.map(componentProjection)
3292
+ ];
3293
+ }
3294
+ function entryFileProjection(provider) {
3295
+ const contents = `<!-- CHERRYWORKS START -->
3296
+ ${[
3297
+ "## Charter",
3298
+ `This repository is governed by a charter. Read [CHARTER.md](./${OUT_DIRECTORY}/CHARTER.md) before anything else here, and work under what it says.`,
3299
+ "It is generated from the charter under `.cw/charter/`: do not edit it, edit the primitive behind it."
3300
+ ].join("\n\n")}
3301
+ <!-- CHERRYWORKS END -->
3302
+ `;
3303
+ let path;
3304
+ switch (provider) {
3305
+ case "claude":
3306
+ path = CLAUDE_ENTRY_FILE;
3307
+ break;
3308
+ }
3309
+ return { path, contents, projectionPolicy: "upsertWithMarker" };
3310
+ }
3311
+ function componentProjection(one) {
3312
+ const projectionPolicy = one.projection;
3313
+ switch (one.kind) {
3314
+ case ClaudeCommandComponent.kind:
3315
+ return { path: `${CLAUDE_DIRECTORY}/commands/${one.name}.md`, contents: one.toStampedDocument(), projectionPolicy };
3316
+ case ClaudeAgentComponent.kind:
3317
+ return { path: `${CLAUDE_DIRECTORY}/agents/${one.name}.md`, contents: one.toStampedDocument(), projectionPolicy };
3318
+ // A skill is a directory holding one file of that name, which is where this
3319
+ // host looks for it.
3320
+ case ClaudeSkillComponent.kind:
3321
+ return {
3322
+ path: `${CLAUDE_DIRECTORY}/skills/${one.name}/SKILL.md`,
3323
+ contents: one.toStampedDocument(),
3324
+ projectionPolicy
3325
+ };
3326
+ // A rule of its own file, under the directory this host loads every one of:
3327
+ // at the start of a session where it declares no `paths`, and when one of
3328
+ // them is touched where it does.
3329
+ case ClaudeRuleComponent.kind:
3330
+ return { path: `${CLAUDE_DIRECTORY}/rules/${one.name}.md`, contents: one.toStampedDocument(), projectionPolicy };
3331
+ // Every posture and every sensor of the charter lands in the one file this
3332
+ // host reads its settings from.
3333
+ case ClaudeSettingsComponent.kind:
3334
+ return {
3335
+ path: `${CLAUDE_DIRECTORY}/settings.json`,
3336
+ contents: `${JSON.stringify(one.settings, void 0, 2)}
3337
+ `,
3338
+ projectionPolicy
3339
+ };
3340
+ }
3341
+ }
3342
+
3343
+ // src/hexagon/service/buildService.ts
3344
+ async function plan(repo, charter, agentProviders, fileReaders) {
3345
+ const [cleanupPlan, projectionPlan] = await Promise.all([
3346
+ planForCleanup(repo, agentProviders, fileReaders),
3347
+ planForProjection(repo, compile(charter, agentProviders), agentProviders, fileReaders)
3348
+ ]);
3349
+ return { cleanupPlan, projectionPlan };
3350
+ }
3351
+ async function planForProjection(repo, output, agentProviders, fileReaders) {
3352
+ const planned = /* @__PURE__ */ new Map();
3353
+ for (const one of charterOutputProjection(output, agentProviders)) {
3354
+ const file = new URL(one.path, repo);
3355
+ const held = await fileReaders.readIfThere(file);
3356
+ let contents;
3357
+ switch (one.projectionPolicy) {
3358
+ case "replace":
3359
+ contents = one.contents;
3360
+ break;
3361
+ // What the repository set for itself is read and written back: the fields
3362
+ // this charter speaks for are put over the top of it, and every other one
3363
+ // is theirs and comes through untouched (FR-018).
3364
+ case "mergeJSON": {
3365
+ const already = JSON.parse(held?.trim() || "{}");
3366
+ contents = `${JSON.stringify({ ...already, ...JSON.parse(one.contents) }, void 0, 2)}
3367
+ `;
3368
+ break;
3369
+ }
3370
+ // A section says where it starts and ends itself — its first line and its
3371
+ // last — so nothing here knows what a marker of this engine's looks like,
3372
+ // and a second output written this way brings its own (FR-051).
3373
+ case "upsertWithMarker": {
3374
+ const already = held ?? "";
3375
+ const lines3 = one.contents.trimEnd().split("\n");
3376
+ const [start, end] = [lines3.at(0) ?? "", lines3.at(-1) ?? ""].map(
3377
+ (line) => line.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
3378
+ );
3379
+ const section = new RegExp(`${start}[\\s\\S]*?${end}\\n?`, "g");
3380
+ if (already.match(section) === null) {
3381
+ contents = already === "" ? one.contents : `${already.replace(/\n*$/, "\n")}
3382
+ ${one.contents}`;
3383
+ break;
3384
+ }
3385
+ let written = false;
3386
+ contents = already.replace(section, () => {
3387
+ if (written) return "";
3388
+ written = true;
3389
+ return one.contents;
3390
+ });
3391
+ break;
3392
+ }
3393
+ }
3394
+ planned.set(one.path, { path: one.path, file, contents, held });
3395
+ }
3396
+ return [...planned.values()];
3397
+ }
3398
+ async function planForCleanup(repo, agentProviders, fileReaders) {
3399
+ const outFolder = outFolderIn(repo);
3400
+ const folders = [outFolder, ...agentProviders.map((agent) => agentProviderFolderIn(repo, agent))];
3401
+ const found = await Promise.all(folders.map((folder) => fileReaders.readFilesRecursively(folder)));
3402
+ return found.flat().filter(({ file, contents }) => file.href.startsWith(outFolder.href) || isStamped(contents)).map(({ file, contents }) => ({ path: decodeURIComponent(file.href.slice(repo.href.length)), file, held: contents }));
3403
+ }
3404
+ var PlanSummary = class {
3405
+ constructor(added, edited, deleted, unchanged) {
3406
+ this.added = added;
3407
+ this.edited = edited;
3408
+ this.deleted = deleted;
3409
+ this.unchanged = unchanged;
3410
+ }
3411
+ added;
3412
+ edited;
3413
+ deleted;
3414
+ unchanged;
3415
+ };
3416
+ function previewPlan(cleanupPlan, projectionPlan) {
3417
+ return new PlanSummary(
3418
+ projectionPlan.filter((one) => one.held === void 0).map((one) => one.path),
3419
+ projectionPlan.filter((one) => one.held !== void 0 && one.held !== one.contents).map((one) => one.path),
3420
+ cleanupPlan.filter((one) => !projectionPlan.some((other) => other.path === one.path)).map((one) => one.path),
3421
+ projectionPlan.filter((one) => one.held === one.contents).map((one) => one.path)
3422
+ );
3423
+ }
3424
+ async function executePlan(cleanupPlan, projectionPlan, fileWriter2) {
3425
+ try {
3426
+ await Promise.all(cleanupPlan.map((one) => fileWriter2.delete(one.file)));
3427
+ for (const one of projectionPlan) await fileWriter2.write(one.file, one.contents ?? "");
3428
+ } catch (raised) {
3429
+ throw new Fault(
3430
+ `This build could not be written: ${raised instanceof Error ? raised.message : String(raised)}`,
3431
+ "Check that the files this build writes can be written, then build again."
3432
+ );
3433
+ }
3434
+ }
3435
+
3436
+ // src/hexagon/application/CharterAuthoring.ts
3437
+ var CharterAuthoring = class {
3438
+ #repoPath;
3439
+ #fileReader;
3440
+ #yamlParser;
3441
+ #fileWriter;
3442
+ #vcs;
3443
+ constructor(repoPath2, fileReader2, yamlParser2, fileWriter2, vcs2) {
3444
+ this.#repoPath = repoPath2;
3445
+ this.#fileReader = fileReader2;
3446
+ this.#yamlParser = yamlParser2;
3447
+ this.#fileWriter = fileWriter2;
3448
+ this.#vcs = vcs2;
3449
+ }
3450
+ /**
3451
+ * The four questions about this repository, asked in one go (FR-040): which
3452
+ * agents it compiles for, what validating its charter finds, which installed
3453
+ * charters are edited here, and how many of those are unwell.
3454
+ *
3455
+ * Each is asked of whoever already answers it — the settings, the charter's
3456
+ * validation, version control — and whether each is well is worked out here,
3457
+ * so what `cw doctor` says and what the portal's health check says are one
3458
+ * answer said twice (SC-003). Reads and says: nothing here writes (FR-041).
3459
+ *
3460
+ * Where the charter holds, what a build would still change is the preview run
3461
+ * for it, and every guide and sensor no test case names is a warning too, so
3462
+ * the test files are read here: a file that will not read names no case, and
3463
+ * `cw test` is what refuses it (FR-014). Nothing else reads them for this — a
3464
+ * warning stops nothing, and every other use case asks only for the errors.
3465
+ * Where it does not hold, nothing is previewed (FR-005, FR-009).
3466
+ */
3467
+ async doctor() {
3468
+ const [settings, [charter, , faultsByFiles], drifted, testRoot] = await Promise.all([
3469
+ loadSettings(this.#repoPath, this.#fileReader),
3470
+ this.#read(),
3471
+ driftedVendors(this.#repoPath, this.#vcs),
3472
+ loadTestRoot(this.#repoPath, this.#fileReader)
3473
+ ]);
3474
+ if (charter === void 0) return doctorOutcomeDTO(settings, faultsByFiles, void 0, drifted, this.#repoPath);
3475
+ const { cleanupPlan, projectionPlan } = await plan(this.#repoPath, charter, settings.agents, this.#fileReader);
3476
+ return doctorOutcomeDTO(
3477
+ settings,
3478
+ faultsByFiles.with(findUntestedPrimitives(charter, Object.values(testRoot.suitesByFile))),
3479
+ previewPlan(cleanupPlan, projectionPlan),
3480
+ drifted,
3481
+ this.#repoPath
3482
+ );
3483
+ }
3484
+ /** Everything wrong with this repository's charter under the file that has to
3485
+ * change, and — where none of it is an error — the charter and what the
3486
+ * repository configured itself with (FR-005, FR-009): what every use case
3487
+ * here reads before it asks the charter anything. Settings that do not read
3488
+ * are neither: there is nothing to validate against until they do, so
3489
+ * reading them raises rather than reports. */
3490
+ async #read() {
3491
+ const [charter, settings] = await Promise.all([
3492
+ loadCharterRoot(this.#repoPath, this.#fileReader, this.#yamlParser),
3493
+ loadSettings(this.#repoPath, this.#fileReader)
3494
+ ]);
3495
+ const faultsByFiles = charter.allFaultsByFiles;
3496
+ return faultsByFiles.errors().isEmpty ? [charter, settings, faultsByFiles] : [void 0, void 0, faultsByFiles];
3497
+ }
3498
+ /**
3499
+ * What the charter holds, listed, narrowed to one kind where one is named
3500
+ * (FR-011, FR-012).
3501
+ *
3502
+ * The listing is the one the charter compiles to, asked for the way everything
3503
+ * else is: `compile` hands back everything one reading produces, and this
3504
+ * takes the listing out of it (SC-004). No agent is named, since what an agent
3505
+ * reads is not what is being asked for here — a repository that compiles for
3506
+ * none still lists what it holds.
3507
+ *
3508
+ * Whether the word narrowing it is a kind at all is asked here, of the
3509
+ * domain that holds the set. A caller passes on what was typed and knows
3510
+ * nothing about what a kind is.
3511
+ *
3512
+ * Reads and says: this holds a writing port and never reaches for it, which is
3513
+ * what keeps a reading use case unable to write (FR-041).
3514
+ */
3515
+ async list(kind) {
3516
+ if (kind !== void 0 && !isKind(kind))
3517
+ throw new Fault(`"${kind}" is not a kind the charter knows.`, `Ask for one the charter knows: ${KINDS.join(", ")}.`);
3518
+ const [charter, , faultsByFiles] = await this.#read();
3519
+ if (charter === void 0) return faultsByFileDTO(faultsByFiles.errors(), this.#repoPath);
3520
+ const { catalogue: catalogue2 } = compile(charter, []);
3521
+ return catalogueDTO(kind === void 0 ? catalogue2 : catalogue2.filterByKind(kind));
3522
+ }
3523
+ /** Every primitive the charter read, or those mentioning a word, each as the
3524
+ * whole charter sees it (FR-112, FR-114). Whether one mentions the word is
3525
+ * the primitive's own to say. */
3526
+ async fullList(matching) {
3527
+ const [charter, , faultsByFiles] = await this.#read();
3528
+ if (charter === void 0) return faultsByFileDTO(faultsByFiles.errors(), this.#repoPath);
3529
+ return scopedPrimitivesDTO(
3530
+ matching === void 0 ? charter.primitives : charter.primitives.filter((one) => one.mentions(matching))
3531
+ );
3532
+ }
3533
+ /**
3534
+ * Which file declares one identity, which layer it arrived in, when it comes
3535
+ * up, the mixins it uses and the corpus it cites, what uses or cites it, and
3536
+ * the situations this repository wrote down about it (FR-014, FR-017).
3537
+ *
3538
+ * The charter is asked, not searched here: one identity space covers every
3539
+ * layer, and which file answers to a name, and which names one primitive
3540
+ * shares with another, are the charter's own questions (FR-014). What is here
3541
+ * is the order — read, refuse a charter that does not hold, then ask.
3542
+ *
3543
+ * An identity nothing answers to is raised rather than reported, the way a
3544
+ * word that is no kind is: there is no file it is wrong with, and the next
3545
+ * move is to go and look at what the charter does hold.
3546
+ *
3547
+ * Reads and says: this holds a writing port and never reaches for it (FR-041).
3548
+ */
3549
+ async explain(identity) {
3550
+ const primitiveIdentity = identityOf(identity);
3551
+ const [charter, , faultsByFiles] = await this.#read();
3552
+ if (charter === void 0) return faultsByFileDTO(faultsByFiles.errors(), this.#repoPath);
3553
+ const declared = charter.primitiveById.get(primitiveIdentity);
3554
+ if (declared === void 0)
3555
+ throw new Fault(
3556
+ `This charter holds no "${identity}".`,
3557
+ 'Run "cw list --min" to see every identity it does hold.'
3558
+ );
3559
+ const testRoot = await loadTestRoot(this.#repoPath, this.#fileReader);
3560
+ const testCasesByFile2 = Object.fromEntries(
3561
+ Object.entries(testRoot.suitesByFile).map(([file, suite]) => [
3562
+ file,
3563
+ suite.cases.filter((each) => each.activatedIdentity === primitiveIdentity).map((each) => each.describe())
3564
+ ]).filter(([, situations]) => situations.length > 0)
3565
+ );
3566
+ return explanationOutcomeDTO(
3567
+ declared,
3568
+ // When it comes up is its kind's to say, in the words `cw kinds` says it
3569
+ // in, so it is read off the class that reads the kind (FR-029).
3570
+ PRIMITIVE_CLASSES.find((one) => one.kind === declared.primitive.kind).activatesWhen,
3571
+ charter.mixinsOf(declared),
3572
+ charter.rationaleOf(declared),
3573
+ charter.hostsOf(declared),
3574
+ charter.citersOf(declared),
3575
+ testCasesByFile2
3576
+ );
3577
+ }
3578
+ /**
3579
+ * One reading of the charter, put on disk (FR-020, FR-021).
3580
+ *
3581
+ * Read, ask what is wrong, take away what the last build left, then compile and
3582
+ * write. The order is the whole of this method: a charter with an error is
3583
+ * refused before anything is touched, so a half-built surface never stands —
3584
+ * and a warning is not an error, so a rationale citing a corpus that is gone
3585
+ * still builds (FR-005, FR-009).
3586
+ *
3587
+ * Which agents are compiled for is what the repository said at setup and
3588
+ * keeps in `.cw/settings.json`, never what happens to be installed on the
3589
+ * machine running this: the same charter checked out anywhere builds the same
3590
+ * files (FR-033). A repository naming none still gets its neutral surface
3591
+ * (FR-019), and one whose settings do not read is sent back to `cw init`
3592
+ * rather than built against a guess.
3593
+ *
3594
+ * What a file holds and where it goes is `compile` and the two plans; what is
3595
+ * here is the order of the three and the ports that make it possible.
3596
+ */
3597
+ async build() {
3598
+ const [charter, settings, faultsByFiles] = await this.#read();
3599
+ if (charter === void 0) return faultsByFileDTO(faultsByFiles.errors(), this.#repoPath);
3600
+ const { cleanupPlan, projectionPlan } = await plan(this.#repoPath, charter, settings.agents, this.#fileReader);
3601
+ const planSummary2 = previewPlan(cleanupPlan, projectionPlan);
3602
+ await executePlan(cleanupPlan, projectionPlan, this.#fileWriter);
3603
+ return planSummaryDTO(planSummary2);
3604
+ }
3605
+ /**
3606
+ * The build, worked out and stopped short of disk (FR-022).
3607
+ *
3608
+ * The same two plans a build carries out, read rather than run: planning only
3609
+ * reads, and what a preview adds is `previewPlan` in place of `executePlan`.
3610
+ * It holds a writing port, as everything here does, and reaches for none of it
3611
+ * (FR-041).
3612
+ */
3613
+ async preview() {
3614
+ const [charter, settings, faultsByFiles] = await this.#read();
3615
+ if (charter === void 0) return faultsByFileDTO(faultsByFiles.errors(), this.#repoPath);
3616
+ const { cleanupPlan, projectionPlan } = await plan(this.#repoPath, charter, settings.agents, this.#fileReader);
3617
+ return planSummaryDTO(previewPlan(cleanupPlan, projectionPlan));
3618
+ }
3619
+ /**
3620
+ * Every situation this repository wrote down, resolved against its charter
3621
+ * (FR-048, FR-049, FR-050).
3622
+ *
3623
+ * The same two steps every use case here is: read the charter, then ask it.
3624
+ * What is added is the cases, read off `.cw/test/` by whoever knows where they
3625
+ * live, and the resolving each one comes out of — which runs nothing, fetches
3626
+ * nothing and asks no agent, so a run is decided by the charter alone.
3627
+ *
3628
+ * A charter with an error resolves nothing, for the reason a listing lists
3629
+ * nothing, and neither does a test file that is there and will not read — both
3630
+ * come back as faults under the file that has to change (FR-009). A repository
3631
+ * that wrote no tests resolves nothing either, and that is no fault: it has
3632
+ * not pinned its charter down yet, and the command says so.
3633
+ *
3634
+ * Reads and says: this holds a writing port and never reaches for it (FR-041).
3635
+ */
3636
+ async test() {
3637
+ const [charter, , faultsByFiles] = await this.#read();
3638
+ if (charter === void 0) return faultsByFileDTO(faultsByFiles.errors(), this.#repoPath);
3639
+ const testRoot = await loadTestRoot(this.#repoPath, this.#fileReader);
3640
+ if (!testRoot.faultsByFiles.isEmpty) return faultsByFileDTO(testRoot.faultsByFiles, this.#repoPath);
3641
+ return testRunReportDTO(
3642
+ new TestRunReport(
3643
+ Object.entries(testRoot.suitesByFile).flatMap(([file, suite]) => runSuite(charter, testSuiteNameOf(file), suite))
3644
+ )
3645
+ );
3646
+ }
3647
+ /** What one kind takes of whoever authors a primitive of it, asked of the
3648
+ * kind itself (FR-004, FR-039). No charter is read: a kind's contract is the
3649
+ * same in a repository that has authored nothing. A word that is no kind is
3650
+ * raised the way a listing raises it — there is no file it is wrong with. */
3651
+ async listPrimitiveRequirements(kind) {
3652
+ const primitiveKind = this.#kindOf(kind);
3653
+ return primitiveRequirementsDTO(
3654
+ new PrimitiveRequirements(primitiveHeadersOf(primitiveKind), primitiveSampleOf(primitiveKind))
3655
+ );
3656
+ }
3657
+ /** Every kind the charter knows, and when a primitive of that kind comes up
3658
+ * (FR-010). Asked of the classes themselves, as the requirements above are:
3659
+ * no charter is read, and a kind this repository has authored nothing of is a
3660
+ * kind all the same. */
3661
+ async kinds() {
3662
+ return primitiveKindsDTO(PRIMITIVE_CLASSES);
3663
+ }
3664
+ /**
3665
+ * One primitive written where its kind is authored (FR-039), with the body
3666
+ * its author typed — empty from the command line, where the body is written
3667
+ * in an editor afterwards (FR-117).
3668
+ *
3669
+ * Nothing is validated and nothing is asked of the charter but which
3670
+ * identities it already holds. What arrives is what an author answered, and
3671
+ * reading it as the primitive it claims to be is what refuses an answer its
3672
+ * kind will not take — the same reading a file of it would get, so there is no
3673
+ * second contract here to keep in step with the kind's (FR-004). Where it goes
3674
+ * is the convention: a directory per kind, since nothing reads the directory
3675
+ * (FR-002).
3676
+ *
3677
+ * An identity the charter already holds, in any layer, is refused naming the
3678
+ * file holding it: one identity names one primitive in the whole charter, and
3679
+ * a second file claiming it is a collision the next read would report
3680
+ * (FR-014). The files are read without being validated, so a charter wrong
3681
+ * somewhere else can still be added to.
3682
+ */
3683
+ async add(kindWord, id, headers, body = "") {
3684
+ const kind = this.#kindOf(kindWord);
3685
+ let primitive;
3686
+ try {
3687
+ primitive = primitiveOf({ headers: { ...headers, kind, id }, body });
3688
+ } catch (raised) {
3689
+ if (!(raised instanceof AggregateError)) throw raised;
3690
+ return faultsDTO(new Faults(raised.errors));
3691
+ }
3692
+ const charter = await loadCharterRoot(this.#repoPath, this.#fileReader, this.#yamlParser);
3693
+ const claimingPrimitive = charter.primitiveById.get(identityOf({ kind, id }));
3694
+ if (claimingPrimitive !== void 0)
3695
+ throw new Fault(
3696
+ `"${identityOf({ kind, id })}" is already there, declared by ${claimingPrimitive.file}, and one identity names one primitive in the whole charter.`,
3697
+ `Open ${claimingPrimitive.file}, or run this again with an id this charter has not got.`
3698
+ );
3699
+ return scopedPrimitiveDTO(await writeCharter(this.#repoPath, primitive, this.#fileReader, this.#fileWriter));
3700
+ }
3701
+ /**
3702
+ * One primitive as the charter read it: its headers, its body, the file and
3703
+ * layer it is in, and its revision (FR-075, FR-078).
3704
+ *
3705
+ * Asked of the files as read rather than of the validation, so a primitive
3706
+ * opens whatever else in the charter is wrong — which is when its author most
3707
+ * needs to open it. The revision is the content hash of `toMarkdown()`, the
3708
+ * text a save writes, so a save made over it is checked against the primitive
3709
+ * read again then.
3710
+ */
3711
+ async open(identity) {
3712
+ const scopedPrimitive2 = await this.#scopedPrimitiveOf(identity);
3713
+ return primitiveSnapshotDTO(scopedPrimitive2, await contentHashOf(scopedPrimitive2.primitive.toMarkdown()));
3714
+ }
3715
+ /**
3716
+ * One repository primitive's file written over with new headers and a new
3717
+ * body, keeping its kind, its id and the file it is in (FR-075).
3718
+ *
3719
+ * Refused — raised, with nothing written — for a primitive this repository
3720
+ * did not author (FR-077), and for one whose revision is no longer the one
3721
+ * its author opened, so an edit made on disk since is not lost under
3722
+ * this one (FR-078). Answers the kind will not take come back as the faults
3723
+ * `add` gives for them, since both read answers the one way.
3724
+ *
3725
+ * Kind and id are the identity's, never the headers': an edit cannot move a
3726
+ * file, and a rename is a new primitive and a deletion. Nothing is compiled
3727
+ * and nothing committed (FR-079).
3728
+ */
3729
+ async rewrite(identity, headers, body, revision) {
3730
+ const scopedPrimitive2 = await this.#scopedPrimitiveOf(identity);
3731
+ if (!RepoScopedPrimitive.isSatisfiedBy(scopedPrimitive2))
3732
+ throw new Fault(
3733
+ `${identity} was not authored in this repository, and ${scopedPrimitive2.file} is read-only here.`,
3734
+ `To differ from it, author a primitive of your own under an identity of its own.`
3735
+ );
3736
+ if (await contentHashOf(scopedPrimitive2.primitive.toMarkdown()) !== revision)
3737
+ throw new Fault(
3738
+ `${scopedPrimitive2.file} changed on disk after it was opened, and saving would write over that change.`,
3739
+ `Open it again to see what changed, then make your edit there.`
3740
+ );
3741
+ let primitive;
3742
+ try {
3743
+ primitive = primitiveOf({
3744
+ headers: { ...headers, kind: scopedPrimitive2.primitive.kind, id: scopedPrimitive2.primitive.headers.id },
3745
+ body
3746
+ });
3747
+ } catch (raised) {
3748
+ if (!(raised instanceof AggregateError)) throw raised;
3749
+ return faultsDTO(new Faults(raised.errors));
3750
+ }
3751
+ await this.#fileWriter.write(new URL(scopedPrimitive2.file, this.#repoPath), primitive.toMarkdown());
3752
+ return scopedPrimitiveDTO(new ScopedPrimitive(identity, REPO_SCOPE, scopedPrimitive2.file, primitive));
3753
+ }
3754
+ /**
3755
+ * One repository primitive's file taken away, and nothing else (FR-076).
3756
+ *
3757
+ * Refused for a primitive this repository did not author, as a rewrite is
3758
+ * (FR-077). What still names it is left as it is: the next validation reports
3759
+ * each as dangling, and what to do about them is its author's call. Nothing is
3760
+ * compiled and nothing committed (FR-079).
3761
+ */
3762
+ async remove(identity) {
3763
+ const scopedPrimitive2 = await this.#scopedPrimitiveOf(identity);
3764
+ if (!RepoScopedPrimitive.isSatisfiedBy(scopedPrimitive2))
3765
+ throw new Fault(
3766
+ `${identity} was not authored in this repository, and ${scopedPrimitive2.file} is read-only here.`,
3767
+ `To differ from it, author a primitive of your own under an identity of its own.`
3768
+ );
3769
+ await this.#fileWriter.delete(new URL(scopedPrimitive2.file, this.#repoPath));
3770
+ return scopedPrimitiveDTO(scopedPrimitive2);
3771
+ }
3772
+ /** The primitive one identity names, off the files as read and not the
3773
+ * validation. Raised when the charter holds nothing of it, the way `explain`
3774
+ * raises it: there is no file it is wrong with. */
3775
+ async #scopedPrimitiveOf(identity) {
3776
+ const primitiveIdentity = identityOf(identity);
3777
+ const charter = await loadCharterRoot(this.#repoPath, this.#fileReader, this.#yamlParser);
3778
+ const scopedPrimitive2 = charter.primitiveById.get(primitiveIdentity);
3779
+ if (scopedPrimitive2 === void 0)
3780
+ throw new Fault(
3781
+ `This charter holds no "${identity}".`,
3782
+ 'Run "cw list --min" to see every identity it does hold.'
3783
+ );
3784
+ return scopedPrimitive2;
3785
+ }
3786
+ /** One word read as the kind it names. Raised rather than reported, the way a
3787
+ * listing's is: there is no file it is wrong with, and what comes back names
3788
+ * every kind there is. */
3789
+ #kindOf(kind) {
3790
+ if (!isKind(kind))
3791
+ throw new Fault(`"${kind}" is not a kind the charter knows.`, `Ask for one the charter knows: ${KINDS.join(", ")}.`);
3792
+ return kind;
3793
+ }
3794
+ /** What this repository configured itself with, read the one way settings are
3795
+ * ever read (FR-038). */
3796
+ async settings() {
3797
+ return workspaceSettingsDTO(await loadSettings(this.#repoPath, this.#fileReader));
3798
+ }
3799
+ /**
3800
+ * Refused outside version control, and then without a settings file, which
3801
+ * setup writes whatever it was answered: its absence is what a repository
3802
+ * never set up looks like, while `loadSettings` reads it as settings naming
3803
+ * no agent (FR-004, FR-037).
3804
+ */
3805
+ async ensureRepoReady() {
3806
+ if (!await this.#vcs.isInstalled(this.#repoPath))
3807
+ throw new Fault(
3808
+ "This folder is not inside a git repository, and a charter is authored inside one.",
3809
+ 'Run "git init" here, then "cw init", or run this again where your repository is.'
3810
+ );
3811
+ if (await this.#fileReader.readIfThere(settingsFileIn(this.#repoPath)) === void 0)
3812
+ throw new Fault("This repository was never set up under a charter.", 'Run "cw init" first.');
3813
+ }
3814
+ /**
3815
+ * One repository, set up under a charter (FR-032, FR-037).
3816
+ *
3817
+ * Refused outside version control before anything is written: what setup
3818
+ * writes is committed as one change, and a folder with nothing to commit into
3819
+ * is a folder this has no business writing in (FR-032).
3820
+ *
3821
+ * Refused, too, without an agent to compile for: a charter is authored to
3822
+ * instruct one, and a repository compiling for none would build a surface no
3823
+ * agent it runs reads (FR-033).
3824
+ *
3825
+ * Then the two things setup owns — what the repository configured itself
3826
+ * with, and a directory per kind for a charter to be authored in — and
3827
+ * nothing more: compiling is `build`'s, run once there is a charter to
3828
+ * compile (FR-037). What comes back is that it is set up; what stops it is
3829
+ * raised.
3830
+ *
3831
+ * Nothing is written into an ignore file: everything under the workspace is
3832
+ * committed, what a vendor installed included, so a checkout of this
3833
+ * repository holds the whole charter it is governed by without fetching
3834
+ * anything.
3835
+ *
3836
+ * Every one of those is written whole and none of them is authored content,
3837
+ * so running this again reconfigures the repository and discards nothing
3838
+ * anybody wrote (FR-038).
3839
+ */
3840
+ async init({ agents }) {
3841
+ if (!await this.#vcs.isInstalled(this.#repoPath))
3842
+ throw new Fault(
3843
+ "This folder is not inside a git repository, and a charter is authored inside one.",
3844
+ 'Run "git init" here, or run this again where your repository is.'
3845
+ );
3846
+ if (agents.length === 0)
3847
+ throw new Fault(
3848
+ "Setup was given no agent to compile for, and a charter compiles for at least one.",
3849
+ `Choose at least one of: ${AGENT_PROVIDERS.join(", ")}.`
3850
+ );
3851
+ await this.#fileWriter.write(settingsFileIn(this.#repoPath), `${JSON.stringify({ agents }, null, 2)}
3852
+ `);
3853
+ const kindFolders = KINDS.map((kind) => `${CHARTER_DIRECTORY}/${kind}/.gitkeep`);
3854
+ for (const path of kindFolders) await this.#fileWriter.write(new URL(path, this.#repoPath), "");
3855
+ return true;
3856
+ }
3857
+ };
3858
+
3859
+ // src/hexagon/application/CharterVendoring.ts
3860
+ var CharterVendoring = class {
3861
+ #repoPath;
3862
+ #fileReader;
3863
+ #vcs;
3864
+ constructor(repoPath2, fileReader2, vcs2) {
3865
+ this.#repoPath = repoPath2;
3866
+ this.#fileReader = fileReader2;
3867
+ this.#vcs = vcs2;
3868
+ }
3869
+ async add(source, version) {
3870
+ return addVendor(this.#repoPath, this.#vcs, source, version);
3871
+ }
3872
+ async remove(name) {
3873
+ return removeVendor(this.#repoPath, this.#vcs, name);
3874
+ }
3875
+ async installed() {
3876
+ return loadVendorNames(this.#repoPath, this.#fileReader);
3877
+ }
3878
+ };
3879
+
3880
+ // src/hexagon/application/TestAuthoring.ts
3881
+ var TestAuthoring = class {
3882
+ #repoPath;
3883
+ #fileReader;
3884
+ #fileWriter;
3885
+ constructor(repoPath2, fileReader2, fileWriter2) {
3886
+ this.#repoPath = repoPath2;
3887
+ this.#fileReader = fileReader2;
3888
+ this.#fileWriter = fileWriter2;
3889
+ }
3890
+ /** Every test file as it was read, with its text, one that does not read
3891
+ * among them (FR-124). */
3892
+ async suites() {
3893
+ return testSuitesDTO(await loadTestRoot(this.#repoPath, this.#fileReader));
3894
+ }
3895
+ async addSuite() {
3896
+ return addTestSuite(this.#repoPath, this.#fileReader, this.#fileWriter);
3897
+ }
3898
+ async writeSuite(name, text) {
3899
+ return writeTestSuite(this.#repoPath, this.#fileReader, this.#fileWriter, name, text);
3900
+ }
3901
+ async removeSuite(name) {
3902
+ return removeTestSuite(this.#repoPath, this.#fileReader, this.#fileWriter, name);
3903
+ }
3904
+ };
3905
+
3906
+ // src/zdriven/FileReaders.ts
3907
+ import { readdir, readFile } from "fs/promises";
3908
+ var FileReaders = class {
3909
+ async readFilesRecursively(folder) {
3910
+ const found = await Promise.all(
3911
+ (await entriesOf(folder)).map(async (entry) => {
3912
+ if (entry.isDirectory()) return this.readFilesRecursively(under(folder, entry.name));
3913
+ const file = new URL(encodeURIComponent(entry.name), folder);
3914
+ return [{ file, contents: await readFile(file, "utf8") }];
3915
+ })
3916
+ );
3917
+ return found.flat();
3918
+ }
3919
+ async listFolders(folder) {
3920
+ return (await entriesOf(folder)).filter((entry) => entry.isDirectory()).map((entry) => under(folder, entry.name));
3921
+ }
3922
+ async readIfThere(file) {
3923
+ try {
3924
+ return await readFile(file, "utf8");
3925
+ } catch (raised) {
3926
+ if (raised.code === "ENOENT") return void 0;
3927
+ throw raised;
3928
+ }
3929
+ }
3930
+ };
3931
+ async function entriesOf(at) {
3932
+ try {
3933
+ return await readdir(at, { withFileTypes: true });
3934
+ } catch (raised) {
3935
+ if (raised.code === "ENOENT") return [];
3936
+ throw raised;
3937
+ }
3938
+ }
3939
+ function under(at, directory) {
3940
+ return new URL(`${encodeURIComponent(directory)}/`, at);
3941
+ }
3942
+
3943
+ // src/zdriven/YamlParser.ts
3944
+ import { parse } from "yaml";
3945
+ var YamlParser = class {
3946
+ parse(block) {
3947
+ let value;
3948
+ try {
3949
+ value = parse(block, { merge: false });
3950
+ } catch (cause) {
3951
+ throw new CharterPrimitiveFault(
3952
+ `The frontmatter is not readable: ${cause.message.split("\n")[0]}`,
3953
+ `Correct the notation: each header is "name: value", one per line.`
3954
+ );
3955
+ }
3956
+ if (value === null || value === void 0) return {};
3957
+ if (typeof value !== "object" || Array.isArray(value)) {
3958
+ throw new CharterPrimitiveFault(
3959
+ "The frontmatter must be a list of named headers, one per line \u2014 not a bare value or a list.",
3960
+ `Write it as "id: <name>" and the rest of the headers this kind requires, one per line.`
3961
+ );
3962
+ }
3963
+ return value;
3964
+ }
3965
+ };
3966
+
3967
+ // src/zdriven/FileOutput.ts
3968
+ import { mkdir, rm, writeFile } from "fs/promises";
3969
+ var FileOutput = class {
3970
+ async write(file, contents) {
3971
+ await mkdir(new URL(".", file), { recursive: true });
3972
+ await writeFile(file, contents, "utf8");
3973
+ }
3974
+ async delete(file) {
3975
+ await rm(file, { force: true });
3976
+ }
3977
+ };
3978
+
3979
+ // src/zdriven/Git.ts
3980
+ import { execFile } from "child_process";
3981
+ import { promisify } from "util";
3982
+ import { fileURLToPath as fileURLToPath2 } from "url";
3983
+ var run = promisify(execFile);
3984
+ var Git = class {
3985
+ /** How it looks at a folder. Reading files is somebody else's port, and this
3986
+ * asks it rather than reaching for the filesystem itself. */
3987
+ constructor(fileReader2) {
3988
+ this.fileReader = fileReader2;
3989
+ }
3990
+ fileReader;
3991
+ async isInstalled(folder) {
3992
+ try {
3993
+ await run("git", ["rev-parse", "--git-dir"], { cwd: fileURLToPath2(folder) });
3994
+ return true;
3995
+ } catch {
3996
+ return false;
3997
+ }
3998
+ }
3999
+ /** `git status --porcelain`: nothing said is nothing staged and nothing
4000
+ * modified. A folder git refuses to answer about is not clean either — there
4001
+ * is no repository there to have anything in hand. */
4002
+ async isClean(folder) {
4003
+ try {
4004
+ const { stdout } = await run("git", ["status", "--porcelain"], { cwd: fileURLToPath2(folder) });
4005
+ return stdout.trim() === "";
4006
+ } catch {
4007
+ return false;
4008
+ }
4009
+ }
4010
+ /**
4011
+ * `git status --porcelain` narrowed to one folder, with every untracked file
4012
+ * named rather than the folder holding them: what a vendor folder holds that
4013
+ * the commit it arrived in does not (FR-040).
4014
+ *
4015
+ * The path comes off each line after the two status letters and the space
4016
+ * following them; a rename says `old -> new`, and what is there now is the
4017
+ * half that matters. A folder git refuses to answer about has nothing
4018
+ * changed under it, for the reason a folder outside version control is not
4019
+ * clean: there is nothing recorded to differ from.
4020
+ */
4021
+ async changedUnder(repo, subFolder) {
4022
+ try {
4023
+ const { stdout } = await run("git", ["status", "--porcelain", "--untracked-files=all", "--", subFolder], {
4024
+ cwd: fileURLToPath2(repo)
4025
+ });
4026
+ return stdout.split("\n").filter((line) => line.trim() !== "").map((line) => line.slice(3).trim()).map((path) => path.split(" -> ").at(-1) ?? path);
4027
+ } catch {
4028
+ return [];
4029
+ }
4030
+ }
4031
+ /**
4032
+ * `git subtree`, handed the source exactly as it was written: `add` where the
4033
+ * folder is not there yet and `pull` where it is, so installing a source and
4034
+ * bringing it up to date are one call (FR-023, FR-030).
4035
+ *
4036
+ * Which transport it is, whether the credentials are there, whether the
4037
+ * version exists: git answers all three, and what it says is what the user
4038
+ * reads (FR-031).
4039
+ *
4040
+ * `--squash` because what is installed is content to read and not a history
4041
+ * to walk: one commit lands, holding the source's files under the folder
4042
+ * given (FR-028). `HEAD` where no version is named, which is what the source
4043
+ * calls its own default.
4044
+ */
4045
+ async subtreeAdd(source, repo, intoSubFolder, version) {
4046
+ const cwd = fileURLToPath2(repo);
4047
+ const targetFolder = new URL(`${intoSubFolder}/`, repo);
4048
+ const targetFolderExist = (await this.fileReader.readFilesRecursively(targetFolder)).length > 0;
4049
+ try {
4050
+ await run(
4051
+ "git",
4052
+ ["subtree", targetFolderExist ? "pull" : "add", `--prefix=${intoSubFolder}`, "--squash", "--", source, version ?? "HEAD"],
4053
+ { cwd }
4054
+ );
4055
+ } catch (raised) {
4056
+ throw new Fault(
4057
+ `${raised}`,
4058
+ version === void 0 ? "Check the address, and that you can reach it from here." : `Check the address, and that "${version}" is a tag or branch it has.`
4059
+ );
4060
+ }
4061
+ }
4062
+ /** `git rm -r` and a commit of its own: the folder is gone from the working
4063
+ * tree and from the history from here on, the way adding it put it into
4064
+ * both. */
4065
+ async removeSubFolder(repo, subFolder) {
4066
+ const cwd = fileURLToPath2(repo);
4067
+ try {
4068
+ await run("git", ["rm", "-r", "--quiet", "--", subFolder], { cwd });
4069
+ await run("git", ["commit", "-qm", `Remove ${subFolder}`], { cwd });
4070
+ } catch (raised) {
4071
+ throw new Fault(`${raised}`, `Check that "${subFolder}" is a folder this repository holds.`);
4072
+ }
4073
+ }
4074
+ };
4075
+
4076
+ // main.ts
4077
+ var fileReader = new FileReaders();
4078
+ var yamlParser = new YamlParser();
4079
+ var fileWriter = new FileOutput();
4080
+ var vcs = new Git(fileReader);
4081
+ var repoPath = pathToFileURL2(`${process.cwd()}/`);
4082
+ var charterAuthoringApp = new CharterAuthoring(repoPath, fileReader, yamlParser, fileWriter, vcs);
4083
+ var charterVendoringApp = new CharterVendoring(repoPath, fileReader, vcs);
4084
+ var testAuthoringApp = new TestAuthoring(repoPath, fileReader, fileWriter);
4085
+ var cli = new Commander({ cwd: process.cwd(), version: package_default.version, charterAuthoringApp, charterVendoringApp, testAuthoringApp });
4086
+ process.exitCode = await cli.run(process.argv.slice(2));