@ghostbyte/agent-docs-kit 4.0.2 → 5.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.
- package/CHANGELOG.md +32 -0
- package/README.md +43 -37
- package/SECURITY.md +11 -0
- package/content/catalog.json +191 -22
- package/content/documentation/01-system-documentation.md +13 -0
- package/content/documentation/02-project-governance.md +13 -0
- package/content/documentation/03-development-records.md +13 -0
- package/content/documentation/04-testing-evidence.md +13 -0
- package/content/documentation/05-operations-runbooks.md +13 -0
- package/content/documentation/06-future-proposals.md +13 -0
- package/content/documentation/07-archive-governance.md +13 -0
- package/content/documentation/full-reference.md +24 -0
- package/content/engineering/01-query-performance.md +17 -27
- package/content/engineering/02-networking-fundamentals.md +17 -27
- package/content/engineering/03-rate-limiting-algorithms.md +18 -28
- package/content/engineering/04-production-debugging.md +17 -27
- package/content/engineering/05-code-review-practices.md +17 -27
- package/content/engineering/06-data-modeling.md +17 -27
- package/content/engineering/07-cloud-cost-saving.md +17 -27
- package/content/engineering/08-testing-types.md +17 -27
- package/content/engineering/09-message-queues.md +17 -27
- package/content/engineering/10-api-design-compatibility.md +14 -0
- package/content/engineering/11-reliability-slos.md +14 -0
- package/content/engineering/12-observability-telemetry.md +14 -0
- package/content/engineering/13-deployment-change-safety.md +14 -0
- package/content/engineering/14-capacity-concurrency.md +14 -0
- package/content/engineering/full-reference.md +24 -144
- package/content/security/01-design-architecture.md +11 -19
- package/content/security/02-local-development.md +13 -21
- package/content/security/03-database-backend.md +18 -26
- package/content/security/04-api-layer.md +23 -31
- package/content/security/05-frontend-ui.md +16 -24
- package/content/security/06-application-security-testing.md +22 -28
- package/content/security/07-cicd-release-engineering.md +13 -21
- package/content/security/08-cloud-infrastructure.md +17 -25
- package/content/security/09-edge-cdn-dns.md +16 -24
- package/content/security/10-release-rollout.md +13 -21
- package/content/security/11-post-release-monitoring-ir.md +15 -23
- package/content/security/12-ongoing-operations.md +24 -32
- package/content/security/full-reference.md +30 -3163
- package/docs/ARCHITECTURE.md +31 -92
- package/docs/CONTENT_MAINTENANCE.md +5 -1
- package/docs/GITHUB_AND_NPM_SETUP.md +1 -1
- package/docs/RELEASE_PROCESS.md +5 -5
- package/package.json +12 -5
- package/scripts/build-site.mjs +13 -0
- package/scripts/build-skill.mjs +24 -0
- package/scripts/check-links.mjs +56 -0
- package/scripts/check-review-dates.mjs +21 -0
- package/scripts/preview-site.mjs +43 -0
- package/scripts/smoke-package.mjs +58 -0
- package/scripts/validate-content.mjs +85 -0
- package/site/app.js +121 -0
- package/site/index.html +346 -0
- package/site/styles.css +159 -0
- package/skills/manage-project-quality/SKILL.md +18 -2
- package/skills/manage-project-quality/assets/documentation-report.md +3 -3
- package/skills/manage-project-quality/assets/engineering-report.md +3 -3
- package/skills/manage-project-quality/assets/security-report.md +3 -3
- package/skills/manage-project-quality/references/documentation-01-system-documentation.md +13 -0
- package/skills/manage-project-quality/references/documentation-02-project-governance.md +13 -0
- package/skills/manage-project-quality/references/documentation-03-development-records.md +13 -0
- package/skills/manage-project-quality/references/documentation-04-testing-evidence.md +13 -0
- package/skills/manage-project-quality/references/documentation-05-operations-runbooks.md +13 -0
- package/skills/manage-project-quality/references/documentation-06-future-proposals.md +13 -0
- package/skills/manage-project-quality/references/documentation-07-archive-governance.md +13 -0
- package/skills/manage-project-quality/references/documentation-full-reference.md +24 -0
- package/skills/manage-project-quality/references/documentation-governance.md +2 -2
- package/skills/manage-project-quality/references/engineering-01-query-performance.md +17 -27
- package/skills/manage-project-quality/references/engineering-02-networking-fundamentals.md +17 -27
- package/skills/manage-project-quality/references/engineering-03-rate-limiting-algorithms.md +18 -28
- package/skills/manage-project-quality/references/engineering-04-production-debugging.md +17 -27
- package/skills/manage-project-quality/references/engineering-05-code-review-practices.md +17 -27
- package/skills/manage-project-quality/references/engineering-06-data-modeling.md +17 -27
- package/skills/manage-project-quality/references/engineering-07-cloud-cost-saving.md +17 -27
- package/skills/manage-project-quality/references/engineering-08-testing-types.md +17 -27
- package/skills/manage-project-quality/references/engineering-09-message-queues.md +17 -27
- package/skills/manage-project-quality/references/engineering-10-api-design-compatibility.md +14 -0
- package/skills/manage-project-quality/references/engineering-11-reliability-slos.md +14 -0
- package/skills/manage-project-quality/references/engineering-12-observability-telemetry.md +14 -0
- package/skills/manage-project-quality/references/engineering-13-deployment-change-safety.md +14 -0
- package/skills/manage-project-quality/references/engineering-14-capacity-concurrency.md +14 -0
- package/skills/manage-project-quality/references/engineering-full-reference.md +24 -144
- package/skills/manage-project-quality/references/engineering-protocol.md +1 -1
- package/skills/manage-project-quality/references/security-01-design-architecture.md +11 -19
- package/skills/manage-project-quality/references/security-02-local-development.md +13 -21
- package/skills/manage-project-quality/references/security-03-database-backend.md +18 -26
- package/skills/manage-project-quality/references/security-04-api-layer.md +23 -31
- package/skills/manage-project-quality/references/security-05-frontend-ui.md +16 -24
- package/skills/manage-project-quality/references/security-06-application-security-testing.md +22 -28
- package/skills/manage-project-quality/references/security-07-cicd-release-engineering.md +13 -21
- package/skills/manage-project-quality/references/security-08-cloud-infrastructure.md +17 -25
- package/skills/manage-project-quality/references/security-09-edge-cdn-dns.md +16 -24
- package/skills/manage-project-quality/references/security-10-release-rollout.md +13 -21
- package/skills/manage-project-quality/references/security-11-post-release-monitoring-ir.md +15 -23
- package/skills/manage-project-quality/references/security-12-ongoing-operations.md +24 -32
- package/skills/manage-project-quality/references/security-full-reference.md +30 -3163
- package/skills/manage-project-quality/scripts/check-report.mjs +67 -16
- package/src/api/index.mjs +4 -0
- package/src/cli/arguments.mjs +27 -3
- package/src/cli/help.mjs +53 -18
- package/src/cli/main.mjs +66 -9
- package/src/commands/audit.mjs +146 -31
- package/src/commands/doctor.mjs +1 -1
- package/src/commands/guide.mjs +12 -0
- package/src/commands/list.mjs +1 -1
- package/src/commands/setup.mjs +9 -4
- package/src/commands/skill.mjs +1 -0
- package/src/commands/template.mjs +32 -0
- package/src/commands/validate.mjs +36 -4
- package/src/commands/website.mjs +26 -0
- package/src/core/config.mjs +61 -0
- package/src/core/files.mjs +47 -6
- package/templates/adr.md +20 -0
- package/templates/change-record.md +18 -0
- package/templates/incident-review.md +20 -0
- package/templates/runbook.md +18 -0
- package/templates/test-evidence.md +18 -0
- package/tests/cli.test.mjs +226 -0
- package/tests/content.test.mjs +18 -0
|
@@ -1,23 +1,74 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
4
6
|
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
const statuses = {
|
|
8
|
+
security: new Set(["Pass", "Fail", "Unverified", "N/A"]),
|
|
9
|
+
engineering: new Set(["Applied", "Partial", "Recommended", "Unverified", "N/A"]),
|
|
10
|
+
documentation: new Set(["Pass", "Fail", "Unverified", "N/A"]),
|
|
11
|
+
generic: new Set(["Pass", "Fail", "Unverified", "N/A", "Applied", "Partial", "Recommended"])
|
|
12
|
+
};
|
|
13
|
+
const placeholders = /^(?:|_|-+|none|not verified|unassigned)$/i;
|
|
14
|
+
|
|
15
|
+
function splitRow(line) {
|
|
16
|
+
const cells = [];
|
|
17
|
+
let cell = "";
|
|
18
|
+
for (let index = 1; index < line.length - 1; index += 1) {
|
|
19
|
+
if (line[index] === "|" && line[index - 1] !== "\\") {
|
|
20
|
+
cells.push(cell.trim());
|
|
21
|
+
cell = "";
|
|
22
|
+
} else {
|
|
23
|
+
cell += line[index];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
cells.push(cell.trim());
|
|
27
|
+
return cells;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function validateReport(text) {
|
|
31
|
+
const errors = [];
|
|
32
|
+
let header;
|
|
33
|
+
let allowed = statuses.generic;
|
|
34
|
+
let foundFindings = false;
|
|
35
|
+
const lines = text.split(/\r?\n/);
|
|
36
|
+
for (const [index, line] of lines.entries()) {
|
|
37
|
+
if (!line.startsWith("|")) { header = undefined; continue; }
|
|
38
|
+
if (/^\|[-: |]+\|$/.test(line)) continue;
|
|
39
|
+
const cells = splitRow(line);
|
|
40
|
+
if (/^\|[-: |]+\|$/.test(lines[index + 1] ?? "")) {
|
|
41
|
+
header = cells.map((cell) => cell.toLowerCase());
|
|
42
|
+
if (!header.includes("status")) { header = undefined; continue; }
|
|
43
|
+
foundFindings = true;
|
|
44
|
+
allowed = header.includes("control id") ? statuses.security : header.includes("practice id") ? statuses.engineering : statuses.generic;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (!header) continue;
|
|
48
|
+
const statusIndex = header.indexOf("status");
|
|
49
|
+
const status = cells[statusIndex];
|
|
50
|
+
if (!status) { errors.push(`Line ${index + 1}: status is empty.`); continue; }
|
|
51
|
+
if (!allowed.has(status)) { errors.push(`Line ${index + 1}: invalid status '${status}'.`); continue; }
|
|
52
|
+
if (!["Unverified", "N/A"].includes(status)) {
|
|
53
|
+
for (const name of ["evidence", "owner", "verification"]) {
|
|
54
|
+
const column = header.findIndex((cell) => cell === name || cell.includes(name));
|
|
55
|
+
if (column >= 0 && placeholders.test(cells[column] ?? "")) errors.push(`Line ${index + 1}: status '${status}' requires ${name}.`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
16
58
|
}
|
|
59
|
+
if (!foundFindings) errors.push("No findings table with a Status column was found.");
|
|
60
|
+
return errors;
|
|
17
61
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
62
|
+
|
|
63
|
+
export async function checkReport(file) {
|
|
64
|
+
const errors = validateReport(await readFile(file, "utf8"));
|
|
65
|
+
if (errors.length) throw new Error(errors.join("\n"));
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
|
|
70
|
+
const file = process.argv[2];
|
|
71
|
+
if (!file) throw new Error("Usage: node check-report.mjs <report.md>");
|
|
72
|
+
try { await checkReport(file); console.log("Report structure check passed."); }
|
|
73
|
+
catch (error) { console.error(error.message); process.exitCode = 1; }
|
|
23
74
|
}
|
package/src/api/index.mjs
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export { setupProject } from "../commands/setup.mjs";
|
|
2
2
|
export { auditDocumentation, auditSecurity, reviewEngineering } from "../commands/audit.mjs";
|
|
3
3
|
export { installSkill } from "../commands/skill.mjs";
|
|
4
|
+
export { createTemplate, listTemplates, templateTypes } from "../commands/template.mjs";
|
|
5
|
+
export { showDocumentationGuide } from "../commands/guide.mjs";
|
|
6
|
+
export { openWebsite, websiteUrl } from "../commands/website.mjs";
|
|
4
7
|
export { loadCatalog } from "../core/catalog.mjs";
|
|
8
|
+
export { loadProjectConfig, defaultConfig } from "../core/config.mjs";
|
package/src/cli/arguments.mjs
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
const booleanFlags = new Set(["dry-run", "force", "report", "strict", "help", "version"]);
|
|
2
|
+
const valueFlags = new Set(["cwd", "style", "target", "phase", "topic", "format", "type", "name"]);
|
|
3
|
+
const allowedStyles = new Set(["standard", "learning"]);
|
|
4
|
+
const allowedFormats = new Set(["text", "markdown", "json"]);
|
|
5
|
+
|
|
1
6
|
export function parseArguments(argv) {
|
|
2
7
|
const positionals = [];
|
|
3
8
|
const flags = {};
|
|
@@ -8,10 +13,16 @@ export function parseArguments(argv) {
|
|
|
8
13
|
continue;
|
|
9
14
|
}
|
|
10
15
|
const [rawKey, inline] = token.slice(2).split("=", 2);
|
|
16
|
+
if (!booleanFlags.has(rawKey) && !valueFlags.has(rawKey)) {
|
|
17
|
+
throw new Error(`Unknown option '--${rawKey}'. Run 'agent-docs --help'.`);
|
|
18
|
+
}
|
|
11
19
|
if (inline !== undefined) {
|
|
20
|
+
if (booleanFlags.has(rawKey)) throw new Error(`Option '--${rawKey}' does not accept a value.`);
|
|
12
21
|
flags[rawKey] = inline;
|
|
13
|
-
} else if (argv[index + 1] && !argv[index + 1].startsWith("--")) {
|
|
22
|
+
} else if (valueFlags.has(rawKey) && argv[index + 1] && !argv[index + 1].startsWith("--")) {
|
|
14
23
|
flags[rawKey] = argv[++index];
|
|
24
|
+
} else if (valueFlags.has(rawKey)) {
|
|
25
|
+
throw new Error(`Option '--${rawKey}' requires a value.`);
|
|
15
26
|
} else {
|
|
16
27
|
flags[rawKey] = true;
|
|
17
28
|
}
|
|
@@ -20,11 +31,24 @@ export function parseArguments(argv) {
|
|
|
20
31
|
}
|
|
21
32
|
|
|
22
33
|
export function optionsFromFlags(flags) {
|
|
34
|
+
const style = flags.style;
|
|
35
|
+
const format = flags.format ?? "text";
|
|
36
|
+
if (style !== undefined && !allowedStyles.has(style)) throw new Error(`Invalid style '${style}'. Use standard or learning.`);
|
|
37
|
+
if (!allowedFormats.has(format)) throw new Error(`Invalid format '${format}'. Use text, markdown, or json.`);
|
|
38
|
+
if (flags.target && !["project", "codex"].includes(flags.target)) {
|
|
39
|
+
throw new Error(`Invalid target '${flags.target}'. Use project or codex.`);
|
|
40
|
+
}
|
|
41
|
+
if (flags.name && !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(flags.name)) {
|
|
42
|
+
throw new Error(`Invalid name '${flags.name}'. Use a lowercase hyphenated slug.`);
|
|
43
|
+
}
|
|
23
44
|
return {
|
|
24
45
|
cwd: flags.cwd,
|
|
25
46
|
dryRun: Boolean(flags["dry-run"]),
|
|
26
47
|
force: Boolean(flags.force),
|
|
27
|
-
style
|
|
28
|
-
|
|
48
|
+
style,
|
|
49
|
+
format,
|
|
50
|
+
report: Boolean(flags.report),
|
|
51
|
+
strict: Boolean(flags.strict),
|
|
52
|
+
name: flags.name
|
|
29
53
|
};
|
|
30
54
|
}
|
package/src/cli/help.mjs
CHANGED
|
@@ -1,24 +1,59 @@
|
|
|
1
1
|
export const helpText = `Agent Docs & Quality Kit
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
agent-docs
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
agent-docs
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
agent-docs
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
3
|
+
Run without installing:
|
|
4
|
+
npx @ghostbyte/agent-docs-kit@latest [command] [options]
|
|
5
|
+
|
|
6
|
+
After installing in a project:
|
|
7
|
+
npx agent-docs [command] [options]
|
|
8
|
+
|
|
9
|
+
After installing globally:
|
|
10
|
+
agent-docs [command] [options]
|
|
11
|
+
|
|
12
|
+
Commands:
|
|
13
|
+
(no command) Open the guided menu (TTY only)
|
|
14
|
+
setup [--dry-run] Create the documentation workspace
|
|
15
|
+
audit documentation [--strict] Check documentation health
|
|
16
|
+
documentation guide <area> Print one documentation guidance area
|
|
17
|
+
security worksheet <phase> Create a scoped security worksheet
|
|
18
|
+
engineering worksheet <topic> Create a scoped engineering worksheet
|
|
19
|
+
audit security <phase> Legacy alias for security worksheet
|
|
20
|
+
review engineering <topic> Legacy alias for engineering worksheet
|
|
21
|
+
list [documentation|security|engineering]
|
|
22
|
+
List available areas, phases, or topics
|
|
23
|
+
template list List reusable documentation templates
|
|
24
|
+
template create <type> [--name <slug>]
|
|
25
|
+
Create a uniquely named documentation record
|
|
26
|
+
skill install --target project|codex
|
|
27
|
+
Install the unified quality skill
|
|
28
|
+
validate Validate the distributed library
|
|
29
|
+
doctor Check Node.js and Git prerequisites
|
|
30
|
+
website Open the documentation website
|
|
31
|
+
--version Show the package version
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
npx @ghostbyte/agent-docs-kit@latest setup --dry-run
|
|
35
|
+
npx @ghostbyte/agent-docs-kit@latest audit documentation --strict --format text
|
|
36
|
+
npx @ghostbyte/agent-docs-kit@latest documentation guide system-documentation
|
|
37
|
+
npx @ghostbyte/agent-docs-kit@latest security worksheet api-layer
|
|
38
|
+
npx @ghostbyte/agent-docs-kit@latest engineering worksheet testing-types
|
|
39
|
+
npx @ghostbyte/agent-docs-kit@latest template create adr --name cache-policy
|
|
40
|
+
npx @ghostbyte/agent-docs-kit@latest skill install --target project
|
|
41
|
+
npx @ghostbyte/agent-docs-kit@latest website
|
|
42
|
+
|
|
43
|
+
Check or update a global installation:
|
|
44
|
+
agent-docs --version
|
|
45
|
+
npm view @ghostbyte/agent-docs-kit version
|
|
46
|
+
npm install --global @ghostbyte/agent-docs-kit@latest
|
|
15
47
|
|
|
16
48
|
Options:
|
|
17
|
-
--cwd <path>
|
|
18
|
-
--style standard|learning
|
|
19
|
-
--
|
|
20
|
-
--
|
|
21
|
-
--
|
|
22
|
-
--
|
|
49
|
+
--cwd <path> Target project (default: current directory)
|
|
50
|
+
--style standard|learning Select concise or teaching-oriented output
|
|
51
|
+
--format text|markdown|json Select documentation-audit output
|
|
52
|
+
--name <slug> Give a template a lowercase hyphenated name
|
|
53
|
+
--dry-run Preview writes without changing files
|
|
54
|
+
--force Replace files owned by the command
|
|
55
|
+
--report Persist documentation audit results
|
|
56
|
+
--strict Fail when documentation checks fail
|
|
57
|
+
--help Show this help
|
|
23
58
|
|
|
24
59
|
Installing the npm package performs no project setup. Run a command explicitly.`;
|
package/src/cli/main.mjs
CHANGED
|
@@ -8,13 +8,26 @@ import { listCatalog } from "../commands/list.mjs";
|
|
|
8
8
|
import { installSkill } from "../commands/skill.mjs";
|
|
9
9
|
import { doctor } from "../commands/doctor.mjs";
|
|
10
10
|
import { validatePackage } from "../commands/validate.mjs";
|
|
11
|
+
import { openWebsite } from "../commands/website.mjs";
|
|
12
|
+
import { createTemplate, listTemplates } from "../commands/template.mjs";
|
|
13
|
+
import { showDocumentationGuide } from "../commands/guide.mjs";
|
|
11
14
|
import { readFile } from "node:fs/promises";
|
|
12
15
|
import { resolveFromPackage } from "../core/paths.mjs";
|
|
13
16
|
|
|
17
|
+
function assertInvocation(positionals, flags, { counts, allowedFlags = [], usage, exclusive = [] }) {
|
|
18
|
+
if (!counts.includes(positionals.length)) throw new Error(`Invalid arguments. Usage: ${usage}`);
|
|
19
|
+
const allowed = new Set(allowedFlags);
|
|
20
|
+
const unexpected = Object.keys(flags).filter((flag) => !allowed.has(flag));
|
|
21
|
+
if (unexpected.length) throw new Error(`Option '--${unexpected[0]}' is not valid here. Usage: ${usage}`);
|
|
22
|
+
for (const [positionalIndex, flag] of exclusive) {
|
|
23
|
+
if (positionals[positionalIndex] && flags[flag]) throw new Error(`Choose either '${positionals[positionalIndex]}' or '--${flag}', not both.`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
14
27
|
async function menu() {
|
|
15
28
|
const ui = createInterface({ input: stdin, output: stdout });
|
|
16
29
|
try {
|
|
17
|
-
console.log(`\nAgent Docs & Quality Kit\n\n1. Set up documentation\n2. Audit documentation\n3.
|
|
30
|
+
console.log(`\nAgent Docs & Quality Kit\n\n1. Set up documentation\n2. Audit documentation\n3. Create security worksheet\n4. Create engineering worksheet\n5. Install project skill\n6. Validate package\n7. Open documentation website\n8. List documentation templates\n0. Exit`);
|
|
18
31
|
const choice = await ui.question("\nChoose an action: ");
|
|
19
32
|
if (choice === "1") return setupProject();
|
|
20
33
|
if (choice === "2") return auditDocumentation();
|
|
@@ -28,6 +41,8 @@ async function menu() {
|
|
|
28
41
|
}
|
|
29
42
|
if (choice === "5") return installSkill("project");
|
|
30
43
|
if (choice === "6") return validatePackage();
|
|
44
|
+
if (choice === "7") return openWebsite();
|
|
45
|
+
if (choice === "8") return listTemplates();
|
|
31
46
|
if (choice !== "0") throw new Error("Unknown menu choice.");
|
|
32
47
|
} finally {
|
|
33
48
|
ui.close();
|
|
@@ -37,27 +52,69 @@ async function menu() {
|
|
|
37
52
|
export async function main(argv) {
|
|
38
53
|
const { positionals, flags } = parseArguments(argv);
|
|
39
54
|
const [command, subject, selector] = positionals;
|
|
40
|
-
const options = optionsFromFlags(flags);
|
|
41
55
|
if (flags.version || command === "version") {
|
|
56
|
+
if (!flags.version) assertInvocation(positionals, flags, { counts: [1], usage: "agent-docs version" });
|
|
42
57
|
const metadata = JSON.parse(await readFile(resolveFromPackage("package.json"), "utf8"));
|
|
43
58
|
return console.log(metadata.version);
|
|
44
59
|
}
|
|
45
60
|
if (flags.help || command === "help") return console.log(helpText);
|
|
61
|
+
const options = optionsFromFlags(flags);
|
|
46
62
|
if (!command) {
|
|
63
|
+
assertInvocation(positionals, flags, { counts: [0], usage: "agent-docs" });
|
|
47
64
|
if (!stdin.isTTY) {
|
|
48
65
|
console.log(helpText);
|
|
49
66
|
return;
|
|
50
67
|
}
|
|
51
68
|
return menu();
|
|
52
69
|
}
|
|
53
|
-
if (command === "setup")
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (command === "
|
|
58
|
-
|
|
59
|
-
|
|
70
|
+
if (command === "setup") {
|
|
71
|
+
assertInvocation(positionals, flags, { counts: [1], allowedFlags: ["cwd", "dry-run", "force"], usage: "agent-docs setup [--cwd <path>] [--dry-run] [--force]" });
|
|
72
|
+
return setupProject(options);
|
|
73
|
+
}
|
|
74
|
+
if (command === "audit" && subject === "documentation") {
|
|
75
|
+
assertInvocation(positionals, flags, { counts: [2], allowedFlags: ["cwd", "dry-run", "force", "report", "strict", "format"], usage: "agent-docs audit documentation [--strict] [--report] [--format text|markdown|json]" });
|
|
76
|
+
const result = await auditDocumentation(options);
|
|
77
|
+
if (options.strict && !result.pass) process.exitCode = 1;
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
if ((command === "audit" && subject === "security") || (command === "security" && subject === "worksheet")) {
|
|
81
|
+
assertInvocation(positionals, flags, { counts: [2, 3], allowedFlags: ["cwd", "dry-run", "force", "style", "phase"], usage: "agent-docs security worksheet <phase>", exclusive: [[2, "phase"]] });
|
|
82
|
+
return auditSecurity(selector ?? flags.phase, options);
|
|
83
|
+
}
|
|
84
|
+
if ((command === "review" && subject === "engineering") || (command === "engineering" && subject === "worksheet")) {
|
|
85
|
+
assertInvocation(positionals, flags, { counts: [2, 3], allowedFlags: ["cwd", "dry-run", "force", "style", "topic"], usage: "agent-docs engineering worksheet <topic>", exclusive: [[2, "topic"]] });
|
|
86
|
+
return reviewEngineering(selector ?? flags.topic, options);
|
|
87
|
+
}
|
|
88
|
+
if (command === "list") {
|
|
89
|
+
assertInvocation(positionals, flags, { counts: [2], usage: "agent-docs list documentation|security|engineering" });
|
|
90
|
+
return listCatalog(subject);
|
|
91
|
+
}
|
|
92
|
+
if (command === "documentation" && subject === "guide") {
|
|
93
|
+
assertInvocation(positionals, flags, { counts: [3], usage: "agent-docs documentation guide <area>" });
|
|
94
|
+
return showDocumentationGuide(selector);
|
|
95
|
+
}
|
|
96
|
+
if (command === "skill" && subject === "install") {
|
|
97
|
+
assertInvocation(positionals, flags, { counts: [2], allowedFlags: ["cwd", "dry-run", "force", "target"], usage: "agent-docs skill install --target project|codex" });
|
|
98
|
+
return installSkill(flags.target ?? "project", options);
|
|
99
|
+
}
|
|
100
|
+
if (command === "template" && subject === "list") {
|
|
101
|
+
assertInvocation(positionals, flags, { counts: [2], usage: "agent-docs template list" });
|
|
102
|
+
return listTemplates();
|
|
103
|
+
}
|
|
104
|
+
if (command === "template" && subject === "create") {
|
|
105
|
+
assertInvocation(positionals, flags, { counts: [2, 3], allowedFlags: ["cwd", "dry-run", "force", "type", "name"], usage: "agent-docs template create <type> [--name <slug>]", exclusive: [[2, "type"]] });
|
|
106
|
+
return createTemplate(selector ?? flags.type, options);
|
|
107
|
+
}
|
|
108
|
+
if (command === "validate") {
|
|
109
|
+
assertInvocation(positionals, flags, { counts: [1], usage: "agent-docs validate" });
|
|
110
|
+
return validatePackage();
|
|
111
|
+
}
|
|
112
|
+
if (command === "website") {
|
|
113
|
+
assertInvocation(positionals, flags, { counts: [1], usage: "agent-docs website" });
|
|
114
|
+
return openWebsite();
|
|
115
|
+
}
|
|
60
116
|
if (command === "doctor") {
|
|
117
|
+
assertInvocation(positionals, flags, { counts: [1], usage: "agent-docs doctor" });
|
|
61
118
|
if (!doctor()) process.exitCode = 1;
|
|
62
119
|
return;
|
|
63
120
|
}
|
package/src/commands/audit.mjs
CHANGED
|
@@ -1,49 +1,164 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { resolve } from "node:path";
|
|
1
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
2
|
+
import { dirname, extname, relative, resolve } from "node:path";
|
|
3
3
|
import { loadCatalog, selectEntry } from "../core/catalog.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { loadProjectConfig } from "../core/config.mjs";
|
|
5
|
+
import { exists, writeUnique } from "../core/files.mjs";
|
|
5
6
|
import { resolveFromPackage } from "../core/paths.mjs";
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
export function localDate(value = new Date()) {
|
|
9
|
+
const year = value.getFullYear();
|
|
10
|
+
const month = String(value.getMonth() + 1).padStart(2, "0");
|
|
11
|
+
const day = String(value.getDate()).padStart(2, "0");
|
|
12
|
+
return `${year}-${month}-${day}`;
|
|
13
|
+
}
|
|
8
14
|
|
|
9
15
|
async function reportFor(kind, selector, options = {}) {
|
|
10
16
|
const catalog = await loadCatalog();
|
|
11
|
-
const
|
|
12
|
-
const entry = selectEntry(entries, selector);
|
|
17
|
+
const entry = selectEntry(catalog[kind], selector);
|
|
13
18
|
if (!entry) throw new Error(`Unknown ${kind} selection '${selector ?? ""}'. Run 'agent-docs list ${kind}'.`);
|
|
19
|
+
const { root, config } = await loadProjectConfig(options.cwd);
|
|
14
20
|
const source = await readFile(resolveFromPackage(entry.source), "utf8");
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
const reference = source.replace(/^#\s+(.+)$/m, "### $1");
|
|
22
|
+
const isSecurity = kind === "security";
|
|
23
|
+
const heading = isSecurity ? "Security worksheet" : "Engineering worksheet";
|
|
24
|
+
const destinationRoot = isSecurity ? config.securityReports : config.engineeringReports;
|
|
25
|
+
const output = resolve(root, destinationRoot, `${localDate()}-${entry.slug}.md`);
|
|
26
|
+
const context = isSecurity
|
|
27
|
+
? `## Scope and authorization\n\n- Target: _Describe the code, configuration, or authorized test environment_\n- Authorization: _Required before active testing_\n- Exclusions: _List exclusions_\n- Test safety: _Record rate limits, test data, and stop conditions_`
|
|
28
|
+
: `## Review context\n\n- System or change: _Describe the reviewed scope_\n- Goal: _State the desired engineering outcome_\n- Constraints: _List material technical or delivery constraints_`;
|
|
29
|
+
const columns = isSecurity
|
|
30
|
+
? "| Control ID | Priority | Status | Evidence | Remediation | Owner | Verification |\n|---|---|---|---|---|---|---|\n| Add each applicable control | Medium | Unverified | File/line, command output, or approved observation | Required follow-up | Unassigned | Not verified |"
|
|
31
|
+
: "| Practice ID | Status | Evidence | Impact | Recommendation | Owner | Verification |\n|---|---|---|---|---|---|---|\n| Add each applicable practice | Unverified | File/line, metric, trace, or test output | State the measurable impact | Recommended follow-up | Unassigned | Not verified |";
|
|
32
|
+
const body = `# ${heading}: ${entry.title}\n\n- Date: ${localDate()}\n- Style: ${options.style ?? config.interactionStyle}\n- Overall status: Unverified\n- Reviewer: _Unassigned_\n\n${context}\n\n## Findings\n\n${columns}\n\n## Reference checklist\n\n${reference.trim()}\n`;
|
|
33
|
+
const result = await writeUnique(output, body, options);
|
|
34
|
+
console.log(`${result.status} ${result.path}`);
|
|
24
35
|
return result;
|
|
25
36
|
}
|
|
26
37
|
|
|
27
38
|
export const auditSecurity = (selector, options) => reportFor("security", selector, options);
|
|
28
39
|
export const reviewEngineering = (selector, options) => reportFor("engineering", selector, options);
|
|
29
40
|
|
|
41
|
+
async function markdownFiles(directory) {
|
|
42
|
+
if (!(await exists(directory))) return [];
|
|
43
|
+
const found = [];
|
|
44
|
+
for (const item of await readdir(directory, { withFileTypes: true })) {
|
|
45
|
+
const path = resolve(directory, item.name);
|
|
46
|
+
if (item.isDirectory()) found.push(...await markdownFiles(path));
|
|
47
|
+
else if (item.isFile() && extname(item.name).toLowerCase() === ".md") found.push(path);
|
|
48
|
+
}
|
|
49
|
+
return found;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function anchors(markdown) {
|
|
53
|
+
return new Set([...markdown.matchAll(/^#{1,6}\s+(.+)$/gm)].map(([, heading]) => heading.toLowerCase().trim().replace(/[^\p{L}\p{N}\s-]/gu, "").replace(/\s+/g, "-")));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function validDate(value) {
|
|
57
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value ?? "")) return false;
|
|
58
|
+
const parsed = new Date(`${value}T00:00:00Z`);
|
|
59
|
+
return !Number.isNaN(parsed.getTime()) && parsed.toISOString().slice(0, 10) === value;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function frontMatterValues(block) {
|
|
63
|
+
return Object.fromEntries([...block.matchAll(/^([A-Za-z][\w-]*):\s*(.*?)\s*$/gm)].map((match) => [match[1], match[2].replace(/^['"]|['"]$/g, "")]));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function withoutFencedCode(value) {
|
|
67
|
+
return value.replace(/^(```|~~~)[\s\S]*?^\1\s*$/gm, "");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function inspectMarkdown(file, root, config, cache) {
|
|
71
|
+
const readCached = async (path) => {
|
|
72
|
+
if (!cache.has(path)) cache.set(path, await readFile(path, "utf8"));
|
|
73
|
+
return cache.get(path);
|
|
74
|
+
};
|
|
75
|
+
const value = await readCached(file);
|
|
76
|
+
const inspectable = withoutFencedCode(value);
|
|
77
|
+
const issues = [];
|
|
78
|
+
const frontMatter = value.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/);
|
|
79
|
+
const h1Count = (inspectable.match(/^#\s+/gm) ?? []).length;
|
|
80
|
+
if (h1Count !== 1) issues.push({ rule: "single-h1", path: relative(root, file), detail: `Expected one H1; found ${h1Count}.` });
|
|
81
|
+
const headingCounts = new Map();
|
|
82
|
+
for (const [, heading] of inspectable.matchAll(/^#{1,6}\s+(.+)$/gm)) {
|
|
83
|
+
const key = heading.toLowerCase().trim();
|
|
84
|
+
headingCounts.set(key, (headingCounts.get(key) ?? 0) + 1);
|
|
85
|
+
}
|
|
86
|
+
for (const [heading, count] of headingCounts) {
|
|
87
|
+
if (count > 1) issues.push({ rule: "duplicate-heading", path: relative(root, file), detail: `Heading '${heading}' appears ${count} times.` });
|
|
88
|
+
}
|
|
89
|
+
if (/\b(?:TODO|TBD|FIXME)\b/i.test(inspectable)) issues.push({ rule: "placeholder", path: relative(root, file), detail: "Contains TODO, TBD, or FIXME outside a fenced code block." });
|
|
90
|
+
if (config.requireMetadata && !frontMatter) issues.push({ rule: "metadata", path: relative(root, file), detail: "Required front matter is missing." });
|
|
91
|
+
if (frontMatter) {
|
|
92
|
+
const metadata = frontMatterValues(frontMatter[1]);
|
|
93
|
+
if (config.requireMetadata) {
|
|
94
|
+
for (const key of ["owner", "status", "lastReviewed", "reviewDue"]) {
|
|
95
|
+
if (!metadata[key]) issues.push({ rule: "metadata", path: relative(root, file), detail: `Required front matter field '${key}' is missing.` });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
for (const key of ["lastReviewed", "reviewDue"]) {
|
|
99
|
+
if (metadata[key] && !validDate(metadata[key])) issues.push({ rule: "metadata-date", path: relative(root, file), detail: `Front matter field '${key}' is not a valid YYYY-MM-DD date.` });
|
|
100
|
+
}
|
|
101
|
+
const { lastReviewed, reviewDue } = metadata;
|
|
102
|
+
if (validDate(reviewDue) && reviewDue < localDate()) issues.push({ rule: "review-overdue", path: relative(root, file), detail: `Review was due ${reviewDue}.` });
|
|
103
|
+
if (validDate(lastReviewed)) {
|
|
104
|
+
const ageDays = Math.floor((Date.now() - new Date(`${lastReviewed}T00:00:00`).getTime()) / 86400000);
|
|
105
|
+
if (ageDays > config.maxAgeDays) issues.push({ rule: "stale-document", path: relative(root, file), detail: `Last reviewed ${ageDays} days ago; limit is ${config.maxAgeDays}.` });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
for (const match of inspectable.matchAll(/\[[^\]]+\]\((<[^>]+>|[^\s)]+)(?:\s+["'][^"']*["'])?\)/g)) {
|
|
109
|
+
const rawTarget = match[1].trim().replace(/^<|>$/g, "");
|
|
110
|
+
if (!rawTarget || /^(?:https?:|mailto:)/i.test(rawTarget)) continue;
|
|
111
|
+
const [target, fragment] = rawTarget.split("#", 2);
|
|
112
|
+
let decodedTarget;
|
|
113
|
+
let decodedFragment;
|
|
114
|
+
try {
|
|
115
|
+
decodedTarget = target ? decodeURIComponent(target) : "";
|
|
116
|
+
decodedFragment = fragment ? decodeURIComponent(fragment).toLowerCase() : "";
|
|
117
|
+
} catch {
|
|
118
|
+
issues.push({ rule: "invalid-link", path: relative(root, file), detail: `Invalid URL encoding: ${rawTarget}` });
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const linked = decodedTarget ? resolve(dirname(file), decodedTarget) : file;
|
|
122
|
+
if (!(await exists(linked))) issues.push({ rule: "broken-link", path: relative(root, file), detail: `Missing target: ${rawTarget}` });
|
|
123
|
+
else if (fragment && extname(linked).toLowerCase() === ".md") {
|
|
124
|
+
const targetValue = await readCached(linked);
|
|
125
|
+
if (!anchors(targetValue).has(decodedFragment)) issues.push({ rule: "broken-anchor", path: relative(root, file), detail: `Missing anchor: ${rawTarget}` });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return issues;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function renderAudit(result, format) {
|
|
132
|
+
if (format === "json") return JSON.stringify(result, null, 2);
|
|
133
|
+
if (format === "markdown") {
|
|
134
|
+
const rows = result.checks.map((row) => `| ${row.pass ? "PASS" : "FAIL"} | ${row.rule} | ${row.path} | ${row.detail} |`).join("\n");
|
|
135
|
+
return `# Documentation audit\n\n- Checked: ${result.checkedAt}\n- Result: ${result.pass ? "PASS" : "FAIL"}\n\n| Status | Rule | Path | Detail |\n|---|---|---|---|\n${rows}\n`;
|
|
136
|
+
}
|
|
137
|
+
return result.checks.map((row) => `${row.pass ? "PASS" : "FAIL"} ${row.rule} ${row.path}${row.detail ? ` — ${row.detail}` : ""}`).join("\n");
|
|
138
|
+
}
|
|
139
|
+
|
|
30
140
|
export async function auditDocumentation(options = {}) {
|
|
31
|
-
const root =
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
for (const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
141
|
+
const { root, config, configPath } = await loadProjectConfig(options.cwd);
|
|
142
|
+
const checks = [];
|
|
143
|
+
checks.push({ rule: "configuration", path: relative(root, configPath), pass: await exists(configPath), detail: "Project audit configuration" });
|
|
144
|
+
for (const path of config.requiredDocuments) {
|
|
145
|
+
let pass = false;
|
|
146
|
+
try { pass = (await stat(resolve(root, path))).isFile(); } catch {}
|
|
147
|
+
checks.push({ rule: "required-document", path, pass, detail: "Required file from configuration" });
|
|
148
|
+
}
|
|
149
|
+
const cache = new Map();
|
|
150
|
+
for (const file of await markdownFiles(resolve(root, config.documentationRoot))) {
|
|
151
|
+
for (const issue of await inspectMarkdown(file, root, config, cache)) checks.push({ ...issue, pass: false });
|
|
152
|
+
}
|
|
153
|
+
const result = { checkedAt: new Date().toISOString(), pass: checks.every((row) => row.pass), checks };
|
|
154
|
+
console.log(renderAudit(result, options.format));
|
|
155
|
+
if (options.report) {
|
|
156
|
+
const extension = options.format === "json" ? "json" : options.format === "markdown" ? "md" : "txt";
|
|
157
|
+
const output = resolve(root, config.documentationReports, `${localDate()}-documentation.${extension}`);
|
|
158
|
+
const body = `${renderAudit(result, options.format)}\n`;
|
|
159
|
+
const written = await writeUnique(output, body, options);
|
|
160
|
+
console.log(`${written.status} ${written.path}`);
|
|
161
|
+
result.reportPath = written.path;
|
|
47
162
|
}
|
|
48
163
|
return result;
|
|
49
164
|
}
|
package/src/commands/doctor.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
|
|
3
3
|
export function doctor() {
|
|
4
|
-
const checks = [{ name: "Node.js >=
|
|
4
|
+
const checks = [{ name: "Node.js >=22", pass: Number(process.versions.node.split(".")[0]) >= 22, value: process.version }];
|
|
5
5
|
try {
|
|
6
6
|
checks.push({ name: "Git", pass: true, value: execFileSync("git", ["--version"], { encoding: "utf8" }).trim() });
|
|
7
7
|
} catch {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { loadCatalog, selectEntry } from "../core/catalog.mjs";
|
|
3
|
+
import { resolveFromPackage } from "../core/paths.mjs";
|
|
4
|
+
|
|
5
|
+
export async function showDocumentationGuide(selector) {
|
|
6
|
+
const catalog = await loadCatalog();
|
|
7
|
+
const entry = selectEntry(catalog.documentation, selector);
|
|
8
|
+
if (!entry) throw new Error(`Unknown documentation area '${selector ?? ""}'. Run 'agent-docs list documentation'.`);
|
|
9
|
+
const contents = await readFile(resolveFromPackage(entry.source), "utf8");
|
|
10
|
+
console.log(contents.trim());
|
|
11
|
+
return { entry, contents };
|
|
12
|
+
}
|
package/src/commands/list.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { loadCatalog } from "../core/catalog.mjs";
|
|
|
2
2
|
|
|
3
3
|
export async function listCatalog(kind) {
|
|
4
4
|
const catalog = await loadCatalog();
|
|
5
|
-
const groups = kind ? [kind] : ["security", "engineering"];
|
|
5
|
+
const groups = kind ? [kind] : ["documentation", "security", "engineering"];
|
|
6
6
|
for (const group of groups) {
|
|
7
7
|
if (!catalog[group]) throw new Error(`Unknown catalog '${group}'.`);
|
|
8
8
|
console.log(`\n${group.toUpperCase()}`);
|
package/src/commands/setup.mjs
CHANGED
|
@@ -3,11 +3,16 @@ import { displayResults, writeSafe } from "../core/files.mjs";
|
|
|
3
3
|
|
|
4
4
|
const files = {
|
|
5
5
|
".agent-docs/config.json": `{
|
|
6
|
-
"schemaVersion":
|
|
6
|
+
"schemaVersion": 2,
|
|
7
7
|
"documentationRoot": "docs",
|
|
8
|
+
"documentationReports": "docs/03-development/audits",
|
|
8
9
|
"securityReports": "docs/04-testing/security-audits",
|
|
9
10
|
"engineeringReports": "docs/03-development/engineering-reviews",
|
|
10
|
-
"
|
|
11
|
+
"templateOutput": "docs",
|
|
12
|
+
"interactionStyle": "standard",
|
|
13
|
+
"maxAgeDays": 180,
|
|
14
|
+
"requireMetadata": false,
|
|
15
|
+
"requiredDocuments": ["docs/README.md", "docs/01-system/overview.md", "docs/02-project/README.md", "docs/05-operations/README.md"]
|
|
11
16
|
}\n`,
|
|
12
17
|
"docs/README.md": `# Project documentation
|
|
13
18
|
|
|
@@ -18,7 +23,7 @@ This directory is the maintained project record. Keep decisions and system facts
|
|
|
18
23
|
- \`03-development/\`: implementation notes and engineering reviews
|
|
19
24
|
- \`04-testing/\`: test plans, security audits, and evidence
|
|
20
25
|
- \`05-operations/\`: setup, deployment, runbooks, and agent operations
|
|
21
|
-
- \`06-future-
|
|
26
|
+
- \`06-future-proposals/\`: proposals that are not current commitments
|
|
22
27
|
- \`archive/\`: superseded material retained for traceability
|
|
23
28
|
`,
|
|
24
29
|
"docs/01-system/overview.md": "# System overview\n\nStatus: Needs update\n\nDescribe the system boundary, users, dependencies, and authoritative sources.\n",
|
|
@@ -27,7 +32,7 @@ This directory is the maintained project record. Keep decisions and system facts
|
|
|
27
32
|
"docs/04-testing/security-audits/.gitkeep": "",
|
|
28
33
|
"docs/04-testing/evidence/.gitkeep": "",
|
|
29
34
|
"docs/05-operations/README.md": "# Operations\n\nDocument setup, deployment, observability, incident response, and recovery procedures.\n",
|
|
30
|
-
"docs/06-future-
|
|
35
|
+
"docs/06-future-proposals/README.md": "# Future proposals\n\nIdeas here are proposals, not part of the current scope until accepted.\n",
|
|
31
36
|
"docs/archive/README.md": "# Archive\n\nKeep superseded documents here with replacement links and archive dates.\n"
|
|
32
37
|
};
|
|
33
38
|
|
package/src/commands/skill.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { copySafe } from "../core/files.mjs";
|
|
|
4
4
|
import { resolveFromPackage } from "../core/paths.mjs";
|
|
5
5
|
|
|
6
6
|
export async function installSkill(target = "project", options = {}) {
|
|
7
|
+
if (!["project", "codex"].includes(target)) throw new Error(`Invalid target '${target}'. Use project or codex.`);
|
|
7
8
|
const root = resolve(options.cwd ?? process.cwd());
|
|
8
9
|
const destination = target === "codex"
|
|
9
10
|
? resolve(homedir(), ".codex", "skills", "manage-project-quality")
|