@ybotok/loam 0.0.0 → 0.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/CHANGELOG.md +138 -0
  2. package/COMPARISON.md +63 -0
  3. package/LICENSE +21 -0
  4. package/MIGRATING-from-OpenSpec.md +202 -0
  5. package/README.md +246 -9
  6. package/SCHEMA.md +425 -0
  7. package/SECURITY.md +9 -0
  8. package/dist/cli.js +142 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/commands/adopt.js +240 -0
  11. package/dist/commands/adopt.js.map +1 -0
  12. package/dist/commands/archive.js +1313 -0
  13. package/dist/commands/archive.js.map +1 -0
  14. package/dist/commands/delta.js +388 -0
  15. package/dist/commands/delta.js.map +1 -0
  16. package/dist/commands/dependencies.js +83 -0
  17. package/dist/commands/dependencies.js.map +1 -0
  18. package/dist/commands/docs-repo-gate.js +104 -0
  19. package/dist/commands/docs-repo-gate.js.map +1 -0
  20. package/dist/commands/doctor.js +85 -0
  21. package/dist/commands/doctor.js.map +1 -0
  22. package/dist/commands/explore.js +195 -0
  23. package/dist/commands/explore.js.map +1 -0
  24. package/dist/commands/format.js +17 -0
  25. package/dist/commands/format.js.map +1 -0
  26. package/dist/commands/gherkin.js +309 -0
  27. package/dist/commands/gherkin.js.map +1 -0
  28. package/dist/commands/init.js +250 -0
  29. package/dist/commands/init.js.map +1 -0
  30. package/dist/commands/instructions.js +70 -0
  31. package/dist/commands/instructions.js.map +1 -0
  32. package/dist/commands/list.js +291 -0
  33. package/dist/commands/list.js.map +1 -0
  34. package/dist/commands/migrate-openspec.js +950 -0
  35. package/dist/commands/migrate-openspec.js.map +1 -0
  36. package/dist/commands/new.js +413 -0
  37. package/dist/commands/new.js.map +1 -0
  38. package/dist/commands/rebase.js +356 -0
  39. package/dist/commands/rebase.js.map +1 -0
  40. package/dist/commands/show.js +320 -0
  41. package/dist/commands/show.js.map +1 -0
  42. package/dist/commands/status.js +254 -0
  43. package/dist/commands/status.js.map +1 -0
  44. package/dist/commands/unarchive.js +317 -0
  45. package/dist/commands/unarchive.js.map +1 -0
  46. package/dist/commands/validate.js +1591 -0
  47. package/dist/commands/validate.js.map +1 -0
  48. package/dist/commands/verify.js +775 -0
  49. package/dist/commands/verify.js.map +1 -0
  50. package/dist/commands/vouch.js +343 -0
  51. package/dist/commands/vouch.js.map +1 -0
  52. package/dist/core/agent.js +2065 -0
  53. package/dist/core/agent.js.map +1 -0
  54. package/dist/core/agents-stamp.js +68 -0
  55. package/dist/core/agents-stamp.js.map +1 -0
  56. package/dist/core/arch.js +130 -0
  57. package/dist/core/arch.js.map +1 -0
  58. package/dist/core/brief.js +733 -0
  59. package/dist/core/brief.js.map +1 -0
  60. package/dist/core/coherence.js +577 -0
  61. package/dist/core/coherence.js.map +1 -0
  62. package/dist/core/concurrency.js +51 -0
  63. package/dist/core/concurrency.js.map +1 -0
  64. package/dist/core/config.js +180 -0
  65. package/dist/core/config.js.map +1 -0
  66. package/dist/core/delta.js +476 -0
  67. package/dist/core/delta.js.map +1 -0
  68. package/dist/core/dependencies.js +356 -0
  69. package/dist/core/dependencies.js.map +1 -0
  70. package/dist/core/docs.js +144 -0
  71. package/dist/core/docs.js.map +1 -0
  72. package/dist/core/doctor.js +559 -0
  73. package/dist/core/doctor.js.map +1 -0
  74. package/dist/core/document-bytes.js +59 -0
  75. package/dist/core/document-bytes.js.map +1 -0
  76. package/dist/core/explore.js +225 -0
  77. package/dist/core/explore.js.map +1 -0
  78. package/dist/core/fleet-context.js +225 -0
  79. package/dist/core/fleet-context.js.map +1 -0
  80. package/dist/core/frontmatter.js +199 -0
  81. package/dist/core/frontmatter.js.map +1 -0
  82. package/dist/core/gherkin.js +463 -0
  83. package/dist/core/gherkin.js.map +1 -0
  84. package/dist/core/health.js +67 -0
  85. package/dist/core/health.js.map +1 -0
  86. package/dist/core/ids.js +122 -0
  87. package/dist/core/ids.js.map +1 -0
  88. package/dist/core/issue.js +9 -0
  89. package/dist/core/issue.js.map +1 -0
  90. package/dist/core/json.js +74 -0
  91. package/dist/core/json.js.map +1 -0
  92. package/dist/core/likec4.js +479 -0
  93. package/dist/core/likec4.js.map +1 -0
  94. package/dist/core/maturity.js +66 -0
  95. package/dist/core/maturity.js.map +1 -0
  96. package/dist/core/openapi-merge.js +552 -0
  97. package/dist/core/openapi-merge.js.map +1 -0
  98. package/dist/core/openapi.js +255 -0
  99. package/dist/core/openapi.js.map +1 -0
  100. package/dist/core/openspec-inventory.js +1440 -0
  101. package/dist/core/openspec-inventory.js.map +1 -0
  102. package/dist/core/path-safety.js +168 -0
  103. package/dist/core/path-safety.js.map +1 -0
  104. package/dist/core/provenance.js +720 -0
  105. package/dist/core/provenance.js.map +1 -0
  106. package/dist/core/records.js +33 -0
  107. package/dist/core/records.js.map +1 -0
  108. package/dist/core/repo.js +436 -0
  109. package/dist/core/repo.js.map +1 -0
  110. package/dist/core/report.js +47 -0
  111. package/dist/core/report.js.map +1 -0
  112. package/dist/core/results.js +199 -0
  113. package/dist/core/results.js.map +1 -0
  114. package/dist/core/spec.js +481 -0
  115. package/dist/core/spec.js.map +1 -0
  116. package/dist/core/staging.js +890 -0
  117. package/dist/core/staging.js.map +1 -0
  118. package/dist/core/status.js +1104 -0
  119. package/dist/core/status.js.map +1 -0
  120. package/dist/core/steps.js +72 -0
  121. package/dist/core/steps.js.map +1 -0
  122. package/dist/core/verify.js +807 -0
  123. package/dist/core/verify.js.map +1 -0
  124. package/dist/core/version.js +24 -0
  125. package/dist/core/version.js.map +1 -0
  126. package/package.json +63 -4
