@workflow-cannon/workspace-kit 0.17.0 → 0.24.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/README.md +23 -9
- package/dist/cli/doctor-planning-issues.js +3 -22
- package/dist/cli/run-command.js +22 -38
- package/dist/cli.js +95 -4
- package/dist/contracts/command-manifest.d.ts +17 -0
- package/dist/contracts/command-manifest.js +1 -0
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/module-contract.d.ts +12 -11
- package/dist/core/agent-instruction-surface.d.ts +33 -0
- package/dist/core/agent-instruction-surface.js +46 -0
- package/dist/core/config-cli.js +13 -17
- package/dist/core/config-metadata.js +101 -2
- package/dist/core/index.d.ts +4 -1
- package/dist/core/index.js +3 -0
- package/dist/core/module-command-router.js +19 -1
- package/dist/core/module-registry-resolve.d.ts +27 -0
- package/dist/core/module-registry-resolve.js +91 -0
- package/dist/core/module-registry.d.ts +14 -0
- package/dist/core/module-registry.js +57 -0
- package/dist/core/planning/build-plan-session-file.d.ts +29 -0
- package/dist/core/planning/build-plan-session-file.js +58 -0
- package/dist/core/planning/index.d.ts +17 -0
- package/dist/core/planning/index.js +15 -0
- package/dist/core/policy.js +18 -8
- package/dist/core/state/unified-state-db.d.ts +21 -0
- package/dist/core/state/unified-state-db.js +80 -0
- package/dist/core/workspace-kit-config.js +13 -1
- package/dist/modules/agent-behavior/builtins.d.ts +3 -0
- package/dist/modules/agent-behavior/builtins.js +71 -0
- package/dist/modules/agent-behavior/explain.d.ts +6 -0
- package/dist/modules/agent-behavior/explain.js +46 -0
- package/dist/modules/agent-behavior/index.d.ts +4 -0
- package/dist/modules/agent-behavior/index.js +461 -0
- package/dist/modules/agent-behavior/interview-session-file.d.ts +9 -0
- package/dist/modules/agent-behavior/interview-session-file.js +43 -0
- package/dist/modules/agent-behavior/interview.d.ts +13 -0
- package/dist/modules/agent-behavior/interview.js +88 -0
- package/dist/modules/agent-behavior/persistence.d.ts +6 -0
- package/dist/modules/agent-behavior/persistence.js +89 -0
- package/dist/modules/agent-behavior/store.d.ts +34 -0
- package/dist/modules/agent-behavior/store.js +119 -0
- package/dist/modules/agent-behavior/types.d.ts +28 -0
- package/dist/modules/agent-behavior/types.js +1 -0
- package/dist/modules/agent-behavior/validate.d.ts +11 -0
- package/dist/modules/agent-behavior/validate.js +123 -0
- package/dist/modules/approvals/index.js +54 -51
- package/dist/modules/approvals/policy-sensitive-commands.d.ts +4 -0
- package/dist/modules/approvals/policy-sensitive-commands.js +4 -0
- package/dist/modules/approvals/review-runtime.js +1 -2
- package/dist/modules/documentation/index.js +47 -45
- package/dist/modules/documentation/normalizer.d.ts +3 -0
- package/dist/modules/documentation/normalizer.js +171 -0
- package/dist/modules/documentation/parser.d.ts +7 -0
- package/dist/modules/documentation/parser.js +39 -0
- package/dist/modules/documentation/policy-sensitive-commands.d.ts +5 -0
- package/dist/modules/documentation/policy-sensitive-commands.js +8 -0
- package/dist/modules/documentation/renderer.d.ts +23 -0
- package/dist/modules/documentation/renderer.js +105 -0
- package/dist/modules/documentation/runtime-batch.d.ts +10 -0
- package/dist/modules/documentation/runtime-batch.js +67 -0
- package/dist/modules/documentation/runtime-config.d.ts +11 -0
- package/dist/modules/documentation/runtime-config.js +54 -0
- package/dist/modules/documentation/runtime-render-support.d.ts +8 -0
- package/dist/modules/documentation/runtime-render-support.js +36 -0
- package/dist/modules/documentation/runtime.js +22 -510
- package/dist/modules/documentation/types.d.ts +182 -0
- package/dist/modules/documentation/validator.d.ts +8 -0
- package/dist/modules/documentation/validator.js +234 -0
- package/dist/modules/documentation/view-models.d.ts +3 -0
- package/dist/modules/documentation/view-models.js +124 -0
- package/dist/modules/improvement/generate-recommendations-runtime.js +3 -3
- package/dist/modules/improvement/improvement-state.d.ts +2 -2
- package/dist/modules/improvement/improvement-state.js +52 -23
- package/dist/modules/improvement/index.js +140 -138
- package/dist/modules/improvement/ingest.d.ts +1 -1
- package/dist/modules/improvement/policy-sensitive-commands.d.ts +4 -0
- package/dist/modules/improvement/policy-sensitive-commands.js +7 -0
- package/dist/modules/index.d.ts +6 -0
- package/dist/modules/index.js +17 -0
- package/dist/modules/planning/artifact.d.ts +19 -0
- package/dist/modules/planning/artifact.js +72 -0
- package/dist/modules/planning/index.js +605 -6
- package/dist/modules/planning/question-engine.d.ts +25 -0
- package/dist/modules/planning/question-engine.js +284 -0
- package/dist/modules/planning/types.d.ts +9 -0
- package/dist/modules/planning/types.js +39 -0
- package/dist/modules/task-engine/doctor-planning-persistence.js +21 -13
- package/dist/modules/task-engine/index.d.ts +1 -2
- package/dist/modules/task-engine/index.js +1 -1143
- package/dist/modules/task-engine/migrate-task-persistence-runtime.js +31 -4
- package/dist/modules/task-engine/migrate-wishlist-intake-runtime.d.ts +2 -0
- package/dist/modules/task-engine/migrate-wishlist-intake-runtime.js +146 -0
- package/dist/modules/task-engine/planning-open.d.ts +2 -9
- package/dist/modules/task-engine/planning-open.js +4 -15
- package/dist/modules/task-engine/policy-sensitive-commands.d.ts +5 -0
- package/dist/modules/task-engine/policy-sensitive-commands.js +5 -0
- package/dist/modules/task-engine/sqlite-dual-planning.d.ts +11 -2
- package/dist/modules/task-engine/sqlite-dual-planning.js +134 -28
- package/dist/modules/task-engine/strict-task-validation.js +3 -0
- package/dist/modules/task-engine/suggestions.js +2 -1
- package/dist/modules/task-engine/task-engine-internal.d.ts +2 -0
- package/dist/modules/task-engine/task-engine-internal.js +1304 -0
- package/dist/modules/task-engine/task-type-validation.js +40 -0
- package/dist/modules/task-engine/wishlist-intake.d.ts +22 -0
- package/dist/modules/task-engine/wishlist-intake.js +180 -0
- package/dist/modules/task-engine/wishlist-validation.d.ts +4 -0
- package/dist/modules/task-engine/wishlist-validation.js +19 -0
- package/dist/modules/workspace-config/index.js +9 -11
- package/package.json +2 -2
- package/schemas/agent-behavior-profile.schema.json +52 -0
- package/schemas/task-engine-run-contracts.schema.json +80 -5
- package/src/modules/documentation/README.md +16 -25
- package/src/modules/documentation/RULES.md +9 -9
- package/src/modules/documentation/index.ts +54 -49
- package/src/modules/documentation/instructions/document-project.md +6 -6
- package/src/modules/documentation/instructions/generate-document.md +4 -4
- package/src/modules/documentation/normalizer.ts +187 -0
- package/src/modules/documentation/parser.ts +41 -0
- package/src/modules/documentation/policy-sensitive-commands.ts +8 -0
- package/src/modules/documentation/renderer.ts +121 -0
- package/src/modules/documentation/runtime-batch.ts +74 -0
- package/src/modules/documentation/runtime-config.ts +68 -0
- package/src/modules/documentation/runtime-render-support.ts +39 -0
- package/src/modules/documentation/runtime.ts +28 -600
- package/src/modules/documentation/schemas/documentation-schema.md +37 -54
- package/src/modules/documentation/types.ts +228 -0
- package/src/modules/documentation/validator.ts +247 -0
- package/src/modules/documentation/view-models.ts +132 -0
- package/src/modules/documentation/views/agents.view.yaml +18 -0
- package/src/modules/documentation/views/architecture.view.yaml +18 -0
- package/src/modules/documentation/views/principles.view.yaml +18 -0
- package/src/modules/documentation/views/readme.view.yaml +18 -0
- package/src/modules/documentation/views/releasing.view.yaml +18 -0
- package/src/modules/documentation/views/roadmap.view.yaml +18 -0
- package/src/modules/documentation/views/runbooks-consumer-cadence.view.yaml +18 -0
- package/src/modules/documentation/views/runbooks-parity-validation-flow.view.yaml +18 -0
- package/src/modules/documentation/views/runbooks-release-channels.view.yaml +18 -0
- package/src/modules/documentation/views/security.view.yaml +18 -0
- package/src/modules/documentation/views/support.view.yaml +18 -0
- package/src/modules/documentation/views/terms.view.yaml +18 -0
- package/src/modules/documentation/views/workbooks-phase2-config-policy-workbook.view.yaml +18 -0
- package/src/modules/documentation/views/workbooks-task-engine-workbook.view.yaml +18 -0
- package/src/modules/documentation/views/workbooks-transcript-automation-baseline.view.yaml +18 -0
- package/src/modules/documentation/state.md +0 -8
|
@@ -1,516 +1,19 @@
|
|
|
1
1
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
|
-
import { dirname, resolve
|
|
4
|
-
import {
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
import type {
|
|
7
|
-
DocumentationBatchResult,
|
|
8
|
-
DocumentationConflict,
|
|
9
|
-
DocumentationGenerateOptions,
|
|
10
|
-
DocumentationGenerateResult,
|
|
11
|
-
DocumentationValidationIssue
|
|
12
|
-
} from "./types.js";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
import type { DocumentationBatchResult, DocumentationConflict, DocumentationGenerateOptions, DocumentationGenerateResult, DocumentationValidationIssue } from "./types.js";
|
|
13
5
|
import type { ModuleLifecycleContext } from "../../contracts/module-contract.js";
|
|
6
|
+
import { parseAiDocument } from "./parser.js";
|
|
7
|
+
import { normalizeDocument } from "./normalizer.js";
|
|
8
|
+
import { renderDocument } from "./renderer.js";
|
|
9
|
+
import { autoResolveAiSchema, validateAiSchema } from "./validator.js";
|
|
10
|
+
import { isPathWithinRoot, loadRuntimeConfig } from "./runtime-config.js";
|
|
11
|
+
import { detectConflicts, renderTemplate, resolveExpectedDocFamily, validateSectionCoverage } from "./runtime-render-support.js";
|
|
12
|
+
import { runGenerateAllDocuments } from "./runtime-batch.js";
|
|
14
13
|
|
|
15
|
-
type
|
|
16
|
-
aiRoot: string;
|
|
17
|
-
humanRoot: string;
|
|
18
|
-
templatesRoot: string;
|
|
19
|
-
instructionsRoot: string;
|
|
20
|
-
schemasRoot: string;
|
|
21
|
-
maxValidationAttempts: number;
|
|
22
|
-
sourceRoot: string;
|
|
23
|
-
};
|
|
14
|
+
type GenerateDocumentArgs = { documentType?: string; options?: DocumentationGenerateOptions };
|
|
24
15
|
|
|
25
|
-
function
|
|
26
|
-
return path === root || path.startsWith(`${root}${sep}`);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function parseDefaultValue(fileContent: string, key: string, fallback: string): string {
|
|
30
|
-
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
31
|
-
const regex = new RegExp(`\\\`${escaped}\\\`[^\\n]*default:\\s*\\\`([^\\\`]+)\\\``);
|
|
32
|
-
const match = fileContent.match(regex);
|
|
33
|
-
return match?.[1] ?? fallback;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async function loadRuntimeConfig(workspacePath: string): Promise<DocumentationRuntimeConfig> {
|
|
37
|
-
const runtimeSourceRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
|
|
38
|
-
const sourceRoots = [workspacePath, runtimeSourceRoot];
|
|
39
|
-
let sourceRoot = workspacePath;
|
|
40
|
-
let configContent: string | undefined;
|
|
41
|
-
for (const candidateRoot of sourceRoots) {
|
|
42
|
-
const candidate = resolve(candidateRoot, "src/modules/documentation/config.md");
|
|
43
|
-
if (!existsSync(candidate)) {
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
configContent = await readFile(candidate, "utf8");
|
|
47
|
-
sourceRoot = candidateRoot;
|
|
48
|
-
break;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (!configContent) {
|
|
52
|
-
return {
|
|
53
|
-
aiRoot: "/.ai",
|
|
54
|
-
humanRoot: "docs/maintainers",
|
|
55
|
-
templatesRoot: "src/modules/documentation/templates",
|
|
56
|
-
instructionsRoot: "src/modules/documentation/instructions",
|
|
57
|
-
schemasRoot: "src/modules/documentation/schemas",
|
|
58
|
-
maxValidationAttempts: 3,
|
|
59
|
-
sourceRoot
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const aiRoot = parseDefaultValue(configContent, "sources.aiRoot", "/.ai");
|
|
64
|
-
const humanRoot = parseDefaultValue(configContent, "sources.humanRoot", "docs/maintainers");
|
|
65
|
-
const templatesRoot = parseDefaultValue(
|
|
66
|
-
configContent,
|
|
67
|
-
"sources.templatesRoot",
|
|
68
|
-
"src/modules/documentation/templates"
|
|
69
|
-
);
|
|
70
|
-
const instructionsRoot = parseDefaultValue(
|
|
71
|
-
configContent,
|
|
72
|
-
"sources.instructionsRoot",
|
|
73
|
-
"src/modules/documentation/instructions"
|
|
74
|
-
);
|
|
75
|
-
const schemasRoot = parseDefaultValue(
|
|
76
|
-
configContent,
|
|
77
|
-
"sources.schemasRoot",
|
|
78
|
-
"src/modules/documentation/schemas"
|
|
79
|
-
);
|
|
80
|
-
const maxValidationAttemptsRaw = parseDefaultValue(configContent, "generation.maxValidationAttempts", "3");
|
|
81
|
-
const maxValidationAttempts = Number.parseInt(maxValidationAttemptsRaw, 10);
|
|
82
|
-
|
|
83
|
-
return {
|
|
84
|
-
aiRoot,
|
|
85
|
-
humanRoot,
|
|
86
|
-
templatesRoot,
|
|
87
|
-
instructionsRoot,
|
|
88
|
-
schemasRoot,
|
|
89
|
-
maxValidationAttempts: Number.isFinite(maxValidationAttempts) ? maxValidationAttempts : 3,
|
|
90
|
-
sourceRoot
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
type AiValidationContext = {
|
|
95
|
-
strict: boolean;
|
|
96
|
-
workspacePath: string;
|
|
97
|
-
expectedDoc?: "rules" | "runbook" | "workbook";
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
type AiRecord = {
|
|
101
|
-
type: string;
|
|
102
|
-
positional: string[];
|
|
103
|
-
kv: Record<string, string>;
|
|
104
|
-
raw: string;
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
function parseAiRecordLine(line: string): AiRecord | null {
|
|
108
|
-
const trimmed = line.trim();
|
|
109
|
-
if (!trimmed || trimmed.startsWith("#")) return null;
|
|
110
|
-
const parts = trimmed.split("|");
|
|
111
|
-
// Record format is `type|token|token...`. Ignore non-record markdown lines.
|
|
112
|
-
if (parts.length < 2) return null;
|
|
113
|
-
const type = parts[0] ?? "";
|
|
114
|
-
if (!type) return null;
|
|
115
|
-
const positional: string[] = [];
|
|
116
|
-
const kv: Record<string, string> = {};
|
|
117
|
-
for (const token of parts.slice(1)) {
|
|
118
|
-
if (!token) continue;
|
|
119
|
-
const idx = token.indexOf("=");
|
|
120
|
-
if (idx >= 0) {
|
|
121
|
-
const k = token.slice(0, idx).trim();
|
|
122
|
-
const v = token.slice(idx + 1).trim();
|
|
123
|
-
if (!k) continue;
|
|
124
|
-
kv[k] = v;
|
|
125
|
-
} else {
|
|
126
|
-
positional.push(token);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return { type, positional, kv, raw: line };
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function isAllowedMetaDoc(doc: string): boolean {
|
|
133
|
-
return (
|
|
134
|
-
doc === "rules" ||
|
|
135
|
-
doc === "runbook" ||
|
|
136
|
-
doc === "workbook" ||
|
|
137
|
-
doc === "generator" ||
|
|
138
|
-
doc === "map" ||
|
|
139
|
-
doc === "workflows" ||
|
|
140
|
-
doc === "commands" ||
|
|
141
|
-
doc === "decisions" ||
|
|
142
|
-
doc === "glossary" ||
|
|
143
|
-
doc === "observed" ||
|
|
144
|
-
doc === "planned" ||
|
|
145
|
-
doc === "checks" ||
|
|
146
|
-
doc === "manifest"
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function validateAiSchema(aiOutput: string, ctx: AiValidationContext): DocumentationValidationIssue[] {
|
|
151
|
-
const issues: DocumentationValidationIssue[] = [];
|
|
152
|
-
const lines = aiOutput.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
153
|
-
if (lines.length === 0) {
|
|
154
|
-
return [
|
|
155
|
-
{
|
|
156
|
-
check: "schema",
|
|
157
|
-
message: "AI output is empty",
|
|
158
|
-
resolved: false,
|
|
159
|
-
}
|
|
160
|
-
];
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const metaLine = lines[0];
|
|
164
|
-
const meta = parseAiRecordLine(metaLine);
|
|
165
|
-
if (!meta || meta.type !== "meta") {
|
|
166
|
-
return [
|
|
167
|
-
{
|
|
168
|
-
check: "schema",
|
|
169
|
-
message: "AI output must start with a meta record",
|
|
170
|
-
resolved: false,
|
|
171
|
-
}
|
|
172
|
-
];
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const v = meta.kv["v"];
|
|
176
|
-
const doc = meta.kv["doc"];
|
|
177
|
-
const truth = meta.kv["truth"];
|
|
178
|
-
const st = meta.kv["st"];
|
|
179
|
-
|
|
180
|
-
if (v !== "1") {
|
|
181
|
-
issues.push({
|
|
182
|
-
check: "schema",
|
|
183
|
-
message: "AI meta schemaVersion must be v=1",
|
|
184
|
-
resolved: false,
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if (!doc || !isAllowedMetaDoc(doc)) {
|
|
189
|
-
issues.push({
|
|
190
|
-
check: "schema",
|
|
191
|
-
message: `Unsupported meta.doc '${doc ?? ""}'`,
|
|
192
|
-
resolved: false,
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (!truth || truth.length === 0) {
|
|
197
|
-
issues.push({
|
|
198
|
-
check: "schema",
|
|
199
|
-
message: "AI meta.truth is required",
|
|
200
|
-
resolved: false,
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (!st || st.length === 0) {
|
|
205
|
-
issues.push({
|
|
206
|
-
check: "schema",
|
|
207
|
-
message: "AI meta.st is required",
|
|
208
|
-
resolved: false,
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (ctx.expectedDoc && doc && ctx.expectedDoc !== doc) {
|
|
213
|
-
issues.push({
|
|
214
|
-
check: "schema",
|
|
215
|
-
message: `meta.doc '${doc}' does not match expected doc family for '${ctx.expectedDoc}'`,
|
|
216
|
-
resolved: !ctx.strict,
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const requireActiveRecords = st === "active";
|
|
221
|
-
|
|
222
|
-
const allowedTypes = new Set([
|
|
223
|
-
// Global ai record families used across .ai/*.
|
|
224
|
-
"project",
|
|
225
|
-
"stack",
|
|
226
|
-
"prio",
|
|
227
|
-
"ref",
|
|
228
|
-
"rule",
|
|
229
|
-
"check",
|
|
230
|
-
"path",
|
|
231
|
-
"role",
|
|
232
|
-
"has",
|
|
233
|
-
"xhas",
|
|
234
|
-
"deps",
|
|
235
|
-
"xdeps",
|
|
236
|
-
"module",
|
|
237
|
-
"wf",
|
|
238
|
-
"cmd",
|
|
239
|
-
"decision",
|
|
240
|
-
"term",
|
|
241
|
-
"observed",
|
|
242
|
-
"planned",
|
|
243
|
-
"map",
|
|
244
|
-
// Runbooks
|
|
245
|
-
"runbook",
|
|
246
|
-
"intent",
|
|
247
|
-
"chain",
|
|
248
|
-
"artifact",
|
|
249
|
-
"state",
|
|
250
|
-
"transition",
|
|
251
|
-
"promotion",
|
|
252
|
-
"rollback",
|
|
253
|
-
// Workbooks
|
|
254
|
-
"workbook",
|
|
255
|
-
"scope",
|
|
256
|
-
"command",
|
|
257
|
-
"config",
|
|
258
|
-
"cadence",
|
|
259
|
-
"guardrail",
|
|
260
|
-
]);
|
|
261
|
-
|
|
262
|
-
const presentByType: Record<string, boolean> = {};
|
|
263
|
-
const missingRequired: string[] = [];
|
|
264
|
-
|
|
265
|
-
for (const line of lines.slice(1)) {
|
|
266
|
-
const rec = parseAiRecordLine(line);
|
|
267
|
-
if (!rec) continue;
|
|
268
|
-
presentByType[rec.type] = true;
|
|
269
|
-
|
|
270
|
-
if (!allowedTypes.has(rec.type)) {
|
|
271
|
-
issues.push({
|
|
272
|
-
check: "schema",
|
|
273
|
-
message: `Unknown AI record type '${rec.type}'`,
|
|
274
|
-
resolved: !ctx.strict,
|
|
275
|
-
});
|
|
276
|
-
continue;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Minimal record-level validation for current runbook/workbook families.
|
|
280
|
-
if (rec.type === "ref") {
|
|
281
|
-
const p = rec.kv["path"];
|
|
282
|
-
const n = rec.kv["name"];
|
|
283
|
-
if (!p || !n) {
|
|
284
|
-
issues.push({
|
|
285
|
-
check: "schema",
|
|
286
|
-
message: "ref records require both 'name' and 'path'",
|
|
287
|
-
resolved: !ctx.strict,
|
|
288
|
-
});
|
|
289
|
-
} else {
|
|
290
|
-
const abs = resolve(ctx.workspacePath, p);
|
|
291
|
-
const ok = existsSync(abs);
|
|
292
|
-
if (!ok) {
|
|
293
|
-
issues.push({
|
|
294
|
-
check: "schema",
|
|
295
|
-
message: `ref.path does not exist: '${p}'`,
|
|
296
|
-
resolved: !ctx.strict,
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
continue;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if (rec.type === "rule") {
|
|
304
|
-
const rid = rec.positional[0];
|
|
305
|
-
const lvl = rec.positional[1] ?? rec.kv["lvl"];
|
|
306
|
-
const directive = (() => {
|
|
307
|
-
// rule lines can be either:
|
|
308
|
-
// rule|RID|lvl|scope|directive|...
|
|
309
|
-
// or the scope can be omitted:
|
|
310
|
-
// rule|RID|lvl|directive|...
|
|
311
|
-
const nonKey = rec.positional.slice(2);
|
|
312
|
-
return nonKey[nonKey.length - 1];
|
|
313
|
-
})();
|
|
314
|
-
|
|
315
|
-
if (!rid || !/^R\d{3,}$/.test(rid)) {
|
|
316
|
-
issues.push({
|
|
317
|
-
check: "schema",
|
|
318
|
-
message: "rule records require RID formatted like R### or R####",
|
|
319
|
-
resolved: !ctx.strict,
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
if (!lvl || !["must", "must_not", "should", "may"].includes(lvl)) {
|
|
323
|
-
issues.push({
|
|
324
|
-
check: "schema",
|
|
325
|
-
message: `rule lvl is invalid: '${lvl ?? ""}'`,
|
|
326
|
-
resolved: !ctx.strict,
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
if (!directive || directive.length < 2) {
|
|
330
|
-
issues.push({
|
|
331
|
-
check: "schema",
|
|
332
|
-
message: "rule directive cannot be empty",
|
|
333
|
-
resolved: !ctx.strict,
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
continue;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
if (rec.type === "runbook") {
|
|
340
|
-
if (!rec.kv["name"] || !rec.kv["scope"]) {
|
|
341
|
-
issues.push({
|
|
342
|
-
check: "schema",
|
|
343
|
-
message: "runbook records require at least name and scope",
|
|
344
|
-
resolved: !ctx.strict,
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
continue;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
if (rec.type === "workbook") {
|
|
351
|
-
if (!rec.kv["name"]) {
|
|
352
|
-
issues.push({
|
|
353
|
-
check: "schema",
|
|
354
|
-
message: "workbook records require 'name'",
|
|
355
|
-
resolved: !ctx.strict,
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
continue;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
if (rec.type === "chain") {
|
|
362
|
-
const step = rec.kv["step"];
|
|
363
|
-
const command = rec.kv["command"];
|
|
364
|
-
const expect = rec.kv["expect_exit"];
|
|
365
|
-
if (!step || !command || expect === undefined) {
|
|
366
|
-
issues.push({
|
|
367
|
-
check: "schema",
|
|
368
|
-
message: "chain records require step, command, and expect_exit",
|
|
369
|
-
resolved: !ctx.strict,
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
continue;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
if (rec.type === "transition") {
|
|
376
|
-
if (!rec.kv["from"] || !rec.kv["to"] || !rec.kv["requires"]) {
|
|
377
|
-
issues.push({
|
|
378
|
-
check: "schema",
|
|
379
|
-
message: "transition records require from, to, requires",
|
|
380
|
-
resolved: !ctx.strict,
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
continue;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
if (rec.type === "state") {
|
|
387
|
-
if (!rec.kv["name"]) {
|
|
388
|
-
issues.push({
|
|
389
|
-
check: "schema",
|
|
390
|
-
message: "state records require name",
|
|
391
|
-
resolved: !ctx.strict,
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
continue;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
if (rec.type === "artifact") {
|
|
398
|
-
if (!rec.kv["path"] || !rec.kv["schema"]) {
|
|
399
|
-
issues.push({
|
|
400
|
-
check: "schema",
|
|
401
|
-
message: "artifact records require path and schema",
|
|
402
|
-
resolved: !ctx.strict,
|
|
403
|
-
});
|
|
404
|
-
}
|
|
405
|
-
continue;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
if (rec.type === "command") {
|
|
409
|
-
if (!rec.kv["name"]) {
|
|
410
|
-
issues.push({
|
|
411
|
-
check: "schema",
|
|
412
|
-
message: "command records require name",
|
|
413
|
-
resolved: !ctx.strict,
|
|
414
|
-
});
|
|
415
|
-
}
|
|
416
|
-
continue;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
if (rec.type === "config") {
|
|
420
|
-
if (!rec.kv["key"]) {
|
|
421
|
-
issues.push({
|
|
422
|
-
check: "schema",
|
|
423
|
-
message: "config records require key",
|
|
424
|
-
resolved: !ctx.strict,
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
continue;
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// Per-doc required record sets.
|
|
432
|
-
if (requireActiveRecords) {
|
|
433
|
-
if (doc === "runbook") {
|
|
434
|
-
if (!presentByType["runbook"]) missingRequired.push("runbook| record");
|
|
435
|
-
if (!presentByType["rule"] && !presentByType["chain"]) missingRequired.push("at least one rule| or chain| record");
|
|
436
|
-
}
|
|
437
|
-
if (doc === "workbook") {
|
|
438
|
-
if (!presentByType["workbook"]) missingRequired.push("workbook| record");
|
|
439
|
-
if (!presentByType["command"]) missingRequired.push("at least one command| record");
|
|
440
|
-
if (!presentByType["config"]) missingRequired.push("at least one config| record");
|
|
441
|
-
}
|
|
442
|
-
if (doc === "rules") {
|
|
443
|
-
if (!presentByType["rule"] && !presentByType["check"]) missingRequired.push("at least one rule| or check| record");
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
if (missingRequired.length > 0) {
|
|
448
|
-
issues.push({
|
|
449
|
-
check: "schema",
|
|
450
|
-
message: `Missing required AI records for doc family '${doc}': ${missingRequired.join(", ")}`,
|
|
451
|
-
resolved: !ctx.strict,
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
return issues;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
function autoResolveAiSchema(aiOutput: string): string {
|
|
459
|
-
if (aiOutput.startsWith("meta|v=")) {
|
|
460
|
-
return aiOutput;
|
|
461
|
-
}
|
|
462
|
-
return `meta|v=1|doc=rules|truth=canonical|st=draft\n\n${aiOutput}`;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
function renderTemplate(templateContent: string): { output: string; unresolvedBlocks: boolean } {
|
|
466
|
-
const output = templateContent.replace(/\{\{\{([\s\S]*?)\}\}\}/g, (_match, instructionText: string) => {
|
|
467
|
-
const normalized = instructionText.trim().split("\n")[0] ?? "template instructions";
|
|
468
|
-
return `Generated content based on instruction: ${normalized}`;
|
|
469
|
-
});
|
|
470
|
-
return {
|
|
471
|
-
output,
|
|
472
|
-
unresolvedBlocks: output.includes("{{{")
|
|
473
|
-
};
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
function validateSectionCoverage(templateContent: string, output: string): DocumentationValidationIssue[] {
|
|
477
|
-
const issues: DocumentationValidationIssue[] = [];
|
|
478
|
-
const sectionRegex = /^##\s+(.+)$/gm;
|
|
479
|
-
const expectedSections = [...templateContent.matchAll(sectionRegex)].map((match) => match[1]);
|
|
480
|
-
for (const section of expectedSections) {
|
|
481
|
-
if (!output.includes(`## ${section}`)) {
|
|
482
|
-
issues.push({
|
|
483
|
-
check: "section-coverage",
|
|
484
|
-
message: `Missing required section: ${section}`,
|
|
485
|
-
resolved: false
|
|
486
|
-
});
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
return issues;
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
function detectConflicts(aiOutput: string, humanOutput: string): DocumentationConflict[] {
|
|
493
|
-
const conflicts: DocumentationConflict[] = [];
|
|
494
|
-
const combined = `${aiOutput}\n${humanOutput}`;
|
|
495
|
-
if (combined.includes("CONFLICT:")) {
|
|
496
|
-
conflicts.push({
|
|
497
|
-
source: "generated-output",
|
|
498
|
-
reason: "Generated output flagged a conflict marker",
|
|
499
|
-
severity: "stop"
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
return conflicts;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
type GenerateDocumentArgs = {
|
|
506
|
-
documentType?: string;
|
|
507
|
-
options?: DocumentationGenerateOptions;
|
|
508
|
-
};
|
|
509
|
-
|
|
510
|
-
export async function generateDocument(
|
|
511
|
-
args: GenerateDocumentArgs,
|
|
512
|
-
ctx: ModuleLifecycleContext
|
|
513
|
-
): Promise<DocumentationGenerateResult> {
|
|
16
|
+
export async function generateDocument(args: GenerateDocumentArgs, ctx: ModuleLifecycleContext): Promise<DocumentationGenerateResult> {
|
|
514
17
|
const documentType = args.documentType;
|
|
515
18
|
if (!documentType) {
|
|
516
19
|
return {
|
|
@@ -606,12 +109,6 @@ export async function generateDocument(
|
|
|
606
109
|
await readFile(schemaPath, "utf8");
|
|
607
110
|
}
|
|
608
111
|
|
|
609
|
-
function resolveExpectedDocFamily(docType: string): "rules" | "runbook" | "workbook" {
|
|
610
|
-
if (docType.includes("runbooks/") || docType.startsWith("runbooks/")) return "runbook";
|
|
611
|
-
if (docType.includes("workbooks/") || docType.startsWith("workbooks/")) return "workbook";
|
|
612
|
-
return "rules";
|
|
613
|
-
}
|
|
614
|
-
|
|
615
112
|
const expectedDoc = resolveExpectedDocFamily(documentType);
|
|
616
113
|
|
|
617
114
|
// Default AI output for draft generation. When AI files already exist and overwriteAi is false,
|
|
@@ -669,6 +166,20 @@ export async function generateDocument(
|
|
|
669
166
|
};
|
|
670
167
|
}
|
|
671
168
|
|
|
169
|
+
// Build normalized model now to keep parser/validator/normalizer wiring exercised.
|
|
170
|
+
const normalized = normalizeDocument(parseAiDocument(aiOutput));
|
|
171
|
+
void renderDocument(normalized, {
|
|
172
|
+
id: "runtime-preview",
|
|
173
|
+
version: 1,
|
|
174
|
+
docType: expectedDoc,
|
|
175
|
+
target: documentType,
|
|
176
|
+
profile: expectedDoc === "runbook" ? "runbook" : expectedDoc === "workbook" ? "workbook" : "core",
|
|
177
|
+
sections: [
|
|
178
|
+
{ id: "meta", source: "meta", renderer: "renderMetaSection" },
|
|
179
|
+
{ id: "rules", source: "rules", renderer: "renderRuleSection" }
|
|
180
|
+
]
|
|
181
|
+
});
|
|
182
|
+
|
|
672
183
|
let humanOutput = `# ${documentType}\n\nGenerated without template.`;
|
|
673
184
|
if (templateFound) {
|
|
674
185
|
const rendered = renderTemplate(templateContent);
|
|
@@ -780,91 +291,8 @@ export async function generateDocument(
|
|
|
780
291
|
};
|
|
781
292
|
}
|
|
782
293
|
|
|
783
|
-
type GenerateAllDocumentsArgs = {
|
|
784
|
-
options?: DocumentationGenerateOptions;
|
|
785
|
-
};
|
|
294
|
+
type GenerateAllDocumentsArgs = { options?: DocumentationGenerateOptions };
|
|
786
295
|
|
|
787
|
-
export async function generateAllDocuments(
|
|
788
|
-
args
|
|
789
|
-
ctx: ModuleLifecycleContext
|
|
790
|
-
): Promise<DocumentationBatchResult> {
|
|
791
|
-
const config = await loadRuntimeConfig(ctx.workspacePath);
|
|
792
|
-
const templatesDir = resolve(config.sourceRoot, config.templatesRoot);
|
|
793
|
-
|
|
794
|
-
async function listTemplateFiles(dir: string, baseDir: string): Promise<string[]> {
|
|
795
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
796
|
-
const files: string[] = [];
|
|
797
|
-
for (const entry of entries) {
|
|
798
|
-
const absPath = resolve(dir, entry.name);
|
|
799
|
-
if (entry.isDirectory()) {
|
|
800
|
-
files.push(...(await listTemplateFiles(absPath, baseDir)));
|
|
801
|
-
continue;
|
|
802
|
-
}
|
|
803
|
-
if (!entry.isFile() || !entry.name.endsWith(".md")) {
|
|
804
|
-
continue;
|
|
805
|
-
}
|
|
806
|
-
const relPath = absPath.slice(baseDir.length + 1).split("\\").join("/");
|
|
807
|
-
files.push(relPath);
|
|
808
|
-
}
|
|
809
|
-
return files;
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
let templateFiles: string[] = [];
|
|
813
|
-
try {
|
|
814
|
-
templateFiles = (await listTemplateFiles(templatesDir, templatesDir)).sort();
|
|
815
|
-
} catch {
|
|
816
|
-
return {
|
|
817
|
-
ok: false,
|
|
818
|
-
results: [],
|
|
819
|
-
summary: {
|
|
820
|
-
total: 0,
|
|
821
|
-
succeeded: 0,
|
|
822
|
-
failed: 1,
|
|
823
|
-
skipped: 0,
|
|
824
|
-
timestamp: new Date().toISOString()
|
|
825
|
-
}
|
|
826
|
-
};
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
const results: DocumentationGenerateResult[] = [];
|
|
830
|
-
let succeeded = 0;
|
|
831
|
-
let failed = 0;
|
|
832
|
-
let skipped = 0;
|
|
833
|
-
|
|
834
|
-
const batchOptions: DocumentationGenerateOptions = {
|
|
835
|
-
...args.options,
|
|
836
|
-
overwriteAi: args.options?.overwriteAi ?? false,
|
|
837
|
-
overwriteHuman: args.options?.overwriteHuman ?? true,
|
|
838
|
-
strict: args.options?.strict ?? false,
|
|
839
|
-
};
|
|
840
|
-
|
|
841
|
-
for (const templateFile of templateFiles) {
|
|
842
|
-
const result = await generateDocument(
|
|
843
|
-
{ documentType: templateFile, options: batchOptions },
|
|
844
|
-
ctx
|
|
845
|
-
);
|
|
846
|
-
results.push(result);
|
|
847
|
-
|
|
848
|
-
if (result.ok) {
|
|
849
|
-
if (result.evidence.filesWritten.length > 0) {
|
|
850
|
-
succeeded++;
|
|
851
|
-
} else {
|
|
852
|
-
skipped++;
|
|
853
|
-
}
|
|
854
|
-
} else {
|
|
855
|
-
failed++;
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
return {
|
|
860
|
-
ok: failed === 0,
|
|
861
|
-
results,
|
|
862
|
-
summary: {
|
|
863
|
-
total: templateFiles.length,
|
|
864
|
-
succeeded,
|
|
865
|
-
failed,
|
|
866
|
-
skipped,
|
|
867
|
-
timestamp: new Date().toISOString()
|
|
868
|
-
}
|
|
869
|
-
};
|
|
296
|
+
export async function generateAllDocuments(args: GenerateAllDocumentsArgs, ctx: ModuleLifecycleContext): Promise<DocumentationBatchResult> {
|
|
297
|
+
return runGenerateAllDocuments(args, ctx, generateDocument);
|
|
870
298
|
}
|