@ybotok/loam 0.1.0-beta.1 → 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.
- package/CHANGELOG.md +49 -0
- package/README.md +6 -1
- package/dist/cli.js +4 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt.js +17 -3
- package/dist/commands/adopt.js.map +1 -1
- package/dist/commands/archive.js +24 -8
- package/dist/commands/archive.js.map +1 -1
- package/dist/commands/delta.js +74 -47
- package/dist/commands/delta.js.map +1 -1
- package/dist/commands/dependencies.js +7 -2
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/docs-repo-gate.js +104 -0
- package/dist/commands/docs-repo-gate.js.map +1 -0
- package/dist/commands/explore.js +195 -0
- package/dist/commands/explore.js.map +1 -0
- package/dist/commands/format.js +17 -0
- package/dist/commands/format.js.map +1 -0
- package/dist/commands/gherkin.js +87 -48
- package/dist/commands/gherkin.js.map +1 -1
- package/dist/commands/init.js +3 -3
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/instructions.js +70 -0
- package/dist/commands/instructions.js.map +1 -0
- package/dist/commands/list.js +6 -76
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/migrate-openspec.js +45 -28
- package/dist/commands/migrate-openspec.js.map +1 -1
- package/dist/commands/new.js +35 -34
- package/dist/commands/new.js.map +1 -1
- package/dist/commands/rebase.js +2 -4
- package/dist/commands/rebase.js.map +1 -1
- package/dist/commands/show.js +26 -31
- package/dist/commands/show.js.map +1 -1
- package/dist/commands/status.js +8 -24
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/unarchive.js +12 -5
- package/dist/commands/unarchive.js.map +1 -1
- package/dist/commands/validate.js +115 -133
- package/dist/commands/validate.js.map +1 -1
- package/dist/commands/verify.js +33 -17
- package/dist/commands/verify.js.map +1 -1
- package/dist/commands/vouch.js +77 -59
- package/dist/commands/vouch.js.map +1 -1
- package/dist/core/agent.js +273 -31
- package/dist/core/agent.js.map +1 -1
- package/dist/core/arch.js +28 -2
- package/dist/core/arch.js.map +1 -1
- package/dist/core/brief.js +3 -3
- package/dist/core/brief.js.map +1 -1
- package/dist/core/coherence.js +37 -13
- package/dist/core/coherence.js.map +1 -1
- package/dist/core/concurrency.js +51 -0
- package/dist/core/concurrency.js.map +1 -0
- package/dist/core/config.js +10 -2
- package/dist/core/config.js.map +1 -1
- package/dist/core/delta.js +21 -2
- package/dist/core/delta.js.map +1 -1
- package/dist/core/dependencies.js +46 -42
- package/dist/core/dependencies.js.map +1 -1
- package/dist/core/docs.js +2 -1
- package/dist/core/docs.js.map +1 -1
- package/dist/core/doctor.js +41 -26
- package/dist/core/doctor.js.map +1 -1
- package/dist/core/document-bytes.js +59 -0
- package/dist/core/document-bytes.js.map +1 -0
- package/dist/core/explore.js +225 -0
- package/dist/core/explore.js.map +1 -0
- package/dist/core/fleet-context.js +16 -63
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/frontmatter.js +1 -5
- package/dist/core/frontmatter.js.map +1 -1
- package/dist/core/gherkin.js +3 -61
- package/dist/core/gherkin.js.map +1 -1
- package/dist/core/health.js +1 -5
- package/dist/core/health.js.map +1 -1
- package/dist/core/ids.js +34 -5
- package/dist/core/ids.js.map +1 -1
- package/dist/core/json.js +8 -1
- package/dist/core/json.js.map +1 -1
- package/dist/core/likec4.js +6 -1
- package/dist/core/likec4.js.map +1 -1
- package/dist/core/maturity.js +66 -0
- package/dist/core/maturity.js.map +1 -0
- package/dist/core/openapi-merge.js +39 -6
- package/dist/core/openapi-merge.js.map +1 -1
- package/dist/core/openapi.js +17 -5
- package/dist/core/openapi.js.map +1 -1
- package/dist/core/openspec-inventory.js +64 -27
- package/dist/core/openspec-inventory.js.map +1 -1
- package/dist/core/path-safety.js +59 -0
- package/dist/core/path-safety.js.map +1 -1
- package/dist/core/provenance.js +35 -10
- package/dist/core/provenance.js.map +1 -1
- package/dist/core/records.js +33 -0
- package/dist/core/records.js.map +1 -0
- package/dist/core/repo.js +75 -1
- package/dist/core/repo.js.map +1 -1
- package/dist/core/report.js +9 -0
- package/dist/core/report.js.map +1 -1
- package/dist/core/results.js +1 -3
- package/dist/core/results.js.map +1 -1
- package/dist/core/spec.js +3 -3
- package/dist/core/spec.js.map +1 -1
- package/dist/core/staging.js +124 -17
- package/dist/core/staging.js.map +1 -1
- package/dist/core/status.js +122 -33
- package/dist/core/status.js.map +1 -1
- package/dist/core/steps.js +72 -0
- package/dist/core/steps.js.map +1 -0
- package/dist/core/verify.js +18 -4
- package/dist/core/verify.js.map +1 -1
- package/dist/core/version.js +17 -1
- package/dist/core/version.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gherkin.js","sourceRoot":"","sources":["../../src/commands/gherkin.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `loam gherkin` — the third writer in the CLI (after archive and vouch), and\n * the only one that writes into the SERVICE repo, never the docs repo: the\n * docs state what must be true, the tests live with the code that makes it so.\n *\n * Two scopes, one emitter. `loam gherkin <FEAT>` emits the feature's ADDED and\n * MODIFIED requirements for this service — both spec axes — as the acceptance\n * skeleton to implement against, each file tagged with the feature id.\n * `loam gherkin --service <id>` (no feature) emits the full living suite from\n * spec.md + arch.spec.md: the regression skeleton a legacy service gets at\n * adoption. Either way the output is `<gherkinDir>/loam/` inside this repo,\n * a directory loam owns outright: the scope's files are rewritten, the scope's\n * orphans are deleted (and reported), and nothing outside `loam/` is touched —\n * except that a living run neither deletes NOR overwrites a file tagged with a\n * feature still in flight: it answers to that feature's delta until it\n * archives, and is reported as kept. A FEATURE run cannot keep — the file it\n * wants holds one feature's delta — so when the file it would write belongs to\n * a different feature still in flight, the whole run refuses (`gherkin-conflict`)\n * and names the owner rather than reverting it.\n *\n * It refuses to run anywhere but the service's own repo — vouch's discipline,\n * for vouch's reason: the output lands in the repo loam is standing in, and\n * from anywhere else that repo is somebody else's.\n */\nimport type { Command } from \"commander\";\nimport { existsSync } from \"node:fs\";\nimport { mkdir, readFile, unlink, writeFile } from \"node:fs/promises\";\nimport { join, relative } from \"node:path\";\nimport { loadConfig } from \"../core/config.js\";\nimport { decodeDocument, NotUtf8DocumentError } from \"../core/fleet-context.js\";\nimport { emitJson, emitJsonError, fail, reportNoConfig } from \"../core/json.js\";\nimport {\n featureSpecPaths,\n listFeatures,\n missingFeatureMessage,\n resolveFeature,\n servicePaths,\n SPEC_AXES,\n type SpecAxis,\n} from \"../core/repo.js\";\nimport { parseRequirements, type Requirement } from \"../core/spec.js\";\nimport {\n axisLabel,\n featureFilesUnder,\n gherkinRoot,\n parseStampedFeature,\n planEmission,\n type PlannedFeature,\n type StampedFeature,\n} from \"../core/gherkin.js\";\nimport { LOAM_VERSION } from \"../core/version.js\";\nimport { resolveInside, UnsafePathError } from \"../core/path-safety.js\";\n\ninterface GherkinOptions {\n service?: string;\n dryRun?: boolean;\n json?: boolean;\n}\n\nexport function registerGherkin(program: Command): void {\n program\n .command(\"gherkin\")\n .argument(\"[featureId]\", \"feature id — emit its ADDED/MODIFIED requirements; omit for the full living suite\")\n .description(\n \"Emit Gherkin .feature files from spec scenarios into this service repo's <gherkinDir>/loam/\",\n )\n .option(\"--service <id>\", \"service to emit for (defaults to the configured service)\")\n .option(\"--dry-run\", \"print the plan (writes, replacements, deletions) and write nothing\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (featureArg: string | undefined, opts: GherkinOptions) => {\n const json = opts.json === true;\n const dryRun = opts.dryRun === true;\n\n const config = await loadConfig();\n if (!config) {\n reportNoConfig(json);\n return;\n }\n const service = opts.service ?? config.service;\n if (service === undefined) {\n return fail(json, \"invalid-option\", \"No service. Pass --service <id> or set it in loam.json.\");\n }\n // The emission writes into the repo loam is standing in, so it can only\n // run where that repo is this service's — vouch's refusal, verbatim logic.\n if (config.service !== service) {\n const here =\n config.service === undefined ? \"this is not a service repo\" : `this repo is '${config.service}'`;\n return fail(\n json,\n \"invalid-option\",\n `Cannot emit gherkin for '${service}' from here: ${here}. Generated .feature files land in the service's own repo — run it there.`,\n );\n }\n\n // The repo root, not the cwd: `config.root` is the directory loam.json\n // was actually found in, and config discovery walks upward. Taken from\n // `process.cwd()` this emitted the whole suite into whatever directory\n // the caller happened to stand in — `src/api/features/loam/…` inside the\n // source tree, which the next run from the root neither finds, replaces\n // nor deletes, and which `loam validate` grades as a suite that was never\n // generated.\n const repoDir = config.root ?? process.cwd();\n let root: string;\n try {\n root = gherkinRoot(repoDir, config.gherkinDir);\n } catch (err) {\n if (!(err instanceof UnsafePathError)) throw err;\n return fail(\n json,\n \"invalid-option\",\n `Cannot emit Gherkin: ${err.message}. The output directory must stay inside the service repo.`,\n );\n }\n const rel = (abs: string): string => relative(repoDir, abs).split(/[\\\\/]/).join(\"/\");\n\n // The requirement set, by scope. Feature mode takes ADDED and MODIFIED —\n // a BASE requirement is the living state quoted, a REMOVED one is being\n // retired, and neither is behaviour anybody is about to test. Living mode\n // takes everything the living files hold.\n //\n // Read through `decodeDocument`, not `readFile(…, \"utf8\")`, and refused\n // rather than degraded: a spec.md saved as UTF-16 decodes to a document\n // with no requirements in it, which here is not merely a wrong answer but\n // a destructive one — an empty plan makes every existing .feature under\n // loam/ an orphan, and a living run DELETES its orphans. The whole\n // generated suite would go because a docs file was written by PowerShell.\n let mode: \"feature\" | \"living\" = \"living\";\n let featureId: string | undefined;\n const byAxis: Array<{ axis: SpecAxis; reqs: Requirement[] }> = [];\n const readSpec = async (path: string): Promise<Requirement[]> =>\n parseRequirements(decodeDocument(await readFile(path), path));\n try {\n if (featureArg !== undefined) {\n const feature = await resolveFeature(config.docsDir, featureArg, \"exclude\");\n if (!feature) {\n return fail(json, \"unknown-target\", await missingFeatureMessage(config.docsDir, featureArg));\n }\n mode = \"feature\";\n featureId = feature.id;\n const paths = featureSpecPaths(feature.dir, service);\n for (const axis of SPEC_AXES) {\n const path = paths[axis.key];\n const reqs = existsSync(path)\n ? (await readSpec(path)).filter((r) => r.kind === \"ADDED\" || r.kind === \"MODIFIED\")\n : [];\n byAxis.push({ axis, reqs });\n }\n } else {\n const paths = servicePaths(config.docsDir, service);\n if (!existsSync(paths.spec)) {\n return fail(\n json,\n \"unknown-target\",\n `No living spec at ${paths.spec}. Run \\`loam adopt\\` for '${service}' first.`,\n );\n }\n for (const axis of SPEC_AXES) {\n const path = paths[axis.key];\n const reqs = existsSync(path) ? (await readSpec(path)).filter((r) => r.kind !== \"REMOVED\") : [];\n byAxis.push({ axis, reqs });\n }\n }\n } catch (err) {\n if (!(err instanceof NotUtf8DocumentError)) throw err;\n return fail(json, \"repository-unavailable\", `Cannot emit gherkin: ${err.message}`);\n }\n\n // `service` is not decoration: it salts every `@loam-digest-…` stamp, and\n // it is the same string `loam verify` files the matching claims under.\n const plan = planEmission(byAxis, { service, featureTag: featureId, version: LOAM_VERSION });\n\n // Orphans of THIS scope, inside loam/ and nowhere else. Feature mode owns\n // only its own emissions: stamped files carrying this feature's tag whose\n // file is no longer in the plan (a requirement renamed or dropped).\n // Living mode owns the whole suite — any .feature not in the plan goes —\n // EXCEPT files tagged with a feature still in flight: those answer to\n // their feature's delta until it archives, and `loam gherkin <FEAT>` is\n // their regeneration.\n const planned = new Set(plan.map((f) => f.fileName));\n // Needed by BOTH modes now: living mode exempts in-flight files from\n // deletion and replacement, and feature mode has to recognise another\n // feature's in-flight file to refuse overwriting it.\n const activeIds = new Set((await listFeatures(config.docsDir)).map((f) => f.id));\n const orphans: string[] = [];\n if (existsSync(root)) {\n for (const abs of await featureFilesUnder(root)) {\n if (planned.has(relative(root, abs).split(/[\\\\/]/).join(\"/\"))) continue;\n const stamped = parseStampedFeature(await readFile(abs, \"utf8\"));\n if (mode === \"feature\") {\n if (stamped !== null && stamped.tags.includes(featureId!)) orphans.push(abs);\n } else {\n if (stamped !== null && stamped.tags.some((t) => activeIds.has(t))) continue;\n orphans.push(abs);\n }\n }\n }\n\n // The in-flight exemption guards the OVERWRITE path too, not only the\n // orphan scan above: files are named by requirement slug in both modes,\n // so a MODIFIED requirement's living emission always collides with the\n // active feature's file — and replacing it reverted the delta's wording\n // mid-flight, feature tag and new digest stamps destroyed, invisibly\n // (the reverted file grades current against the living spec). A planned\n // path whose existing content is stamped and tagged with a feature still\n // in flight is KEPT and reported as such; it answers to its feature's\n // delta until the feature archives, and then living regeneration\n // replaces it normally.\n //\n // FEATURE mode has the same collision and cannot solve it by keeping:\n // two features in flight against one requirement slug want the same\n // file, and whichever runs second used to `replace` — silently reverting\n // the other feature's wording, feature tag and digest stamps, so its\n // `verify --results` could never confirm a scenario again. Nothing loam\n // can write is right here (the file holds ONE feature's delta), so the\n // run refuses and names the owner: the two features have to be sequenced,\n // or the requirement renamed.\n type Action = \"written\" | \"replaced\" | \"kept\" | \"conflict\";\n const actions: Array<\n PlannedFeature & { path: string; action: Action; kept?: StampedFeature; owners?: string[] }\n > = [];\n for (const f of plan) {\n let path: string;\n try {\n // Check the final file as well as the owned root: a pre-planted\n // `<slug>.feature` symlink must not turn writeFile into an overwrite\n // outside the repository.\n path = resolveInside(\n repoDir,\n relative(repoDir, join(root, f.fileName)),\n `gherkin file '${f.fileName}'`,\n );\n } catch (err) {\n if (!(err instanceof UnsafePathError)) throw err;\n return fail(json, \"invalid-option\", `Cannot emit Gherkin: ${err.message}.`);\n }\n if (!existsSync(path)) {\n actions.push({ ...f, path, action: \"written\" });\n continue;\n }\n const existing = parseStampedFeature(await readFile(path, \"utf8\"));\n if (mode === \"living\") {\n if (existing !== null && existing.tags.some((t) => activeIds.has(t))) {\n actions.push({ ...f, path, action: \"kept\", kept: existing });\n continue;\n }\n } else {\n const owners =\n existing === null\n ? []\n : existing.tags.filter((t) => t !== featureId && activeIds.has(t));\n if (owners.length > 0) {\n actions.push({ ...f, path, action: \"conflict\", kept: existing!, owners });\n continue;\n }\n }\n actions.push({ ...f, path, action: \"replaced\" });\n }\n\n // All or nothing: one conflicting file refuses the whole emission, so a\n // half-written suite can never be the state an agent has to reason about.\n const conflicts = actions.filter((a) => a.action === \"conflict\");\n if (conflicts.length > 0) {\n const detail = conflicts\n .map((c) => `${rel(c.path)} is @${c.owners!.join(\" @\")} (requirement '${c.requirement.name}')`)\n .join(\"; \");\n const message =\n `Cannot emit gherkin for ${featureId}: ${conflicts.length} file(s) belong to another feature still in flight — ${detail}. ` +\n `A .feature file carries one feature's delta; overwriting would revert that feature's wording and destroy the digest stamps \\`loam verify --results\\` matches on. ` +\n `Archive (or abandon) the owning feature first, or rename the requirement in ${featureId}'s delta so the two stop sharing a file name.`;\n if (json) {\n emitJsonError(\"gherkin-conflict\", message, {\n conflicts: conflicts.map((c) => ({\n path: rel(c.path),\n action: \"conflict\",\n requirement: c.requirement.name,\n inFlight: c.owners,\n })),\n });\n return;\n }\n return fail(json, \"gherkin-conflict\", message);\n }\n\n const writes = actions.filter((a) => a.action !== \"kept\");\n\n if (!dryRun) {\n // The root is created only when something lands in it: an emission with\n // nothing to emit must not flip the repo into \"opted in\" — an empty\n // loam/ tells `loam validate` the whole living suite is missing.\n if (writes.length > 0) await mkdir(root, { recursive: true });\n for (const a of writes) await writeFile(a.path, a.content, \"utf8\");\n for (const o of orphans) await unlink(o);\n }\n\n if (json) {\n emitJson({\n mode,\n ...(featureId === undefined ? {} : { feature: featureId }),\n service,\n root: rel(root),\n written: !dryRun,\n files: actions.map((a) => ({\n path: rel(a.path),\n action: a.action,\n axis: axisLabel(a.axis),\n requirement: a.requirement.name,\n // A kept file's numbers describe what STAYS on disk (the in-flight\n // emission), not the emission that was withheld.\n scenarios: a.action === \"kept\" ? a.kept!.scenarios.length : a.digests.length,\n digests: a.action === \"kept\" ? a.kept!.scenarios.map((s) => s.digest) : a.digests,\n ...(a.action === \"kept\"\n ? { inFlight: a.kept!.tags.filter((t) => activeIds.has(t)) }\n : { stepless: a.stepless }),\n })),\n deleted: orphans.map(rel),\n });\n return;\n }\n\n const head = mode === \"feature\" ? `${featureId} · ${service}` : `${service} (living suite)`;\n console.log(`gherkin ${head} → ${rel(root)}/${dryRun ? \" (dry run)\" : \"\"}\\n`);\n if (actions.length === 0) {\n console.log(\n mode === \"feature\"\n ? ` ${featureId} has no ADDED or MODIFIED requirements for ${service} — nothing to emit.`\n : ` the living specs hold no requirements for ${service} — nothing to emit.`,\n );\n }\n // `conflict` never reaches here — the run refused above — but the map is\n // total so a future action cannot silently print `undefined`.\n const VERB: Record<Action, string> = {\n written: \"write \",\n replaced: \"replace\",\n kept: \"keep \",\n conflict: \"CONFLICT\",\n };\n for (const a of actions) {\n if (a.action === \"kept\") {\n const owners = a.kept!.tags.filter((t) => activeIds.has(t));\n console.log(\n ` keep ${a.fileName} — ${a.requirement.name} (in flight: @${owners.join(\" @\")} — \\`loam gherkin ${owners[0]}\\` regenerates it)`,\n );\n continue;\n }\n const n = a.digests.length;\n const arch = a.axis.key === \"archSpec\" ? \", arch\" : \"\";\n console.log(\n ` ${VERB[a.action]} ${a.fileName} — ${a.requirement.name} (${n} scenario${n === 1 ? \"\" : \"s\"}${arch})`,\n );\n for (const name of a.stepless) {\n console.log(\n ` ⚠ scenario '${name}' has NO recognizable steps — cucumber runs it vacuously green and \\`verify --results\\` can never confirm it; reword its body as \\`- **Given/When/Then**\\` bullets`,\n );\n }\n }\n for (const o of orphans) console.log(` delete ${relative(root, o).split(/[\\\\/]/).join(\"/\")} — no longer in this scope`);\n const wrote = `${writes.length} file(s)`;\n const keptNote = actions.length > writes.length ? `, ${actions.length - writes.length} kept in flight` : \"\";\n const dropped = orphans.length > 0 ? `, ${orphans.length} deletion(s)` : \"\";\n console.log(\n dryRun\n ? `\\n ${wrote}${keptNote}${dropped} — dry run, nothing was written.`\n : `\\n ${wrote} written${keptNote}${dropped}. Write step definitions OUTSIDE ${rel(root)}/ — regeneration rewrites it.`,\n );\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"gherkin.js","sourceRoot":"","sources":["../../src/commands/gherkin.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `loam gherkin` — the third writer in the CLI (after archive and vouch), and\n * the only one that writes into the SERVICE repo, never the docs repo: the\n * docs state what must be true, the tests live with the code that makes it so.\n *\n * Two scopes, one emitter. `loam gherkin <FEAT>` emits the feature's ADDED and\n * MODIFIED requirements for this service — both spec axes — as the acceptance\n * skeleton to implement against, each file tagged with the feature id.\n * `loam gherkin --service <id>` (no feature) emits the full living suite from\n * spec.md + arch.spec.md: the regression skeleton a legacy service gets at\n * adoption. Either way the output is `<gherkinDir>/loam/` inside this repo,\n * a directory loam owns outright: the scope's files are rewritten, the scope's\n * orphans are deleted (and reported), and nothing outside `loam/` is touched —\n * except that a living run neither deletes NOR overwrites a file tagged with a\n * feature still in flight: it answers to that feature's delta until it\n * archives, and is reported as kept. A FEATURE run cannot keep — the file it\n * wants holds one feature's delta — so when the file it would write belongs to\n * a different feature still in flight, the whole run refuses (`gherkin-conflict`)\n * and names the owner rather than reverting it.\n *\n * It refuses to run anywhere but the service's own repo — vouch's discipline,\n * for vouch's reason: the output lands in the repo loam is standing in, and\n * from anywhere else that repo is somebody else's.\n */\nimport type { Command } from \"commander\";\nimport { existsSync, realpathSync } from \"node:fs\";\nimport { mkdir, readFile, unlink, writeFile } from \"node:fs/promises\";\nimport { dirname, join, relative } from \"node:path\";\nimport { loadConfig } from \"../core/config.js\";\nimport { decodeDocument, NotUtf8DocumentError } from \"../core/document-bytes.js\";\nimport { emitJson, emitJsonError, fail, NO_SERVICE_MESSAGE, reportNoConfig } from \"../core/json.js\";\nimport {\n featureSpecPaths,\n listFeatures,\n missingFeatureMessage,\n resolveFeature,\n servicePaths,\n SPEC_AXES,\n type SpecAxis,\n} from \"../core/repo.js\";\nimport { parseRequirements, type Requirement } from \"../core/spec.js\";\nimport {\n axisLabel,\n featureFilesUnder,\n gherkinRoot,\n parseStampedFeature,\n planEmission,\n type PlannedFeature,\n type StampedFeature,\n} from \"../core/gherkin.js\";\nimport { LOAM_VERSION } from \"../core/version.js\";\nimport { isPathInside, resolveInside, UnsafePathError } from \"../core/path-safety.js\";\n\ninterface GherkinOptions {\n service?: string;\n dryRun?: boolean;\n json?: boolean;\n}\n\nexport function registerGherkin(program: Command): void {\n program\n .command(\"gherkin\")\n .argument(\"[featureId]\", \"feature id — emit its ADDED/MODIFIED requirements; omit for the full living suite\")\n .description(\n \"Emit Gherkin .feature files from spec scenarios into this service repo's <gherkinDir>/loam/\",\n )\n .option(\"--service <id>\", \"service to emit for (defaults to the configured service)\")\n .option(\"--dry-run\", \"print the plan (writes, replacements, deletions) and write nothing\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (featureArg: string | undefined, opts: GherkinOptions) => {\n const json = opts.json === true;\n const dryRun = opts.dryRun === true;\n\n const config = await loadConfig();\n if (!config) {\n reportNoConfig(json);\n return;\n }\n const service = opts.service ?? config.service;\n if (service === undefined) {\n return fail(json, \"invalid-option\", NO_SERVICE_MESSAGE);\n }\n // The emission writes into the repo loam is standing in, so it can only\n // run where that repo is this service's — vouch's refusal, verbatim logic.\n if (config.service !== service) {\n const here =\n config.service === undefined ? \"this is not a service repo\" : `this repo is '${config.service}'`;\n return fail(\n json,\n \"invalid-option\",\n `Cannot emit gherkin for '${service}' from here: ${here}. Generated .feature files land in the service's own repo — run it there.`,\n );\n }\n\n // The repo root, not the cwd: `config.root` is the directory loam.json\n // was actually found in, and config discovery walks upward. Taken from\n // `process.cwd()` this emitted the whole suite into whatever directory\n // the caller happened to stand in — `src/api/features/loam/…` inside the\n // source tree, which the next run from the root neither finds, replaces\n // nor deletes, and which `loam validate` grades as a suite that was never\n // generated.\n const repoDir = config.root ?? process.cwd();\n let root: string;\n try {\n root = gherkinRoot(repoDir, config.gherkinDir);\n } catch (err) {\n if (!(err instanceof UnsafePathError)) throw err;\n return fail(\n json,\n \"invalid-option\",\n `Cannot emit Gherkin: ${err.message}. The output directory must stay inside the service repo.`,\n );\n }\n const rel = (abs: string): string => relative(repoDir, abs).split(/[\\\\/]/).join(\"/\");\n\n // The scope is one value, not a mode with an id lying beside it that only\n // one of the modes ever fills in: the emission tag, the orphan filter,\n // the overwrite rule and the conflict refusal all want the feature id\n // exactly when the run is a feature run. A union states that; two\n // variables could only be asserted to agree.\n type Scope = { mode: \"living\" } | { mode: \"feature\"; featureId: string };\n const feature =\n featureArg === undefined ? null : await resolveFeature(config.docsDir, featureArg, \"exclude\");\n if (featureArg !== undefined && feature === null) {\n return fail(json, \"unknown-target\", await missingFeatureMessage(config.docsDir, featureArg));\n }\n const scope: Scope =\n feature === null ? { mode: \"living\" } : { mode: \"feature\", featureId: feature.id };\n\n // The requirement set, by scope. Feature mode takes ADDED and MODIFIED —\n // a BASE requirement is the living state quoted, a REMOVED one is being\n // retired, and neither is behaviour anybody is about to test. Living mode\n // takes everything the living files hold.\n //\n // Read through `decodeDocument`, not `readFile(…, \"utf8\")`, and refused\n // rather than degraded: a spec.md saved as UTF-16 decodes to a document\n // with no requirements in it, which here is not merely a wrong answer but\n // a destructive one — an empty plan makes every existing .feature under\n // loam/ an orphan, and a living run DELETES its orphans. The whole\n // generated suite would go because a docs file was written by PowerShell.\n const byAxis: Array<{ axis: SpecAxis; reqs: Requirement[] }> = [];\n const readSpec = async (path: string): Promise<Requirement[]> =>\n parseRequirements(decodeDocument(await readFile(path), path));\n try {\n if (feature !== null) {\n const paths = featureSpecPaths(feature.dir, service);\n for (const axis of SPEC_AXES) {\n const path = paths[axis.key];\n const reqs = existsSync(path)\n ? (await readSpec(path)).filter((r) => r.kind === \"ADDED\" || r.kind === \"MODIFIED\")\n : [];\n byAxis.push({ axis, reqs });\n }\n } else {\n const paths = servicePaths(config.docsDir, service);\n if (!existsSync(paths.spec)) {\n return fail(\n json,\n \"unknown-target\",\n `No living spec at ${paths.spec}. Run \\`loam adopt\\` for '${service}' first.`,\n );\n }\n for (const axis of SPEC_AXES) {\n const path = paths[axis.key];\n const reqs = existsSync(path) ? (await readSpec(path)).filter((r) => r.kind !== \"REMOVED\") : [];\n byAxis.push({ axis, reqs });\n }\n }\n } catch (err) {\n if (!(err instanceof NotUtf8DocumentError)) throw err;\n return fail(json, \"repository-unavailable\", `Cannot emit gherkin: ${err.message}`);\n }\n\n // `service` is not decoration: it salts every `@loam-digest-…` stamp, and\n // it is the same string `loam verify` files the matching claims under.\n const plan = planEmission(byAxis, {\n service,\n featureTag: scope.mode === \"feature\" ? scope.featureId : undefined,\n version: LOAM_VERSION,\n });\n\n // Orphans of THIS scope, inside loam/ and nowhere else. Feature mode owns\n // only its own emissions: stamped files carrying this feature's tag whose\n // file is no longer in the plan (a requirement renamed or dropped).\n // Living mode owns the whole suite — any .feature not in the plan goes —\n // EXCEPT files tagged with a feature still in flight: those answer to\n // their feature's delta until it archives, and `loam gherkin <FEAT>` is\n // their regeneration.\n const planned = new Set(plan.map((f) => f.fileName));\n // Needed by BOTH modes now: living mode exempts in-flight files from\n // deletion and replacement, and feature mode has to recognise another\n // feature's in-flight file to refuse overwriting it.\n const activeIds = new Set((await listFeatures(config.docsDir)).map((f) => f.id));\n // \"Inside loam/\" has to hold after the links are resolved, not merely in\n // the spelling of the path. `featureFilesUnder` FOLLOWS symlinks and\n // recurses with the LINK's path, so a planted `<gherkinDir>/loam/sub ->\n // /outside` hands back candidates that read as ours while `unlink`\n // resolves them and destroys files that were never in this repo. Planned\n // names are flat, so nothing nested is ever spared by `planned.has(...)`,\n // and deletion is the one irreversible thing this command does: a\n // candidate is an orphan only when its CONTAINING directory really\n // resolves inside the owned root. The write path below refuses the same\n // attack for the same reason.\n //\n // The candidate FILE is deliberately NOT resolved. A symlinked .feature\n // sitting directly in loam/ is loam's to remove — unlinking it takes the\n // link and leaves its target alone — so only the directory chain has to\n // be ours.\n const realOrNull = (p: string): string | null => {\n try {\n return realpathSync(p);\n } catch {\n return null;\n }\n };\n // A root that does not resolve — absent, dangling, or vanished mid-run —\n // proves nothing about what lives beneath it, so nothing is collected.\n // Leaving an orphan behind is self-repairing (`loam validate` grades it\n // `gherkin.orphaned` and the next run removes it); a deletion taken on an\n // unproven path is not.\n const rootReal = realOrNull(root);\n const orphans: string[] = [];\n if (rootReal !== null) {\n for (const abs of await featureFilesUnder(root)) {\n if (planned.has(relative(root, abs).split(/[\\\\/]/).join(\"/\"))) continue;\n const holder = realOrNull(dirname(abs));\n if (holder === null || !isPathInside(rootReal, holder)) continue;\n const stamped = parseStampedFeature(await readFile(abs, \"utf8\"));\n if (scope.mode === \"feature\") {\n if (stamped !== null && stamped.tags.includes(scope.featureId)) orphans.push(abs);\n } else {\n if (stamped !== null && stamped.tags.some((t) => activeIds.has(t))) continue;\n orphans.push(abs);\n }\n }\n }\n\n // The in-flight exemption guards the OVERWRITE path too, not only the\n // orphan scan above: files are named by requirement slug in both modes,\n // so a MODIFIED requirement's living emission always collides with the\n // active feature's file — and replacing it reverted the delta's wording\n // mid-flight, feature tag and new digest stamps destroyed, invisibly\n // (the reverted file grades current against the living spec). A planned\n // path whose existing content is stamped and tagged with a feature still\n // in flight is KEPT and reported as such; it answers to its feature's\n // delta until the feature archives, and then living regeneration\n // replaces it normally.\n //\n // FEATURE mode has the same collision and cannot solve it by keeping:\n // two features in flight against one requirement slug want the same\n // file, and whichever runs second used to `replace` — silently reverting\n // the other feature's wording, feature tag and digest stamps, so its\n // `verify --results` could never confirm a scenario again. Nothing loam\n // can write is right here (the file holds ONE feature's delta), so the\n // run refuses and names the owner: the two features have to be sequenced,\n // or the requirement renamed.\n //\n // The row's shape follows its action instead of carrying every field any\n // action might want: only a kept or a conflicting row has read the file\n // that is already there, and only a conflicting one has owners. Held as\n // one flat row with optional fields, every reader below had to assert\n // what the writer three lines up already knew.\n type Emission = PlannedFeature & { path: string };\n type ActionRow =\n | (Emission & { action: \"written\" | \"replaced\" })\n | (Emission & { action: \"kept\"; kept: StampedFeature })\n | (Emission & { action: \"conflict\"; kept: StampedFeature; owners: string[] });\n type Action = ActionRow[\"action\"];\n const actions: ActionRow[] = [];\n for (const f of plan) {\n let path: string;\n try {\n // Check the final file as well as the owned root: a pre-planted\n // `<slug>.feature` symlink must not turn writeFile into an overwrite\n // outside the repository.\n path = resolveInside(\n repoDir,\n relative(repoDir, join(root, f.fileName)),\n `gherkin file '${f.fileName}'`,\n );\n } catch (err) {\n if (!(err instanceof UnsafePathError)) throw err;\n return fail(json, \"invalid-option\", `Cannot emit Gherkin: ${err.message}.`);\n }\n if (!existsSync(path)) {\n actions.push({ ...f, path, action: \"written\" });\n continue;\n }\n const existing = parseStampedFeature(await readFile(path, \"utf8\"));\n if (scope.mode === \"living\") {\n if (existing !== null && existing.tags.some((t) => activeIds.has(t))) {\n actions.push({ ...f, path, action: \"kept\", kept: existing });\n continue;\n }\n } else if (existing !== null) {\n // An unstamped file is nobody's delta — it owns nothing, so it is\n // replaced like any other, which is what the empty owner list used\n // to say the long way round.\n const owners = existing.tags.filter((t) => t !== scope.featureId && activeIds.has(t));\n if (owners.length > 0) {\n actions.push({ ...f, path, action: \"conflict\", kept: existing, owners });\n continue;\n }\n }\n actions.push({ ...f, path, action: \"replaced\" });\n }\n\n // All or nothing: one conflicting file refuses the whole emission, so a\n // half-written suite can never be the state an agent has to reason about.\n // Only a feature run can conflict — living mode keeps the in-flight file\n // rather than refusing — and standing inside that scope is also how the\n // message names the feature without asserting there is one.\n if (scope.mode === \"feature\") {\n const conflicts = actions.filter(\n (a): a is Extract<ActionRow, { action: \"conflict\" }> => a.action === \"conflict\",\n );\n if (conflicts.length > 0) {\n const detail = conflicts\n .map((c) => `${rel(c.path)} is @${c.owners.join(\" @\")} (requirement '${c.requirement.name}')`)\n .join(\"; \");\n const message =\n `Cannot emit gherkin for ${scope.featureId}: ${conflicts.length} file(s) belong to another feature still in flight — ${detail}. ` +\n `A .feature file carries one feature's delta; overwriting would revert that feature's wording and destroy the digest stamps \\`loam verify --results\\` matches on. ` +\n `Archive (or abandon) the owning feature first, or rename the requirement in ${scope.featureId}'s delta so the two stop sharing a file name.`;\n if (json) {\n emitJsonError(\"gherkin-conflict\", message, {\n conflicts: conflicts.map((c) => ({\n path: rel(c.path),\n action: \"conflict\",\n requirement: c.requirement.name,\n inFlight: c.owners,\n })),\n });\n return;\n }\n return fail(json, \"gherkin-conflict\", message);\n }\n }\n\n const writes = actions.filter((a) => a.action !== \"kept\");\n\n if (!dryRun) {\n // The root is created only when something lands in it: an emission with\n // nothing to emit must not flip the repo into \"opted in\" — an empty\n // loam/ tells `loam validate` the whole living suite is missing.\n if (writes.length > 0) await mkdir(root, { recursive: true });\n for (const a of writes) await writeFile(a.path, a.content, \"utf8\");\n for (const o of orphans) await unlink(o);\n }\n\n if (json) {\n emitJson({\n mode: scope.mode,\n ...(scope.mode === \"feature\" ? { feature: scope.featureId } : {}),\n service,\n root: rel(root),\n written: !dryRun,\n files: actions.map((a) => ({\n path: rel(a.path),\n action: a.action,\n axis: axisLabel(a.axis),\n requirement: a.requirement.name,\n // A kept file's numbers describe what STAYS on disk (the in-flight\n // emission), not the emission that was withheld.\n scenarios: a.action === \"kept\" ? a.kept.scenarios.length : a.digests.length,\n digests: a.action === \"kept\" ? a.kept.scenarios.map((s) => s.digest) : a.digests,\n ...(a.action === \"kept\"\n ? { inFlight: a.kept.tags.filter((t) => activeIds.has(t)) }\n : { stepless: a.stepless }),\n })),\n deleted: orphans.map(rel),\n });\n return;\n }\n\n const head =\n scope.mode === \"feature\" ? `${scope.featureId} · ${service}` : `${service} (living suite)`;\n console.log(`gherkin ${head} → ${rel(root)}/${dryRun ? \" (dry run)\" : \"\"}\\n`);\n if (actions.length === 0) {\n console.log(\n scope.mode === \"feature\"\n ? ` ${scope.featureId} has no ADDED or MODIFIED requirements for ${service} — nothing to emit.`\n : ` the living specs hold no requirements for ${service} — nothing to emit.`,\n );\n }\n // `conflict` never reaches here — the run refused above — but the map is\n // total so a future action cannot silently print `undefined`.\n const VERB: Record<Action, string> = {\n written: \"write \",\n replaced: \"replace\",\n kept: \"keep \",\n conflict: \"CONFLICT\",\n };\n for (const a of actions) {\n if (a.action === \"kept\") {\n const owners = a.kept.tags.filter((t) => activeIds.has(t));\n console.log(\n ` keep ${a.fileName} — ${a.requirement.name} (in flight: @${owners.join(\" @\")} — \\`loam gherkin ${owners[0]}\\` regenerates it)`,\n );\n continue;\n }\n const n = a.digests.length;\n const arch = a.axis.key === \"archSpec\" ? \", arch\" : \"\";\n console.log(\n ` ${VERB[a.action]} ${a.fileName} — ${a.requirement.name} (${n} scenario${n === 1 ? \"\" : \"s\"}${arch})`,\n );\n for (const name of a.stepless) {\n console.log(\n ` ⚠ scenario '${name}' has NO recognizable steps — cucumber runs it vacuously green and \\`verify --results\\` can never confirm it; reword its body as \\`- **Given/When/Then**\\` bullets`,\n );\n }\n }\n for (const o of orphans) console.log(` delete ${relative(root, o).split(/[\\\\/]/).join(\"/\")} — no longer in this scope`);\n const wrote = `${writes.length} file(s)`;\n const keptNote = actions.length > writes.length ? `, ${actions.length - writes.length} kept in flight` : \"\";\n const dropped = orphans.length > 0 ? `, ${orphans.length} deletion(s)` : \"\";\n console.log(\n dryRun\n ? `\\n ${wrote}${keptNote}${dropped} — dry run, nothing was written.`\n : `\\n ${wrote} written${keptNote}${dropped}. Write step definitions OUTSIDE ${rel(root)}/ — regeneration rewrites it.`,\n );\n });\n}\n"]}
|
package/dist/commands/init.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { existsSync, statSync } from "node:fs";
|
|
2
|
-
import {
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
3
|
import { CONFIG_FILENAME, findConfigPath, loadConfig, localConfigPath, saveConfig, } from "../core/config.js";
|
|
4
4
|
import { emitJson, fail } from "../core/json.js";
|
|
5
5
|
import { plannedDocsFiles, scaffoldDocs } from "../core/docs.js";
|
|
6
|
-
import { docsRepoState, listServices } from "../core/repo.js";
|
|
6
|
+
import { agentsPath, docsRepoState, listServices } from "../core/repo.js";
|
|
7
7
|
import { InvalidIdError, assertServiceId } from "../core/ids.js";
|
|
8
8
|
import { AGENT_TOOLS, detectAgentTools, plannedCommandFiles, scaffoldAgentCommands, } from "../core/agent.js";
|
|
9
9
|
/**
|
|
@@ -57,7 +57,7 @@ function storedDocsDir(raw) {
|
|
|
57
57
|
* `../docs`) look like a join and adopt the service repo as the fleet.
|
|
58
58
|
*/
|
|
59
59
|
function isDocsRepo(dir) {
|
|
60
|
-
return docsRepoState(dir).kind === "ok" && existsSync(
|
|
60
|
+
return docsRepoState(dir).kind === "ok" && existsSync(agentsPath(dir));
|
|
61
61
|
}
|
|
62
62
|
export function registerInit(program) {
|
|
63
63
|
program
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Command } from \"commander\";\nimport { existsSync, statSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport {\n CONFIG_FILENAME,\n findConfigPath,\n loadConfig,\n localConfigPath,\n saveConfig,\n type LoamConfig,\n} from \"../core/config.js\";\nimport { emitJson, fail } from \"../core/json.js\";\nimport { plannedDocsFiles, scaffoldDocs } from \"../core/docs.js\";\nimport { docsRepoState, listServices } from \"../core/repo.js\";\nimport { InvalidIdError, assertServiceId } from \"../core/ids.js\";\nimport {\n AGENT_TOOLS,\n detectAgentTools,\n plannedCommandFiles,\n scaffoldAgentCommands,\n type Delivery,\n} from \"../core/agent.js\";\n\ninterface InitOptions {\n docs: string;\n service?: string;\n /** --create: make a new docs repo at --docs instead of joining an existing one. */\n create?: boolean;\n /** --force: write loam.json here even though one already governs a parent directory. */\n force?: boolean;\n /** commander's --no-commands: true unless the flag is passed. */\n commands: boolean;\n /** commander's --no-skills: true unless the flag is passed. */\n skills: boolean;\n /** --tools: comma-separated AGENT_TOOLS ids, or \"all\". Absent = autodetect. */\n tools?: string;\n json?: boolean;\n}\n\n/**\n * Resolve --tools to registry ids, or null after reporting the refusal. The\n * default (the cwd scan) lives at the call site, not in commander, so an\n * explicit `--tools` is distinguishable from none — which is what lets both the\n * autodetection fallback and the `--no-*` contradiction be decided instead of\n * silently arbitrated.\n */\nfunction resolveTools(raw: string, json: boolean): string[] | null {\n const supported = Object.keys(AGENT_TOOLS);\n if (raw === \"all\") return supported;\n const ids = [...new Set(raw.split(\",\").map((t) => t.trim()).filter((t) => t !== \"\"))];\n const unknown = ids.filter((t) => !(t in AGENT_TOOLS));\n if (ids.length === 0 || unknown.length > 0) {\n fail(\n json,\n \"invalid-option\",\n (ids.length === 0\n ? \"--tools names no tool.\"\n : `--tools does not recognize: ${unknown.join(\", \")}.`) +\n ` Supported: ${supported.join(\", \")} — or \"all\".`,\n );\n return null;\n }\n return ids;\n}\n\n/**\n * `--docs` exactly as the caller wrote it, with separators normalised.\n *\n * Stored verbatim on purpose. `init` used to resolve it and write the absolute\n * result, which meant a committed loam.json named a directory that existed on\n * one laptop: every teammate who cloned the service repo got a docsDir under\n * someone else's home directory, and `loam list` reported an empty fleet\n * instead of saying so. `loadConfig` resolves relative paths against the config\n * file's own directory, so `../docs` keeps meaning \"next to this repo\" wherever\n * the pair is checked out. An absolute `--docs` is still stored absolute — that\n * is the caller's explicit choice, and `loam doctor` warns about it.\n *\n * Backslashes become forward slashes so a config written on Windows resolves on\n * POSIX; a trailing separator is dropped so the stored spelling is stable.\n */\nfunction storedDocsDir(raw: string): string {\n const slashed = raw.split(\"\\\\\").join(\"/\");\n if (slashed.length <= 1) return slashed;\n const trimmed = slashed.replace(/\\/+$/, \"\");\n return trimmed === \"\" ? \"/\" : trimmed;\n}\n\n/**\n * Does this directory already hold a docs repo? `services/` plus `AGENTS.md` —\n * the two things every docs repo has and no service repo does. The pair is the\n * whole point: a single marker would make `init --docs ../srv` (a typo for\n * `../docs`) look like a join and adopt the service repo as the fleet.\n */\nfunction isDocsRepo(dir: string): boolean {\n return docsRepoState(dir).kind === \"ok\" && existsSync(join(dir, \"AGENTS.md\"));\n}\n\nexport function registerInit(program: Command): void {\n program\n .command(\"init\")\n .description(\"Initialize loam: create/point at the shared docs repo and write local config\")\n .option(\"--docs <dir>\", \"path to the shared docs repo (the source of truth)\", \".loam-docs\")\n .option(\"--service <id>\", \"canonical id of the service in this repo\")\n .option(\"--create\", \"create a new docs repo at --docs instead of joining an existing one\")\n .option(\"--force\", `write ${CONFIG_FILENAME} here even though one governs a parent directory`)\n .option(\n \"--tools <ids>\",\n \"agent tools to generate for, overriding the scan of this directory: \" +\n `comma-separated (${Object.keys(AGENT_TOOLS).join(\", \")}) or \"all\"`,\n )\n .option(\"--no-commands\", \"skip the slash commands for this repo entirely\")\n .option(\"--no-skills\", \"skip the agent skills for this repo entirely\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (opts: InitOptions, command: Command) => {\n const json = opts.json === true;\n const cwd = process.cwd();\n\n // The id becomes services/<id>/ in the shared repo, so it is validated\n // before anything is written — by the same rule adopt, vouch and gherkin\n // use, never by a guard invented here.\n if (opts.service !== undefined) {\n try {\n assertServiceId(opts.service);\n } catch (err) {\n if (!(err instanceof InvalidIdError)) throw err;\n fail(json, \"invalid-option\", err.message);\n return;\n }\n }\n\n // A loam.json in an ancestor already governs this directory: every command\n // run from here resolves to it. Writing a second one below it would shadow\n // the first for this subtree only — the kind of split-brain nobody\n // discovers until two commands disagree about which docs repo is the fleet.\n const governing = findConfigPath(cwd);\n if (governing !== null && governing !== localConfigPath(cwd) && opts.force !== true) {\n fail(\n json,\n \"already-exists\",\n `${governing} already governs this directory. ` +\n `Run loam from ${resolve(governing, \"..\")}, or pass --force to give this ` +\n `subdirectory its own ${CONFIG_FILENAME}.`,\n );\n return;\n }\n\n // Only THIS directory's config is spread forward. Under --force there is\n // a config in an ancestor too, and inheriting its `service` would bind\n // this repo to a service somebody else's repo declared.\n const existing = existsSync(localConfigPath(cwd)) ? await loadConfig(cwd) : null;\n\n // A re-run must not move a committed pointer. `--docs` has a default\n // (`.loam-docs`), so an init invoked for any OTHER reason — `--service`,\n // a new `--tools`, the fix `doctor.agent-files-missing` prints — used to\n // arrive here carrying a docsDir nobody typed: the default then named no\n // docs repo, the refusal steered the caller to `--create`, and `--create`\n // rewrote `docsDir` in the committed loam.json to a freshly scaffolded\n // empty one. `validate --all` went green over an empty fleet, which is\n // the exact outcome `--create` exists to prevent. So the flag wins only\n // when it was actually passed; otherwise the file this repo already\n // committed does.\n const committedDocs = existing?.docsDirAsWritten;\n const docsTyped = command.getOptionValueSource(\"docs\") !== \"default\";\n const docsSource: \"flag\" | \"config\" | \"default\" =\n docsTyped ? \"flag\" : committedDocs === undefined ? \"default\" : \"config\";\n const docsOption = !docsTyped && committedDocs !== undefined ? committedDocs : opts.docs;\n const docsDir = resolve(cwd, docsOption);\n\n // The one predictable failure: --docs naming a file. Refused here so the\n // caller gets a clean envelope/message instead of mkdir's ENOTDIR throw.\n if (existsSync(docsDir) && !statSync(docsDir).isDirectory()) {\n fail(json, \"invalid-option\", `--docs points at a file, not a directory: ${docsDir}`);\n return;\n }\n\n // Joining an existing docs repo and creating a new one are different\n // intentions, and `init` used to perform the second one whenever the first\n // was misspelled: `--docs ../dcos` scaffolded a second, empty docs repo\n // beside the real one, and the fleet quietly split in two. Creation is now\n // something the caller asks for.\n const joining = isDocsRepo(docsDir);\n if (!joining && opts.create !== true) {\n fail(\n json,\n \"invalid-option\",\n docsRepoState(docsDir).kind === \"missing\"\n ? `--docs points at a directory that does not exist: ${docsDir}. ` +\n \"If that is a typo, fix it; to create a new docs repo there, pass --create.\"\n : `--docs points at ${docsDir}, which is not a docs repo ` +\n \"(a docs repo has services/ and AGENTS.md). \" +\n \"Point --docs at the shared docs repo, or pass --create to make a new one here.\",\n );\n return;\n }\n\n // --tools selects the files to write; --no-commands and --no-skills each\n // say write none of that kind. Together they contradict — refused, not\n // arbitrated, in either combination.\n const suppressed = [\n ...(opts.commands ? [] : [\"--no-commands\"]),\n ...(opts.skills ? [] : [\"--no-skills\"]),\n ];\n if (opts.tools !== undefined && suppressed.length > 0) {\n fail(\n json,\n \"invalid-option\",\n `--tools contradicts ${suppressed.join(\" and \")}: ` +\n \"one selects the files to generate, the other suppresses them.\",\n );\n return;\n }\n const delivery: Delivery[] = [\n ...(opts.commands ? ([\"commands\"] as const) : []),\n ...(opts.skills ? ([\"skills\"] as const) : []),\n ];\n\n // Without --tools the repo decides: whichever tools have left their own\n // dot-directories here. Falling back to claude keeps a bare repo behaving\n // exactly as it did before the scan existed. `detected` is reported either\n // way — what the scan saw is a fact about the repo, not about the flags.\n const detected = detectAgentTools(cwd);\n const scanned = detected.length > 0 ? detected : [\"claude\"];\n const tools = opts.tools === undefined ? scanned : resolveTools(opts.tools, json);\n if (tools === null) return;\n\n // `skipped` is the other half of the never-overwrite contract: the\n // scaffolds return only what they created, so what they left alone is\n // probed here, before they run — in the order they would create it, so\n // created + skipped is the same list on every repo. Both halves come from\n // the scaffolds' own planned-file lists, so the probe cannot drift from\n // what is actually written.\n const agentFiles = delivery.length > 0 ? plannedCommandFiles(cwd, tools, delivery) : [];\n const candidates = [...plannedDocsFiles(docsDir), ...agentFiles.map((f) => f.path)];\n const skipped = candidates.filter((p) => existsSync(p));\n\n // Joining touches the docs repo not at all: it is somebody's committed\n // repository, and \"point at it\" must never mean \"write to it\".\n const created = joining && opts.create !== true ? [] : (await scaffoldDocs(docsDir)).created;\n const generatedFor = delivery.length > 0 ? tools : [];\n if (delivery.length > 0) {\n created.push(...(await scaffoldAgentCommands(cwd, tools, delivery)));\n }\n\n const stored = storedDocsDir(docsOption);\n // Union, not replacement: this run's files join the ones earlier runs left\n // on disk, and `agentTools` is a record of what the repo HOLDS. A run that\n // generated nothing adds nothing and erases nothing.\n const recordedTools = [...new Set([...(existing?.agentTools ?? []), ...generatedFor])];\n const config: LoamConfig = {\n ...existing,\n docsDir: stored,\n ...(opts.service ? { service: opts.service } : {}),\n ...(recordedTools.length > 0 ? { agentTools: recordedTools } : {}),\n };\n const configFile = await saveConfig(config, cwd);\n\n const serviceCount = (await listServices(docsDir)).length;\n\n if (json) {\n // `docsDir` stays the resolved absolute path — it is what a consumer\n // needs to find the repo. `docsDirStored` is what was written to\n // loam.json, which is the fact this command is now careful about.\n // `tools` names what files were generated FOR — empty when every\n // delivery was suppressed and none were. `detected` is the separate\n // question of what the scan SAW, which `--tools` overrides without\n // changing: an agent comparing the two learns whether its selection\n // matches the repo.\n emitJson({\n docsDir,\n docsDirStored: stored,\n // Where the pointer came from: `flag` (a --docs somebody typed),\n // `config` (the one this repo already committed — a re-run cannot\n // move it), or `default` (nothing said, nothing committed).\n docsDirSource: docsSource,\n docsRepo: joining ? \"existing\" : \"created\",\n services: serviceCount,\n created,\n skipped,\n tools: generatedFor,\n detected,\n });\n return;\n }\n\n console.log(\"loam initialized.\");\n if (joining) {\n console.log(` docs repo: ${docsDir}`);\n console.log(` pointing at existing docs repo (${serviceCount} services)`);\n } else {\n console.log(` docs repo: ${docsDir} (created)`);\n }\n console.log(\n ` docsDir: ${stored} (as stored in ${CONFIG_FILENAME}`\n + (docsSource === \"config\" ? \" — kept; no --docs was passed\" : \"\") + \")\",\n );\n if (config.service) console.log(` service: ${config.service}`);\n console.log(` config: ${configFile}`);\n console.log(` commit ${CONFIG_FILENAME} — docsDir is resolved relative to it`);\n if (opts.tools === undefined && detected.length > 0) {\n console.log(` detected: ${detected.join(\", \")}`);\n }\n if (opts.commands) console.log(` commands: ${tools.join(\", \")}`);\n if (opts.skills) console.log(` skills: ${tools.join(\", \")}`);\n if (created.length > 0) {\n console.log(\" scaffolded:\");\n for (const c of created) console.log(` + ${c}`);\n }\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Command } from \"commander\";\nimport { existsSync, statSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport {\n CONFIG_FILENAME,\n findConfigPath,\n loadConfig,\n localConfigPath,\n saveConfig,\n type LoamConfig,\n} from \"../core/config.js\";\nimport { emitJson, fail } from \"../core/json.js\";\nimport { plannedDocsFiles, scaffoldDocs } from \"../core/docs.js\";\nimport { agentsPath, docsRepoState, listServices } from \"../core/repo.js\";\nimport { InvalidIdError, assertServiceId } from \"../core/ids.js\";\nimport {\n AGENT_TOOLS,\n detectAgentTools,\n plannedCommandFiles,\n scaffoldAgentCommands,\n type Delivery,\n} from \"../core/agent.js\";\n\ninterface InitOptions {\n docs: string;\n service?: string;\n /** --create: make a new docs repo at --docs instead of joining an existing one. */\n create?: boolean;\n /** --force: write loam.json here even though one already governs a parent directory. */\n force?: boolean;\n /** commander's --no-commands: true unless the flag is passed. */\n commands: boolean;\n /** commander's --no-skills: true unless the flag is passed. */\n skills: boolean;\n /** --tools: comma-separated AGENT_TOOLS ids, or \"all\". Absent = autodetect. */\n tools?: string;\n json?: boolean;\n}\n\n/**\n * Resolve --tools to registry ids, or null after reporting the refusal. The\n * default (the cwd scan) lives at the call site, not in commander, so an\n * explicit `--tools` is distinguishable from none — which is what lets both the\n * autodetection fallback and the `--no-*` contradiction be decided instead of\n * silently arbitrated.\n */\nfunction resolveTools(raw: string, json: boolean): string[] | null {\n const supported = Object.keys(AGENT_TOOLS);\n if (raw === \"all\") return supported;\n const ids = [...new Set(raw.split(\",\").map((t) => t.trim()).filter((t) => t !== \"\"))];\n const unknown = ids.filter((t) => !(t in AGENT_TOOLS));\n if (ids.length === 0 || unknown.length > 0) {\n fail(\n json,\n \"invalid-option\",\n (ids.length === 0\n ? \"--tools names no tool.\"\n : `--tools does not recognize: ${unknown.join(\", \")}.`) +\n ` Supported: ${supported.join(\", \")} — or \"all\".`,\n );\n return null;\n }\n return ids;\n}\n\n/**\n * `--docs` exactly as the caller wrote it, with separators normalised.\n *\n * Stored verbatim on purpose. `init` used to resolve it and write the absolute\n * result, which meant a committed loam.json named a directory that existed on\n * one laptop: every teammate who cloned the service repo got a docsDir under\n * someone else's home directory, and `loam list` reported an empty fleet\n * instead of saying so. `loadConfig` resolves relative paths against the config\n * file's own directory, so `../docs` keeps meaning \"next to this repo\" wherever\n * the pair is checked out. An absolute `--docs` is still stored absolute — that\n * is the caller's explicit choice, and `loam doctor` warns about it.\n *\n * Backslashes become forward slashes so a config written on Windows resolves on\n * POSIX; a trailing separator is dropped so the stored spelling is stable.\n */\nfunction storedDocsDir(raw: string): string {\n const slashed = raw.split(\"\\\\\").join(\"/\");\n if (slashed.length <= 1) return slashed;\n const trimmed = slashed.replace(/\\/+$/, \"\");\n return trimmed === \"\" ? \"/\" : trimmed;\n}\n\n/**\n * Does this directory already hold a docs repo? `services/` plus `AGENTS.md` —\n * the two things every docs repo has and no service repo does. The pair is the\n * whole point: a single marker would make `init --docs ../srv` (a typo for\n * `../docs`) look like a join and adopt the service repo as the fleet.\n */\nfunction isDocsRepo(dir: string): boolean {\n return docsRepoState(dir).kind === \"ok\" && existsSync(agentsPath(dir));\n}\n\nexport function registerInit(program: Command): void {\n program\n .command(\"init\")\n .description(\"Initialize loam: create/point at the shared docs repo and write local config\")\n .option(\"--docs <dir>\", \"path to the shared docs repo (the source of truth)\", \".loam-docs\")\n .option(\"--service <id>\", \"canonical id of the service in this repo\")\n .option(\"--create\", \"create a new docs repo at --docs instead of joining an existing one\")\n .option(\"--force\", `write ${CONFIG_FILENAME} here even though one governs a parent directory`)\n .option(\n \"--tools <ids>\",\n \"agent tools to generate for, overriding the scan of this directory: \" +\n `comma-separated (${Object.keys(AGENT_TOOLS).join(\", \")}) or \"all\"`,\n )\n .option(\"--no-commands\", \"skip the slash commands for this repo entirely\")\n .option(\"--no-skills\", \"skip the agent skills for this repo entirely\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (opts: InitOptions, command: Command) => {\n const json = opts.json === true;\n const cwd = process.cwd();\n\n // The id becomes services/<id>/ in the shared repo, so it is validated\n // before anything is written — by the same rule adopt, vouch and gherkin\n // use, never by a guard invented here.\n if (opts.service !== undefined) {\n try {\n assertServiceId(opts.service);\n } catch (err) {\n if (!(err instanceof InvalidIdError)) throw err;\n fail(json, \"invalid-option\", err.message);\n return;\n }\n }\n\n // A loam.json in an ancestor already governs this directory: every command\n // run from here resolves to it. Writing a second one below it would shadow\n // the first for this subtree only — the kind of split-brain nobody\n // discovers until two commands disagree about which docs repo is the fleet.\n const governing = findConfigPath(cwd);\n if (governing !== null && governing !== localConfigPath(cwd) && opts.force !== true) {\n fail(\n json,\n \"already-exists\",\n `${governing} already governs this directory. ` +\n `Run loam from ${resolve(governing, \"..\")}, or pass --force to give this ` +\n `subdirectory its own ${CONFIG_FILENAME}.`,\n );\n return;\n }\n\n // Only THIS directory's config is spread forward. Under --force there is\n // a config in an ancestor too, and inheriting its `service` would bind\n // this repo to a service somebody else's repo declared.\n const existing = existsSync(localConfigPath(cwd)) ? await loadConfig(cwd) : null;\n\n // A re-run must not move a committed pointer. `--docs` has a default\n // (`.loam-docs`), so an init invoked for any OTHER reason — `--service`,\n // a new `--tools`, the fix `doctor.agent-files-missing` prints — used to\n // arrive here carrying a docsDir nobody typed: the default then named no\n // docs repo, the refusal steered the caller to `--create`, and `--create`\n // rewrote `docsDir` in the committed loam.json to a freshly scaffolded\n // empty one. `validate --all` went green over an empty fleet, which is\n // the exact outcome `--create` exists to prevent. So the flag wins only\n // when it was actually passed; otherwise the file this repo already\n // committed does.\n const committedDocs = existing?.docsDirAsWritten;\n const docsTyped = command.getOptionValueSource(\"docs\") !== \"default\";\n const docsSource: \"flag\" | \"config\" | \"default\" =\n docsTyped ? \"flag\" : committedDocs === undefined ? \"default\" : \"config\";\n const docsOption = !docsTyped && committedDocs !== undefined ? committedDocs : opts.docs;\n const docsDir = resolve(cwd, docsOption);\n\n // The one predictable failure: --docs naming a file. Refused here so the\n // caller gets a clean envelope/message instead of mkdir's ENOTDIR throw.\n if (existsSync(docsDir) && !statSync(docsDir).isDirectory()) {\n fail(json, \"invalid-option\", `--docs points at a file, not a directory: ${docsDir}`);\n return;\n }\n\n // Joining an existing docs repo and creating a new one are different\n // intentions, and `init` used to perform the second one whenever the first\n // was misspelled: `--docs ../dcos` scaffolded a second, empty docs repo\n // beside the real one, and the fleet quietly split in two. Creation is now\n // something the caller asks for.\n const joining = isDocsRepo(docsDir);\n if (!joining && opts.create !== true) {\n fail(\n json,\n \"invalid-option\",\n docsRepoState(docsDir).kind === \"missing\"\n ? `--docs points at a directory that does not exist: ${docsDir}. ` +\n \"If that is a typo, fix it; to create a new docs repo there, pass --create.\"\n : `--docs points at ${docsDir}, which is not a docs repo ` +\n \"(a docs repo has services/ and AGENTS.md). \" +\n \"Point --docs at the shared docs repo, or pass --create to make a new one here.\",\n );\n return;\n }\n\n // --tools selects the files to write; --no-commands and --no-skills each\n // say write none of that kind. Together they contradict — refused, not\n // arbitrated, in either combination.\n const suppressed = [\n ...(opts.commands ? [] : [\"--no-commands\"]),\n ...(opts.skills ? [] : [\"--no-skills\"]),\n ];\n if (opts.tools !== undefined && suppressed.length > 0) {\n fail(\n json,\n \"invalid-option\",\n `--tools contradicts ${suppressed.join(\" and \")}: ` +\n \"one selects the files to generate, the other suppresses them.\",\n );\n return;\n }\n const delivery: Delivery[] = [\n ...(opts.commands ? ([\"commands\"] as const) : []),\n ...(opts.skills ? ([\"skills\"] as const) : []),\n ];\n\n // Without --tools the repo decides: whichever tools have left their own\n // dot-directories here. Falling back to claude keeps a bare repo behaving\n // exactly as it did before the scan existed. `detected` is reported either\n // way — what the scan saw is a fact about the repo, not about the flags.\n const detected = detectAgentTools(cwd);\n const scanned = detected.length > 0 ? detected : [\"claude\"];\n const tools = opts.tools === undefined ? scanned : resolveTools(opts.tools, json);\n if (tools === null) return;\n\n // `skipped` is the other half of the never-overwrite contract: the\n // scaffolds return only what they created, so what they left alone is\n // probed here, before they run — in the order they would create it, so\n // created + skipped is the same list on every repo. Both halves come from\n // the scaffolds' own planned-file lists, so the probe cannot drift from\n // what is actually written.\n const agentFiles = delivery.length > 0 ? plannedCommandFiles(cwd, tools, delivery) : [];\n const candidates = [...plannedDocsFiles(docsDir), ...agentFiles.map((f) => f.path)];\n const skipped = candidates.filter((p) => existsSync(p));\n\n // Joining touches the docs repo not at all: it is somebody's committed\n // repository, and \"point at it\" must never mean \"write to it\".\n const created = joining && opts.create !== true ? [] : (await scaffoldDocs(docsDir)).created;\n const generatedFor = delivery.length > 0 ? tools : [];\n if (delivery.length > 0) {\n created.push(...(await scaffoldAgentCommands(cwd, tools, delivery)));\n }\n\n const stored = storedDocsDir(docsOption);\n // Union, not replacement: this run's files join the ones earlier runs left\n // on disk, and `agentTools` is a record of what the repo HOLDS. A run that\n // generated nothing adds nothing and erases nothing.\n const recordedTools = [...new Set([...(existing?.agentTools ?? []), ...generatedFor])];\n const config: LoamConfig = {\n ...existing,\n docsDir: stored,\n ...(opts.service ? { service: opts.service } : {}),\n ...(recordedTools.length > 0 ? { agentTools: recordedTools } : {}),\n };\n const configFile = await saveConfig(config, cwd);\n\n const serviceCount = (await listServices(docsDir)).length;\n\n if (json) {\n // `docsDir` stays the resolved absolute path — it is what a consumer\n // needs to find the repo. `docsDirStored` is what was written to\n // loam.json, which is the fact this command is now careful about.\n // `tools` names what files were generated FOR — empty when every\n // delivery was suppressed and none were. `detected` is the separate\n // question of what the scan SAW, which `--tools` overrides without\n // changing: an agent comparing the two learns whether its selection\n // matches the repo.\n emitJson({\n docsDir,\n docsDirStored: stored,\n // Where the pointer came from: `flag` (a --docs somebody typed),\n // `config` (the one this repo already committed — a re-run cannot\n // move it), or `default` (nothing said, nothing committed).\n docsDirSource: docsSource,\n docsRepo: joining ? \"existing\" : \"created\",\n services: serviceCount,\n created,\n skipped,\n tools: generatedFor,\n detected,\n });\n return;\n }\n\n console.log(\"loam initialized.\");\n if (joining) {\n console.log(` docs repo: ${docsDir}`);\n console.log(` pointing at existing docs repo (${serviceCount} services)`);\n } else {\n console.log(` docs repo: ${docsDir} (created)`);\n }\n console.log(\n ` docsDir: ${stored} (as stored in ${CONFIG_FILENAME}`\n + (docsSource === \"config\" ? \" — kept; no --docs was passed\" : \"\") + \")\",\n );\n if (config.service) console.log(` service: ${config.service}`);\n console.log(` config: ${configFile}`);\n console.log(` commit ${CONFIG_FILENAME} — docsDir is resolved relative to it`);\n if (opts.tools === undefined && detected.length > 0) {\n console.log(` detected: ${detected.join(\", \")}`);\n }\n if (opts.commands) console.log(` commands: ${tools.join(\", \")}`);\n if (opts.skills) console.log(` skills: ${tools.join(\", \")}`);\n if (created.length > 0) {\n console.log(\" scaffolded:\");\n for (const c of created) console.log(` + ${c}`);\n }\n });\n}\n"]}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { PROTOCOLS, WORKFLOWS, protocolFor } from "../core/agent.js";
|
|
2
|
+
import { emitJson, fail } from "../core/json.js";
|
|
3
|
+
import { LOAM_VERSION } from "../core/version.js";
|
|
4
|
+
/**
|
|
5
|
+
* The workflow protocols, from the binary rather than from a file somebody
|
|
6
|
+
* generated once.
|
|
7
|
+
*
|
|
8
|
+
* This is the only command in loam that reads nothing: no `loam.json`, no docs
|
|
9
|
+
* repo, no service. That is deliberate and it is the point — an agent runs this
|
|
10
|
+
* BEFORE the repository is wired, and a protocol that refused with `no-config`
|
|
11
|
+
* would be unreachable exactly when it is most needed, which is the first
|
|
12
|
+
* minute in an unfamiliar repository. `loam-adopt`'s own first step is to run
|
|
13
|
+
* `loam init` when there is no config, and it cannot be the step that requires
|
|
14
|
+
* one.
|
|
15
|
+
*/
|
|
16
|
+
export function registerInstructions(program) {
|
|
17
|
+
program
|
|
18
|
+
.command("instructions")
|
|
19
|
+
.argument("[workflow]", "workflow name (loam-adopt, loam-feature, …); omit to list them")
|
|
20
|
+
.argument("[args...]", "values substituted for the protocol's $1, $2, … placeholders")
|
|
21
|
+
.description("Print a workflow protocol, version-matched to this binary")
|
|
22
|
+
.option("--json", "emit the machine contract instead of the human view")
|
|
23
|
+
// Every positional here is DATA — a feature id, a service id, a validate
|
|
24
|
+
// target — and one of those targets is spelled `--all`. Without this,
|
|
25
|
+
// commander reads a leading-dash argument as an option it does not have and
|
|
26
|
+
// refuses the whole invocation, so `/loam-check --all` (the first form the
|
|
27
|
+
// command file's own argument hint offers) got a usage error instead of the
|
|
28
|
+
// protocol. `--json` still parses as the flag it is, because it is declared.
|
|
29
|
+
.allowUnknownOption()
|
|
30
|
+
.action((workflow, args, opts) => {
|
|
31
|
+
const json = opts.json === true;
|
|
32
|
+
if (workflow === undefined) {
|
|
33
|
+
if (json) {
|
|
34
|
+
emitJson({ command: "instructions", version: LOAM_VERSION, workflows: WORKFLOWS });
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
console.log(`loam ${LOAM_VERSION} — workflows\n`);
|
|
38
|
+
const width = Math.max(...WORKFLOWS.map((w) => w.name.length));
|
|
39
|
+
for (const w of WORKFLOWS) {
|
|
40
|
+
console.log(` ${w.name.padEnd(width)} ${w.description}`);
|
|
41
|
+
}
|
|
42
|
+
console.log(`\nloam instructions <workflow> [args...] prints one, placeholders filled in.`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const body = protocolFor(workflow, args);
|
|
46
|
+
if (body === null) {
|
|
47
|
+
// `unknown-target`, the same code `show` uses for a name that resolves
|
|
48
|
+
// to nothing. The known set is small and closed, so it is listed rather
|
|
49
|
+
// than described: a caller that mistyped one of six names should not
|
|
50
|
+
// have to run a second command to see them.
|
|
51
|
+
fail(json, "unknown-target", `No workflow '${workflow}'. Known: ${Object.keys(PROTOCOLS).join(", ")}.`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (json) {
|
|
55
|
+
// `body` is the substituted text, not the template: a consumer that
|
|
56
|
+
// asked for `loam instructions loam-adopt payment-service --json` wants
|
|
57
|
+
// the protocol it is about to follow, and re-implementing the
|
|
58
|
+
// substitution on the other side is a second copy of the rule.
|
|
59
|
+
emitJson({ command: "instructions", version: LOAM_VERSION, workflow, args, body });
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
// `console.log`, like every other command, rather than a raw stdout
|
|
63
|
+
// write: the test harness captures the console and a direct write is
|
|
64
|
+
// invisible to it — an untestable print is how a protocol nobody notices
|
|
65
|
+
// went missing would ship. The body already ends in a newline, so the
|
|
66
|
+
// one console.log adds is trimmed rather than doubled.
|
|
67
|
+
console.log(body.replace(/\n+$/, ""));
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=instructions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instructions.js","sourceRoot":"","sources":["../../src/commands/instructions.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAMlD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAgB;IACnD,OAAO;SACJ,OAAO,CAAC,cAAc,CAAC;SACvB,QAAQ,CAAC,YAAY,EAAE,gEAAgE,CAAC;SACxF,QAAQ,CAAC,WAAW,EAAE,8DAA8D,CAAC;SACrF,WAAW,CAAC,2DAA2D,CAAC;SACxE,MAAM,CAAC,QAAQ,EAAE,qDAAqD,CAAC;QACxE,yEAAyE;QACzE,sEAAsE;QACtE,4EAA4E;QAC5E,2EAA2E;QAC3E,4EAA4E;QAC5E,6EAA6E;SAC5E,kBAAkB,EAAE;SACpB,MAAM,CAAC,CAAC,QAA4B,EAAE,IAAc,EAAE,IAAyB,EAAE,EAAE;QAClF,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC;QAEhC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,IAAI,IAAI,EAAE,CAAC;gBACT,QAAQ,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,CAAC;gBACnF,OAAO;YACT,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,QAAQ,YAAY,gBAAgB,CAAC,CAAC;YAClD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;YAC/D,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;gBAC1B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;YAC7D,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,8EAA8E,CAAC,CAAC;YAC5F,OAAO;QACT,CAAC;QAED,MAAM,IAAI,GAAG,WAAW,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACzC,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClB,uEAAuE;YACvE,wEAAwE;YACxE,qEAAqE;YACrE,4CAA4C;YAC5C,IAAI,CACF,IAAI,EACJ,gBAAgB,EAChB,gBAAgB,QAAQ,aAAa,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAC1E,CAAC;YACF,OAAO;QACT,CAAC;QAED,IAAI,IAAI,EAAE,CAAC;YACT,oEAAoE;YACpE,wEAAwE;YACxE,8DAA8D;YAC9D,+DAA+D;YAC/D,QAAQ,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YACnF,OAAO;QACT,CAAC;QACD,oEAAoE;QACpE,qEAAqE;QACrE,yEAAyE;QACzE,sEAAsE;QACtE,uDAAuD;QACvD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;AACP,CAAC","sourcesContent":["import type { Command } from \"commander\";\nimport { PROTOCOLS, WORKFLOWS, protocolFor } from \"../core/agent.js\";\nimport { emitJson, fail } from \"../core/json.js\";\nimport { LOAM_VERSION } from \"../core/version.js\";\n\ninterface InstructionsOptions {\n json?: boolean;\n}\n\n/**\n * The workflow protocols, from the binary rather than from a file somebody\n * generated once.\n *\n * This is the only command in loam that reads nothing: no `loam.json`, no docs\n * repo, no service. That is deliberate and it is the point — an agent runs this\n * BEFORE the repository is wired, and a protocol that refused with `no-config`\n * would be unreachable exactly when it is most needed, which is the first\n * minute in an unfamiliar repository. `loam-adopt`'s own first step is to run\n * `loam init` when there is no config, and it cannot be the step that requires\n * one.\n */\nexport function registerInstructions(program: Command): void {\n program\n .command(\"instructions\")\n .argument(\"[workflow]\", \"workflow name (loam-adopt, loam-feature, …); omit to list them\")\n .argument(\"[args...]\", \"values substituted for the protocol's $1, $2, … placeholders\")\n .description(\"Print a workflow protocol, version-matched to this binary\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n // Every positional here is DATA — a feature id, a service id, a validate\n // target — and one of those targets is spelled `--all`. Without this,\n // commander reads a leading-dash argument as an option it does not have and\n // refuses the whole invocation, so `/loam-check --all` (the first form the\n // command file's own argument hint offers) got a usage error instead of the\n // protocol. `--json` still parses as the flag it is, because it is declared.\n .allowUnknownOption()\n .action((workflow: string | undefined, args: string[], opts: InstructionsOptions) => {\n const json = opts.json === true;\n\n if (workflow === undefined) {\n if (json) {\n emitJson({ command: \"instructions\", version: LOAM_VERSION, workflows: WORKFLOWS });\n return;\n }\n console.log(`loam ${LOAM_VERSION} — workflows\\n`);\n const width = Math.max(...WORKFLOWS.map((w) => w.name.length));\n for (const w of WORKFLOWS) {\n console.log(` ${w.name.padEnd(width)} ${w.description}`);\n }\n console.log(`\\nloam instructions <workflow> [args...] prints one, placeholders filled in.`);\n return;\n }\n\n const body = protocolFor(workflow, args);\n if (body === null) {\n // `unknown-target`, the same code `show` uses for a name that resolves\n // to nothing. The known set is small and closed, so it is listed rather\n // than described: a caller that mistyped one of six names should not\n // have to run a second command to see them.\n fail(\n json,\n \"unknown-target\",\n `No workflow '${workflow}'. Known: ${Object.keys(PROTOCOLS).join(\", \")}.`,\n );\n return;\n }\n\n if (json) {\n // `body` is the substituted text, not the template: a consumer that\n // asked for `loam instructions loam-adopt payment-service --json` wants\n // the protocol it is about to follow, and re-implementing the\n // substitution on the other side is a second copy of the rule.\n emitJson({ command: \"instructions\", version: LOAM_VERSION, workflow, args, body });\n return;\n }\n // `console.log`, like every other command, rather than a raw stdout\n // write: the test harness captures the console and a direct write is\n // invisible to it — an untestable print is how a protocol nobody notices\n // went missing would ship. The body already ends in a newline, so the\n // one console.log adds is trimmed rather than doubled.\n console.log(body.replace(/\\n+$/, \"\"));\n });\n}\n"]}
|
package/dist/commands/list.js
CHANGED
|
@@ -2,9 +2,10 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import { loadConfig } from "../core/config.js";
|
|
3
3
|
import { emitJson, fail, repoPath, reportNoConfig } from "../core/json.js";
|
|
4
4
|
import { loadFile, serviceResolver } from "../core/likec4.js";
|
|
5
|
+
import { MATURITY_LADDER, maturityGaps, maturityRollup, serviceMaturity, } from "../core/maturity.js";
|
|
5
6
|
import { DocsRepoUnavailableError, compareIds, landscapePath, listFeatures, listServices, servicePaths, } from "../core/repo.js";
|
|
6
7
|
import { featureChecklist, readVerification, tallyRecord, verificationVerdict, } from "../core/verify.js";
|
|
7
|
-
import { docsRepoReady, reportDocsRepoError } from "./
|
|
8
|
+
import { docsRepoReady, reportDocsRepoError, reportRepositoryUnavailable } from "./docs-repo-gate.js";
|
|
8
9
|
const SECTIONS = ["services", "features"];
|
|
9
10
|
export function registerList(program) {
|
|
10
11
|
program
|
|
@@ -37,7 +38,7 @@ export function registerList(program) {
|
|
|
37
38
|
}
|
|
38
39
|
const { docsDir } = config;
|
|
39
40
|
// A docsDir that is not a docs repo is refused, never rendered as an empty
|
|
40
|
-
// fleet — the whole point of the gate (see
|
|
41
|
+
// fleet — the whole point of the gate (see docs-repo-gate.ts).
|
|
41
42
|
// `list features` alone does not need services/, so it asks for less.
|
|
42
43
|
if (!docsRepoReady(json, docsDir, wanted.includes("services") ? "services" : "docs"))
|
|
43
44
|
return;
|
|
@@ -86,10 +87,7 @@ export function registerList(program) {
|
|
|
86
87
|
// --json), naming nothing. The enumeration is all-or-nothing — it reads
|
|
87
88
|
// every service's frontmatter to build one table — so the honest answer
|
|
88
89
|
// is a refusal that says WHICH path could not be read.
|
|
89
|
-
|
|
90
|
-
if (path === undefined)
|
|
91
|
-
throw err;
|
|
92
|
-
fail(json, "repository-unavailable", `${path} could not be read, so the listing would be missing a service. ${err instanceof Error ? err.message : String(err)}`);
|
|
90
|
+
reportRepositoryUnavailable(json, err, "the listing would be missing a service", docsDir);
|
|
93
91
|
}
|
|
94
92
|
});
|
|
95
93
|
}
|
|
@@ -126,8 +124,8 @@ async function serviceViews(docsDir, services, boundService) {
|
|
|
126
124
|
apiExpected,
|
|
127
125
|
unverifiableFromHere: entry.sources.declared && boundService !== entry.id,
|
|
128
126
|
};
|
|
129
|
-
const missing =
|
|
130
|
-
return { ...view, maturity:
|
|
127
|
+
const missing = maturityGaps(view);
|
|
128
|
+
return { ...view, maturity: serviceMaturity(view), missing };
|
|
131
129
|
});
|
|
132
130
|
}
|
|
133
131
|
/* ------------------------------------------------------------------ */
|
|
@@ -161,74 +159,6 @@ function featureJson(docsDir, f, verification) {
|
|
|
161
159
|
verification,
|
|
162
160
|
};
|
|
163
161
|
}
|
|
164
|
-
/* ------------------------------------------------------------------ */
|
|
165
|
-
/* Maturity — the adoption campaign's dial */
|
|
166
|
-
/* ------------------------------------------------------------------ */
|
|
167
|
-
/** The adoption-maturity ladder, in order. Each rung stands on every rung below it. */
|
|
168
|
-
const MATURITY_LADDER = ["empty", "partial", "documented", "sourced", "vouched"];
|
|
169
|
-
/**
|
|
170
|
-
* One monotone word for how far a service's documentation has got. Derived
|
|
171
|
-
* from artifact PRESENCE and provenance state only — the data list already
|
|
172
|
-
* holds — never from what the artifacts say. COMPLETENESS of adopted docs is
|
|
173
|
-
* explicitly on the unchecked list (brief.ts): a service with one endpoint
|
|
174
|
-
* documented out of thirty climbs this ladder exactly as fast as a thorough
|
|
175
|
-
* one, which is why no rung is called "adopted".
|
|
176
|
-
*
|
|
177
|
-
* empty services/<id>/ exists, no artifact is in it
|
|
178
|
-
* partial some artifacts, but not the required set
|
|
179
|
-
* documented the artifacts the adopt brief marks required are present
|
|
180
|
-
* sourced the living spec declares `sources` — something ties it to code
|
|
181
|
-
* vouched status: verified with a sources_digest behind it — a person
|
|
182
|
-
* stamped it. `verified` with no digest is a claim with nothing
|
|
183
|
-
* behind it and stays below this rung.
|
|
184
|
-
*
|
|
185
|
-
* `openapi.yaml` is required ONLY where an API is expected. The rung used to
|
|
186
|
-
* demand it of everything, which permanently pinned every worker, cron and
|
|
187
|
-
* consumer in the fleet at `partial` — services that are fully documented and
|
|
188
|
-
* vouched for, reported as unfinished forever, and a rollup that therefore said
|
|
189
|
-
* a correctly-adopted fleet was half-done. The evidence is the same one
|
|
190
|
-
* `validate` uses to keep `service.no-openapi` quiet, so a service cannot be
|
|
191
|
-
* green in one command and unfinished in the other.
|
|
192
|
-
*/
|
|
193
|
-
function maturity(v) {
|
|
194
|
-
const s = v.entry;
|
|
195
|
-
if (!Object.values(s.has).some(Boolean) && !v.archSpec && s.adrs === 0)
|
|
196
|
-
return "empty";
|
|
197
|
-
if (!(s.has.model && s.has.spec && (!v.apiExpected || s.has.openapi)))
|
|
198
|
-
return "partial";
|
|
199
|
-
if (!s.sources.declared)
|
|
200
|
-
return "documented";
|
|
201
|
-
if (!(s.status === "verified" && s.sources.stamped))
|
|
202
|
-
return "sourced";
|
|
203
|
-
return "vouched";
|
|
204
|
-
}
|
|
205
|
-
/**
|
|
206
|
-
* What stands between this service and `vouched`, named as the thing to go and
|
|
207
|
-
* do. One rung's worth at a time: telling somebody at `empty` that they also
|
|
208
|
-
* need a vouch is noise, and the vouch is not even possible yet.
|
|
209
|
-
*/
|
|
210
|
-
function gaps(v) {
|
|
211
|
-
const s = v.entry;
|
|
212
|
-
const files = [
|
|
213
|
-
...(s.has.model ? [] : ["model.likec4"]),
|
|
214
|
-
...(s.has.spec ? [] : ["spec.md"]),
|
|
215
|
-
...(v.apiExpected && !s.has.openapi ? ["openapi.yaml"] : []),
|
|
216
|
-
];
|
|
217
|
-
if (files.length > 0)
|
|
218
|
-
return files;
|
|
219
|
-
if (!s.sources.declared)
|
|
220
|
-
return ["sources: in the spec.md frontmatter"];
|
|
221
|
-
if (!(s.status === "verified" && s.sources.stamped))
|
|
222
|
-
return [`\`loam vouch --service ${s.id}\``];
|
|
223
|
-
return [];
|
|
224
|
-
}
|
|
225
|
-
/** Counts per rung, every rung present — a stable shape a fleet dashboard can diff. */
|
|
226
|
-
function maturityRollup(views) {
|
|
227
|
-
const out = Object.fromEntries(MATURITY_LADDER.map((m) => [m, 0]));
|
|
228
|
-
for (const v of views)
|
|
229
|
-
out[v.maturity] += 1;
|
|
230
|
-
return out;
|
|
231
|
-
}
|
|
232
162
|
/**
|
|
233
163
|
* The verification column. `readVerification` is one file read; the checklist
|
|
234
164
|
* is derived only when there is a record to judge — on a fleet-sized repo most
|