@acta-dev/cli 0.1.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +186 -7
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -226,8 +226,170 @@ import { existsSync as existsSync2 } from "fs";
226
226
  import { mkdir, writeFile } from "fs/promises";
227
227
  import { join as join2, resolve as resolve2 } from "path";
228
228
  import { createInterface } from "readline";
229
- import { resolveConfig as resolveConfig2 } from "@acta-dev/core";
229
+ import { resolveConfig as resolveConfig3 } from "@acta-dev/core";
230
230
  import { defineCommand as defineCommand3 } from "citty";
231
+
232
+ // src/skill.ts
233
+ import {
234
+ adrStatuses,
235
+ documentKinds,
236
+ internalLinkKeys,
237
+ linkKeys,
238
+ resolveConfig as resolveConfig2,
239
+ specStatuses
240
+ } from "@acta-dev/core";
241
+ var SKILL_NAME = "acta-document";
242
+ var LINK_DESCRIPTIONS = {
243
+ related: "Loosely related documents.",
244
+ supersedes: "This document supersedes the target; mirror with `replacedBy`.",
245
+ replacedBy: "This document is replaced by the target.",
246
+ decidedBy: "This spec was decided by an ADR.",
247
+ dependsOn: "This document depends on the target decision.",
248
+ validates: "This spec validates/implements an ADR decision.",
249
+ references: "External URLs only; must be valid http(s)."
250
+ };
251
+ function table(header, rows) {
252
+ const head = `| ${header.join(" | ")} |`;
253
+ const sep = `| ${header.map(() => "---").join(" | ")} |`;
254
+ const body = rows.map((r) => `| ${r.join(" | ")} |`).join("\n");
255
+ return [head, sep, body].join("\n");
256
+ }
257
+ function renderSkill() {
258
+ const config = resolveConfig2({}, { rootDir: process.cwd() });
259
+ const dirFor = {
260
+ adr: config.docs.adrDir,
261
+ spec: config.docs.specDir
262
+ };
263
+ const prefixFor = {
264
+ adr: config.ids.adrPrefix,
265
+ spec: config.ids.specPrefix
266
+ };
267
+ const required = config.validation.requiredSections;
268
+ const kindRows = documentKinds.map((kind) => [
269
+ `\`${kind}\``,
270
+ `\`${prefixFor[kind]}\``,
271
+ `\`${dirFor[kind]}/\``
272
+ ]);
273
+ const linkRows = linkKeys.map((key) => [
274
+ `\`${key}\``,
275
+ key === "references" ? "external" : "internal",
276
+ LINK_DESCRIPTIONS[key]
277
+ ]);
278
+ const frontmatter = [
279
+ "---",
280
+ `name: ${SKILL_NAME}`,
281
+ "description: >-",
282
+ " Create and maintain Acta ADR/spec documents with the `acta` CLI. Use after",
283
+ " implementing a feature, fixing a notable bug, or making an architectural",
284
+ " decision, or when asked to document a decision, write an ADR, or create a spec.",
285
+ "allowed-tools: Bash",
286
+ "---"
287
+ ].join("\n");
288
+ return `${frontmatter}
289
+
290
+ # Acta: document decisions and specs
291
+
292
+ Acta is a docs-as-code CLI that keeps Architecture Decision Records (ADRs) and
293
+ specs validated and linked in Git. Drive it through the \`acta\` binary; every
294
+ data command supports \`--json\` for reliable parsing. **Never scrape human
295
+ output \u2014 always pass \`--json\` and parse stdout.** Human logs go to stderr.
296
+
297
+ ## When to use
298
+
299
+ - After implementing a feature or system \u2192 write a \`spec\`.
300
+ - After making an architectural decision (or choosing between options) \u2192 write an \`adr\`.
301
+ - When the user asks to "document this decision", "write an ADR", or "create a spec".
302
+
303
+ ## Document model
304
+
305
+ ${table(["Kind", "ID prefix", "Directory"], kindRows)}
306
+
307
+ IDs are \`<PREFIX>-<NNNN>\` (zero-padded to width ${config.ids.width}); the CLI allocates the next ID.
308
+
309
+ **ADR statuses:** ${adrStatuses.map((s) => `\`${s}\``).join(", ")} (default \`proposed\`).
310
+ **Spec statuses:** ${specStatuses.map((s) => `\`${s}\``).join(", ")} (default \`draft\`).
311
+
312
+ ### Link types (frontmatter \`links:\`)
313
+
314
+ ${table(["Key", "Kind", "Meaning"], linkRows)}
315
+
316
+ Internal link keys (${internalLinkKeys.map((k) => `\`${k}\``).join(", ")}) reference other
317
+ document IDs. \`references\` holds external URLs only.
318
+
319
+ ### Required sections
320
+
321
+ - **ADR:** ${required.adr.map((s) => `\`# ${s}\``).join(", ")}
322
+ - **Spec:** ${required.spec.map((s) => `\`# ${s}\``).join(", ")}
323
+
324
+ ## Procedure
325
+
326
+ 1. **Pick the kind.** Architectural decision \u2192 \`adr\`. Feature/system \u2192 \`spec\`.
327
+ 2. **Create it** and capture the path from JSON:
328
+ \`\`\`sh
329
+ acta new adr "Short title" --json # or: acta new spec "Short title" --json
330
+ \`\`\`
331
+ Returns \`{ "id", "kind", "title", "status", "path", "relativePath" }\`. Edit \`path\`.
332
+ 3. **Fill frontmatter:** set \`status\`, \`tags\`, \`component\`, \`owners\`, a one-line
333
+ \`summary\`, and \`links\` (use the link types above; \`references\` for external URLs).
334
+ 4. **Fill required sections** (see above) with real content \u2014 no placeholders.
335
+ 5. **Validate and fix-loop:**
336
+ \`\`\`sh
337
+ acta validate --json
338
+ \`\`\`
339
+ Returns \`{ "valid", "errorCount", "warningCount", "issues": [{ severity, code, documentId, message, path, line }] }\`.
340
+ If \`valid\` is \`false\`, fix each \`issues[].message\` and re-run. Repeat until
341
+ \`valid\` is \`true\` (cap at ~5 iterations; if still failing, report the issues).
342
+ 6. **Verify links** with \`acta show <id> --json\` (inspect \`links\` and \`backlinks\`).
343
+
344
+ ## Command reference (all accept \`--json\` unless noted)
345
+
346
+ - \`acta new adr|spec "<title>" [--status <s>] [--tags a,b] [--id <ID>]\` \u2192 created doc.
347
+ - \`acta list [--kind adr|spec] [--status <s>] [--tag <t>]\` \u2192 array of documents.
348
+ - \`acta show <id>\` \u2192 one normalized document with \`links\` + \`backlinks\`.
349
+ - \`acta validate\` \u2192 validation result; exit \`1\` if invalid.
350
+ - \`acta graph --format json|mermaid|dot\` \u2192 relationship graph.
351
+ - \`acta build\` \u2192 build manifest (\`documentCount\`, \`errorCount\`, \`warningCount\`).
352
+
353
+ Exit codes: \`0\` ok \xB7 \`1\` validation/operation failure \xB7 \`2\` usage error.
354
+ `;
355
+ }
356
+ var AGENTS_BLOCK_START = "<!-- acta:skill:start -->";
357
+ var AGENTS_BLOCK_END = "<!-- acta:skill:end -->";
358
+ function renderAgentsBlock() {
359
+ const adr = adrStatuses.join(", ");
360
+ const spec = specStatuses.join(", ");
361
+ return `${AGENTS_BLOCK_START}
362
+ ## Documenting work with Acta
363
+
364
+ After implementing a feature or making an architectural decision, record it with
365
+ the \`acta\` CLI (always use \`--json\` and parse stdout):
366
+
367
+ 1. \`acta new adr|spec "Title" --json\` \u2014 \`adr\` for decisions, \`spec\` for features. Capture \`path\`.
368
+ 2. Fill frontmatter (\`status\`, \`tags\`, \`component\`, \`owners\`, \`summary\`, \`links\`) and required sections.
369
+ 3. \`acta validate --json\` \u2014 fix each \`issues[].message\` and repeat until \`valid\` is \`true\`.
370
+ 4. \`acta show <id> --json\` \u2014 verify \`links\`/\`backlinks\`.
371
+
372
+ ADR statuses: ${adr}. Spec statuses: ${spec}. Link keys: ${linkKeys.join(", ")}.
373
+ ${AGENTS_BLOCK_END}`;
374
+ }
375
+ function upsertAgentsBlock(existing) {
376
+ const block = renderAgentsBlock();
377
+ const start = existing.indexOf(AGENTS_BLOCK_START);
378
+ const end = existing.indexOf(AGENTS_BLOCK_END);
379
+ if (start !== -1 && end !== -1 && end > start) {
380
+ const before = existing.slice(0, start);
381
+ const after = existing.slice(end + AGENTS_BLOCK_END.length);
382
+ return `${before}${block}${after}`;
383
+ }
384
+ const trimmed = existing.replace(/\s+$/, "");
385
+ return trimmed.length > 0 ? `${trimmed}
386
+
387
+ ${block}
388
+ ` : `${block}
389
+ `;
390
+ }
391
+
392
+ // src/commands/init.ts
231
393
  var ADR_TEMPLATE = `---
232
394
  id: ADR-0000
233
395
  kind: adr
@@ -440,6 +602,11 @@ var initCommand = defineCommand3({
440
602
  description: "Install GitHub Actions workflow template",
441
603
  default: false
442
604
  },
605
+ skill: {
606
+ type: "boolean",
607
+ description: "Install the acta-document agent skill and AGENTS.md guidance",
608
+ default: false
609
+ },
443
610
  config: {
444
611
  type: "string",
445
612
  alias: "c",
@@ -449,7 +616,7 @@ var initCommand = defineCommand3({
449
616
  async run({ args }) {
450
617
  const cwd = resolve2(process.cwd());
451
618
  const yes = args.yes;
452
- const config = resolveConfig2({}, { rootDir: cwd });
619
+ const config = resolveConfig3({}, { rootDir: cwd });
453
620
  printLine("Initializing Acta docs structure...");
454
621
  printLine();
455
622
  const configPath = join2(cwd, "acta.config.ts");
@@ -491,6 +658,18 @@ var initCommand = defineCommand3({
491
658
  const workflowWritten = await safeWriteFile(workflowPath, GITHUB_ACTION_TEMPLATE, yes);
492
659
  if (workflowWritten) printSuccess(`Created ${workflowPath}`);
493
660
  }
661
+ if (args.skill) {
662
+ const skillDir = join2(cwd, ".claude", "skills", "acta-document");
663
+ await mkdir(skillDir, { recursive: true });
664
+ const skillPath = join2(skillDir, "SKILL.md");
665
+ const skillWritten = await safeWriteFile(skillPath, renderSkill(), yes);
666
+ if (skillWritten) printSuccess(`Created ${skillPath}`);
667
+ const { readFile: readFile3 } = await import("fs/promises");
668
+ const agentsPath = join2(cwd, "AGENTS.md");
669
+ const existing = existsSync2(agentsPath) ? await readFile3(agentsPath, "utf8") : "";
670
+ await writeFile(agentsPath, upsertAgentsBlock(existing), "utf8");
671
+ printSuccess(`Updated ${agentsPath} with Acta agent guidance`);
672
+ }
494
673
  printLine();
495
674
  printSuccess("Acta initialized. Run `acta validate` to check your documents.");
496
675
  }
@@ -606,7 +785,7 @@ var listCommand = defineCommand4({
606
785
  import { existsSync as existsSync3 } from "fs";
607
786
  import { writeFile as writeFile2 } from "fs/promises";
608
787
  import { join as join4, relative } from "path";
609
- import { adrStatuses, specStatuses } from "@acta-dev/core";
788
+ import { adrStatuses as adrStatuses2, specStatuses as specStatuses2 } from "@acta-dev/core";
610
789
  import { defineCommand as defineCommand5 } from "citty";
611
790
 
612
791
  // src/id.ts
@@ -668,7 +847,7 @@ async function createDocument(kind, title, opts) {
668
847
  }
669
848
  const defaultStatus = kind === "adr" ? "proposed" : "draft";
670
849
  const status = opts.status ?? defaultStatus;
671
- const validStatuses = kind === "adr" ? adrStatuses : specStatuses;
850
+ const validStatuses = kind === "adr" ? adrStatuses2 : specStatuses2;
672
851
  if (!validStatuses.includes(status)) {
673
852
  exitUsage(`Invalid status "${status}" for ${kind}. Valid: ${validStatuses.join(", ")}`);
674
853
  }
@@ -794,7 +973,7 @@ function parseTags(value) {
794
973
  // src/commands/renumber.ts
795
974
  import { readFile as readFile2, rename, writeFile as writeFile3 } from "fs/promises";
796
975
  import { basename, dirname, join as join5 } from "path";
797
- import { internalLinkKeys, loadProject as loadProject3 } from "@acta-dev/core";
976
+ import { internalLinkKeys as internalLinkKeys2, loadProject as loadProject3 } from "@acta-dev/core";
798
977
  import { defineCommand as defineCommand6 } from "citty";
799
978
  import kleur4 from "kleur";
800
979
  import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
@@ -818,7 +997,7 @@ function buildRenumberPlan(fromId, toId, project) {
818
997
  const oldSlug = oldFilename.replace(`${target.id}-`, "").replace(/\.md$/, "");
819
998
  const newFilename = `${toId}-${oldSlug}.md`;
820
999
  const newPath = join5(dirname(target.file.path), newFilename);
821
- const affectedDocs = project.documents.filter((d) => d.id !== target.id).filter((d) => internalLinkKeys.some((key) => d.links[key].includes(target.id))).map((d) => ({ doc: d, path: d.file.path }));
1000
+ const affectedDocs = project.documents.filter((d) => d.id !== target.id).filter((d) => internalLinkKeys2.some((key) => d.links[key].includes(target.id))).map((d) => ({ doc: d, path: d.file.path }));
822
1001
  return {
823
1002
  target,
824
1003
  oldPath: target.file.path,
@@ -840,7 +1019,7 @@ async function rewriteDocument(filePath, oldId, newId, isTarget) {
840
1019
  }
841
1020
  const linksObj = fm.links;
842
1021
  if (linksObj && typeof linksObj === "object") {
843
- for (const key of internalLinkKeys) {
1022
+ for (const key of internalLinkKeys2) {
844
1023
  const arr = linksObj[key];
845
1024
  if (Array.isArray(arr)) {
846
1025
  linksObj[key] = arr.map((v) => v === oldId ? newId : v);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acta-dev/cli",
3
- "version": "0.1.1",
3
+ "version": "1.0.0",
4
4
  "description": "Acta CLI — TypeScript-first docs-as-code tooling for ADR and spec documents in Git. Provides the `acta` binary.",
5
5
  "keywords": [
6
6
  "adr",
@@ -45,7 +45,7 @@
45
45
  "citty": "^0.2.2",
46
46
  "kleur": "^4.1.5",
47
47
  "yaml": "^2.8.3",
48
- "@acta-dev/core": "0.1.1"
48
+ "@acta-dev/core": "1.0.0"
49
49
  },
50
50
  "devDependencies": {
51
51
  "execa": "^9.6.1",