package/dist/cli.js ADDED
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env node
2
+ import { Command, CommanderError } from "commander";
3
+ import { realpathSync } from "node:fs";
4
+ import { fileURLToPath } from "node:url";
5
+ import { registerInit } from "./commands/init.js";
6
+ import { registerAdopt } from "./commands/adopt.js";
7
+ import { registerList } from "./commands/list.js";
8
+ import { registerNew } from "./commands/new.js";
9
+ import { registerShow } from "./commands/show.js";
10
+ import { registerStatus } from "./commands/status.js";
11
+ import { registerDelta } from "./commands/delta.js";
12
+ import { registerGherkin } from "./commands/gherkin.js";
13
+ import { registerRebase } from "./commands/rebase.js";
14
+ import { registerArchive } from "./commands/archive.js";
15
+ import { registerUnarchive } from "./commands/unarchive.js";
16
+ import { registerValidate } from "./commands/validate.js";
17
+ import { registerVerify } from "./commands/verify.js";
18
+ import { registerVouch } from "./commands/vouch.js";
19
+ import { registerDoctor } from "./commands/doctor.js";
20
+ import { registerDependencies } from "./commands/dependencies.js";
21
+ import { registerExplore } from "./commands/explore.js";
22
+ import { registerInstructions } from "./commands/instructions.js";
23
+ import { registerMigrateOpenSpec } from "./commands/migrate-openspec.js";
24
+ import { emitJsonError } from "./core/json.js";
25
+ import { LOAM_VERSION } from "./core/version.js";
26
+ /**
27
+ * The whole CLI surface, as a program nobody has parsed yet.
28
+ *
29
+ * Exported because loam PRINTS commands — every `loam …` in AGENTS.md, in the
30
+ * generated command and skill bodies, in a `doctor` finding's `fix` and in a
31
+ * `status` next[] step is an instruction somebody (usually an agent) will type
32
+ * back. The only way to prove one of those parses is to hand it to the program
33
+ * that would receive it; a second copy of the registrations in a test agrees
34
+ * with itself and with nothing else, which is exactly how
35
+ * `Run \`loam adopt <id>\`` shipped as the first instruction a bound service
36
+ * repo received. `test/agent-commands-runnable.test.ts` is the consumer.
37
+ */
38
+ export function buildProgram() {
39
+ const program = new Command();
40
+ program
41
+ .name("loam")
42
+ .description("Architecture-first spec framework for microservice fleets")
43
+ .version(LOAM_VERSION);
44
+ // Make commander throw a CommanderError instead of process.exit()ing, so a
45
+ // usage error (mistyped flag, unknown command) can still honour the envelope
46
+ // invariant in the catch below instead of dying with an empty stdout.
47
+ program.exitOverride();
48
+ registerInit(program);
49
+ registerAdopt(program);
50
+ registerList(program);
51
+ registerExplore(program);
52
+ registerNew(program);
53
+ registerShow(program);
54
+ registerStatus(program);
55
+ registerDelta(program);
56
+ registerGherkin(program);
57
+ registerRebase(program);
58
+ registerArchive(program);
59
+ registerUnarchive(program);
60
+ registerValidate(program);
61
+ registerVerify(program);
62
+ registerVouch(program);
63
+ registerDoctor(program);
64
+ registerDependencies(program);
65
+ registerInstructions(program);
66
+ registerMigrateOpenSpec(program);
67
+ return program;
68
+ }
69
+ /** CommanderError codes whose output (already printed) is the point, not a failure to wrap. */
70
+ const PASS_THROUGH = new Set(["commander.help", "commander.helpDisplayed", "commander.version"]);
71
+ function main() {
72
+ const program = buildProgram();
73
+ // Set once a command's action actually runs: every subcommand declares its own
74
+ // `--json`, so past that point the parsed option is the authoritative answer.
75
+ let parsedJson;
76
+ program.hook("preAction", (_thisCommand, actionCommand) => {
77
+ parsedJson = Boolean(actionCommand.optsWithGlobals().json);
78
+ });
79
+ /**
80
+ * Did this invocation ask for --json? Prefers the parsed option once an action
81
+ * has run; before that (commander usage errors abort mid-parse) falls back to
82
+ * scanning raw argv. The scan is a heuristic and cannot be made exact: a token
83
+ * spelled `--json` that was meant as an option VALUE (e.g. --title "--json")
84
+ * is indistinguishable from the flag in raw argv. It gets the common cases
85
+ * right: a `--json` token anywhere before a `--` terminator counts, and tokens
86
+ * after `--` never do.
87
+ */
88
+ const jsonRequested = () => {
89
+ if (parsedJson !== undefined)
90
+ return parsedJson;
91
+ const argv = process.argv.slice(2);
92
+ const terminator = argv.indexOf("--");
93
+ return (terminator === -1 ? argv : argv.slice(0, terminator)).includes("--json");
94
+ };
95
+ program.parseAsync(process.argv).catch((err) => {
96
+ if (err instanceof CommanderError) {
97
+ // Help/version output was already written by commander — pass it through
98
+ // untouched with the exit code it always had.
99
+ //
100
+ // Anything else is a usage error (unknown option/command, missing or
101
+ // invalid argument). Commander has already printed its diagnostic to
102
+ // stderr, and that line stays: the invariant below is about stdout only.
103
+ if (!PASS_THROUGH.has(err.code) && jsonRequested()) {
104
+ emitJsonError("invalid-option", err.message.replace(/^error: /, ""));
105
+ }
106
+ process.exitCode = err.exitCode;
107
+ return;
108
+ }
109
+ const message = err instanceof Error ? err.message : String(err);
110
+ // The envelope's one hard invariant is that stdout is JSON whenever --json
111
+ // was asked for — an unexpected throw must not be the exception to it.
112
+ if (jsonRequested()) {
113
+ emitJsonError("internal", message);
114
+ }
115
+ else {
116
+ console.error(message);
117
+ process.exitCode = 1;
118
+ }
119
+ });
120
+ }
121
+ /**
122
+ * Was this file run, or merely imported? Realpath on both sides because the
123
+ * installed entry point is reached through a symlink (`node_modules/.bin/loam`)
124
+ * on every platform that has them, and a string compare against the link would
125
+ * make the binary a no-op. A failure to resolve reads as "not the entry" only
126
+ * for the import side; `process.argv[1]` that cannot be realpath'd is not a
127
+ * loam invocation either.
128
+ */
129
+ function invokedDirectly() {
130
+ const entry = process.argv[1];
131
+ if (entry === undefined)
132
+ return false;
133
+ try {
134
+ return realpathSync(entry) === realpathSync(fileURLToPath(import.meta.url));
135
+ }
136
+ catch {
137
+ return false;
138
+ }
139
+ }
140
+ if (invokedDirectly())
141
+ main();
142
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\nimport { Command, CommanderError } from \"commander\";\nimport { realpathSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { registerInit } from \"./commands/init.js\";\nimport { registerAdopt } from \"./commands/adopt.js\";\nimport { registerList } from \"./commands/list.js\";\nimport { registerNew } from \"./commands/new.js\";\nimport { registerShow } from \"./commands/show.js\";\nimport { registerStatus } from \"./commands/status.js\";\nimport { registerDelta } from \"./commands/delta.js\";\nimport { registerGherkin } from \"./commands/gherkin.js\";\nimport { registerRebase } from \"./commands/rebase.js\";\nimport { registerArchive } from \"./commands/archive.js\";\nimport { registerUnarchive } from \"./commands/unarchive.js\";\nimport { registerValidate } from \"./commands/validate.js\";\nimport { registerVerify } from \"./commands/verify.js\";\nimport { registerVouch } from \"./commands/vouch.js\";\nimport { registerDoctor } from \"./commands/doctor.js\";\nimport { registerDependencies } from \"./commands/dependencies.js\";\nimport { registerExplore } from \"./commands/explore.js\";\nimport { registerInstructions } from \"./commands/instructions.js\";\nimport { registerMigrateOpenSpec } from \"./commands/migrate-openspec.js\";\nimport { emitJsonError } from \"./core/json.js\";\nimport { LOAM_VERSION } from \"./core/version.js\";\n\n/**\n * The whole CLI surface, as a program nobody has parsed yet.\n *\n * Exported because loam PRINTS commands — every `loam …` in AGENTS.md, in the\n * generated command and skill bodies, in a `doctor` finding's `fix` and in a\n * `status` next[] step is an instruction somebody (usually an agent) will type\n * back. The only way to prove one of those parses is to hand it to the program\n * that would receive it; a second copy of the registrations in a test agrees\n * with itself and with nothing else, which is exactly how\n * `Run \\`loam adopt <id>\\`` shipped as the first instruction a bound service\n * repo received. `test/agent-commands-runnable.test.ts` is the consumer.\n */\nexport function buildProgram(): Command {\n const program = new Command();\n\n program\n .name(\"loam\")\n .description(\"Architecture-first spec framework for microservice fleets\")\n .version(LOAM_VERSION);\n\n // Make commander throw a CommanderError instead of process.exit()ing, so a\n // usage error (mistyped flag, unknown command) can still honour the envelope\n // invariant in the catch below instead of dying with an empty stdout.\n program.exitOverride();\n\n registerInit(program);\n registerAdopt(program);\n registerList(program);\n registerExplore(program);\n registerNew(program);\n registerShow(program);\n registerStatus(program);\n registerDelta(program);\n registerGherkin(program);\n registerRebase(program);\n registerArchive(program);\n registerUnarchive(program);\n registerValidate(program);\n registerVerify(program);\n registerVouch(program);\n registerDoctor(program);\n registerDependencies(program);\n registerInstructions(program);\n registerMigrateOpenSpec(program);\n\n return program;\n}\n\n/** CommanderError codes whose output (already printed) is the point, not a failure to wrap. */\nconst PASS_THROUGH = new Set([\"commander.help\", \"commander.helpDisplayed\", \"commander.version\"]);\n\nfunction main(): void {\n const program = buildProgram();\n\n // Set once a command's action actually runs: every subcommand declares its own\n // `--json`, so past that point the parsed option is the authoritative answer.\n let parsedJson: boolean | undefined;\n program.hook(\"preAction\", (_thisCommand, actionCommand) => {\n parsedJson = Boolean((actionCommand.optsWithGlobals() as { json?: boolean }).json);\n });\n\n /**\n * Did this invocation ask for --json? Prefers the parsed option once an action\n * has run; before that (commander usage errors abort mid-parse) falls back to\n * scanning raw argv. The scan is a heuristic and cannot be made exact: a token\n * spelled `--json` that was meant as an option VALUE (e.g. --title \"--json\")\n * is indistinguishable from the flag in raw argv. It gets the common cases\n * right: a `--json` token anywhere before a `--` terminator counts, and tokens\n * after `--` never do.\n */\n const jsonRequested = (): boolean => {\n if (parsedJson !== undefined) return parsedJson;\n const argv = process.argv.slice(2);\n const terminator = argv.indexOf(\"--\");\n return (terminator === -1 ? argv : argv.slice(0, terminator)).includes(\"--json\");\n };\n\n program.parseAsync(process.argv).catch((err: unknown) => {\n if (err instanceof CommanderError) {\n // Help/version output was already written by commander — pass it through\n // untouched with the exit code it always had.\n //\n // Anything else is a usage error (unknown option/command, missing or\n // invalid argument). Commander has already printed its diagnostic to\n // stderr, and that line stays: the invariant below is about stdout only.\n if (!PASS_THROUGH.has(err.code) && jsonRequested()) {\n emitJsonError(\"invalid-option\", err.message.replace(/^error: /, \"\"));\n }\n process.exitCode = err.exitCode;\n return;\n }\n const message = err instanceof Error ? err.message : String(err);\n // The envelope's one hard invariant is that stdout is JSON whenever --json\n // was asked for — an unexpected throw must not be the exception to it.\n if (jsonRequested()) {\n emitJsonError(\"internal\", message);\n } else {\n console.error(message);\n process.exitCode = 1;\n }\n });\n}\n\n/**\n * Was this file run, or merely imported? Realpath on both sides because the\n * installed entry point is reached through a symlink (`node_modules/.bin/loam`)\n * on every platform that has them, and a string compare against the link would\n * make the binary a no-op. A failure to resolve reads as \"not the entry\" only\n * for the import side; `process.argv[1]` that cannot be realpath'd is not a\n * loam invocation either.\n */\nfunction invokedDirectly(): boolean {\n const entry = process.argv[1];\n if (entry === undefined) return false;\n try {\n return realpathSync(entry) === realpathSync(fileURLToPath(import.meta.url));\n } catch {\n return false;\n }\n}\n\nif (invokedDirectly()) main();\n"]}
@@ -0,0 +1,240 @@
1
+ import { existsSync } from "node:fs";
2
+ import { closeIds } from "../core/arch.js";
3
+ import { loadConfig } from "../core/config.js";
4
+ import { InvalidIdError, assertServiceId } from "../core/ids.js";
5
+ import { emitJson, fail, NO_SERVICE_MESSAGE, reportNoConfig } from "../core/json.js";
6
+ import { DocsRepoUnavailableError, listServices, servicePaths } from "../core/repo.js";
7
+ import { SEVERITY_MARK } from "../core/report.js";
8
+ import { serviceBrief, VIA_ALL } from "../core/brief.js";
9
+ import { docsRepoReady } from "./docs-repo-gate.js";
10
+ /**
11
+ * What is suspicious about this invocation without being wrong.
12
+ *
13
+ * Adopting a service that has no `services/<id>/` yet is the NORMAL case — it
14
+ * is the whole point of the command — so neither of these can be a refusal.
15
+ * But both used to be silent, and both produce the same outcome: a complete,
16
+ * validating baseline written under a directory name nobody meant. `adopt
17
+ * --service biling-service` briefed a phantom service beside the real
18
+ * `billing-service`, and an agent following the brief created it.
19
+ */
20
+ async function invocationWarnings(docsDir, service, bound) {
21
+ const warnings = [];
22
+ if (bound !== undefined && bound !== service) {
23
+ warnings.push(`This repository's loam.json declares service '${bound}', but you asked to adopt '${service}'. ` +
24
+ `That is legal — one repo can author another service's baseline — but \`loam vouch\`, ` +
25
+ `\`loam gherkin\` and \`loam verify --service\` all bind to '${bound}' here, so the baseline ` +
26
+ `you write cannot be vouched from this repo. If '${service}' is a typo, fix it; otherwise run ` +
27
+ `adopt from '${service}''s own repository.`);
28
+ }
29
+ if (existsSync(servicePaths(docsDir, service).dir))
30
+ return warnings;
31
+ // A near-miss is only computable when the docs repo can be enumerated at all;
32
+ // a repo that refuses enumeration is a different problem, reported by every
33
+ // other command, and adopt has nothing useful to add to it.
34
+ let known = [];
35
+ try {
36
+ known = (await listServices(docsDir)).map((s) => s.id);
37
+ }
38
+ catch (err) {
39
+ if (!(err instanceof DocsRepoUnavailableError))
40
+ throw err;
41
+ return warnings;
42
+ }
43
+ const close = closeIds(service, known);
44
+ if (close.length > 0) {
45
+ warnings.push(`There is no services/${service}/ yet, but the docs repo already has ${close.join(", ")}. ` +
46
+ `If '${service}' is a misspelling of one of those, stop now: an agent following this brief ` +
47
+ `writes a complete, validating baseline for a service that does not exist, and nothing ` +
48
+ `downstream can tell it from a real one. If it really is a new service, this is expected.`);
49
+ }
50
+ return warnings;
51
+ }
52
+ export function registerAdopt(program) {
53
+ program
54
+ .command("adopt")
55
+ .description("Brief an agent to write one service's baseline docs from its code, and say what will be checked")
56
+ .option("--service <id>", "service to adopt (defaults to the configured service)")
57
+ .option("--json", "emit the machine contract instead of the human view")
58
+ .action(async (opts) => {
59
+ const json = opts.json === true;
60
+ const config = await loadConfig();
61
+ if (!config) {
62
+ reportNoConfig(json);
63
+ return;
64
+ }
65
+ // This command writes nothing, but it briefs an agent to write a whole
66
+ // baseline — so a docsDir that does not exist is not a harmless read: it
67
+ // hands over eight target paths under a directory nobody has, at exit 0,
68
+ // with the near-miss warning below silently switched off (listServices
69
+ // throws, `invocationWarnings` swallows it and returns nothing). The same
70
+ // refusal every enumerating command owes; see docs-repo-gate.ts.
71
+ //
72
+ // `docs`, not `services`: a docs repo that has no services/ yet is exactly
73
+ // the repo adopt exists to fill, and refusing there would refuse the
74
+ // command's own first use.
75
+ if (!docsRepoReady(json, config.docsDir, "docs"))
76
+ return;
77
+ const service = opts.service ?? config.service;
78
+ if (service === undefined) {
79
+ fail(json, "invalid-option", NO_SERVICE_MESSAGE);
80
+ return;
81
+ }
82
+ // The id becomes `services/<id>/` in a shared repo, so it goes through the
83
+ // one grammar before anything interpolates it into a path.
84
+ try {
85
+ assertServiceId(service, opts.service === undefined ? "service (loam.json)" : "--service");
86
+ }
87
+ catch (err) {
88
+ if (!(err instanceof InvalidIdError))
89
+ throw err;
90
+ fail(json, "invalid-option", err.message);
91
+ return;
92
+ }
93
+ const warnings = await invocationWarnings(config.docsDir, service, config.service);
94
+ const brief = await serviceBrief(config.docsDir, service);
95
+ if (json) {
96
+ emitJson({ ...brief, warnings });
97
+ return;
98
+ }
99
+ render(brief, warnings);
100
+ });
101
+ }
102
+ /* ------------------------------------------------------------------ */
103
+ /* Text */
104
+ /* ------------------------------------------------------------------ */
105
+ function render(b, warnings) {
106
+ console.log(`adopt ${b.service} — write the baseline into ${b.path}/\n`);
107
+ // In the header, before the work: a warning printed after eight artifact
108
+ // shapes is a warning read after the agent has started writing.
109
+ for (const w of warnings) {
110
+ console.log(wrap(`! ${w}`, " "));
111
+ console.log("");
112
+ }
113
+ console.log(wrap(b.rule, " "));
114
+ console.log("\n artifacts\n");
115
+ const width = Math.max(...b.targets.map((t) => t.artifact.length));
116
+ for (const t of b.targets) {
117
+ // `edit` is the shared fleet map: the file exists, but "present" would read
118
+ // as "nothing to do here", which is the opposite of why it is on the list.
119
+ const flag = t.action === "edit" ? "UNDRAWN" : t.exists ? "present" : t.required ? "MISSING" : "missing";
120
+ console.log(` ${t.artifact.padEnd(width)} ${t.action.padEnd(6)} ${flag.padEnd(7)} ${t.purpose}`);
121
+ }
122
+ for (const t of b.targets)
123
+ printShape(t);
124
+ printLandscape(b);
125
+ console.log("\n frontmatter — on every markdown artifact\n");
126
+ for (const [field, what] of Object.entries(b.frontmatter.fields)) {
127
+ console.log(` ${field}`);
128
+ console.log(wrap(what, " "));
129
+ }
130
+ console.log(`\n never write by hand: ${b.frontmatter.never.join(", ")}`);
131
+ console.log(`\n${wrap(b.frontmatter.why, " ")}`);
132
+ // Attribution matters here: the fleet cross-check runs under `--all`, and a
133
+ // header promising it to `--service` sent agents chasing a finding that
134
+ // invocation never reports.
135
+ console.log(`\n what \`loam validate --service ${b.service}\` then checks\n`);
136
+ for (const c of b.checks) {
137
+ if (c.via !== VIA_ALL)
138
+ printCheck(c);
139
+ }
140
+ console.log(`\n and what only \`loam validate --all\` surfaces\n`);
141
+ for (const c of b.checks) {
142
+ if (c.via === VIA_ALL)
143
+ printCheck(c);
144
+ }
145
+ console.log("\n what nothing checks\n");
146
+ for (const u of b.unchecked)
147
+ console.log(bullet(u, " "));
148
+ console.log("\n when you are done\n");
149
+ console.log(` loam validate --service ${b.service} --json # fix every error`);
150
+ // The fleet run is not a nicety: --service never reports the landscape
151
+ // cross-check, so a baseline can pass the line above with the fleet map
152
+ // never edited at all.
153
+ console.log(` loam validate --all --json # in the docs repo: the fleet map`);
154
+ console.log(` loam vouch --service ${b.service} # a HUMAN, in the service's own repo`);
155
+ console.log("");
156
+ console.log(wrap("Leave everything `status: draft`. Vouching is somebody reading the code and saying the document matches it — it is not yours to do, and a status with no digest behind it is a claim with nothing behind it.", " "));
157
+ }
158
+ function printCheck(c) {
159
+ console.log(` ${SEVERITY_MARK[c.severity]} ${c.code}`);
160
+ console.log(wrap(c.what, " "));
161
+ }
162
+ const ACTION_NOTE = {
163
+ create: "",
164
+ diff: " (exists — diff, do not replace)",
165
+ edit: " (the whole fleet's file — ADD to it, never rewrite it)",
166
+ };
167
+ function printShape(t) {
168
+ console.log(`\n ${t.path}${ACTION_NOTE[t.action]}\n`);
169
+ for (const rule of t.shape)
170
+ console.log(bullet(rule, " "));
171
+ if (t.example === undefined)
172
+ return;
173
+ console.log("");
174
+ for (const line of t.example.trimEnd().split("\n"))
175
+ console.log(` ${line}`.trimEnd());
176
+ }
177
+ function printLandscape(b) {
178
+ const l = b.landscape;
179
+ console.log("\n what the fleet already says about this service\n");
180
+ // The instruction is the same sentence the JSON carries, printed from the
181
+ // same field — the text view and `--json` cannot drift into briefing two
182
+ // different writes.
183
+ if (!l.present) {
184
+ console.log(" architecture/landscape.likec4 does not exist yet — nothing to bind to.");
185
+ console.log("");
186
+ console.log(wrap(l.instruction ?? "", " "));
187
+ return;
188
+ }
189
+ if (!l.parses) {
190
+ console.log(" architecture/landscape.likec4 does not parse — bind by hand, and fix it first.");
191
+ console.log("");
192
+ console.log(wrap(l.instruction ?? "", " "));
193
+ return;
194
+ }
195
+ if (l.elements.length === 0) {
196
+ console.log(` Nothing in the landscape models '${b.service}'.`);
197
+ console.log("");
198
+ console.log(wrap(l.instruction ?? "", " "));
199
+ return;
200
+ }
201
+ for (const e of l.elements) {
202
+ const bound = e.bound ? "bound" : "matched by title — bind it";
203
+ console.log(` ${e.id} = ${e.kind} '${e.title}' (${bound})`);
204
+ }
205
+ for (const e of l.inbound)
206
+ console.log(` ← ${e.from} ${e.op ?? `"${e.title ?? ""}"`}`);
207
+ for (const e of l.outbound)
208
+ console.log(` → ${e.to} ${e.op ?? `"${e.title ?? ""}"`}`);
209
+ if (l.expects.length > 0) {
210
+ console.log("");
211
+ console.log(wrap(`openapi.yaml must define ${l.expects.map((o) => `'${o}'`).join(", ")} — the fleet already calls ${l.expects.length === 1 ? "it" : "them"}, and a contract that omits ${l.expects.length === 1 ? "it" : "them"} fails spine.op-undefined the moment it lands.`, " "));
212
+ }
213
+ }
214
+ /**
215
+ * Wrap prose so the brief is readable in a terminal. Continuation lines are
216
+ * indented past the marker, so a wrapped bullet cannot be misread as two.
217
+ */
218
+ function wrap(text, indent, width = 88) {
219
+ const hang = /^\s*-\s/.test(text) ? `${indent} ` : indent;
220
+ const out = [];
221
+ let line = indent;
222
+ let pad = indent;
223
+ for (const word of text.trim().split(/\s+/)) {
224
+ if (line.length > pad.length && line.length + 1 + word.length > width) {
225
+ out.push(line);
226
+ pad = hang;
227
+ line = hang + word;
228
+ }
229
+ else {
230
+ line = line.length > pad.length ? `${line} ${word}` : line + word;
231
+ }
232
+ }
233
+ out.push(line);
234
+ return out.join("\n");
235
+ }
236
+ /** A bullet, wrapped with its continuation lines hanging under the text. */
237
+ function bullet(text, indent) {
238
+ return wrap(`- ${text}`, indent);
239
+ }
240
+ //# sourceMappingURL=adopt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adopt.js","sourceRoot":"","sources":["../../src/commands/adopt.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `loam adopt` — the bootstrap flow, as a contract with an agent.\n *\n * It emits a BRIEF, not an extraction: the artifacts one service's baseline is\n * made of, the grammar each has to be written in, what the fleet already says\n * about the service, the checks that follow, and the checks that do not exist.\n * The agent reads the code; loam states the work and then judges the result.\n *\n * It writes nothing. A command that produced the documents it is asking for\n * would be doing the half of the job it exists to hand over.\n */\nimport type { Command } from \"commander\";\nimport { existsSync } from \"node:fs\";\nimport { closeIds } from \"../core/arch.js\";\nimport { loadConfig } from \"../core/config.js\";\nimport { InvalidIdError, assertServiceId } from \"../core/ids.js\";\nimport { emitJson, fail, NO_SERVICE_MESSAGE, reportNoConfig } from \"../core/json.js\";\nimport { DocsRepoUnavailableError, listServices, servicePaths } from \"../core/repo.js\";\nimport { SEVERITY_MARK } from \"../core/report.js\";\nimport { serviceBrief, VIA_ALL, type Brief, type BriefCheck, type BriefTarget } from \"../core/brief.js\";\nimport { docsRepoReady } from \"./docs-repo-gate.js\";\n\ninterface AdoptOptions {\n service?: string;\n json?: boolean;\n}\n\n/**\n * What is suspicious about this invocation without being wrong.\n *\n * Adopting a service that has no `services/<id>/` yet is the NORMAL case — it\n * is the whole point of the command — so neither of these can be a refusal.\n * But both used to be silent, and both produce the same outcome: a complete,\n * validating baseline written under a directory name nobody meant. `adopt\n * --service biling-service` briefed a phantom service beside the real\n * `billing-service`, and an agent following the brief created it.\n */\nasync function invocationWarnings(\n docsDir: string,\n service: string,\n bound: string | undefined,\n): Promise<string[]> {\n const warnings: string[] = [];\n\n if (bound !== undefined && bound !== service) {\n warnings.push(\n `This repository's loam.json declares service '${bound}', but you asked to adopt '${service}'. ` +\n `That is legal — one repo can author another service's baseline — but \\`loam vouch\\`, ` +\n `\\`loam gherkin\\` and \\`loam verify --service\\` all bind to '${bound}' here, so the baseline ` +\n `you write cannot be vouched from this repo. If '${service}' is a typo, fix it; otherwise run ` +\n `adopt from '${service}''s own repository.`,\n );\n }\n\n if (existsSync(servicePaths(docsDir, service).dir)) return warnings;\n\n // A near-miss is only computable when the docs repo can be enumerated at all;\n // a repo that refuses enumeration is a different problem, reported by every\n // other command, and adopt has nothing useful to add to it.\n let known: string[] = [];\n try {\n known = (await listServices(docsDir)).map((s) => s.id);\n } catch (err) {\n if (!(err instanceof DocsRepoUnavailableError)) throw err;\n return warnings;\n }\n const close = closeIds(service, known);\n if (close.length > 0) {\n warnings.push(\n `There is no services/${service}/ yet, but the docs repo already has ${close.join(\", \")}. ` +\n `If '${service}' is a misspelling of one of those, stop now: an agent following this brief ` +\n `writes a complete, validating baseline for a service that does not exist, and nothing ` +\n `downstream can tell it from a real one. If it really is a new service, this is expected.`,\n );\n }\n return warnings;\n}\n\nexport function registerAdopt(program: Command): void {\n program\n .command(\"adopt\")\n .description(\"Brief an agent to write one service's baseline docs from its code, and say what will be checked\")\n .option(\"--service <id>\", \"service to adopt (defaults to the configured service)\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (opts: AdoptOptions) => {\n const json = opts.json === true;\n\n const config = await loadConfig();\n if (!config) {\n reportNoConfig(json);\n return;\n }\n // This command writes nothing, but it briefs an agent to write a whole\n // baseline — so a docsDir that does not exist is not a harmless read: it\n // hands over eight target paths under a directory nobody has, at exit 0,\n // with the near-miss warning below silently switched off (listServices\n // throws, `invocationWarnings` swallows it and returns nothing). The same\n // refusal every enumerating command owes; see docs-repo-gate.ts.\n //\n // `docs`, not `services`: a docs repo that has no services/ yet is exactly\n // the repo adopt exists to fill, and refusing there would refuse the\n // command's own first use.\n if (!docsRepoReady(json, config.docsDir, \"docs\")) return;\n\n const service = opts.service ?? config.service;\n if (service === undefined) {\n fail(json, \"invalid-option\", NO_SERVICE_MESSAGE);\n return;\n }\n // The id becomes `services/<id>/` in a shared repo, so it goes through the\n // one grammar before anything interpolates it into a path.\n try {\n assertServiceId(service, opts.service === undefined ? \"service (loam.json)\" : \"--service\");\n } catch (err) {\n if (!(err instanceof InvalidIdError)) throw err;\n fail(json, \"invalid-option\", err.message);\n return;\n }\n\n const warnings = await invocationWarnings(config.docsDir, service, config.service);\n const brief = await serviceBrief(config.docsDir, service);\n if (json) {\n emitJson({ ...brief, warnings });\n return;\n }\n render(brief, warnings);\n });\n}\n\n/* ------------------------------------------------------------------ */\n/* Text */\n/* ------------------------------------------------------------------ */\n\nfunction render(b: Brief, warnings: string[]): void {\n console.log(`adopt ${b.service} — write the baseline into ${b.path}/\\n`);\n // In the header, before the work: a warning printed after eight artifact\n // shapes is a warning read after the agent has started writing.\n for (const w of warnings) {\n console.log(wrap(`! ${w}`, \" \"));\n console.log(\"\");\n }\n console.log(wrap(b.rule, \" \"));\n\n console.log(\"\\n artifacts\\n\");\n const width = Math.max(...b.targets.map((t) => t.artifact.length));\n for (const t of b.targets) {\n // `edit` is the shared fleet map: the file exists, but \"present\" would read\n // as \"nothing to do here\", which is the opposite of why it is on the list.\n const flag = t.action === \"edit\" ? \"UNDRAWN\" : t.exists ? \"present\" : t.required ? \"MISSING\" : \"missing\";\n console.log(` ${t.artifact.padEnd(width)} ${t.action.padEnd(6)} ${flag.padEnd(7)} ${t.purpose}`);\n }\n\n for (const t of b.targets) printShape(t);\n\n printLandscape(b);\n\n console.log(\"\\n frontmatter — on every markdown artifact\\n\");\n for (const [field, what] of Object.entries(b.frontmatter.fields)) {\n console.log(` ${field}`);\n console.log(wrap(what, \" \"));\n }\n console.log(`\\n never write by hand: ${b.frontmatter.never.join(\", \")}`);\n console.log(`\\n${wrap(b.frontmatter.why, \" \")}`);\n\n // Attribution matters here: the fleet cross-check runs under `--all`, and a\n // header promising it to `--service` sent agents chasing a finding that\n // invocation never reports.\n console.log(`\\n what \\`loam validate --service ${b.service}\\` then checks\\n`);\n for (const c of b.checks) {\n if (c.via !== VIA_ALL) printCheck(c);\n }\n console.log(`\\n and what only \\`loam validate --all\\` surfaces\\n`);\n for (const c of b.checks) {\n if (c.via === VIA_ALL) printCheck(c);\n }\n\n console.log(\"\\n what nothing checks\\n\");\n for (const u of b.unchecked) console.log(bullet(u, \" \"));\n\n console.log(\"\\n when you are done\\n\");\n console.log(` loam validate --service ${b.service} --json # fix every error`);\n // The fleet run is not a nicety: --service never reports the landscape\n // cross-check, so a baseline can pass the line above with the fleet map\n // never edited at all.\n console.log(` loam validate --all --json # in the docs repo: the fleet map`);\n console.log(` loam vouch --service ${b.service} # a HUMAN, in the service's own repo`);\n console.log(\"\");\n console.log(\n wrap(\n \"Leave everything `status: draft`. Vouching is somebody reading the code and saying the document matches it — it is not yours to do, and a status with no digest behind it is a claim with nothing behind it.\",\n \" \",\n ),\n );\n}\n\nfunction printCheck(c: BriefCheck): void {\n console.log(` ${SEVERITY_MARK[c.severity]} ${c.code}`);\n console.log(wrap(c.what, \" \"));\n}\n\nconst ACTION_NOTE: Record<BriefTarget[\"action\"], string> = {\n create: \"\",\n diff: \" (exists — diff, do not replace)\",\n edit: \" (the whole fleet's file — ADD to it, never rewrite it)\",\n};\n\nfunction printShape(t: BriefTarget): void {\n console.log(`\\n ${t.path}${ACTION_NOTE[t.action]}\\n`);\n for (const rule of t.shape) console.log(bullet(rule, \" \"));\n if (t.example === undefined) return;\n console.log(\"\");\n for (const line of t.example.trimEnd().split(\"\\n\")) console.log(` ${line}`.trimEnd());\n}\n\nfunction printLandscape(b: Brief): void {\n const l = b.landscape;\n console.log(\"\\n what the fleet already says about this service\\n\");\n // The instruction is the same sentence the JSON carries, printed from the\n // same field — the text view and `--json` cannot drift into briefing two\n // different writes.\n if (!l.present) {\n console.log(\" architecture/landscape.likec4 does not exist yet — nothing to bind to.\");\n console.log(\"\");\n console.log(wrap(l.instruction ?? \"\", \" \"));\n return;\n }\n if (!l.parses) {\n console.log(\" architecture/landscape.likec4 does not parse — bind by hand, and fix it first.\");\n console.log(\"\");\n console.log(wrap(l.instruction ?? \"\", \" \"));\n return;\n }\n if (l.elements.length === 0) {\n console.log(` Nothing in the landscape models '${b.service}'.`);\n console.log(\"\");\n console.log(wrap(l.instruction ?? \"\", \" \"));\n return;\n }\n for (const e of l.elements) {\n const bound = e.bound ? \"bound\" : \"matched by title — bind it\";\n console.log(` ${e.id} = ${e.kind} '${e.title}' (${bound})`);\n }\n for (const e of l.inbound) console.log(` ← ${e.from} ${e.op ?? `\"${e.title ?? \"\"}\"`}`);\n for (const e of l.outbound) console.log(` → ${e.to} ${e.op ?? `\"${e.title ?? \"\"}\"`}`);\n if (l.expects.length > 0) {\n console.log(\"\");\n console.log(\n wrap(\n `openapi.yaml must define ${l.expects.map((o) => `'${o}'`).join(\", \")} — the fleet already calls ${l.expects.length === 1 ? \"it\" : \"them\"}, and a contract that omits ${l.expects.length === 1 ? \"it\" : \"them\"} fails spine.op-undefined the moment it lands.`,\n \" \",\n ),\n );\n }\n}\n\n/**\n * Wrap prose so the brief is readable in a terminal. Continuation lines are\n * indented past the marker, so a wrapped bullet cannot be misread as two.\n */\nfunction wrap(text: string, indent: string, width = 88): string {\n const hang = /^\\s*-\\s/.test(text) ? `${indent} ` : indent;\n const out: string[] = [];\n let line = indent;\n let pad = indent;\n for (const word of text.trim().split(/\\s+/)) {\n if (line.length > pad.length && line.length + 1 + word.length > width) {\n out.push(line);\n pad = hang;\n line = hang + word;\n } else {\n line = line.length > pad.length ? `${line} ${word}` : line + word;\n }\n }\n out.push(line);\n return out.join(\"\\n\");\n}\n\n/** A bullet, wrapped with its continuation lines hanging under the text. */\nfunction bullet(text: string, indent: string): string {\n return wrap(`- ${text}`, indent);\n}\n"]}