@ghostbyte/agent-docs-kit 0.0.0-stage → 4.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +82 -2
  3. package/bin/agent-docs.mjs +8 -0
  4. package/content/catalog.json +196 -0
  5. package/content/engineering/01-query-performance.md +48 -0
  6. package/content/engineering/02-networking-fundamentals.md +48 -0
  7. package/content/engineering/03-rate-limiting-algorithms.md +48 -0
  8. package/content/engineering/04-production-debugging.md +48 -0
  9. package/content/engineering/05-code-review-practices.md +48 -0
  10. package/content/engineering/06-data-modeling.md +48 -0
  11. package/content/engineering/07-cloud-cost-saving.md +48 -0
  12. package/content/engineering/08-testing-types.md +48 -0
  13. package/content/engineering/09-message-queues.md +48 -0
  14. package/content/engineering/full-reference.md +144 -0
  15. package/content/security/01-design-architecture.md +39 -0
  16. package/content/security/02-local-development.md +41 -0
  17. package/content/security/03-database-backend.md +46 -0
  18. package/content/security/04-api-layer.md +51 -0
  19. package/content/security/05-frontend-ui.md +44 -0
  20. package/content/security/06-application-security-testing.md +48 -0
  21. package/content/security/07-cicd-release-engineering.md +41 -0
  22. package/content/security/08-cloud-infrastructure.md +45 -0
  23. package/content/security/09-edge-cdn-dns.md +44 -0
  24. package/content/security/10-release-rollout.md +41 -0
  25. package/content/security/11-post-release-monitoring-ir.md +43 -0
  26. package/content/security/12-ongoing-operations.md +52 -0
  27. package/content/security/full-reference.md +3173 -0
  28. package/docs/ARCHITECTURE.md +107 -0
  29. package/docs/CONTENT_MAINTENANCE.md +9 -0
  30. package/docs/GITHUB_AND_NPM_SETUP.md +112 -0
  31. package/docs/PURPOSE.md +23 -0
  32. package/docs/RELEASE_PROCESS.md +22 -0
  33. package/docs/SECURITY_MODEL.md +9 -0
  34. package/package.json +57 -4
  35. package/skills/manage-project-quality/SKILL.md +78 -0
  36. package/skills/manage-project-quality/agents/openai.yaml +4 -0
  37. package/skills/manage-project-quality/assets/documentation-report.md +12 -0
  38. package/skills/manage-project-quality/assets/engineering-report.md +13 -0
  39. package/skills/manage-project-quality/assets/security-report.md +18 -0
  40. package/skills/manage-project-quality/references/documentation-governance.md +39 -0
  41. package/skills/manage-project-quality/references/engineering-01-query-performance.md +48 -0
  42. package/skills/manage-project-quality/references/engineering-02-networking-fundamentals.md +48 -0
  43. package/skills/manage-project-quality/references/engineering-03-rate-limiting-algorithms.md +48 -0
  44. package/skills/manage-project-quality/references/engineering-04-production-debugging.md +48 -0
  45. package/skills/manage-project-quality/references/engineering-05-code-review-practices.md +48 -0
  46. package/skills/manage-project-quality/references/engineering-06-data-modeling.md +48 -0
  47. package/skills/manage-project-quality/references/engineering-07-cloud-cost-saving.md +48 -0
  48. package/skills/manage-project-quality/references/engineering-08-testing-types.md +48 -0
  49. package/skills/manage-project-quality/references/engineering-09-message-queues.md +48 -0
  50. package/skills/manage-project-quality/references/engineering-full-reference.md +144 -0
  51. package/skills/manage-project-quality/references/engineering-protocol.md +23 -0
  52. package/skills/manage-project-quality/references/evidence-and-reporting.md +19 -0
  53. package/skills/manage-project-quality/references/security-01-design-architecture.md +39 -0
  54. package/skills/manage-project-quality/references/security-02-local-development.md +41 -0
  55. package/skills/manage-project-quality/references/security-03-database-backend.md +46 -0
  56. package/skills/manage-project-quality/references/security-04-api-layer.md +51 -0
  57. package/skills/manage-project-quality/references/security-05-frontend-ui.md +44 -0
  58. package/skills/manage-project-quality/references/security-06-application-security-testing.md +48 -0
  59. package/skills/manage-project-quality/references/security-07-cicd-release-engineering.md +41 -0
  60. package/skills/manage-project-quality/references/security-08-cloud-infrastructure.md +45 -0
  61. package/skills/manage-project-quality/references/security-09-edge-cdn-dns.md +44 -0
  62. package/skills/manage-project-quality/references/security-10-release-rollout.md +41 -0
  63. package/skills/manage-project-quality/references/security-11-post-release-monitoring-ir.md +43 -0
  64. package/skills/manage-project-quality/references/security-12-ongoing-operations.md +52 -0
  65. package/skills/manage-project-quality/references/security-full-reference.md +3173 -0
  66. package/skills/manage-project-quality/references/security-protocol.md +22 -0
  67. package/skills/manage-project-quality/scripts/check-report.mjs +23 -0
  68. package/src/api/index.mjs +4 -0
  69. package/src/cli/arguments.mjs +30 -0
  70. package/src/cli/help.mjs +24 -0
  71. package/src/cli/main.mjs +65 -0
  72. package/src/commands/audit.mjs +49 -0
  73. package/src/commands/doctor.mjs +12 -0
  74. package/src/commands/list.mjs +11 -0
  75. package/src/commands/setup.mjs +42 -0
  76. package/src/commands/skill.mjs +14 -0
  77. package/src/commands/validate.mjs +20 -0
  78. package/src/core/catalog.mjs +19 -0
  79. package/src/core/files.mjs +42 -0
  80. package/src/core/paths.mjs +5 -0
