@warpgogol/forge 2.8.0 → 2.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/README.md +23 -13
- package/README.uk.md +19 -12
- package/dist/os/session/handlers/validate.d.ts.map +1 -1
- package/dist/os/session/handlers/validate.js +23 -1
- package/dist/os/session/handlers/validate.js.map +1 -1
- package/dist/os/session/types.d.ts +31 -1
- package/dist/os/session/types.d.ts.map +1 -1
- package/dist/os/session/types.js +7 -0
- package/dist/os/session/types.js.map +1 -1
- package/os/adr/adr.module.ts +158 -0
- package/os/adr/frontmatter-io.ts +80 -0
- package/os/adr/handlers/archive.ts +244 -0
- package/os/adr/handlers/implement-stamp.ts +331 -0
- package/os/adr/handlers/list-create.ts +207 -0
- package/os/adr/handlers/validate.test.ts +232 -0
- package/os/adr/handlers/validate.ts +429 -0
- package/os/adr/index.ts +33 -0
- package/os/adr/types.ts +141 -0
- package/os/audit/audit.module.ts +48 -0
- package/os/audit/frontmatter-io.ts +84 -0
- package/os/audit/handlers/archive.ts +239 -0
- package/os/audit/index.ts +23 -0
- package/os/audit/types.ts +29 -0
- package/os/compass/compass.module.ts +186 -0
- package/os/compass/handlers/compass-audit-handler.ts +382 -0
- package/os/compass/handlers/compass-change-summary-handler.ts +272 -0
- package/os/compass/handlers/compass-inventory-handler.ts +294 -0
- package/os/compass/handlers/compass-inventory.ts +521 -0
- package/os/compass/handlers/git-revision.ts +137 -0
- package/os/compass/handlers/resolve-scan-root-workpiece.test.ts +65 -0
- package/os/compass/handlers/resolve-scan-root.ts +79 -0
- package/os/compass/index.ts +33 -0
- package/os/core/core.module.ts +948 -0
- package/os/core/handlers/assets-check.ts +122 -0
- package/os/core/handlers/assets-helpers.ts +174 -0
- package/os/core/handlers/assets-list.ts +99 -0
- package/os/core/handlers/build.ts +155 -0
- package/os/core/handlers/determinism-check.ts +354 -0
- package/os/core/handlers/dev.ts +127 -0
- package/os/core/handlers/invariant-engine.test.ts +681 -0
- package/os/core/handlers/knowledge-compact.ts +248 -0
- package/os/core/handlers/lifecycle-handlers.test.ts +524 -0
- package/os/core/handlers/note-frontmatter-validate.test.ts +93 -0
- package/os/core/handlers/note-link-validate.test.ts +117 -0
- package/os/core/handlers/note-orphan-detect.test.ts +94 -0
- package/os/core/handlers/package-health.test.ts +226 -0
- package/os/core/handlers/package-health.ts +231 -0
- package/os/core/handlers/pinned-check.ts +200 -0
- package/os/core/handlers/pinned-init.ts +333 -0
- package/os/core/handlers/pinned-types.ts +50 -0
- package/os/core/handlers/pinned-validate.ts +301 -0
- package/os/core/handlers/profile-resolve.ts +92 -0
- package/os/core/handlers/release-prepare.ts +294 -0
- package/os/core/handlers/release-publish.ts +211 -0
- package/os/core/handlers/validate.ts +262 -0
- package/os/core/handlers/workspace-deps.ts +55 -0
- package/os/core/index.ts +13 -0
- package/os/exploration/exploration.module.ts +90 -0
- package/os/exploration/frontmatter-io.ts +77 -0
- package/os/exploration/handlers/archive.ts +159 -0
- package/os/exploration/handlers/list.ts +77 -0
- package/os/exploration/handlers/show.ts +107 -0
- package/os/exploration/index.ts +25 -0
- package/os/exploration/types.ts +65 -0
- package/os/mission/handlers/archive.test.ts +391 -0
- package/os/mission/handlers/archive.ts +453 -0
- package/os/mission/index.ts +1 -0
- package/os/mission/mission.module.ts +48 -0
- package/os/mission/types.ts +41 -0
- package/os/naming/index.ts +14 -0
- package/os/naming/naming-convention.test.ts +163 -0
- package/os/naming/naming-convention.ts +378 -0
- package/os/naming/naming.module.ts +36 -0
- package/os/notes/index.ts +10 -0
- package/os/notes/notes.module.ts +109 -0
- package/os/plan/frontmatter-io.ts +82 -0
- package/os/plan/handlers/archive.ts +238 -0
- package/os/plan/index.ts +23 -0
- package/os/plan/plan.module.ts +48 -0
- package/os/plan/types.ts +29 -0
- package/os/program/discovery.ts +294 -0
- package/os/program/handlers/complete.ts +419 -0
- package/os/program/handlers/lease.ts +433 -0
- package/os/program/handlers/seal.ts +332 -0
- package/os/program/handlers/validate.ts +213 -0
- package/os/program/lease.ts +156 -0
- package/os/program/program.module.ts +212 -0
- package/os/program/schemas.ts +218 -0
- package/os/program/state.ts +264 -0
- package/os/rfc/acceptance.ts +344 -0
- package/os/rfc/decision-log.ts +310 -0
- package/os/rfc/dna-trace.ts +280 -0
- package/os/rfc/frontmatter-io.test.ts +132 -0
- package/os/rfc/frontmatter-io.ts +130 -0
- package/os/rfc/handlers/archive.ts +300 -0
- package/os/rfc/handlers/check.ts +238 -0
- package/os/rfc/handlers/implement-stamp.ts +515 -0
- package/os/rfc/handlers/index-graph.ts +237 -0
- package/os/rfc/handlers/lifecycle.test.ts +127 -0
- package/os/rfc/handlers/lifecycle.ts +249 -0
- package/os/rfc/handlers/list-create.ts +296 -0
- package/os/rfc/handlers/pipeline-status.ts +173 -0
- package/os/rfc/handlers/shared.ts +121 -0
- package/os/rfc/handlers/supersede-propose.ts +241 -0
- package/os/rfc/handlers/validate-rules.test.ts +891 -0
- package/os/rfc/handlers/validate-rules.ts +984 -0
- package/os/rfc/handlers/validate.ts +166 -0
- package/os/rfc/handlers.ts +19 -0
- package/os/rfc/index.ts +97 -0
- package/os/rfc/rfc.module.ts +416 -0
- package/os/rfc/types.ts +596 -0
- package/os/rfc/verification-evidence.ts +244 -0
- package/os/session/atif-parser.ts +166 -0
- package/os/session/frontmatter-io.ts +163 -0
- package/os/session/handlers/archive.ts +288 -0
- package/os/session/handlers/list.ts +174 -0
- package/os/session/handlers/save.ts +348 -0
- package/os/session/handlers/validate.ts +250 -0
- package/os/session/index.ts +50 -0
- package/os/session/session.module.ts +126 -0
- package/os/session/types.ts +203 -0
- package/os/spec/live-spec-list-show-validate.test.ts +257 -0
- package/os/spec/live-spec-list.ts +78 -0
- package/os/spec/live-spec-merge.test.ts +261 -0
- package/os/spec/live-spec-merge.ts +413 -0
- package/os/spec/live-spec-show.ts +117 -0
- package/os/spec/live-spec-types.ts +93 -0
- package/os/spec/live-spec-validate.ts +170 -0
- package/os/spec/spec-materialize.ts +390 -0
- package/os/spec/spec-schema.ts +174 -0
- package/os/spec/spec-status.ts +284 -0
- package/os/spec/spec-validate.ts +513 -0
- package/os/spec/spec.module.ts +135 -0
- package/os/werkstatt/handlers/lock.ts +168 -0
- package/os/werkstatt/handlers/schema.ts +47 -0
- package/os/werkstatt/handlers/werkstatt-lock-recover.ts +175 -0
- package/os/werkstatt/handlers/werkstatt-lock-status.ts +73 -0
- package/os/werkstatt/handlers/werkstatt-operation-validate.ts +104 -0
- package/os/werkstatt/index.ts +39 -0
- package/os/werkstatt/werkstatt.module.ts +62 -0
- package/os/workflow/handlers.ts +304 -0
- package/os/workflow/index.ts +28 -0
- package/os/workflow/types.ts +99 -0
- package/os/workflow/workflow.module.ts +54 -0
- package/package.json +3 -3
- package/profiles/godot-csharp.yaml +4 -4
- package/profiles/knowledge-typescript-turborepo.yaml +110 -0
- package/profiles/phaser-turborepo.yaml +5 -5
- package/skills/_shared/fo-session-summary.md +132 -16
- package/skills/fo/fo-add-tests/pbt-guide.md +13 -13
- package/skills/fo/fo-doc-audit/SKILL.md +6 -6
- package/skills/fo/fo-handoff/SKILL.md +5 -0
- package/skills/fo/fo-idea/SKILL.md +1 -1
- package/skills/fo/fo-review/SKILL.md +1 -1
- package/skills/fo/fo-session-retro/SKILL.md +8 -0
- package/skills/fo/fo-session-save/SKILL.md +8 -0
- package/src/cli-output.ts +59 -0
- package/src/config/__tests__/resolve-terminology.test.ts +45 -0
- package/src/config/forge-config.ts +524 -0
- package/src/forge-module.ts +39 -0
- package/src/index.ts +172 -0
- package/src/knowledge/__tests__/promote.test.ts +219 -0
- package/src/knowledge/__tests__/serialize.test.ts +192 -0
- package/src/knowledge/budgets.ts +202 -0
- package/src/knowledge/compact.ts +405 -0
- package/src/knowledge/index.ts +53 -0
- package/src/knowledge/parse.ts +227 -0
- package/src/knowledge/promote.ts +156 -0
- package/src/knowledge/schema.ts +112 -0
- package/src/knowledge/serialize.ts +72 -0
- package/src/migration-adapters/__tests__/ignored-files.test.ts +51 -0
- package/src/migration-adapters/__tests__/registry.test.ts +131 -0
- package/src/migration-adapters/__tests__/types.test.ts +86 -0
- package/src/migration-adapters/git-utils.ts +71 -0
- package/src/migration-adapters/ignored-files.ts +221 -0
- package/src/migration-adapters/index.ts +21 -0
- package/src/migration-adapters/node-typescript-pnpm/index.ts +189 -0
- package/src/migration-adapters/phaser-pnpm/index.ts +189 -0
- package/src/migration-adapters/registry.ts +61 -0
- package/src/migration-adapters/types.ts +68 -0
- package/src/onboarding/__tests__/workspace-discovery.test.ts +134 -0
- package/src/onboarding/agents-generate.ts +443 -0
- package/src/onboarding/create.ts +427 -0
- package/src/onboarding/doctor.ts +1297 -0
- package/src/onboarding/init.ts +337 -0
- package/src/onboarding/invariant-engine.ts +448 -0
- package/src/onboarding/memory-scaffold.ts +189 -0
- package/src/onboarding/nested-agents-generate.ts +93 -0
- package/src/onboarding/nested-agents-templates.ts +233 -0
- package/src/onboarding/profile-validate.ts +139 -0
- package/src/onboarding/scaffold-project.ts +299 -0
- package/src/onboarding/scaffold.ts +143 -0
- package/src/onboarding/upgrade.ts +558 -0
- package/src/onboarding/workspace-discovery.ts +167 -0
- package/src/profiles/__tests__/profile-schema.test.ts +228 -0
- package/src/profiles/__tests__/stack-profile.test.ts +190 -0
- package/src/profiles/__tests__/terminology-utils.test.ts +71 -0
- package/src/profiles/profile-schema.ts +350 -0
- package/src/profiles/stack-profile.ts +188 -0
- package/src/profiles/terminology-utils.ts +36 -0
- package/src/registry.ts +185 -0
- package/src/skill-schema.ts +37 -0
- package/src/tests/acceptance-criteria.test.ts +99 -0
- package/src/tests/adr-implement-stamp.test.ts +368 -0
- package/src/tests/agents-generate-domain.test.ts +317 -0
- package/src/tests/agents-generate.test.ts +458 -0
- package/src/tests/bindings-schema.test.ts +314 -0
- package/src/tests/budgets.test.ts +213 -0
- package/src/tests/cli-output.test.ts +196 -0
- package/src/tests/compact.test.ts +796 -0
- package/src/tests/create.test.ts +289 -0
- package/src/tests/doctor-autonomy.test.ts +98 -0
- package/src/tests/doctor-bindings.test.ts +286 -0
- package/src/tests/doctor-domain.test.ts +113 -0
- package/src/tests/exploration-handlers.test.ts +256 -0
- package/src/tests/fixtures/agents-generate-business-before.txt +288 -0
- package/src/tests/forge-config.test.ts +420 -0
- package/src/tests/fs-atomic.test.ts +96 -0
- package/src/tests/fs.test.ts +103 -0
- package/src/tests/generated-marker.test.ts +161 -0
- package/src/tests/hash.test.ts +37 -0
- package/src/tests/implement-stamp.test.ts +505 -0
- package/src/tests/init-bindings.test.ts +183 -0
- package/src/tests/knowledge-parse.test.ts +307 -0
- package/src/tests/knowledge-pbt.test.ts +164 -0
- package/src/tests/memory-scaffold.test.ts +192 -0
- package/src/tests/migration-adapters.test.ts +480 -0
- package/src/tests/package-files.test.ts +54 -0
- package/src/tests/pinned-check.test.ts +194 -0
- package/src/tests/pinned-init.test.ts +126 -0
- package/src/tests/profile-schema.test.ts +194 -0
- package/src/tests/profile-validate.test.ts +120 -0
- package/src/tests/program-lease.test.ts +245 -0
- package/src/tests/program-paths-extended.test.ts +117 -0
- package/src/tests/program-paths.test.ts +98 -0
- package/src/tests/program-property.test.ts +139 -0
- package/src/tests/program-schemas.test.ts +445 -0
- package/src/tests/program-spec-node.test.ts +250 -0
- package/src/tests/promote.test.ts +481 -0
- package/src/tests/registry.test.ts +73 -0
- package/src/tests/scaffold-project.test.ts +150 -0
- package/src/tests/session-handlers.test.ts +503 -0
- package/src/tests/session-pbt.test.ts +190 -0
- package/src/tests/skill-schema.test.ts +146 -0
- package/src/tests/skill-validate-knowledge.test.ts +26 -0
- package/src/tests/skill-validate.test.ts +254 -0
- package/src/tests/stack-profile.test.ts +221 -0
- package/src/tests/string-utils.test.ts +42 -0
- package/src/tests/upgrade.test.ts +428 -0
- package/src/tests/werkstatt-lock.test.ts +343 -0
- package/src/tests/workspace-discovery-domain.test.ts +112 -0
- package/src/tests/workspace-discovery.test.ts +135 -0
- package/src/types.ts +221 -0
- package/src/utils/__tests__/fs-idempotent.test.ts +73 -0
- package/src/utils/__tests__/fs-trash.test.ts +45 -0
- package/src/utils/fs-atomic.ts +93 -0
- package/src/utils/fs-idempotent.ts +41 -0
- package/src/utils/fs-trash-sync.ts +37 -0
- package/src/utils/fs-trash.ts +24 -0
- package/src/utils/fs.ts +74 -0
- package/src/utils/generated-marker.ts +166 -0
- package/src/utils/hash.ts +19 -0
- package/src/utils/index.ts +29 -0
- package/src/utils/string-utils.ts +19 -0
- package/src/validators/__tests__/note-orphan-detect.test.ts +94 -0
- package/src/validators/note-frontmatter-validate.ts +142 -0
- package/src/validators/note-link-validate.ts +145 -0
- package/src/validators/note-orphan-detect.ts +146 -0
- package/src/validators/port-validate.ts +97 -0
- package/src/validators/skill-validate.ts +831 -0
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>ADR validation handler — frontmatter, section, referential integrity checks, implementation commit drift detection (AV-16), and directory structure convention (ADR-DIR-01).</purpose>
|
|
4
|
+
<non-goals>
|
|
5
|
+
<item>Do not introduce app-specific runtime composition or deployment behavior.</item>
|
|
6
|
+
</non-goals>
|
|
7
|
+
</MODULE_CONTRACT>
|
|
8
|
+
<CHANGE_SUMMARY>
|
|
9
|
+
<item>RFC-0366: implement fail-hard ADR validation for the new adrModule.</item>
|
|
10
|
+
<item>Post-refactor hardening: allow ADRs to be superseded by existing RFC decisions.</item>
|
|
11
|
+
<item>RFC-0521: migrated from packages/os/site-kernel/src/adr/ to packages/forge/os/adr/.</item>
|
|
12
|
+
<item>RFC-0722: add ADR-DIR-01 directory structure warning rule for unsanctioned subdirectories.</item>
|
|
13
|
+
</CHANGE_SUMMARY>
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
import { execFile } from "node:child_process";
|
|
18
|
+
import {
|
|
19
|
+
listAdrFiles,
|
|
20
|
+
readAndParseAdr,
|
|
21
|
+
adrFileMatchesId,
|
|
22
|
+
type ParsedAdr,
|
|
23
|
+
} from "../frontmatter-io.ts";
|
|
24
|
+
import { listRfcFiles, readAndParseRfc } from "../../rfc/frontmatter-io.ts";
|
|
25
|
+
import type {
|
|
26
|
+
ForgeCommandInput,
|
|
27
|
+
ForgeCommandResult,
|
|
28
|
+
ForgeRuntimeContext,
|
|
29
|
+
} from "../../../src/types.ts";
|
|
30
|
+
import type { AdrValidationViolation, AdrValidationResult, AdrStatus, AdrScope } from "../types.ts";
|
|
31
|
+
import {
|
|
32
|
+
ADR_DIR,
|
|
33
|
+
ADR_ID_PATTERN,
|
|
34
|
+
ADR_KNOWN_KEYS,
|
|
35
|
+
ADR_REQUIRED_SECTIONS,
|
|
36
|
+
ADR_SCOPES,
|
|
37
|
+
ADR_STATUSES,
|
|
38
|
+
} from "../types.ts";
|
|
39
|
+
import { RFC_DIR } from "../../rfc/types.ts";
|
|
40
|
+
|
|
41
|
+
async function loadRfcIds(workspaceRoot: string): Promise<Set<string>> {
|
|
42
|
+
const rfcIds = new Set<string>();
|
|
43
|
+
const rfcDirPath = path.join(workspaceRoot, RFC_DIR);
|
|
44
|
+
const files = await listRfcFiles(rfcDirPath);
|
|
45
|
+
for (const fileName of files) {
|
|
46
|
+
const result = await readAndParseRfc(rfcDirPath, fileName);
|
|
47
|
+
const id = result && "parsed" in result ? String(result.parsed.frontmatter["id"] ?? "") : "";
|
|
48
|
+
if (id) rfcIds.add(id);
|
|
49
|
+
}
|
|
50
|
+
return rfcIds;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function runAdrValidate(
|
|
54
|
+
input: ForgeCommandInput,
|
|
55
|
+
context: ForgeRuntimeContext,
|
|
56
|
+
): Promise<ForgeCommandResult<AdrValidationResult>> {
|
|
57
|
+
const { workspaceRoot, logger, outputFormat } = context;
|
|
58
|
+
const adrDirPath = path.join(workspaceRoot, ADR_DIR);
|
|
59
|
+
|
|
60
|
+
const targetId = input.flags["id"] as string | undefined;
|
|
61
|
+
|
|
62
|
+
const allFiles = await listAdrFiles(adrDirPath);
|
|
63
|
+
const filesToValidate = targetId
|
|
64
|
+
? allFiles.filter((f) => adrFileMatchesId(f, targetId))
|
|
65
|
+
: allFiles;
|
|
66
|
+
|
|
67
|
+
if (filesToValidate.length === 0) {
|
|
68
|
+
if (targetId) {
|
|
69
|
+
throw new Error(`No ADR file found for id ${targetId} in ${ADR_DIR}/`);
|
|
70
|
+
}
|
|
71
|
+
if (outputFormat === "pretty") {
|
|
72
|
+
logger.info("No ADR files found to validate.");
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
data: { command: "adr.validate", status: "pass", count: 0, violations: [] },
|
|
76
|
+
summary: "No ADR files found to validate",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const allParsed: Map<string, { fileName: string; parsed: ParsedAdr }> = new Map();
|
|
81
|
+
const allParsedByFile: Map<string, { fileName: string; parsed: ParsedAdr }> = new Map();
|
|
82
|
+
for (const f of allFiles) {
|
|
83
|
+
const result = await readAndParseAdr(adrDirPath, f);
|
|
84
|
+
if (result) {
|
|
85
|
+
const id = String(result.parsed.frontmatter["id"] ?? "");
|
|
86
|
+
allParsed.set(id, result);
|
|
87
|
+
allParsedByFile.set(f, result);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const knownKeys = new Set<string>(ADR_KNOWN_KEYS);
|
|
92
|
+
const knownRfcIds = await loadRfcIds(workspaceRoot);
|
|
93
|
+
const violations: AdrValidationViolation[] = [];
|
|
94
|
+
const seenIds = new Map<string, string>();
|
|
95
|
+
|
|
96
|
+
function addViolation(
|
|
97
|
+
adrId: string,
|
|
98
|
+
file: string,
|
|
99
|
+
rule: string,
|
|
100
|
+
message: string,
|
|
101
|
+
severity: "error" | "warning" = "error",
|
|
102
|
+
): void {
|
|
103
|
+
violations.push({ adrId, file, rule, message, severity });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (const fileName of filesToValidate) {
|
|
107
|
+
const result = allParsedByFile.get(fileName);
|
|
108
|
+
if (!result) continue;
|
|
109
|
+
|
|
110
|
+
await validateSingleAdr(
|
|
111
|
+
fileName,
|
|
112
|
+
result.parsed,
|
|
113
|
+
allParsed,
|
|
114
|
+
knownRfcIds,
|
|
115
|
+
seenIds,
|
|
116
|
+
knownKeys,
|
|
117
|
+
workspaceRoot,
|
|
118
|
+
addViolation,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const hasErrors = violations.some((v) => v.severity === "error");
|
|
123
|
+
const resultStatus = hasErrors ? "fail" : "pass";
|
|
124
|
+
|
|
125
|
+
if (outputFormat === "pretty") {
|
|
126
|
+
if (violations.length === 0) {
|
|
127
|
+
logger.success(`All ${filesToValidate.length} ADR(s) passed validation.`);
|
|
128
|
+
} else {
|
|
129
|
+
logger.section(`ADR Validation (${filesToValidate.length} file(s))`);
|
|
130
|
+
for (const v of violations) {
|
|
131
|
+
const prefix = v.severity === "error" ? "ERROR" : "WARN";
|
|
132
|
+
logger[v.severity === "error" ? "error" : "warn"](
|
|
133
|
+
`[${prefix}] ${v.adrId} ${v.rule}: ${v.message}`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
const errorCount = violations.filter((v) => v.severity === "error").length;
|
|
137
|
+
const warnCount = violations.filter((v) => v.severity === "warning").length;
|
|
138
|
+
if (errorCount > 0) {
|
|
139
|
+
logger.error(`${errorCount} error(s), ${warnCount} warning(s)`);
|
|
140
|
+
} else {
|
|
141
|
+
logger.warn(`${warnCount} warning(s), 0 errors — passed`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
data: {
|
|
148
|
+
command: "adr.validate",
|
|
149
|
+
status: resultStatus,
|
|
150
|
+
count: filesToValidate.length,
|
|
151
|
+
violations,
|
|
152
|
+
},
|
|
153
|
+
exitCode: hasErrors ? 1 : 0,
|
|
154
|
+
summary: hasErrors
|
|
155
|
+
? `${violations.filter((v) => v.severity === "error").length} error(s) found`
|
|
156
|
+
: `All ${filesToValidate.length} ADR(s) passed validation`,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function validateSingleAdr(
|
|
161
|
+
fileName: string,
|
|
162
|
+
parsed: ParsedAdr,
|
|
163
|
+
allParsed: Map<string, { fileName: string; parsed: ParsedAdr }>,
|
|
164
|
+
knownRfcIds: Set<string>,
|
|
165
|
+
seenIds: Map<string, string>,
|
|
166
|
+
knownKeys: Set<string>,
|
|
167
|
+
workspaceRoot: string,
|
|
168
|
+
addViolation: (
|
|
169
|
+
adrId: string,
|
|
170
|
+
file: string,
|
|
171
|
+
rule: string,
|
|
172
|
+
message: string,
|
|
173
|
+
severity?: "error" | "warning",
|
|
174
|
+
) => void,
|
|
175
|
+
): Promise<void> {
|
|
176
|
+
const fm = parsed.frontmatter;
|
|
177
|
+
const body = parsed.body;
|
|
178
|
+
const relFile = path.join(ADR_DIR, fileName);
|
|
179
|
+
const adrId = String(fm["id"] ?? "UNKNOWN");
|
|
180
|
+
|
|
181
|
+
if (!ADR_ID_PATTERN.test(adrId)) {
|
|
182
|
+
addViolation(adrId, relFile, "AV-01", `id "${adrId}" does not match format ADR-XXXX`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ADR-DIR-01: directory structure convention (RFC-0722)
|
|
186
|
+
// Warn when ADR files are found in subdirectories other than archive/
|
|
187
|
+
const slashIdx = fileName.indexOf("/");
|
|
188
|
+
if (slashIdx > 0) {
|
|
189
|
+
const subDir = fileName.slice(0, slashIdx);
|
|
190
|
+
if (subDir !== "archive") {
|
|
191
|
+
addViolation(
|
|
192
|
+
adrId,
|
|
193
|
+
relFile,
|
|
194
|
+
"ADR-DIR-01",
|
|
195
|
+
`${relFile} is in an unsanctioned subdirectory. Only archive/ is allowed. Move the file to docs/adrs/ root or write an ADR to formalize the subdirectory.`,
|
|
196
|
+
"warning",
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const prevFile = seenIds.get(adrId);
|
|
202
|
+
if (prevFile) {
|
|
203
|
+
addViolation(adrId, relFile, "AV-02", `Duplicate id "${adrId}" — also in ${prevFile}`);
|
|
204
|
+
} else {
|
|
205
|
+
seenIds.set(adrId, relFile);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const status = String(fm["status"] ?? "");
|
|
209
|
+
if (!ADR_STATUSES.includes(status as AdrStatus)) {
|
|
210
|
+
addViolation(
|
|
211
|
+
adrId,
|
|
212
|
+
relFile,
|
|
213
|
+
"AV-03",
|
|
214
|
+
`Invalid status "${status}". Must be one of: ${ADR_STATUSES.join(", ")}`,
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const scope = String(fm["scope"] ?? "");
|
|
219
|
+
if (!ADR_SCOPES.includes(scope as AdrScope)) {
|
|
220
|
+
addViolation(
|
|
221
|
+
adrId,
|
|
222
|
+
relFile,
|
|
223
|
+
"AV-04",
|
|
224
|
+
`Invalid scope "${scope}". Must be one of: ${ADR_SCOPES.join(", ")}`,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const decider = String(fm["decider"] ?? "");
|
|
229
|
+
if (!decider) {
|
|
230
|
+
addViolation(adrId, relFile, "AV-05", "decider must be a non-empty string");
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const createdAt = String(fm["createdAt"] ?? "");
|
|
234
|
+
const updatedAt = String(fm["updatedAt"] ?? "");
|
|
235
|
+
const datePattern = /^\d{4}-\d{2}-\d{2}$/;
|
|
236
|
+
if (!datePattern.test(createdAt)) {
|
|
237
|
+
addViolation(
|
|
238
|
+
adrId,
|
|
239
|
+
relFile,
|
|
240
|
+
"AV-06",
|
|
241
|
+
`createdAt "${createdAt}" is not a valid ISO 8601 date (YYYY-MM-DD)`,
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
if (!datePattern.test(updatedAt)) {
|
|
245
|
+
addViolation(
|
|
246
|
+
adrId,
|
|
247
|
+
relFile,
|
|
248
|
+
"AV-06",
|
|
249
|
+
`updatedAt "${updatedAt}" is not a valid ISO 8601 date (YYYY-MM-DD)`,
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const supersedes = fm["supersedes"];
|
|
254
|
+
if (Array.isArray(supersedes)) {
|
|
255
|
+
for (const ref of supersedes) {
|
|
256
|
+
const refStr = String(ref);
|
|
257
|
+
if (!refStr) continue;
|
|
258
|
+
if (!allParsed.has(refStr)) {
|
|
259
|
+
addViolation(
|
|
260
|
+
adrId,
|
|
261
|
+
relFile,
|
|
262
|
+
"AV-07",
|
|
263
|
+
`supersedes "${refStr}" does not match any existing ADR`,
|
|
264
|
+
);
|
|
265
|
+
} else {
|
|
266
|
+
const otherBy = String(allParsed.get(refStr)!.parsed.frontmatter["supersededBy"] ?? "");
|
|
267
|
+
if (otherBy !== adrId) {
|
|
268
|
+
addViolation(
|
|
269
|
+
adrId,
|
|
270
|
+
relFile,
|
|
271
|
+
"AV-08",
|
|
272
|
+
`${adrId}.supersedes includes ${refStr}, but ${refStr}.supersededBy is "${otherBy || "(empty)"}" (expected ${adrId})`,
|
|
273
|
+
"warning",
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const supersededByRaw = fm["supersededBy"];
|
|
281
|
+
const supersededByList: string[] = Array.isArray(supersededByRaw)
|
|
282
|
+
? supersededByRaw.map((s) => String(s))
|
|
283
|
+
: supersededByRaw
|
|
284
|
+
? [String(supersededByRaw)]
|
|
285
|
+
: [];
|
|
286
|
+
for (const supersededBy of supersededByList) {
|
|
287
|
+
if (/^ADR-\d{4}$/.test(supersededBy) && !allParsed.has(supersededBy)) {
|
|
288
|
+
addViolation(
|
|
289
|
+
adrId,
|
|
290
|
+
relFile,
|
|
291
|
+
"AV-09",
|
|
292
|
+
`supersededBy "${supersededBy}" does not match any existing ADR`,
|
|
293
|
+
);
|
|
294
|
+
} else if (/^RFC-\d{4}$/.test(supersededBy) && !knownRfcIds.has(supersededBy)) {
|
|
295
|
+
addViolation(
|
|
296
|
+
adrId,
|
|
297
|
+
relFile,
|
|
298
|
+
"AV-09",
|
|
299
|
+
`supersededBy "${supersededBy}" does not match any existing RFC`,
|
|
300
|
+
);
|
|
301
|
+
} else if (!/^(ADR|RFC)-\d{4}$/.test(supersededBy)) {
|
|
302
|
+
addViolation(
|
|
303
|
+
adrId,
|
|
304
|
+
relFile,
|
|
305
|
+
"AV-09",
|
|
306
|
+
`supersededBy "${supersededBy}" is not a recognized ADR/RFC id`,
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
const supersededBy = supersededByList[0];
|
|
311
|
+
|
|
312
|
+
if (status === "superseded" && !supersededBy) {
|
|
313
|
+
addViolation(adrId, relFile, "AV-10", `status is "superseded" but supersededBy is empty`);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const related = fm["related"];
|
|
317
|
+
if (Array.isArray(related)) {
|
|
318
|
+
for (const ref of related) {
|
|
319
|
+
const refStr = String(ref).trim();
|
|
320
|
+
if (!refStr) continue;
|
|
321
|
+
if (/^ADR-\d{4}$/.test(refStr)) {
|
|
322
|
+
if (!allParsed.has(refStr)) {
|
|
323
|
+
addViolation(
|
|
324
|
+
adrId,
|
|
325
|
+
relFile,
|
|
326
|
+
"AV-11",
|
|
327
|
+
`related "${refStr}" does not match any existing ADR`,
|
|
328
|
+
"warning",
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
} else if (!/^(RFC|DNA|AP)-\d+$/.test(refStr)) {
|
|
332
|
+
addViolation(
|
|
333
|
+
adrId,
|
|
334
|
+
relFile,
|
|
335
|
+
"AV-11",
|
|
336
|
+
`related "${refStr}" is not a recognized RFC/ADR/DNA/AP reference`,
|
|
337
|
+
"warning",
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const headings = [...body.matchAll(/^##\s+(.+)$/gm)].map((m) => m[1]!.trim());
|
|
344
|
+
for (const section of ADR_REQUIRED_SECTIONS) {
|
|
345
|
+
if (!headings.includes(section)) {
|
|
346
|
+
addViolation(adrId, relFile, "AV-12", `Missing required section "## ${section}"`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const titleStr = String(fm["title"] ?? "");
|
|
351
|
+
const headingMatch = body.match(/^#\s+(?:ADR-\d{4}:\s*)?(.+)$/m);
|
|
352
|
+
if (headingMatch && titleStr) {
|
|
353
|
+
const headingTitle = headingMatch[1]!.trim();
|
|
354
|
+
if (headingTitle !== titleStr) {
|
|
355
|
+
addViolation(
|
|
356
|
+
adrId,
|
|
357
|
+
relFile,
|
|
358
|
+
"AV-13",
|
|
359
|
+
`Frontmatter title "${titleStr}" does not match body heading "${headingTitle}"`,
|
|
360
|
+
"warning",
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const lowerAdrId = adrId.toLowerCase();
|
|
366
|
+
const fileBasename = fileName.split("/").pop()!.split("\\").pop()!;
|
|
367
|
+
if (!fileBasename.startsWith(lowerAdrId)) {
|
|
368
|
+
addViolation(
|
|
369
|
+
adrId,
|
|
370
|
+
relFile,
|
|
371
|
+
"AV-14",
|
|
372
|
+
`Filename "${fileBasename}" must start with lowercase "${lowerAdrId}"`,
|
|
373
|
+
);
|
|
374
|
+
} else if (!/^[a-z0-9-]+\.md$/.test(fileBasename)) {
|
|
375
|
+
addViolation(
|
|
376
|
+
adrId,
|
|
377
|
+
relFile,
|
|
378
|
+
"AV-14",
|
|
379
|
+
`Filename "${fileName}" is not lowercase kebab-case (only a-z, 0-9, hyphens allowed)`,
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
for (const key of Object.keys(fm)) {
|
|
384
|
+
if (!knownKeys.has(key)) {
|
|
385
|
+
addViolation(
|
|
386
|
+
adrId,
|
|
387
|
+
relFile,
|
|
388
|
+
"AV-15",
|
|
389
|
+
`unknown frontmatter key "${key}" (not in the ADR schema)`,
|
|
390
|
+
"warning",
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// AV-16: implementation commit drift detection (RFC-0625)
|
|
396
|
+
// Warns when implement: ADR-XXXX commits exist in git history since createdAt
|
|
397
|
+
// but the ADR status is not yet "implemented".
|
|
398
|
+
if (status !== "implemented") {
|
|
399
|
+
const datePattern = /^\d{4}-\d{2}-\d{2}$/;
|
|
400
|
+
if (datePattern.test(createdAt)) {
|
|
401
|
+
const log = await execGitLog(workspaceRoot, ["log", `--since=${createdAt}`, "--oneline"]);
|
|
402
|
+
if (log) {
|
|
403
|
+
const pattern = new RegExp(`implement:\\s+${adrId}\\b`, "i");
|
|
404
|
+
const matchingLines = log.split("\n").filter((line) => pattern.test(line.trim()));
|
|
405
|
+
if (matchingLines.length > 0) {
|
|
406
|
+
addViolation(
|
|
407
|
+
adrId,
|
|
408
|
+
relFile,
|
|
409
|
+
"AV-16",
|
|
410
|
+
`${adrId} has ${matchingLines.length} implement: commit(s) in git history since ${createdAt} but status is still "${status}". Run: site-kernel run adr.implement.stamp --id ${adrId} --implementation-commit <sha> to transition to implemented.`,
|
|
411
|
+
"warning",
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function execGitLog(workspaceRoot: string, args: string[]): Promise<string> {
|
|
420
|
+
return new Promise((resolve) => {
|
|
421
|
+
execFile("git", args, { cwd: workspaceRoot, timeout: 10000 }, (err, stdout) => {
|
|
422
|
+
if (err) {
|
|
423
|
+
resolve("");
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
resolve(stdout.trim());
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
}
|
package/os/adr/index.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Barrel export for the ADR domain — types, constants, module, and handlers.</purpose>
|
|
4
|
+
<non-goals>
|
|
5
|
+
<item>Do not implement ADR logic here; delegate to handlers/ and adr.module.ts.</item>
|
|
6
|
+
</non-goals>
|
|
7
|
+
</MODULE_CONTRACT>
|
|
8
|
+
<CHANGE_SUMMARY>
|
|
9
|
+
<item>RFC-0366: expose adrModule and ADR types from the ADR domain.</item>
|
|
10
|
+
<item>RFC-0521: migrated from packages/os/site-kernel/src/adr/ to packages/forge/os/adr/.</item>
|
|
11
|
+
</CHANGE_SUMMARY>
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export { forgeAdrModule } from "./adr.module.ts";
|
|
15
|
+
export { runAdrList, runAdrCreate } from "./handlers/list-create.ts";
|
|
16
|
+
export { runAdrValidate } from "./handlers/validate.ts";
|
|
17
|
+
export { runAdrArchive } from "./handlers/archive.ts";
|
|
18
|
+
export type {
|
|
19
|
+
AdrArchiveResult,
|
|
20
|
+
ArchiveMove as AdrArchiveMove,
|
|
21
|
+
ArchiveSkip as AdrArchiveSkip,
|
|
22
|
+
} from "./handlers/archive.ts";
|
|
23
|
+
export type {
|
|
24
|
+
AdrStatus,
|
|
25
|
+
AdrScope,
|
|
26
|
+
AdrFrontmatter,
|
|
27
|
+
AdrListEntry,
|
|
28
|
+
AdrListResult,
|
|
29
|
+
AdrCreateResult,
|
|
30
|
+
AdrValidationViolation,
|
|
31
|
+
AdrValidationResult,
|
|
32
|
+
} from "./types.ts";
|
|
33
|
+
export { ADR_STATUSES, ADR_SCOPES, ADR_DIR, ADR_TEMPLATE_FILE, ADR_ID_PATTERN } from "./types.ts";
|
package/os/adr/types.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>
|
|
4
|
+
Defines types and constants for managing Architectural Decision Records (ADRs):
|
|
5
|
+
statuses, scopes, frontmatter shape, validation results, and list output.
|
|
6
|
+
</purpose>
|
|
7
|
+
<non-goals>
|
|
8
|
+
<item>Do not implement ADR processing or validation logic here.</item>
|
|
9
|
+
<item>Do not handle user input or command execution related to ADRs.</item>
|
|
10
|
+
<item>Do not manage the storage or retrieval of ADR documents.</item>
|
|
11
|
+
</non-goals>
|
|
12
|
+
</MODULE_CONTRACT>
|
|
13
|
+
<CHANGE_SUMMARY>
|
|
14
|
+
<item>RFC-0366: introduce ADR types and constants mirroring the RFC domain contract.</item>
|
|
15
|
+
<item>RFC-0367: extend AdrStatus with reviewing and implemented; add implementedAt, closedAt, reviewers fields.</item>
|
|
16
|
+
<item>Post-refactor hardening: document that an ADR may be superseded by a broader RFC.</item>
|
|
17
|
+
<item>RFC-0521: migrated from packages/os/site-kernel/src/adr/ to packages/forge/os/adr/.</item>
|
|
18
|
+
<item>RFC-0727: add AdrImplementStamp types for atomic ADR status transition.</item>
|
|
19
|
+
</CHANGE_SUMMARY>
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export type AdrStatus =
|
|
23
|
+
"proposed" | "reviewing" | "accepted" | "implemented" | "superseded" | "rejected";
|
|
24
|
+
|
|
25
|
+
export const ADR_STATUSES: readonly AdrStatus[] = [
|
|
26
|
+
"proposed",
|
|
27
|
+
"reviewing",
|
|
28
|
+
"accepted",
|
|
29
|
+
"implemented",
|
|
30
|
+
"superseded",
|
|
31
|
+
"rejected",
|
|
32
|
+
] as const;
|
|
33
|
+
|
|
34
|
+
export type AdrScope = "package" | "app" | "workspace";
|
|
35
|
+
|
|
36
|
+
export const ADR_SCOPES: readonly AdrScope[] = ["package", "app", "workspace"] as const;
|
|
37
|
+
|
|
38
|
+
export interface AdrFrontmatter {
|
|
39
|
+
id: string;
|
|
40
|
+
title: string;
|
|
41
|
+
status: AdrStatus;
|
|
42
|
+
scope: AdrScope;
|
|
43
|
+
decider: string;
|
|
44
|
+
createdAt: string;
|
|
45
|
+
updatedAt: string;
|
|
46
|
+
supersedes?: string[];
|
|
47
|
+
supersededBy?: string;
|
|
48
|
+
related?: string[];
|
|
49
|
+
implementedAt?: string;
|
|
50
|
+
closedAt?: string;
|
|
51
|
+
reviewers?: string[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface AdrListEntry {
|
|
55
|
+
id: string;
|
|
56
|
+
title: string;
|
|
57
|
+
status: AdrStatus;
|
|
58
|
+
scope: AdrScope;
|
|
59
|
+
decider: string;
|
|
60
|
+
updatedAt: string;
|
|
61
|
+
file: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface AdrListResult {
|
|
65
|
+
command: "adr.list";
|
|
66
|
+
status: "ok";
|
|
67
|
+
count: number;
|
|
68
|
+
entries: AdrListEntry[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface AdrValidationViolation {
|
|
72
|
+
adrId: string;
|
|
73
|
+
file: string;
|
|
74
|
+
rule: string;
|
|
75
|
+
message: string;
|
|
76
|
+
severity: "error" | "warning";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface AdrValidationResult {
|
|
80
|
+
command: "adr.validate";
|
|
81
|
+
status: "pass" | "fail";
|
|
82
|
+
count: number;
|
|
83
|
+
violations: AdrValidationViolation[];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface AdrCreateResult {
|
|
87
|
+
command: "adr.create";
|
|
88
|
+
status: "ok";
|
|
89
|
+
file: string;
|
|
90
|
+
id: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const ADR_DIR = "docs/adrs";
|
|
94
|
+
export const ADR_TEMPLATE_FILE = "docs/adrs/adr-0000-template.md";
|
|
95
|
+
export const ADR_ID_PATTERN = /^ADR-\d{4}$/;
|
|
96
|
+
|
|
97
|
+
export const ADR_REQUIRED_SECTIONS = [
|
|
98
|
+
"Context",
|
|
99
|
+
"Decision",
|
|
100
|
+
"Justification",
|
|
101
|
+
"Consequences",
|
|
102
|
+
"Evolution",
|
|
103
|
+
] as const;
|
|
104
|
+
|
|
105
|
+
export const ADR_KNOWN_KEYS: readonly string[] = [
|
|
106
|
+
"id",
|
|
107
|
+
"title",
|
|
108
|
+
"status",
|
|
109
|
+
"scope",
|
|
110
|
+
"decider",
|
|
111
|
+
"createdAt",
|
|
112
|
+
"updatedAt",
|
|
113
|
+
"supersedes",
|
|
114
|
+
"supersededBy",
|
|
115
|
+
"related",
|
|
116
|
+
"implementedAt",
|
|
117
|
+
"closedAt",
|
|
118
|
+
"reviewers",
|
|
119
|
+
] as const;
|
|
120
|
+
|
|
121
|
+
// ─── RFC-0727: adr.implement.stamp types ─────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
export type AdrImplementStampRule = "ADR-IMP-01" | "ADR-IMP-03" | "ADR-IMP-04" | "ADR-IMP-05";
|
|
124
|
+
|
|
125
|
+
export interface AdrImplementStampData {
|
|
126
|
+
adrId: string;
|
|
127
|
+
implementationCommit: string;
|
|
128
|
+
stampedAt: string;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface AdrImplementStampViolation {
|
|
132
|
+
rule: AdrImplementStampRule;
|
|
133
|
+
message: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface AdrImplementStampResult {
|
|
137
|
+
command: "adr.implement.stamp";
|
|
138
|
+
status: "pass" | "fail";
|
|
139
|
+
data?: AdrImplementStampData;
|
|
140
|
+
violations: AdrImplementStampViolation[];
|
|
141
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Register the audit archive command with the forge kernel registry.</purpose>
|
|
4
|
+
<non-goals>
|
|
5
|
+
<item>Do not implement handler logic here — delegate to handlers/archive.ts.</item>
|
|
6
|
+
</non-goals>
|
|
7
|
+
</MODULE_CONTRACT>
|
|
8
|
+
<CHANGE_SUMMARY>
|
|
9
|
+
<item>RFC-0521: initial forgeAuditModule registering audit.archive command.</item>
|
|
10
|
+
</CHANGE_SUMMARY>
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { ForgeModule } from "../../src/forge-module.ts";
|
|
14
|
+
|
|
15
|
+
export const forgeAuditModule: ForgeModule = {
|
|
16
|
+
name: "forge-audit",
|
|
17
|
+
version: "0.1.0",
|
|
18
|
+
async register(registry) {
|
|
19
|
+
const { runAuditArchive } = await import("./handlers/archive.ts");
|
|
20
|
+
|
|
21
|
+
registry.registerCommand({
|
|
22
|
+
name: "audit.archive",
|
|
23
|
+
description:
|
|
24
|
+
"Move audit files whose parent RFC has terminal status " +
|
|
25
|
+
"(implemented, rejected, superseded) into docs/audits/archive/<status>/ " +
|
|
26
|
+
"subdirectories. Bidirectional: moves non-terminal files found in " +
|
|
27
|
+
"subdirectories back to root. Use --dry-run to preview. " +
|
|
28
|
+
"Use --status to filter to a single terminal status. " +
|
|
29
|
+
"Prefer the docs.archive umbrella command unless you need to archive only audits.",
|
|
30
|
+
scope: "workspace",
|
|
31
|
+
mutatesState: true,
|
|
32
|
+
writes: ["docs/audits/*.md", "docs/audits/archive/**"],
|
|
33
|
+
reads: ["docs/audits/**/*.md", "docs/rfcs/**/*.md"],
|
|
34
|
+
cacheable: false,
|
|
35
|
+
flags: {
|
|
36
|
+
"dry-run": {
|
|
37
|
+
kind: "boolean",
|
|
38
|
+
description: "Preview what would be moved without touching the filesystem.",
|
|
39
|
+
},
|
|
40
|
+
status: {
|
|
41
|
+
kind: "string",
|
|
42
|
+
description: "Filter to a single terminal status (implemented, rejected, superseded).",
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
execute: runAuditArchive,
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
};
|