@acta-dev/cli 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Boris Khakhin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # @acta-dev/cli
2
+
3
+ The `acta` command-line tool — TypeScript-first docs-as-code for **ADR** and **spec** documents in a Git repository.
4
+
5
+ Markdown stays the source of truth; Acta adds a strict document model, validation, graph artifacts and CLI workflows so a team can understand why decisions were made and which specs depend on them.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ npm install -g @acta-dev/cli
11
+ # or run without installing:
12
+ npx @acta-dev/cli init
13
+ ```
14
+
15
+ The installed binary is `acta`.
16
+
17
+ ## Quick start
18
+
19
+ ```sh
20
+ acta init
21
+ acta new adr "Adopt Acta"
22
+ acta new spec "Document workflow"
23
+ acta validate
24
+ acta build
25
+ ```
26
+
27
+ `acta init` creates `acta.config.ts`, `docs/decisions/`, `docs/specs/` and starter templates under `docs/templates/`.
28
+
29
+ ## Commands
30
+
31
+ | Command | Purpose |
32
+ |---|---|
33
+ | `acta init` | Create config, document folders and templates. |
34
+ | `acta new adr\|spec <title>` | Create a document with the next available ID. |
35
+ | `acta list` | List documents, filterable by kind/status/tag. |
36
+ | `acta show <id>` | Show metadata, sections, links and backlinks. |
37
+ | `acta validate` | Validate frontmatter, IDs, links, sections and graph rules. |
38
+ | `acta graph` | Print the relationship graph as Mermaid or JSON. |
39
+ | `acta build` | Write `.acta/dist` JSON artifacts. |
40
+ | `acta renumber <from> <to>` | Rename an ID and update internal links. |
41
+
42
+ Full reference: [github.com/jentix/acta](https://github.com/jentix/acta).
43
+
44
+ ## License
45
+
46
+ MIT
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ declare const actaCliPackage = "@acta-dev/cli";
3
+ declare function getCliBootstrapInfo(): {
4
+ name: string;
5
+ packageName: string;
6
+ version: string;
7
+ };
8
+
9
+ export { actaCliPackage, getCliBootstrapInfo };
package/dist/index.js ADDED
@@ -0,0 +1,1152 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { realpathSync } from "fs";
5
+ import { fileURLToPath } from "url";
6
+ import { defineCommand as defineCommand9, runMain } from "citty";
7
+
8
+ // src/commands/build.ts
9
+ import { buildArtifacts } from "@acta-dev/core";
10
+ import { defineCommand } from "citty";
11
+ import kleur2 from "kleur";
12
+
13
+ // src/context.ts
14
+ import { existsSync } from "fs";
15
+ import { join, resolve } from "path";
16
+ import { loadConfig, resolveConfig } from "@acta-dev/core";
17
+ async function resolveContext(opts) {
18
+ const cwd = resolve(opts.cwd ?? process.cwd());
19
+ if (opts.config) {
20
+ const config2 = await loadConfig(opts.config);
21
+ return { config: config2, cwd };
22
+ }
23
+ for (const dir of [cwd, join(cwd, ".."), join(cwd, "../..")]) {
24
+ const candidate = join(dir, "acta.config.ts");
25
+ if (existsSync(candidate)) {
26
+ const config2 = await loadConfig(candidate);
27
+ return { config: config2, cwd };
28
+ }
29
+ }
30
+ const config = resolveConfig({}, { rootDir: cwd });
31
+ return { config, cwd };
32
+ }
33
+
34
+ // src/output.ts
35
+ import kleur from "kleur";
36
+ function printLine(msg = "") {
37
+ process.stdout.write(`${msg}
38
+ `);
39
+ }
40
+ function printError(msg) {
41
+ process.stderr.write(`${kleur.red("error")} ${msg}
42
+ `);
43
+ }
44
+ function printWarn(msg) {
45
+ process.stderr.write(`${kleur.yellow("warn")} ${msg}
46
+ `);
47
+ }
48
+ function printSuccess(msg) {
49
+ process.stdout.write(`${kleur.green("\u2713")} ${msg}
50
+ `);
51
+ }
52
+ function printJson(value) {
53
+ process.stdout.write(`${JSON.stringify(value, null, 2)}
54
+ `);
55
+ }
56
+ function exitFailure(msg) {
57
+ if (msg) printError(msg);
58
+ process.exit(1);
59
+ }
60
+ function exitUsage(msg) {
61
+ process.stderr.write(`${kleur.red("usage error")} ${msg}
62
+ `);
63
+ process.exit(2);
64
+ }
65
+ function printIssues(issues) {
66
+ for (const issue of issues) {
67
+ const prefix = issue.severity === "error" ? kleur.red("\u2717 error") : kleur.yellow("\u26A0 warn ");
68
+ const location = issue.documentId ? kleur.bold(issue.documentId) : issue.path ?? "";
69
+ printLine(` ${prefix} ${location} ${issue.message}`);
70
+ }
71
+ }
72
+ function printValidationSummary(errorCount, warningCount, valid) {
73
+ if (valid) {
74
+ printSuccess(
75
+ `Validation passed ${kleur.dim(`(${warningCount} warning${warningCount !== 1 ? "s" : ""})`)}`
76
+ );
77
+ } else {
78
+ printLine(
79
+ ` ${kleur.red(`${errorCount} error${errorCount !== 1 ? "s" : ""}`)} ${kleur.yellow(`${warningCount} warning${warningCount !== 1 ? "s" : ""}`)}`
80
+ );
81
+ }
82
+ }
83
+ function printTable(rows) {
84
+ if (rows.length === 0) return;
85
+ const widths = rows[0]?.map((_, col) => Math.max(...rows.map((row) => (row[col] ?? "").length)));
86
+ for (const row of rows) {
87
+ printLine(row.map((cell, col) => cell.padEnd(widths[col] ?? 0)).join(" "));
88
+ }
89
+ }
90
+
91
+ // src/commands/build.ts
92
+ var buildCommand = defineCommand({
93
+ meta: {
94
+ name: "build",
95
+ description: "Build normalized JSON artifacts for the web viewer, CI and integrations"
96
+ },
97
+ args: {
98
+ json: {
99
+ type: "boolean",
100
+ description: "Print the build manifest as JSON",
101
+ default: false
102
+ },
103
+ config: {
104
+ type: "string",
105
+ alias: "c",
106
+ description: "Path to acta.config.ts"
107
+ }
108
+ },
109
+ async run({ args }) {
110
+ const { config } = await resolveContext({ config: args.config });
111
+ if (!args.json) {
112
+ printLine("Building artifacts...");
113
+ }
114
+ const result = await buildArtifacts({ config });
115
+ const { manifest, validation } = result;
116
+ if (args.json) {
117
+ printJson({ ...manifest, outDir: config.resolvedBuild.outDir });
118
+ process.exit(validation.errorCount > 0 ? 1 : 0);
119
+ return;
120
+ }
121
+ printLine();
122
+ printSuccess(`Build complete`);
123
+ printLine(` ${kleur2.bold("Documents:")} ${manifest.documentCount}`);
124
+ printLine(
125
+ ` ${kleur2.bold("Errors:")} ${manifest.errorCount === 0 ? kleur2.green("0") : kleur2.red(String(manifest.errorCount))}`
126
+ );
127
+ printLine(
128
+ ` ${kleur2.bold("Warnings:")} ${manifest.warningCount === 0 ? kleur2.dim("0") : kleur2.yellow(String(manifest.warningCount))}`
129
+ );
130
+ printLine(` ${kleur2.bold("Output:")} ${config.resolvedBuild.outDir}`);
131
+ printLine(` ${kleur2.bold("Built at:")} ${manifest.builtAt}`);
132
+ if (validation.errorCount > 0) {
133
+ printLine();
134
+ printWarn(
135
+ `Build completed with ${validation.errorCount} validation error${validation.errorCount !== 1 ? "s" : ""}. Run \`acta validate\` for details.`
136
+ );
137
+ process.exit(1);
138
+ }
139
+ }
140
+ });
141
+
142
+ // src/commands/graph.ts
143
+ import { buildGraph, loadProject } from "@acta-dev/core";
144
+ import { defineCommand as defineCommand2 } from "citty";
145
+ var STATUS_FILL = {
146
+ accepted: "#4ade80",
147
+ proposed: "#60a5fa",
148
+ rejected: "#f87171",
149
+ deprecated: "#a3a3a3",
150
+ superseded: "#d1d5db",
151
+ active: "#4ade80",
152
+ draft: "#60a5fa",
153
+ paused: "#fbbf24",
154
+ implemented: "#34d399",
155
+ obsolete: "#a3a3a3"
156
+ };
157
+ function nodeLabel(node) {
158
+ const safeTitle = node.title.replace(/"/g, "'");
159
+ return `${node.id}["${node.id}<br/>${safeTitle}"]:::${node.kind}_${sanitizeStatus(node.status)}`;
160
+ }
161
+ function sanitizeStatus(status) {
162
+ return status.replace(/[^a-z0-9]/g, "_");
163
+ }
164
+ function edgeLabel(edge) {
165
+ return ` ${edge.source} -->|${edge.type}| ${edge.target}`;
166
+ }
167
+ function toMermaid(graph) {
168
+ const lines = ["flowchart LR"];
169
+ const classNames = /* @__PURE__ */ new Set();
170
+ for (const node of graph.nodes) {
171
+ lines.push(` ${nodeLabel(node)}`);
172
+ classNames.add(`${node.kind}_${sanitizeStatus(node.status)}`);
173
+ }
174
+ if (graph.edges.length > 0) {
175
+ lines.push("");
176
+ for (const edge of graph.edges) {
177
+ lines.push(edgeLabel(edge));
178
+ }
179
+ }
180
+ if (classNames.size > 0) {
181
+ lines.push("");
182
+ for (const cls of classNames) {
183
+ const status = cls.replace(/^adr_|^spec_/, "").replace(/_/g, "");
184
+ const fill = STATUS_FILL[status] ?? "#e5e7eb";
185
+ lines.push(` classDef ${cls} fill:${fill},stroke:#6b7280,color:#111827`);
186
+ }
187
+ }
188
+ return lines.join("\n");
189
+ }
190
+ var graphCommand = defineCommand2({
191
+ meta: {
192
+ name: "graph",
193
+ description: "Print the document relationship graph as Mermaid or JSON"
194
+ },
195
+ args: {
196
+ format: {
197
+ type: "string",
198
+ alias: "f",
199
+ description: "Output format: mermaid | json (default: mermaid)",
200
+ default: "mermaid"
201
+ },
202
+ config: {
203
+ type: "string",
204
+ alias: "c",
205
+ description: "Path to acta.config.ts"
206
+ }
207
+ },
208
+ async run({ args }) {
209
+ const fmt = args.format ?? "mermaid";
210
+ if (fmt !== "mermaid" && fmt !== "json") {
211
+ exitUsage(`Unknown format "${fmt}". Use: mermaid, json`);
212
+ }
213
+ const { config } = await resolveContext({ config: args.config });
214
+ const project = await loadProject({ config });
215
+ const graph = buildGraph(project.documents);
216
+ if (fmt === "json") {
217
+ printJson(graph);
218
+ } else {
219
+ printLine(toMermaid(graph));
220
+ }
221
+ }
222
+ });
223
+
224
+ // src/commands/init.ts
225
+ import { existsSync as existsSync2 } from "fs";
226
+ import { mkdir, writeFile } from "fs/promises";
227
+ import { join as join2, resolve as resolve2 } from "path";
228
+ import { createInterface } from "readline";
229
+ import { resolveConfig as resolveConfig2 } from "@acta-dev/core";
230
+ import { defineCommand as defineCommand3 } from "citty";
231
+ var ADR_TEMPLATE = `---
232
+ id: ADR-0000
233
+ kind: adr
234
+ title: Template ADR
235
+ status: proposed
236
+ date: YYYY-MM-DD
237
+ tags: []
238
+ component: []
239
+ owners: []
240
+ summary: Short summary of the architectural decision.
241
+ links:
242
+ related: []
243
+ supersedes: []
244
+ replacedBy: []
245
+ decidedBy: []
246
+ dependsOn: []
247
+ validates: []
248
+ references: []
249
+ ---
250
+
251
+ # Context
252
+
253
+ Describe the forces, constraints, and problem that make this decision necessary.
254
+
255
+ # Decision
256
+
257
+ State the decision clearly.
258
+
259
+ # Consequences
260
+
261
+ Describe expected tradeoffs, follow-up work, and operational impact.
262
+
263
+ # Alternatives
264
+
265
+ List the meaningful options considered and why they were not chosen.
266
+ `;
267
+ var SPEC_TEMPLATE = `---
268
+ id: SPEC-0000
269
+ kind: spec
270
+ title: Template Spec
271
+ status: draft
272
+ date: YYYY-MM-DD
273
+ tags: []
274
+ component: []
275
+ owners: []
276
+ summary: Short summary of the technical specification.
277
+ links:
278
+ related: []
279
+ decidedBy: []
280
+ dependsOn: []
281
+ validates: []
282
+ references: []
283
+ ---
284
+
285
+ # Summary
286
+
287
+ Describe the feature, system, or technical change.
288
+
289
+ # Goals
290
+
291
+ List the outcomes this spec must achieve.
292
+
293
+ # Requirements
294
+
295
+ Define functional and non-functional requirements.
296
+
297
+ # Proposed design
298
+
299
+ Describe the planned design.
300
+
301
+ # Open questions
302
+
303
+ Track unresolved decisions.
304
+ `;
305
+ var CONFIG_TEMPLATE = `import { defineConfig } from "@acta-dev/core";
306
+
307
+ export default defineConfig({
308
+ docs: {
309
+ adrDir: "docs/decisions",
310
+ specDir: "docs/specs",
311
+ templatesDir: "docs/templates",
312
+ },
313
+ ids: {
314
+ adrPrefix: "ADR",
315
+ specPrefix: "SPEC",
316
+ width: 4,
317
+ },
318
+ validation: {
319
+ draftMaxAgeDays: 30,
320
+ requiredSections: {
321
+ adr: ["Context", "Decision", "Consequences"],
322
+ spec: ["Summary", "Goals", "Requirements"],
323
+ },
324
+ orphanDocuments: "warning",
325
+ asymmetricSupersedes: "error",
326
+ },
327
+ build: {
328
+ outDir: ".acta/dist",
329
+ cacheDir: ".acta/cache",
330
+ },
331
+ });
332
+ `;
333
+ var LEFTHOOK_TEMPLATE = `pre-commit:
334
+ commands:
335
+ biome:
336
+ glob: "*.{js,jsx,ts,tsx,json,jsonc,css,md,yml,yaml}"
337
+ run: pnpm exec biome check --write --files-ignore-unknown=true {staged_files}
338
+ stage_fixed: true
339
+
340
+ pre-push:
341
+ commands:
342
+ typecheck:
343
+ run: pnpm typecheck
344
+ test:
345
+ run: pnpm test
346
+ `;
347
+ var GITHUB_ACTION_TEMPLATE = `name: Acta CI
348
+
349
+ on:
350
+ pull_request:
351
+ push:
352
+ branches:
353
+ - main
354
+
355
+ jobs:
356
+ verify:
357
+ name: Verify
358
+ runs-on: ubuntu-latest
359
+
360
+ steps:
361
+ - name: Checkout
362
+ uses: actions/checkout@v4
363
+
364
+ - name: Setup pnpm
365
+ uses: pnpm/action-setup@v4
366
+
367
+ - name: Setup Node
368
+ uses: actions/setup-node@v4
369
+ with:
370
+ node-version-file: package.json
371
+ cache: pnpm
372
+
373
+ - name: Install dependencies
374
+ run: pnpm install --frozen-lockfile
375
+
376
+ - name: Lint
377
+ run: pnpm lint
378
+
379
+ - name: Check formatting
380
+ run: pnpm format:check
381
+
382
+ - name: Typecheck
383
+ run: pnpm typecheck
384
+
385
+ - name: Test
386
+ run: pnpm test
387
+
388
+ - name: Build
389
+ run: pnpm build
390
+
391
+ - name: Validate Acta docs
392
+ run: pnpm exec acta validate
393
+
394
+ - name: Build Acta artifacts
395
+ run: pnpm exec acta build
396
+ `;
397
+ async function confirm(message) {
398
+ return new Promise((resolvePromise) => {
399
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
400
+ rl.question(`${message} [y/N] `, (answer) => {
401
+ rl.close();
402
+ resolvePromise(answer.trim().toLowerCase() === "y");
403
+ });
404
+ });
405
+ }
406
+ async function safeWriteFile(filePath, content, yes) {
407
+ if (existsSync2(filePath)) {
408
+ if (!yes) {
409
+ const ok = await confirm(` Overwrite ${filePath}?`);
410
+ if (!ok) {
411
+ printWarn(`Skipped ${filePath}`);
412
+ return false;
413
+ }
414
+ } else {
415
+ printWarn(`Overwriting ${filePath}`);
416
+ }
417
+ }
418
+ await writeFile(filePath, content, "utf8");
419
+ return true;
420
+ }
421
+ var initCommand = defineCommand3({
422
+ meta: {
423
+ name: "init",
424
+ description: "Create Acta config, document folders and templates in the current repository"
425
+ },
426
+ args: {
427
+ yes: {
428
+ type: "boolean",
429
+ alias: "y",
430
+ description: "Skip prompts and overwrite existing files",
431
+ default: false
432
+ },
433
+ hooks: {
434
+ type: "boolean",
435
+ description: "Install Lefthook workflow template",
436
+ default: false
437
+ },
438
+ "github-action": {
439
+ type: "boolean",
440
+ description: "Install GitHub Actions workflow template",
441
+ default: false
442
+ },
443
+ config: {
444
+ type: "string",
445
+ alias: "c",
446
+ description: "Path to acta.config.ts (default: acta.config.ts)"
447
+ }
448
+ },
449
+ async run({ args }) {
450
+ const cwd = resolve2(process.cwd());
451
+ const yes = args.yes;
452
+ const config = resolveConfig2({}, { rootDir: cwd });
453
+ printLine("Initializing Acta docs structure...");
454
+ printLine();
455
+ const configPath = join2(cwd, "acta.config.ts");
456
+ const configWritten = await safeWriteFile(configPath, CONFIG_TEMPLATE, yes);
457
+ if (configWritten) printSuccess(`Created ${configPath}`);
458
+ const dirs = [
459
+ config.resolvedDocs.adrDir,
460
+ config.resolvedDocs.specDir,
461
+ config.resolvedDocs.templatesDir
462
+ ];
463
+ for (const dir of dirs) {
464
+ await mkdir(dir, { recursive: true });
465
+ printSuccess(`Created dir ${dir}`);
466
+ }
467
+ const adrTplPath = join2(config.resolvedDocs.templatesDir, "adr.md");
468
+ const specTplPath = join2(config.resolvedDocs.templatesDir, "spec.md");
469
+ const adrWritten = await safeWriteFile(adrTplPath, ADR_TEMPLATE, yes);
470
+ if (adrWritten) printSuccess(`Created ${adrTplPath}`);
471
+ const specWritten = await safeWriteFile(specTplPath, SPEC_TEMPLATE, yes);
472
+ if (specWritten) printSuccess(`Created ${specTplPath}`);
473
+ const gitignorePath = join2(cwd, ".gitignore");
474
+ if (existsSync2(gitignorePath)) {
475
+ const { readFile: readFile3, appendFile } = await import("fs/promises");
476
+ const content = await readFile3(gitignorePath, "utf8");
477
+ if (!content.includes(".acta/")) {
478
+ await appendFile(gitignorePath, "\n# Acta build artifacts\n.acta/\n");
479
+ printSuccess(`Added .acta/ to .gitignore`);
480
+ }
481
+ }
482
+ if (args.hooks) {
483
+ const lefthookPath = join2(cwd, "lefthook.yml");
484
+ const lefthookWritten = await safeWriteFile(lefthookPath, LEFTHOOK_TEMPLATE, yes);
485
+ if (lefthookWritten) printSuccess(`Created ${lefthookPath}`);
486
+ }
487
+ if (args["github-action"]) {
488
+ const workflowsDir = join2(cwd, ".github", "workflows");
489
+ await mkdir(workflowsDir, { recursive: true });
490
+ const workflowPath = join2(workflowsDir, "acta-ci.yml");
491
+ const workflowWritten = await safeWriteFile(workflowPath, GITHUB_ACTION_TEMPLATE, yes);
492
+ if (workflowWritten) printSuccess(`Created ${workflowPath}`);
493
+ }
494
+ printLine();
495
+ printSuccess("Acta initialized. Run `acta validate` to check your documents.");
496
+ }
497
+ });
498
+
499
+ // src/commands/list.ts
500
+ import { loadProject as loadProject2 } from "@acta-dev/core";
501
+ import { defineCommand as defineCommand4 } from "citty";
502
+ import kleur3 from "kleur";
503
+ var STATUS_COLORS = {
504
+ // ADR
505
+ proposed: (s) => kleur3.cyan(s),
506
+ accepted: (s) => kleur3.green(s),
507
+ rejected: (s) => kleur3.red(s),
508
+ deprecated: (s) => kleur3.yellow(s),
509
+ superseded: (s) => kleur3.dim(s),
510
+ // Spec
511
+ draft: (s) => kleur3.cyan(s),
512
+ active: (s) => kleur3.green(s),
513
+ paused: (s) => kleur3.yellow(s),
514
+ implemented: (s) => kleur3.green(s),
515
+ obsolete: (s) => kleur3.dim(s)
516
+ };
517
+ function colorStatus(status) {
518
+ return (STATUS_COLORS[status] ?? ((s) => s))(status);
519
+ }
520
+ var listCommand = defineCommand4({
521
+ meta: {
522
+ name: "list",
523
+ description: "List ADR and spec documents with optional filters"
524
+ },
525
+ args: {
526
+ kind: {
527
+ type: "string",
528
+ alias: "k",
529
+ description: "Filter by kind: adr | spec"
530
+ },
531
+ status: {
532
+ type: "string",
533
+ alias: "s",
534
+ description: "Filter by status"
535
+ },
536
+ tag: {
537
+ type: "string",
538
+ alias: "t",
539
+ description: "Filter by tag"
540
+ },
541
+ json: {
542
+ type: "boolean",
543
+ description: "Output as JSON",
544
+ default: false
545
+ },
546
+ config: {
547
+ type: "string",
548
+ alias: "c",
549
+ description: "Path to acta.config.ts"
550
+ }
551
+ },
552
+ async run({ args }) {
553
+ const { config } = await resolveContext({ config: args.config });
554
+ const project = await loadProject2({ config });
555
+ let docs = project.documents;
556
+ if (args.kind) {
557
+ if (args.kind !== "adr" && args.kind !== "spec") {
558
+ process.stderr.write(`error: unknown kind "${args.kind}". Use: adr, spec
559
+ `);
560
+ process.exit(2);
561
+ }
562
+ docs = docs.filter((d) => d.kind === args.kind);
563
+ }
564
+ if (args.status) {
565
+ docs = docs.filter((d) => d.status === args.status);
566
+ }
567
+ if (args.tag) {
568
+ const tag = args.tag;
569
+ docs = docs.filter((d) => d.tags.includes(tag));
570
+ }
571
+ docs = docs.slice().sort((a, b) => {
572
+ if (a.kind !== b.kind) return a.kind.localeCompare(b.kind);
573
+ return a.id.localeCompare(b.id);
574
+ });
575
+ if (args.json) {
576
+ printJson(
577
+ docs.map((d) => ({
578
+ id: d.id,
579
+ kind: d.kind,
580
+ title: d.title,
581
+ status: d.status,
582
+ date: d.date,
583
+ tags: d.tags,
584
+ component: d.component,
585
+ owners: d.owners,
586
+ summary: d.summary
587
+ }))
588
+ );
589
+ return;
590
+ }
591
+ if (docs.length === 0) {
592
+ printLine("No documents found.");
593
+ return;
594
+ }
595
+ const rows = [
596
+ [kleur3.bold("ID"), kleur3.bold("KIND"), kleur3.bold("STATUS"), kleur3.bold("TITLE")],
597
+ ...docs.map((d) => [kleur3.bold(d.id), d.kind, colorStatus(d.status), d.title])
598
+ ];
599
+ printTable(rows);
600
+ printLine();
601
+ printLine(kleur3.dim(`${docs.length} document${docs.length !== 1 ? "s" : ""}`));
602
+ }
603
+ });
604
+
605
+ // src/commands/new.ts
606
+ import { existsSync as existsSync3 } from "fs";
607
+ import { writeFile as writeFile2 } from "fs/promises";
608
+ import { join as join4, relative } from "path";
609
+ import { adrStatuses, specStatuses } from "@acta-dev/core";
610
+ import { defineCommand as defineCommand5 } from "citty";
611
+
612
+ // src/id.ts
613
+ function allocateNextId(kind, documents, config) {
614
+ const prefix = kind === "adr" ? config.ids.adrPrefix : config.ids.specPrefix;
615
+ const existing = documents.filter((doc) => doc.kind === kind).map((doc) => {
616
+ const match = doc.id.match(/(\d+)$/);
617
+ return match?.[1] !== void 0 ? parseInt(match[1], 10) : 0;
618
+ });
619
+ const next = existing.length > 0 ? Math.max(...existing) + 1 : 1;
620
+ return `${prefix}-${String(next).padStart(config.ids.width, "0")}`;
621
+ }
622
+
623
+ // src/slug.ts
624
+ function titleToSlug(title) {
625
+ return title.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "");
626
+ }
627
+
628
+ // src/template.ts
629
+ import { readFile } from "fs/promises";
630
+ import { join as join3 } from "path";
631
+ async function renderTemplate(kind, vars, config) {
632
+ const templateFile = join3(config.resolvedDocs.templatesDir, `${kind}.md`);
633
+ const raw = await readFile(templateFile, "utf8");
634
+ return interpolate(raw, vars);
635
+ }
636
+ function interpolate(raw, vars) {
637
+ let rendered = raw.replace(/^(id:\s*).*$/m, `$1${vars.id}`).replace(/^(title:\s*).*$/m, `$1${vars.title}`).replace(/^(date:\s*).*$/m, `$1${vars.date}`).replace(/^(status:\s*).*$/m, `$1${vars.status}`);
638
+ if (vars.tags) {
639
+ rendered = rendered.replace(/^(tags:\s*).*$/m, `$1[${vars.tags.join(", ")}]`);
640
+ }
641
+ return rendered;
642
+ }
643
+ function nowIsoDateTime() {
644
+ return (/* @__PURE__ */ new Date()).toISOString();
645
+ }
646
+
647
+ // src/commands/new.ts
648
+ async function createDocument(kind, title, opts) {
649
+ if (!title || title.trim() === "") {
650
+ exitUsage(`Title is required. Usage: acta new ${kind} "My title"`);
651
+ }
652
+ const { config } = await resolveContext({ config: opts.config });
653
+ const { loadProject: loadProject5 } = await import("@acta-dev/core");
654
+ const project = await loadProject5({ config });
655
+ let id;
656
+ if (opts.id) {
657
+ const prefix = kind === "adr" ? config.ids.adrPrefix : config.ids.specPrefix;
658
+ if (!opts.id.startsWith(`${prefix}-`)) {
659
+ exitUsage(`ID "${opts.id}" does not match ${kind} prefix "${prefix}-"`);
660
+ }
661
+ const existing = project.documents.find((d) => d.id === opts.id);
662
+ if (existing) {
663
+ exitFailure(`Document "${opts.id}" already exists at ${existing.file.path}`);
664
+ }
665
+ id = opts.id;
666
+ } else {
667
+ id = allocateNextId(kind, project.documents, config);
668
+ }
669
+ const defaultStatus = kind === "adr" ? "proposed" : "draft";
670
+ const status = opts.status ?? defaultStatus;
671
+ const validStatuses = kind === "adr" ? adrStatuses : specStatuses;
672
+ if (!validStatuses.includes(status)) {
673
+ exitUsage(`Invalid status "${status}" for ${kind}. Valid: ${validStatuses.join(", ")}`);
674
+ }
675
+ const slug = titleToSlug(title.trim());
676
+ const filename = `${id}-${slug}.md`;
677
+ const dir = kind === "adr" ? config.resolvedDocs.adrDir : config.resolvedDocs.specDir;
678
+ const destPath = join4(dir, filename);
679
+ if (existsSync3(destPath)) {
680
+ exitFailure(`File already exists: ${destPath}`);
681
+ }
682
+ const content = await renderTemplate(
683
+ kind,
684
+ { id, title: title.trim(), date: nowIsoDateTime(), status, tags: parseTags(opts.tags) },
685
+ config
686
+ );
687
+ await writeFile2(destPath, content, "utf8");
688
+ if (opts.json) {
689
+ printJson({
690
+ id,
691
+ kind,
692
+ title: title.trim(),
693
+ status,
694
+ path: destPath,
695
+ relativePath: relative(process.cwd(), destPath)
696
+ });
697
+ return;
698
+ }
699
+ printSuccess(`Created ${destPath}`);
700
+ }
701
+ var newCommand = defineCommand5({
702
+ meta: {
703
+ name: "new",
704
+ description: "Create a new ADR or spec from the configured templates"
705
+ },
706
+ args: {
707
+ config: {
708
+ type: "string",
709
+ alias: "c",
710
+ description: "Path to acta.config.ts"
711
+ }
712
+ },
713
+ subCommands: {
714
+ adr: defineCommand5({
715
+ meta: { name: "adr", description: "Create a new ADR with the next available ID" },
716
+ args: {
717
+ title: {
718
+ type: "positional",
719
+ description: "Document title",
720
+ required: true
721
+ },
722
+ id: {
723
+ type: "string",
724
+ description: "Override auto-allocated ID (e.g. ADR-0007)"
725
+ },
726
+ status: {
727
+ type: "string",
728
+ description: "Initial status (default: proposed)"
729
+ },
730
+ tags: {
731
+ type: "string",
732
+ description: "Comma-separated tags"
733
+ },
734
+ json: {
735
+ type: "boolean",
736
+ description: "Print the created document id and path as JSON",
737
+ default: false
738
+ },
739
+ config: {
740
+ type: "string",
741
+ alias: "c",
742
+ description: "Path to acta.config.ts"
743
+ }
744
+ },
745
+ async run({ args }) {
746
+ await createDocument("adr", args.title, args);
747
+ }
748
+ }),
749
+ spec: defineCommand5({
750
+ meta: { name: "spec", description: "Create a new spec with the next available ID" },
751
+ args: {
752
+ title: {
753
+ type: "positional",
754
+ description: "Document title",
755
+ required: true
756
+ },
757
+ id: {
758
+ type: "string",
759
+ description: "Override auto-allocated ID (e.g. SPEC-0005)"
760
+ },
761
+ status: {
762
+ type: "string",
763
+ description: "Initial status (default: draft)"
764
+ },
765
+ tags: {
766
+ type: "string",
767
+ description: "Comma-separated tags"
768
+ },
769
+ json: {
770
+ type: "boolean",
771
+ description: "Print the created document id and path as JSON",
772
+ default: false
773
+ },
774
+ config: {
775
+ type: "string",
776
+ alias: "c",
777
+ description: "Path to acta.config.ts"
778
+ }
779
+ },
780
+ async run({ args }) {
781
+ await createDocument("spec", args.title, args);
782
+ }
783
+ })
784
+ }
785
+ });
786
+ function parseTags(value) {
787
+ if (!value) {
788
+ return void 0;
789
+ }
790
+ const tags = value.split(",").map((tag) => tag.trim()).filter(Boolean);
791
+ return tags.length > 0 ? tags : void 0;
792
+ }
793
+
794
+ // src/commands/renumber.ts
795
+ import { readFile as readFile2, rename, writeFile as writeFile3 } from "fs/promises";
796
+ import { basename, dirname, join as join5 } from "path";
797
+ import { internalLinkKeys, loadProject as loadProject3 } from "@acta-dev/core";
798
+ import { defineCommand as defineCommand6 } from "citty";
799
+ import kleur4 from "kleur";
800
+ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
801
+ function buildRenumberPlan(fromId, toId, project) {
802
+ const target = project.documents.find((d) => d.id.toLowerCase() === fromId.toLowerCase());
803
+ if (!target) {
804
+ exitFailure(`Document "${fromId}" not found.`);
805
+ }
806
+ const collision = project.documents.find((d) => d.id.toLowerCase() === toId.toLowerCase());
807
+ if (collision) {
808
+ exitFailure(`Target ID "${toId}" already exists at ${collision.file.path}.`);
809
+ }
810
+ const fromPrefix = fromId.replace(/-\d+$/, "");
811
+ const toPrefix = toId.replace(/-\d+$/, "");
812
+ if (fromPrefix !== toPrefix) {
813
+ exitUsage(
814
+ `Cannot renumber across kinds. FROM prefix "${fromPrefix}" \u2260 TO prefix "${toPrefix}".`
815
+ );
816
+ }
817
+ const oldFilename = basename(target.file.path);
818
+ const oldSlug = oldFilename.replace(`${target.id}-`, "").replace(/\.md$/, "");
819
+ const newFilename = `${toId}-${oldSlug}.md`;
820
+ 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 }));
822
+ return {
823
+ target,
824
+ oldPath: target.file.path,
825
+ newPath,
826
+ newFilename,
827
+ affectedDocs
828
+ };
829
+ }
830
+ async function rewriteDocument(filePath, oldId, newId, isTarget) {
831
+ const raw = await readFile2(filePath, "utf8");
832
+ const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
833
+ if (!match) {
834
+ throw new Error(`Cannot parse frontmatter in ${filePath}`);
835
+ }
836
+ const [, frontmatterYaml, body] = match;
837
+ const fm = parseYaml(frontmatterYaml);
838
+ if (isTarget) {
839
+ fm.id = newId;
840
+ }
841
+ const linksObj = fm.links;
842
+ if (linksObj && typeof linksObj === "object") {
843
+ for (const key of internalLinkKeys) {
844
+ const arr = linksObj[key];
845
+ if (Array.isArray(arr)) {
846
+ linksObj[key] = arr.map((v) => v === oldId ? newId : v);
847
+ }
848
+ }
849
+ }
850
+ const newFrontmatter = stringifyYaml(fm, { lineWidth: 0 }).trimEnd();
851
+ return `---
852
+ ${newFrontmatter}
853
+ ---
854
+ ${body}`;
855
+ }
856
+ var renumberCommand = defineCommand6({
857
+ meta: {
858
+ name: "renumber",
859
+ description: "Rename a document ID and update its filename plus internal links"
860
+ },
861
+ args: {
862
+ from: {
863
+ type: "positional",
864
+ description: "Current document ID (e.g. ADR-0001)",
865
+ required: true
866
+ },
867
+ to: {
868
+ type: "positional",
869
+ description: "New document ID (e.g. ADR-0007)",
870
+ required: true
871
+ },
872
+ "dry-run": {
873
+ type: "boolean",
874
+ description: "Print what would change without writing",
875
+ default: false
876
+ },
877
+ config: {
878
+ type: "string",
879
+ alias: "c",
880
+ description: "Path to acta.config.ts"
881
+ }
882
+ },
883
+ async run({ args }) {
884
+ const fromId = args.from;
885
+ const toId = args.to;
886
+ const dryRun = args["dry-run"];
887
+ if (!fromId || !toId) {
888
+ exitUsage("Usage: acta renumber <FROM> <TO>");
889
+ }
890
+ const { config } = await resolveContext({ config: args.config });
891
+ const project = await loadProject3({ config });
892
+ const plan = buildRenumberPlan(fromId, toId, project);
893
+ printLine();
894
+ printLine(kleur4.bold("Renumber plan:"));
895
+ printLine(` ${kleur4.dim("rename")} ${plan.target.id} \u2192 ${kleur4.bold(toId)}`);
896
+ printLine(
897
+ ` ${kleur4.dim("file")} ${basename(plan.oldPath)} \u2192 ${kleur4.bold(plan.newFilename)}`
898
+ );
899
+ if (plan.affectedDocs.length > 0) {
900
+ printLine(` ${kleur4.dim("update links in:")}`);
901
+ for (const { doc } of plan.affectedDocs) {
902
+ printLine(` ${doc.id} ${doc.file.relativePath}`);
903
+ }
904
+ } else {
905
+ printLine(` ${kleur4.dim("no other documents reference this ID")}`);
906
+ }
907
+ if (dryRun) {
908
+ printLine();
909
+ printWarn("Dry run \u2014 no changes written.");
910
+ return;
911
+ }
912
+ printLine();
913
+ for (const { doc, path } of plan.affectedDocs) {
914
+ const rewritten = await rewriteDocument(path, fromId, toId, false);
915
+ await writeFile3(path, rewritten, "utf8");
916
+ printSuccess(`Updated links in ${doc.id}`);
917
+ }
918
+ const rewrittenTarget = await rewriteDocument(plan.oldPath, fromId, toId, true);
919
+ await writeFile3(plan.oldPath, rewrittenTarget, "utf8");
920
+ await rename(plan.oldPath, plan.newPath);
921
+ printSuccess(`Renamed ${basename(plan.oldPath)} \u2192 ${plan.newFilename}`);
922
+ printLine();
923
+ printSuccess(`Renumber complete: ${fromId} \u2192 ${toId}`);
924
+ const { validateLoadedProject: validateLoadedProject2 } = await import("@acta-dev/core");
925
+ const result = await validateLoadedProject2({ config });
926
+ if (!result.valid) {
927
+ printLine();
928
+ printWarn(
929
+ `Renumber introduced ${result.errorCount} validation error${result.errorCount !== 1 ? "s" : ""}. Run \`acta validate\` for details.`
930
+ );
931
+ process.exit(1);
932
+ }
933
+ }
934
+ });
935
+
936
+ // src/commands/show.ts
937
+ import { loadProject as loadProject4 } from "@acta-dev/core";
938
+ import { defineCommand as defineCommand7 } from "citty";
939
+ import kleur5 from "kleur";
940
+ var showCommand = defineCommand7({
941
+ meta: {
942
+ name: "show",
943
+ description: "Show metadata, sections, links and backlinks for one document"
944
+ },
945
+ args: {
946
+ id: {
947
+ type: "positional",
948
+ description: "Document ID (e.g. ADR-0001)",
949
+ required: true
950
+ },
951
+ json: {
952
+ type: "boolean",
953
+ description: "Output full document as JSON",
954
+ default: false
955
+ },
956
+ config: {
957
+ type: "string",
958
+ alias: "c",
959
+ description: "Path to acta.config.ts"
960
+ }
961
+ },
962
+ async run({ args }) {
963
+ if (!args.id) {
964
+ exitUsage("ID is required. Usage: acta show <ID>");
965
+ }
966
+ const { config } = await resolveContext({ config: args.config });
967
+ const project = await loadProject4({ config });
968
+ const doc = project.documents.find((d) => d.id.toLowerCase() === args.id.toLowerCase());
969
+ if (!doc) {
970
+ exitFailure(`Document "${args.id}" not found.`);
971
+ }
972
+ if (args.json) {
973
+ printJson(doc);
974
+ return;
975
+ }
976
+ printLine();
977
+ printLine(`${kleur5.bold(doc.id)} ${kleur5.dim(doc.kind)} ${doc.status}`);
978
+ printLine(kleur5.bold(doc.title));
979
+ printLine(kleur5.dim(doc.file.relativePath));
980
+ printLine();
981
+ if (doc.summary) {
982
+ printLine(doc.summary);
983
+ printLine();
984
+ }
985
+ printLine(
986
+ `${kleur5.bold("Date:")} ${formatShowDate(doc.date)}${doc.updated ? ` (updated ${formatShowDate(doc.updated)})` : ""}`
987
+ );
988
+ if (doc.tags.length > 0) {
989
+ printLine(`${kleur5.bold("Tags:")} ${doc.tags.join(", ")}`);
990
+ }
991
+ if (doc.component.length > 0) {
992
+ printLine(`${kleur5.bold("Component:")} ${doc.component.join(", ")}`);
993
+ }
994
+ if (doc.owners.length > 0) {
995
+ printLine(`${kleur5.bold("Owners:")} ${doc.owners.join(", ")}`);
996
+ }
997
+ if (doc.sections.length > 0) {
998
+ printLine();
999
+ printLine(kleur5.bold("Sections:"));
1000
+ for (const section of doc.sections) {
1001
+ printLine(` ${"#".repeat(section.level)} ${section.title}`);
1002
+ }
1003
+ }
1004
+ const linkEntries = Object.entries(doc.links).filter(([, ids]) => ids.length > 0);
1005
+ if (linkEntries.length > 0) {
1006
+ printLine();
1007
+ printLine(kleur5.bold("Links:"));
1008
+ for (const [key, ids] of linkEntries) {
1009
+ printLine(` ${kleur5.cyan(key)}: ${ids.join(", ")}`);
1010
+ }
1011
+ }
1012
+ const backlinkEntries = Object.entries(doc.backlinks).filter(
1013
+ ([, ids]) => ids.length > 0
1014
+ );
1015
+ if (backlinkEntries.length > 0) {
1016
+ printLine();
1017
+ printLine(kleur5.bold("Backlinks:"));
1018
+ for (const [key, ids] of backlinkEntries) {
1019
+ printLine(` ${kleur5.cyan(key)}: ${ids.join(", ")}`);
1020
+ }
1021
+ }
1022
+ printLine();
1023
+ }
1024
+ });
1025
+ function formatShowDate(value) {
1026
+ if (!value) {
1027
+ return "";
1028
+ }
1029
+ const parsed = new Date(value);
1030
+ if (Number.isNaN(parsed.getTime())) {
1031
+ return value;
1032
+ }
1033
+ const year = parsed.getUTCFullYear();
1034
+ const month = String(parsed.getUTCMonth() + 1).padStart(2, "0");
1035
+ const day = String(parsed.getUTCDate()).padStart(2, "0");
1036
+ return `${year}-${month}-${day}`;
1037
+ }
1038
+
1039
+ // src/commands/validate.ts
1040
+ import { mkdir as mkdir2, writeFile as writeFile4 } from "fs/promises";
1041
+ import { join as join6 } from "path";
1042
+ import { validateLoadedProject } from "@acta-dev/core";
1043
+ import { defineCommand as defineCommand8 } from "citty";
1044
+ import kleur6 from "kleur";
1045
+ var validateCommand = defineCommand8({
1046
+ meta: {
1047
+ name: "validate",
1048
+ description: "Validate frontmatter, IDs, links, sections and repository rules"
1049
+ },
1050
+ args: {
1051
+ ci: {
1052
+ type: "boolean",
1053
+ description: "Write validation.json to outDir and use concise output",
1054
+ default: false
1055
+ },
1056
+ json: {
1057
+ type: "boolean",
1058
+ description: "Print machine-readable result to stdout",
1059
+ default: false
1060
+ },
1061
+ config: {
1062
+ type: "string",
1063
+ alias: "c",
1064
+ description: "Path to acta.config.ts"
1065
+ }
1066
+ },
1067
+ async run({ args }) {
1068
+ const { config } = await resolveContext({ config: args.config });
1069
+ const result = await validateLoadedProject({ config });
1070
+ if (args.json) {
1071
+ printJson(result);
1072
+ process.exit(result.valid ? 0 : 1);
1073
+ return;
1074
+ }
1075
+ if (args.ci) {
1076
+ await mkdir2(config.resolvedBuild.outDir, { recursive: true });
1077
+ const outPath = join6(config.resolvedBuild.outDir, "validation.json");
1078
+ await writeFile4(outPath, `${JSON.stringify(result, null, 2)}
1079
+ `, "utf8");
1080
+ if (result.errors.length > 0) {
1081
+ for (const issue of result.errors) {
1082
+ printLine(`${kleur6.red("error")} ${issue.documentId ?? ""} ${issue.message}`);
1083
+ }
1084
+ }
1085
+ for (const issue of result.warnings) {
1086
+ printLine(`${kleur6.yellow("warn")} ${issue.documentId ?? ""} ${issue.message}`);
1087
+ }
1088
+ printLine(`Written ${outPath}`);
1089
+ process.exit(result.valid ? 0 : 1);
1090
+ return;
1091
+ }
1092
+ if (result.issues.length === 0) {
1093
+ printValidationSummary(0, 0, true);
1094
+ } else {
1095
+ const errors = result.issues.filter((i) => i.severity === "error");
1096
+ const warnings = result.issues.filter((i) => i.severity === "warning");
1097
+ if (errors.length > 0) {
1098
+ printLine();
1099
+ printLine(kleur6.bold("Errors:"));
1100
+ printIssues(errors);
1101
+ }
1102
+ if (warnings.length > 0) {
1103
+ printLine();
1104
+ printLine(kleur6.bold("Warnings:"));
1105
+ printIssues(warnings);
1106
+ }
1107
+ printLine();
1108
+ printValidationSummary(result.errorCount, result.warningCount, result.valid);
1109
+ }
1110
+ process.exit(result.valid ? 0 : 1);
1111
+ }
1112
+ });
1113
+
1114
+ // src/index.ts
1115
+ var main = defineCommand9({
1116
+ meta: {
1117
+ name: "acta",
1118
+ version: "0.0.0",
1119
+ description: "Docs-as-code CLI for authoring, validating and building ADR/spec repositories"
1120
+ },
1121
+ subCommands: {
1122
+ init: initCommand,
1123
+ new: newCommand,
1124
+ list: listCommand,
1125
+ show: showCommand,
1126
+ validate: validateCommand,
1127
+ graph: graphCommand,
1128
+ build: buildCommand,
1129
+ renumber: renumberCommand
1130
+ }
1131
+ });
1132
+ var actaCliPackage = "@acta-dev/cli";
1133
+ function getCliBootstrapInfo() {
1134
+ return {
1135
+ name: "acta",
1136
+ packageName: actaCliPackage,
1137
+ version: "0.0.0"
1138
+ };
1139
+ }
1140
+ function isDirectExecution() {
1141
+ if (!process.argv[1]) {
1142
+ return false;
1143
+ }
1144
+ return realpathSync(process.argv[1]) === fileURLToPath(import.meta.url);
1145
+ }
1146
+ if (isDirectExecution()) {
1147
+ await runMain(main);
1148
+ }
1149
+ export {
1150
+ actaCliPackage,
1151
+ getCliBootstrapInfo
1152
+ };
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@acta-dev/cli",
3
+ "version": "0.1.1",
4
+ "description": "Acta CLI — TypeScript-first docs-as-code tooling for ADR and spec documents in Git. Provides the `acta` binary.",
5
+ "keywords": [
6
+ "adr",
7
+ "spec",
8
+ "docs-as-code",
9
+ "architecture-decision-records",
10
+ "cli",
11
+ "documentation",
12
+ "markdown"
13
+ ],
14
+ "license": "MIT",
15
+ "author": "jentix",
16
+ "homepage": "https://github.com/jentix/acta#readme",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/jentix/acta.git",
20
+ "directory": "packages/cli"
21
+ },
22
+ "bugs": {
23
+ "url": "https://github.com/jentix/acta/issues"
24
+ },
25
+ "type": "module",
26
+ "bin": {
27
+ "acta": "./dist/index.js"
28
+ },
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "import": "./dist/index.js"
33
+ }
34
+ },
35
+ "files": [
36
+ "dist"
37
+ ],
38
+ "engines": {
39
+ "node": ">=22.12 <26"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "dependencies": {
45
+ "citty": "^0.2.2",
46
+ "kleur": "^4.1.5",
47
+ "yaml": "^2.8.3",
48
+ "@acta-dev/core": "0.1.1"
49
+ },
50
+ "devDependencies": {
51
+ "execa": "^9.6.1",
52
+ "tsup": "^8.4.0",
53
+ "typescript": "^5.9.3",
54
+ "vitest": "^4.1.5"
55
+ },
56
+ "scripts": {
57
+ "build": "tsup src/index.ts --format esm --dts",
58
+ "dev": "tsup src/index.ts --format esm --dts --watch",
59
+ "test": "vitest run",
60
+ "typecheck": "tsc -p tsconfig.json"
61
+ }
62
+ }