@@ -0,0 +1,22 @@
1
+ # Security review protocol
2
+
3
+ ## Purpose
4
+
5
+ Security assurance looks for exploitable weaknesses and missing controls that can affect confidentiality, integrity, availability, privacy, or release safety. Its output is evidence-based and can block release when a Critical or High control fails.
6
+
7
+ ## Safety boundary
8
+
9
+ Repository inspection, dependency-manifest review, and static configuration review are passive. Dynamic scanning, fuzzing, credential use, exploit validation, cloud changes, load generation, and production access require explicit authorization for the named target. Stop when target ownership or permitted impact is unclear.
10
+
11
+ ## Statuses
12
+
13
+ - `Pass`: sufficient evidence demonstrates the control.
14
+ - `Fail`: evidence demonstrates the control is absent, ineffective, or bypassable.
15
+ - `Unverified`: evidence is unavailable or cannot be safely collected.
16
+ - `N/A`: the control does not apply; record why.
17
+
18
+ Never infer `Pass` from absence of evidence. A tool reporting no finding is evidence only for the scope and limitations of that tool.
19
+
20
+ ## Severity and release effect
21
+
22
+ Use Critical, High, Medium, Low, or Informational. Critical and High failures block release by default. An exception must name the risk owner, justification, compensating control, and expiry date.
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFile } from "node:fs/promises";
4
+
5
+ const file = process.argv[2];
6
+ if (!file) throw new Error("Usage: node check-report.mjs <report.md>");
7
+ const text = await readFile(file, "utf8");
8
+ const allowed = new Set(["Pass", "Fail", "Unverified", "N/A", "Applied", "Partial", "Recommended"]);
9
+ const errors = [];
10
+ for (const [index, line] of text.split(/\r?\n/).entries()) {
11
+ if (!line.startsWith("|") || /^\|[-: |]+\|$/.test(line)) continue;
12
+ const cells = line.split("|").slice(1, -1).map((cell) => cell.trim());
13
+ const status = cells.find((cell) => allowed.has(cell));
14
+ if (status && status !== "Unverified" && !cells.some((cell, cellIndex) => cellIndex > 1 && cell.length > 3)) {
15
+ errors.push(`Line ${index + 1}: status '${status}' has no supporting detail.`);
16
+ }
17
+ }
18
+ if (errors.length) {
19
+ console.error(errors.join("\n"));
20
+ process.exitCode = 1;
21
+ } else {
22
+ console.log("Report structure check passed.");
23
+ }
@@ -0,0 +1,4 @@
1
+ export { setupProject } from "../commands/setup.mjs";
2
+ export { auditDocumentation, auditSecurity, reviewEngineering } from "../commands/audit.mjs";
3
+ export { installSkill } from "../commands/skill.mjs";
4
+ export { loadCatalog } from "../core/catalog.mjs";
@@ -0,0 +1,30 @@
1
+ export function parseArguments(argv) {
2
+ const positionals = [];
3
+ const flags = {};
4
+ for (let index = 0; index < argv.length; index += 1) {
5
+ const token = argv[index];
6
+ if (!token.startsWith("--")) {
7
+ positionals.push(token);
8
+ continue;
9
+ }
10
+ const [rawKey, inline] = token.slice(2).split("=", 2);
11
+ if (inline !== undefined) {
12
+ flags[rawKey] = inline;
13
+ } else if (argv[index + 1] && !argv[index + 1].startsWith("--")) {
14
+ flags[rawKey] = argv[++index];
15
+ } else {
16
+ flags[rawKey] = true;
17
+ }
18
+ }
19
+ return { positionals, flags };
20
+ }
21
+
22
+ export function optionsFromFlags(flags) {
23
+ return {
24
+ cwd: flags.cwd,
25
+ dryRun: Boolean(flags["dry-run"]),
26
+ force: Boolean(flags.force),
27
+ style: flags.style ?? "standard",
28
+ report: Boolean(flags.report)
29
+ };
30
+ }
@@ -0,0 +1,24 @@
1
+ export const helpText = `Agent Docs & Quality Kit
2
+
3
+ Usage:
4
+ agent-docs Open the guided menu (TTY only)
5
+ agent-docs setup [--dry-run] Install the documentation workspace
6
+ agent-docs audit documentation Check documentation health
7
+ agent-docs audit security [phase] Create an evidence-based security worksheet
8
+ agent-docs review engineering [topic]
9
+ Create an engineering review worksheet
10
+ agent-docs list [security|engineering]
11
+ agent-docs skill install [--target project|codex] [--dry-run]
12
+ agent-docs validate Validate this package's catalog and skill
13
+ agent-docs doctor Check local prerequisites
14
+ agent-docs --version Show the package version
15
+
16
+ Options:
17
+ --cwd <path> Target project (default: current directory)
18
+ --style standard|learning
19
+ --dry-run Preview writes without changing files
20
+ --force Replace files that this command owns
21
+ --report Write documentation audit results
22
+ --help Show this help
23
+
24
+ Installing the npm package performs no project setup. Run a command explicitly.`;
@@ -0,0 +1,65 @@
1
+ import { createInterface } from "node:readline/promises";
2
+ import { stdin, stdout } from "node:process";
3
+ import { parseArguments, optionsFromFlags } from "./arguments.mjs";
4
+ import { helpText } from "./help.mjs";
5
+ import { setupProject } from "../commands/setup.mjs";
6
+ import { auditDocumentation, auditSecurity, reviewEngineering } from "../commands/audit.mjs";
7
+ import { listCatalog } from "../commands/list.mjs";
8
+ import { installSkill } from "../commands/skill.mjs";
9
+ import { doctor } from "../commands/doctor.mjs";
10
+ import { validatePackage } from "../commands/validate.mjs";
11
+ import { readFile } from "node:fs/promises";
12
+ import { resolveFromPackage } from "../core/paths.mjs";
13
+
14
+ async function menu() {
15
+ const ui = createInterface({ input: stdin, output: stdout });
16
+ try {
17
+ console.log(`\nAgent Docs & Quality Kit\n\n1. Set up documentation\n2. Audit documentation\n3. Start security audit\n4. Start engineering review\n5. Install project skill\n6. Validate package\n0. Exit`);
18
+ const choice = await ui.question("\nChoose an action: ");
19
+ if (choice === "1") return setupProject();
20
+ if (choice === "2") return auditDocumentation();
21
+ if (choice === "3") {
22
+ await listCatalog("security");
23
+ return auditSecurity(await ui.question("Phase id or slug: "));
24
+ }
25
+ if (choice === "4") {
26
+ await listCatalog("engineering");
27
+ return reviewEngineering(await ui.question("Topic id or slug: "));
28
+ }
29
+ if (choice === "5") return installSkill("project");
30
+ if (choice === "6") return validatePackage();
31
+ if (choice !== "0") throw new Error("Unknown menu choice.");
32
+ } finally {
33
+ ui.close();
34
+ }
35
+ }
36
+
37
+ export async function main(argv) {
38
+ const { positionals, flags } = parseArguments(argv);
39
+ const [command, subject, selector] = positionals;
40
+ const options = optionsFromFlags(flags);
41
+ if (flags.version || command === "version") {
42
+ const metadata = JSON.parse(await readFile(resolveFromPackage("package.json"), "utf8"));
43
+ return console.log(metadata.version);
44
+ }
45
+ if (flags.help || command === "help") return console.log(helpText);
46
+ if (!command) {
47
+ if (!stdin.isTTY) {
48
+ console.log(helpText);
49
+ return;
50
+ }
51
+ return menu();
52
+ }
53
+ if (command === "setup") return setupProject(options);
54
+ if (command === "audit" && subject === "documentation") return auditDocumentation(options);
55
+ if (command === "audit" && subject === "security") return auditSecurity(selector ?? flags.phase, options);
56
+ if (command === "review" && subject === "engineering") return reviewEngineering(selector ?? flags.topic, options);
57
+ if (command === "list") return listCatalog(subject);
58
+ if (command === "skill" && subject === "install") return installSkill(flags.target ?? "project", options);
59
+ if (command === "validate") return validatePackage();
60
+ if (command === "doctor") {
61
+ if (!doctor()) process.exitCode = 1;
62
+ return;
63
+ }
64
+ throw new Error(`Unknown command.\n\n${helpText}`);
65
+ }
@@ -0,0 +1,49 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { resolve } from "node:path";
3
+ import { loadCatalog, selectEntry } from "../core/catalog.mjs";
4
+ import { exists, writeSafe } from "../core/files.mjs";
5
+ import { resolveFromPackage } from "../core/paths.mjs";
6
+
7
+ const date = () => new Date().toISOString().slice(0, 10);
8
+
9
+ async function reportFor(kind, selector, options = {}) {
10
+ const catalog = await loadCatalog();
11
+ const entries = catalog[kind];
12
+ const entry = selectEntry(entries, selector);
13
+ if (!entry) throw new Error(`Unknown ${kind} selection '${selector ?? ""}'. Run 'agent-docs list ${kind}'.`);
14
+ const source = await readFile(resolveFromPackage(entry.source), "utf8");
15
+ const status = kind === "security" ? "Unverified" : "Unverified";
16
+ const heading = kind === "security" ? "Security audit" : "Engineering review";
17
+ const destinationRoot = kind === "security"
18
+ ? "docs/04-testing/security-audits"
19
+ : "docs/03-development/engineering-reviews";
20
+ const output = resolve(options.cwd ?? process.cwd(), destinationRoot, `${date()}-${entry.slug}.md`);
21
+ const body = `# ${heading}: ${entry.title}\n\n- Date: ${date()}\n- Style: ${options.style ?? "standard"}\n- Overall status: ${status}\n- Reviewer: _Unassigned_\n\n## Scope and authorization\n\n- Target: _Describe the code, configuration, or authorized environment_\n- Authorization: _Required before active security testing_\n- Exclusions: _List exclusions_\n\n## Findings\n\n| Check | Status | Evidence | Action |\n|---|---|---|---|\n| Add each applicable check | ${status} | File/line, command output, or external evidence | Required follow-up |\n\n## Reference checklist\n\n${source}\n`;
22
+ const result = await writeSafe(output, body, options);
23
+ console.log(`${result.status} ${output}`);
24
+ return result;
25
+ }
26
+
27
+ export const auditSecurity = (selector, options) => reportFor("security", selector, options);
28
+ export const reviewEngineering = (selector, options) => reportFor("engineering", selector, options);
29
+
30
+ export async function auditDocumentation(options = {}) {
31
+ const root = resolve(options.cwd ?? process.cwd());
32
+ const required = [
33
+ ".agent-docs/config.json",
34
+ "docs/README.md",
35
+ "docs/01-system/overview.md",
36
+ "docs/02-project/README.md",
37
+ "docs/05-operations/README.md"
38
+ ];
39
+ const rows = await Promise.all(required.map(async (path) => ({ path, present: await exists(resolve(root, path)) })));
40
+ for (const row of rows) console.log(`${row.present ? "PASS" : "FAIL"} ${row.path}`);
41
+ const result = { checkedAt: new Date().toISOString(), pass: rows.every((row) => row.present), files: rows };
42
+ if (options.report && !options.dryRun) {
43
+ const output = resolve(root, "docs/03-development/audits", `${date()}-documentation.json`);
44
+ await mkdir(resolve(output, ".."), { recursive: true });
45
+ await writeFile(output, `${JSON.stringify(result, null, 2)}\n`, "utf8");
46
+ console.log(`created ${output}`);
47
+ }
48
+ return result;
49
+ }
@@ -0,0 +1,12 @@
1
+ import { execFileSync } from "node:child_process";
2
+
3
+ export function doctor() {
4
+ const checks = [{ name: "Node.js >=20", pass: Number(process.versions.node.split(".")[0]) >= 20, value: process.version }];
5
+ try {
6
+ checks.push({ name: "Git", pass: true, value: execFileSync("git", ["--version"], { encoding: "utf8" }).trim() });
7
+ } catch {
8
+ checks.push({ name: "Git", pass: false, value: "not found" });
9
+ }
10
+ for (const check of checks) console.log(`${check.pass ? "PASS" : "FAIL"} ${check.name}: ${check.value}`);
11
+ return checks.every((check) => check.pass);
12
+ }
@@ -0,0 +1,11 @@
1
+ import { loadCatalog } from "../core/catalog.mjs";
2
+
3
+ export async function listCatalog(kind) {
4
+ const catalog = await loadCatalog();
5
+ const groups = kind ? [kind] : ["security", "engineering"];
6
+ for (const group of groups) {
7
+ if (!catalog[group]) throw new Error(`Unknown catalog '${group}'.`);
8
+ console.log(`\n${group.toUpperCase()}`);
9
+ for (const entry of catalog[group]) console.log(` ${entry.id.padEnd(3)} ${entry.slug.padEnd(34)} ${entry.title}`);
10
+ }
11
+ }
@@ -0,0 +1,42 @@
1
+ import { resolve } from "node:path";
2
+ import { displayResults, writeSafe } from "../core/files.mjs";
3
+
4
+ const files = {
5
+ ".agent-docs/config.json": `{
6
+ "schemaVersion": 1,
7
+ "documentationRoot": "docs",
8
+ "securityReports": "docs/04-testing/security-audits",
9
+ "engineeringReports": "docs/03-development/engineering-reviews",
10
+ "interactionStyle": "standard"
11
+ }\n`,
12
+ "docs/README.md": `# Project documentation
13
+
14
+ This directory is the maintained project record. Keep decisions and system facts separate from generated audit evidence.
15
+
16
+ - \`01-system/\`: system scope, architecture, data, and security design
17
+ - \`02-project/\`: requirements, roadmap, decisions, and status
18
+ - \`03-development/\`: implementation notes and engineering reviews
19
+ - \`04-testing/\`: test plans, security audits, and evidence
20
+ - \`05-operations/\`: setup, deployment, runbooks, and agent operations
21
+ - \`06-future-enhancements/\`: proposals that are not current commitments
22
+ - \`archive/\`: superseded material retained for traceability
23
+ `,
24
+ "docs/01-system/overview.md": "# System overview\n\nStatus: Needs update\n\nDescribe the system boundary, users, dependencies, and authoritative sources.\n",
25
+ "docs/02-project/README.md": "# Project\n\nTrack requirements, decisions, milestones, and current delivery status here.\n",
26
+ "docs/03-development/engineering-reviews/.gitkeep": "",
27
+ "docs/04-testing/security-audits/.gitkeep": "",
28
+ "docs/04-testing/evidence/.gitkeep": "",
29
+ "docs/05-operations/README.md": "# Operations\n\nDocument setup, deployment, observability, incident response, and recovery procedures.\n",
30
+ "docs/06-future-enhancements/README.md": "# Future enhancements\n\nIdeas here are proposals, not part of the current scope until accepted.\n",
31
+ "docs/archive/README.md": "# Archive\n\nKeep superseded documents here with replacement links and archive dates.\n"
32
+ };
33
+
34
+ export async function setupProject(options = {}) {
35
+ const root = resolve(options.cwd ?? process.cwd());
36
+ const results = [];
37
+ for (const [path, contents] of Object.entries(files)) {
38
+ results.push(await writeSafe(resolve(root, path), contents, options));
39
+ }
40
+ displayResults(root, results);
41
+ return results;
42
+ }
@@ -0,0 +1,14 @@
1
+ import { homedir } from "node:os";
2
+ import { resolve } from "node:path";
3
+ import { copySafe } from "../core/files.mjs";
4
+ import { resolveFromPackage } from "../core/paths.mjs";
5
+
6
+ export async function installSkill(target = "project", options = {}) {
7
+ const root = resolve(options.cwd ?? process.cwd());
8
+ const destination = target === "codex"
9
+ ? resolve(homedir(), ".codex", "skills", "manage-project-quality")
10
+ : resolve(root, ".agents", "skills", "manage-project-quality");
11
+ const result = await copySafe(resolveFromPackage("skills", "manage-project-quality"), destination, options);
12
+ console.log(`${result.status} ${destination}`);
13
+ return result;
14
+ }
@@ -0,0 +1,20 @@
1
+ import { access, readFile } from "node:fs/promises";
2
+ import { loadCatalog } from "../core/catalog.mjs";
3
+ import { resolveFromPackage } from "../core/paths.mjs";
4
+
5
+ export async function validatePackage() {
6
+ const catalog = await loadCatalog();
7
+ const errors = [];
8
+ for (const group of ["security", "engineering"]) {
9
+ for (const entry of catalog[group]) {
10
+ try { await access(resolveFromPackage(entry.source)); }
11
+ catch { errors.push(`Missing catalog source: ${entry.source}`); }
12
+ }
13
+ }
14
+ const skill = await readFile(resolveFromPackage("skills/manage-project-quality/SKILL.md"), "utf8");
15
+ if (!skill.includes("name: manage-project-quality")) errors.push("Skill name is invalid.");
16
+ if (errors.length) throw new Error(errors.join("\n"));
17
+ console.log(`PASS catalog (${catalog.security.length} security phases, ${catalog.engineering.length} engineering topics)`);
18
+ console.log("PASS unified skill");
19
+ return true;
20
+ }
@@ -0,0 +1,19 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { resolveFromPackage } from "./paths.mjs";
3
+
4
+ let cached;
5
+
6
+ export async function loadCatalog() {
7
+ if (!cached) {
8
+ cached = JSON.parse(await readFile(resolveFromPackage("content", "catalog.json"), "utf8"));
9
+ }
10
+ return cached;
11
+ }
12
+
13
+ export function selectEntry(entries, selector) {
14
+ if (!selector) return undefined;
15
+ const normalized = selector.toLowerCase();
16
+ return entries.find((entry) =>
17
+ entry.id === normalized || entry.slug === normalized || entry.aliases?.includes(normalized)
18
+ );
19
+ }
@@ -0,0 +1,42 @@
1
+ import { access, cp, mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { dirname, relative, resolve } from "node:path";
3
+ import { resolveFromPackage } from "./paths.mjs";
4
+
5
+ export async function exists(path) {
6
+ try {
7
+ await access(path);
8
+ return true;
9
+ } catch {
10
+ return false;
11
+ }
12
+ }
13
+
14
+ export async function writeSafe(path, contents, { force = false, dryRun = false } = {}) {
15
+ if (!force && await exists(path)) return { path, status: "skipped" };
16
+ if (!dryRun) {
17
+ await mkdir(dirname(path), { recursive: true });
18
+ await writeFile(path, contents, "utf8");
19
+ }
20
+ return { path, status: dryRun ? "planned" : "created" };
21
+ }
22
+
23
+ export async function copySafe(source, destination, options = {}) {
24
+ if (!options.force && await exists(destination)) return { path: destination, status: "skipped" };
25
+ if (!options.dryRun) {
26
+ await mkdir(dirname(destination), { recursive: true });
27
+ await cp(source, destination, { recursive: true, force: Boolean(options.force) });
28
+ }
29
+ return { path: destination, status: options.dryRun ? "planned" : "created" };
30
+ }
31
+
32
+ export async function readTemplate(name, replacements = {}) {
33
+ let value = await readFile(resolveFromPackage("templates", name), "utf8");
34
+ for (const [key, replacement] of Object.entries(replacements)) {
35
+ value = value.replaceAll(`{{${key}}}`, replacement);
36
+ }
37
+ return value;
38
+ }
39
+
40
+ export function displayResults(root, results) {
41
+ for (const result of results) console.log(`${result.status.padEnd(8)} ${relative(root, result.path)}`);
42
+ }
@@ -0,0 +1,5 @@
1
+ import { dirname, resolve } from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+
4
+ export const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
5
+ export const resolveFromPackage = (...parts) => resolve(packageRoot, ...parts);