@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.
Files changed (144) hide show
  1. package/README.md +23 -9
  2. package/dist/cli/doctor-planning-issues.js +3 -22
  3. package/dist/cli/run-command.js +22 -38
  4. package/dist/cli.js +95 -4
  5. package/dist/contracts/command-manifest.d.ts +17 -0
  6. package/dist/contracts/command-manifest.js +1 -0
  7. package/dist/contracts/index.d.ts +1 -1
  8. package/dist/contracts/module-contract.d.ts +12 -11
  9. package/dist/core/agent-instruction-surface.d.ts +33 -0
  10. package/dist/core/agent-instruction-surface.js +46 -0
  11. package/dist/core/config-cli.js +13 -17
  12. package/dist/core/config-metadata.js +101 -2
  13. package/dist/core/index.d.ts +4 -1
  14. package/dist/core/index.js +3 -0
  15. package/dist/core/module-command-router.js +19 -1
  16. package/dist/core/module-registry-resolve.d.ts +27 -0
  17. package/dist/core/module-registry-resolve.js +91 -0
  18. package/dist/core/module-registry.d.ts +14 -0
  19. package/dist/core/module-registry.js +57 -0
  20. package/dist/core/planning/build-plan-session-file.d.ts +29 -0
  21. package/dist/core/planning/build-plan-session-file.js +58 -0
  22. package/dist/core/planning/index.d.ts +17 -0
  23. package/dist/core/planning/index.js +15 -0
  24. package/dist/core/policy.js +18 -8
  25. package/dist/core/state/unified-state-db.d.ts +21 -0
  26. package/dist/core/state/unified-state-db.js +80 -0
  27. package/dist/core/workspace-kit-config.js +13 -1
  28. package/dist/modules/agent-behavior/builtins.d.ts +3 -0
  29. package/dist/modules/agent-behavior/builtins.js +71 -0
  30. package/dist/modules/agent-behavior/explain.d.ts +6 -0
  31. package/dist/modules/agent-behavior/explain.js +46 -0
  32. package/dist/modules/agent-behavior/index.d.ts +4 -0
  33. package/dist/modules/agent-behavior/index.js +461 -0
  34. package/dist/modules/agent-behavior/interview-session-file.d.ts +9 -0
  35. package/dist/modules/agent-behavior/interview-session-file.js +43 -0
  36. package/dist/modules/agent-behavior/interview.d.ts +13 -0
  37. package/dist/modules/agent-behavior/interview.js +88 -0
  38. package/dist/modules/agent-behavior/persistence.d.ts +6 -0
  39. package/dist/modules/agent-behavior/persistence.js +89 -0
  40. package/dist/modules/agent-behavior/store.d.ts +34 -0
  41. package/dist/modules/agent-behavior/store.js +119 -0
  42. package/dist/modules/agent-behavior/types.d.ts +28 -0
  43. package/dist/modules/agent-behavior/types.js +1 -0
  44. package/dist/modules/agent-behavior/validate.d.ts +11 -0
  45. package/dist/modules/agent-behavior/validate.js +123 -0
  46. package/dist/modules/approvals/index.js +54 -51
  47. package/dist/modules/approvals/policy-sensitive-commands.d.ts +4 -0
  48. package/dist/modules/approvals/policy-sensitive-commands.js +4 -0
  49. package/dist/modules/approvals/review-runtime.js +1 -2
  50. package/dist/modules/documentation/index.js +47 -45
  51. package/dist/modules/documentation/normalizer.d.ts +3 -0
  52. package/dist/modules/documentation/normalizer.js +171 -0
  53. package/dist/modules/documentation/parser.d.ts +7 -0
  54. package/dist/modules/documentation/parser.js +39 -0
  55. package/dist/modules/documentation/policy-sensitive-commands.d.ts +5 -0
  56. package/dist/modules/documentation/policy-sensitive-commands.js +8 -0
  57. package/dist/modules/documentation/renderer.d.ts +23 -0
  58. package/dist/modules/documentation/renderer.js +105 -0
  59. package/dist/modules/documentation/runtime-batch.d.ts +10 -0
  60. package/dist/modules/documentation/runtime-batch.js +67 -0
  61. package/dist/modules/documentation/runtime-config.d.ts +11 -0
  62. package/dist/modules/documentation/runtime-config.js +54 -0
  63. package/dist/modules/documentation/runtime-render-support.d.ts +8 -0
  64. package/dist/modules/documentation/runtime-render-support.js +36 -0
  65. package/dist/modules/documentation/runtime.js +22 -510
  66. package/dist/modules/documentation/types.d.ts +182 -0
  67. package/dist/modules/documentation/validator.d.ts +8 -0
  68. package/dist/modules/documentation/validator.js +234 -0
  69. package/dist/modules/documentation/view-models.d.ts +3 -0
  70. package/dist/modules/documentation/view-models.js +124 -0
  71. package/dist/modules/improvement/generate-recommendations-runtime.js +3 -3
  72. package/dist/modules/improvement/improvement-state.d.ts +2 -2
  73. package/dist/modules/improvement/improvement-state.js +52 -23
  74. package/dist/modules/improvement/index.js +140 -138
  75. package/dist/modules/improvement/ingest.d.ts +1 -1
  76. package/dist/modules/improvement/policy-sensitive-commands.d.ts +4 -0
  77. package/dist/modules/improvement/policy-sensitive-commands.js +7 -0
  78. package/dist/modules/index.d.ts +6 -0
  79. package/dist/modules/index.js +17 -0
  80. package/dist/modules/planning/artifact.d.ts +19 -0
  81. package/dist/modules/planning/artifact.js +72 -0
  82. package/dist/modules/planning/index.js +605 -6
  83. package/dist/modules/planning/question-engine.d.ts +25 -0
  84. package/dist/modules/planning/question-engine.js +284 -0
  85. package/dist/modules/planning/types.d.ts +9 -0
  86. package/dist/modules/planning/types.js +39 -0
  87. package/dist/modules/task-engine/doctor-planning-persistence.js +21 -13
  88. package/dist/modules/task-engine/index.d.ts +1 -2
  89. package/dist/modules/task-engine/index.js +1 -1143
  90. package/dist/modules/task-engine/migrate-task-persistence-runtime.js +31 -4
  91. package/dist/modules/task-engine/migrate-wishlist-intake-runtime.d.ts +2 -0
  92. package/dist/modules/task-engine/migrate-wishlist-intake-runtime.js +146 -0
  93. package/dist/modules/task-engine/planning-open.d.ts +2 -9
  94. package/dist/modules/task-engine/planning-open.js +4 -15
  95. package/dist/modules/task-engine/policy-sensitive-commands.d.ts +5 -0
  96. package/dist/modules/task-engine/policy-sensitive-commands.js +5 -0
  97. package/dist/modules/task-engine/sqlite-dual-planning.d.ts +11 -2
  98. package/dist/modules/task-engine/sqlite-dual-planning.js +134 -28
  99. package/dist/modules/task-engine/strict-task-validation.js +3 -0
  100. package/dist/modules/task-engine/suggestions.js +2 -1
  101. package/dist/modules/task-engine/task-engine-internal.d.ts +2 -0
  102. package/dist/modules/task-engine/task-engine-internal.js +1304 -0
  103. package/dist/modules/task-engine/task-type-validation.js +40 -0
  104. package/dist/modules/task-engine/wishlist-intake.d.ts +22 -0
  105. package/dist/modules/task-engine/wishlist-intake.js +180 -0
  106. package/dist/modules/task-engine/wishlist-validation.d.ts +4 -0
  107. package/dist/modules/task-engine/wishlist-validation.js +19 -0
  108. package/dist/modules/workspace-config/index.js +9 -11
  109. package/package.json +2 -2
  110. package/schemas/agent-behavior-profile.schema.json +52 -0
  111. package/schemas/task-engine-run-contracts.schema.json +80 -5
  112. package/src/modules/documentation/README.md +16 -25
  113. package/src/modules/documentation/RULES.md +9 -9
  114. package/src/modules/documentation/index.ts +54 -49
  115. package/src/modules/documentation/instructions/document-project.md +6 -6
  116. package/src/modules/documentation/instructions/generate-document.md +4 -4
  117. package/src/modules/documentation/normalizer.ts +187 -0
  118. package/src/modules/documentation/parser.ts +41 -0
  119. package/src/modules/documentation/policy-sensitive-commands.ts +8 -0
  120. package/src/modules/documentation/renderer.ts +121 -0
  121. package/src/modules/documentation/runtime-batch.ts +74 -0
  122. package/src/modules/documentation/runtime-config.ts +68 -0
  123. package/src/modules/documentation/runtime-render-support.ts +39 -0
  124. package/src/modules/documentation/runtime.ts +28 -600
  125. package/src/modules/documentation/schemas/documentation-schema.md +37 -54
  126. package/src/modules/documentation/types.ts +228 -0
  127. package/src/modules/documentation/validator.ts +247 -0
  128. package/src/modules/documentation/view-models.ts +132 -0
  129. package/src/modules/documentation/views/agents.view.yaml +18 -0
  130. package/src/modules/documentation/views/architecture.view.yaml +18 -0
  131. package/src/modules/documentation/views/principles.view.yaml +18 -0
  132. package/src/modules/documentation/views/readme.view.yaml +18 -0
  133. package/src/modules/documentation/views/releasing.view.yaml +18 -0
  134. package/src/modules/documentation/views/roadmap.view.yaml +18 -0
  135. package/src/modules/documentation/views/runbooks-consumer-cadence.view.yaml +18 -0
  136. package/src/modules/documentation/views/runbooks-parity-validation-flow.view.yaml +18 -0
  137. package/src/modules/documentation/views/runbooks-release-channels.view.yaml +18 -0
  138. package/src/modules/documentation/views/security.view.yaml +18 -0
  139. package/src/modules/documentation/views/support.view.yaml +18 -0
  140. package/src/modules/documentation/views/terms.view.yaml +18 -0
  141. package/src/modules/documentation/views/workbooks-phase2-config-policy-workbook.view.yaml +18 -0
  142. package/src/modules/documentation/views/workbooks-task-engine-workbook.view.yaml +18 -0
  143. package/src/modules/documentation/views/workbooks-transcript-automation-baseline.view.yaml +18 -0
  144. package/src/modules/documentation/state.md +0 -8
@@ -0,0 +1,36 @@
1
+ export function resolveExpectedDocFamily(docType) {
2
+ if (docType.includes("runbooks/") || docType.startsWith("runbooks/"))
3
+ return "runbook";
4
+ if (docType.includes("workbooks/") || docType.startsWith("workbooks/"))
5
+ return "workbook";
6
+ return "rules";
7
+ }
8
+ export function renderTemplate(templateContent) {
9
+ const output = templateContent.replace(/\{\{\{([\s\S]*?)\}\}\}/g, (_match, instructionText) => {
10
+ const normalized = instructionText.trim().split("\n")[0] ?? "template instructions";
11
+ return `Generated content based on instruction: ${normalized}`;
12
+ });
13
+ return { output, unresolvedBlocks: output.includes("{{{") };
14
+ }
15
+ export function validateSectionCoverage(templateContent, output) {
16
+ const issues = [];
17
+ const sectionRegex = /^##\s+(.+)$/gm;
18
+ const expectedSections = [...templateContent.matchAll(sectionRegex)].map((match) => match[1]);
19
+ for (const section of expectedSections) {
20
+ if (!output.includes(`## ${section}`)) {
21
+ issues.push({ check: "section-coverage", message: `Missing required section: ${section}`, resolved: false });
22
+ }
23
+ }
24
+ return issues;
25
+ }
26
+ export function detectConflicts(aiOutput, humanOutput) {
27
+ const conflicts = [];
28
+ if (`${aiOutput}\n${humanOutput}`.includes("CONFLICT:")) {
29
+ conflicts.push({
30
+ source: "generated-output",
31
+ reason: "Generated output flagged a conflict marker",
32
+ severity: "stop"
33
+ });
34
+ }
35
+ return conflicts;
36
+ }
@@ -1,436 +1,13 @@
1
1
  import { mkdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { existsSync } from "node:fs";
3
- import { dirname, resolve, sep } from "node:path";
4
- import { readdir } from "node:fs/promises";
5
- import { fileURLToPath } from "node:url";
6
- function isPathWithinRoot(path, root) {
7
- return path === root || path.startsWith(`${root}${sep}`);
8
- }
9
- function parseDefaultValue(fileContent, key, fallback) {
10
- const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11
- const regex = new RegExp(`\\\`${escaped}\\\`[^\\n]*default:\\s*\\\`([^\\\`]+)\\\``);
12
- const match = fileContent.match(regex);
13
- return match?.[1] ?? fallback;
14
- }
15
- async function loadRuntimeConfig(workspacePath) {
16
- const runtimeSourceRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
17
- const sourceRoots = [workspacePath, runtimeSourceRoot];
18
- let sourceRoot = workspacePath;
19
- let configContent;
20
- for (const candidateRoot of sourceRoots) {
21
- const candidate = resolve(candidateRoot, "src/modules/documentation/config.md");
22
- if (!existsSync(candidate)) {
23
- continue;
24
- }
25
- configContent = await readFile(candidate, "utf8");
26
- sourceRoot = candidateRoot;
27
- break;
28
- }
29
- if (!configContent) {
30
- return {
31
- aiRoot: "/.ai",
32
- humanRoot: "docs/maintainers",
33
- templatesRoot: "src/modules/documentation/templates",
34
- instructionsRoot: "src/modules/documentation/instructions",
35
- schemasRoot: "src/modules/documentation/schemas",
36
- maxValidationAttempts: 3,
37
- sourceRoot
38
- };
39
- }
40
- const aiRoot = parseDefaultValue(configContent, "sources.aiRoot", "/.ai");
41
- const humanRoot = parseDefaultValue(configContent, "sources.humanRoot", "docs/maintainers");
42
- const templatesRoot = parseDefaultValue(configContent, "sources.templatesRoot", "src/modules/documentation/templates");
43
- const instructionsRoot = parseDefaultValue(configContent, "sources.instructionsRoot", "src/modules/documentation/instructions");
44
- const schemasRoot = parseDefaultValue(configContent, "sources.schemasRoot", "src/modules/documentation/schemas");
45
- const maxValidationAttemptsRaw = parseDefaultValue(configContent, "generation.maxValidationAttempts", "3");
46
- const maxValidationAttempts = Number.parseInt(maxValidationAttemptsRaw, 10);
47
- return {
48
- aiRoot,
49
- humanRoot,
50
- templatesRoot,
51
- instructionsRoot,
52
- schemasRoot,
53
- maxValidationAttempts: Number.isFinite(maxValidationAttempts) ? maxValidationAttempts : 3,
54
- sourceRoot
55
- };
56
- }
57
- function parseAiRecordLine(line) {
58
- const trimmed = line.trim();
59
- if (!trimmed || trimmed.startsWith("#"))
60
- return null;
61
- const parts = trimmed.split("|");
62
- // Record format is `type|token|token...`. Ignore non-record markdown lines.
63
- if (parts.length < 2)
64
- return null;
65
- const type = parts[0] ?? "";
66
- if (!type)
67
- return null;
68
- const positional = [];
69
- const kv = {};
70
- for (const token of parts.slice(1)) {
71
- if (!token)
72
- continue;
73
- const idx = token.indexOf("=");
74
- if (idx >= 0) {
75
- const k = token.slice(0, idx).trim();
76
- const v = token.slice(idx + 1).trim();
77
- if (!k)
78
- continue;
79
- kv[k] = v;
80
- }
81
- else {
82
- positional.push(token);
83
- }
84
- }
85
- return { type, positional, kv, raw: line };
86
- }
87
- function isAllowedMetaDoc(doc) {
88
- return (doc === "rules" ||
89
- doc === "runbook" ||
90
- doc === "workbook" ||
91
- doc === "generator" ||
92
- doc === "map" ||
93
- doc === "workflows" ||
94
- doc === "commands" ||
95
- doc === "decisions" ||
96
- doc === "glossary" ||
97
- doc === "observed" ||
98
- doc === "planned" ||
99
- doc === "checks" ||
100
- doc === "manifest");
101
- }
102
- function validateAiSchema(aiOutput, ctx) {
103
- const issues = [];
104
- const lines = aiOutput.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
105
- if (lines.length === 0) {
106
- return [
107
- {
108
- check: "schema",
109
- message: "AI output is empty",
110
- resolved: false,
111
- }
112
- ];
113
- }
114
- const metaLine = lines[0];
115
- const meta = parseAiRecordLine(metaLine);
116
- if (!meta || meta.type !== "meta") {
117
- return [
118
- {
119
- check: "schema",
120
- message: "AI output must start with a meta record",
121
- resolved: false,
122
- }
123
- ];
124
- }
125
- const v = meta.kv["v"];
126
- const doc = meta.kv["doc"];
127
- const truth = meta.kv["truth"];
128
- const st = meta.kv["st"];
129
- if (v !== "1") {
130
- issues.push({
131
- check: "schema",
132
- message: "AI meta schemaVersion must be v=1",
133
- resolved: false,
134
- });
135
- }
136
- if (!doc || !isAllowedMetaDoc(doc)) {
137
- issues.push({
138
- check: "schema",
139
- message: `Unsupported meta.doc '${doc ?? ""}'`,
140
- resolved: false,
141
- });
142
- }
143
- if (!truth || truth.length === 0) {
144
- issues.push({
145
- check: "schema",
146
- message: "AI meta.truth is required",
147
- resolved: false,
148
- });
149
- }
150
- if (!st || st.length === 0) {
151
- issues.push({
152
- check: "schema",
153
- message: "AI meta.st is required",
154
- resolved: false,
155
- });
156
- }
157
- if (ctx.expectedDoc && doc && ctx.expectedDoc !== doc) {
158
- issues.push({
159
- check: "schema",
160
- message: `meta.doc '${doc}' does not match expected doc family for '${ctx.expectedDoc}'`,
161
- resolved: !ctx.strict,
162
- });
163
- }
164
- const requireActiveRecords = st === "active";
165
- const allowedTypes = new Set([
166
- // Global ai record families used across .ai/*.
167
- "project",
168
- "stack",
169
- "prio",
170
- "ref",
171
- "rule",
172
- "check",
173
- "path",
174
- "role",
175
- "has",
176
- "xhas",
177
- "deps",
178
- "xdeps",
179
- "module",
180
- "wf",
181
- "cmd",
182
- "decision",
183
- "term",
184
- "observed",
185
- "planned",
186
- "map",
187
- // Runbooks
188
- "runbook",
189
- "intent",
190
- "chain",
191
- "artifact",
192
- "state",
193
- "transition",
194
- "promotion",
195
- "rollback",
196
- // Workbooks
197
- "workbook",
198
- "scope",
199
- "command",
200
- "config",
201
- "cadence",
202
- "guardrail",
203
- ]);
204
- const presentByType = {};
205
- const missingRequired = [];
206
- for (const line of lines.slice(1)) {
207
- const rec = parseAiRecordLine(line);
208
- if (!rec)
209
- continue;
210
- presentByType[rec.type] = true;
211
- if (!allowedTypes.has(rec.type)) {
212
- issues.push({
213
- check: "schema",
214
- message: `Unknown AI record type '${rec.type}'`,
215
- resolved: !ctx.strict,
216
- });
217
- continue;
218
- }
219
- // Minimal record-level validation for current runbook/workbook families.
220
- if (rec.type === "ref") {
221
- const p = rec.kv["path"];
222
- const n = rec.kv["name"];
223
- if (!p || !n) {
224
- issues.push({
225
- check: "schema",
226
- message: "ref records require both 'name' and 'path'",
227
- resolved: !ctx.strict,
228
- });
229
- }
230
- else {
231
- const abs = resolve(ctx.workspacePath, p);
232
- const ok = existsSync(abs);
233
- if (!ok) {
234
- issues.push({
235
- check: "schema",
236
- message: `ref.path does not exist: '${p}'`,
237
- resolved: !ctx.strict,
238
- });
239
- }
240
- }
241
- continue;
242
- }
243
- if (rec.type === "rule") {
244
- const rid = rec.positional[0];
245
- const lvl = rec.positional[1] ?? rec.kv["lvl"];
246
- const directive = (() => {
247
- // rule lines can be either:
248
- // rule|RID|lvl|scope|directive|...
249
- // or the scope can be omitted:
250
- // rule|RID|lvl|directive|...
251
- const nonKey = rec.positional.slice(2);
252
- return nonKey[nonKey.length - 1];
253
- })();
254
- if (!rid || !/^R\d{3,}$/.test(rid)) {
255
- issues.push({
256
- check: "schema",
257
- message: "rule records require RID formatted like R### or R####",
258
- resolved: !ctx.strict,
259
- });
260
- }
261
- if (!lvl || !["must", "must_not", "should", "may"].includes(lvl)) {
262
- issues.push({
263
- check: "schema",
264
- message: `rule lvl is invalid: '${lvl ?? ""}'`,
265
- resolved: !ctx.strict,
266
- });
267
- }
268
- if (!directive || directive.length < 2) {
269
- issues.push({
270
- check: "schema",
271
- message: "rule directive cannot be empty",
272
- resolved: !ctx.strict,
273
- });
274
- }
275
- continue;
276
- }
277
- if (rec.type === "runbook") {
278
- if (!rec.kv["name"] || !rec.kv["scope"]) {
279
- issues.push({
280
- check: "schema",
281
- message: "runbook records require at least name and scope",
282
- resolved: !ctx.strict,
283
- });
284
- }
285
- continue;
286
- }
287
- if (rec.type === "workbook") {
288
- if (!rec.kv["name"]) {
289
- issues.push({
290
- check: "schema",
291
- message: "workbook records require 'name'",
292
- resolved: !ctx.strict,
293
- });
294
- }
295
- continue;
296
- }
297
- if (rec.type === "chain") {
298
- const step = rec.kv["step"];
299
- const command = rec.kv["command"];
300
- const expect = rec.kv["expect_exit"];
301
- if (!step || !command || expect === undefined) {
302
- issues.push({
303
- check: "schema",
304
- message: "chain records require step, command, and expect_exit",
305
- resolved: !ctx.strict,
306
- });
307
- }
308
- continue;
309
- }
310
- if (rec.type === "transition") {
311
- if (!rec.kv["from"] || !rec.kv["to"] || !rec.kv["requires"]) {
312
- issues.push({
313
- check: "schema",
314
- message: "transition records require from, to, requires",
315
- resolved: !ctx.strict,
316
- });
317
- }
318
- continue;
319
- }
320
- if (rec.type === "state") {
321
- if (!rec.kv["name"]) {
322
- issues.push({
323
- check: "schema",
324
- message: "state records require name",
325
- resolved: !ctx.strict,
326
- });
327
- }
328
- continue;
329
- }
330
- if (rec.type === "artifact") {
331
- if (!rec.kv["path"] || !rec.kv["schema"]) {
332
- issues.push({
333
- check: "schema",
334
- message: "artifact records require path and schema",
335
- resolved: !ctx.strict,
336
- });
337
- }
338
- continue;
339
- }
340
- if (rec.type === "command") {
341
- if (!rec.kv["name"]) {
342
- issues.push({
343
- check: "schema",
344
- message: "command records require name",
345
- resolved: !ctx.strict,
346
- });
347
- }
348
- continue;
349
- }
350
- if (rec.type === "config") {
351
- if (!rec.kv["key"]) {
352
- issues.push({
353
- check: "schema",
354
- message: "config records require key",
355
- resolved: !ctx.strict,
356
- });
357
- }
358
- continue;
359
- }
360
- }
361
- // Per-doc required record sets.
362
- if (requireActiveRecords) {
363
- if (doc === "runbook") {
364
- if (!presentByType["runbook"])
365
- missingRequired.push("runbook| record");
366
- if (!presentByType["rule"] && !presentByType["chain"])
367
- missingRequired.push("at least one rule| or chain| record");
368
- }
369
- if (doc === "workbook") {
370
- if (!presentByType["workbook"])
371
- missingRequired.push("workbook| record");
372
- if (!presentByType["command"])
373
- missingRequired.push("at least one command| record");
374
- if (!presentByType["config"])
375
- missingRequired.push("at least one config| record");
376
- }
377
- if (doc === "rules") {
378
- if (!presentByType["rule"] && !presentByType["check"])
379
- missingRequired.push("at least one rule| or check| record");
380
- }
381
- }
382
- if (missingRequired.length > 0) {
383
- issues.push({
384
- check: "schema",
385
- message: `Missing required AI records for doc family '${doc}': ${missingRequired.join(", ")}`,
386
- resolved: !ctx.strict,
387
- });
388
- }
389
- return issues;
390
- }
391
- function autoResolveAiSchema(aiOutput) {
392
- if (aiOutput.startsWith("meta|v=")) {
393
- return aiOutput;
394
- }
395
- return `meta|v=1|doc=rules|truth=canonical|st=draft\n\n${aiOutput}`;
396
- }
397
- function renderTemplate(templateContent) {
398
- const output = templateContent.replace(/\{\{\{([\s\S]*?)\}\}\}/g, (_match, instructionText) => {
399
- const normalized = instructionText.trim().split("\n")[0] ?? "template instructions";
400
- return `Generated content based on instruction: ${normalized}`;
401
- });
402
- return {
403
- output,
404
- unresolvedBlocks: output.includes("{{{")
405
- };
406
- }
407
- function validateSectionCoverage(templateContent, output) {
408
- const issues = [];
409
- const sectionRegex = /^##\s+(.+)$/gm;
410
- const expectedSections = [...templateContent.matchAll(sectionRegex)].map((match) => match[1]);
411
- for (const section of expectedSections) {
412
- if (!output.includes(`## ${section}`)) {
413
- issues.push({
414
- check: "section-coverage",
415
- message: `Missing required section: ${section}`,
416
- resolved: false
417
- });
418
- }
419
- }
420
- return issues;
421
- }
422
- function detectConflicts(aiOutput, humanOutput) {
423
- const conflicts = [];
424
- const combined = `${aiOutput}\n${humanOutput}`;
425
- if (combined.includes("CONFLICT:")) {
426
- conflicts.push({
427
- source: "generated-output",
428
- reason: "Generated output flagged a conflict marker",
429
- severity: "stop"
430
- });
431
- }
432
- return conflicts;
433
- }
3
+ import { dirname, resolve } from "node:path";
4
+ import { parseAiDocument } from "./parser.js";
5
+ import { normalizeDocument } from "./normalizer.js";
6
+ import { renderDocument } from "./renderer.js";
7
+ import { autoResolveAiSchema, validateAiSchema } from "./validator.js";
8
+ import { isPathWithinRoot, loadRuntimeConfig } from "./runtime-config.js";
9
+ import { detectConflicts, renderTemplate, resolveExpectedDocFamily, validateSectionCoverage } from "./runtime-render-support.js";
10
+ import { runGenerateAllDocuments } from "./runtime-batch.js";
434
11
  export async function generateDocument(args, ctx) {
435
12
  const documentType = args.documentType;
436
13
  if (!documentType) {
@@ -522,13 +99,6 @@ export async function generateDocument(args, ctx) {
522
99
  filesRead.push(schemaPath);
523
100
  await readFile(schemaPath, "utf8");
524
101
  }
525
- function resolveExpectedDocFamily(docType) {
526
- if (docType.includes("runbooks/") || docType.startsWith("runbooks/"))
527
- return "runbook";
528
- if (docType.includes("workbooks/") || docType.startsWith("workbooks/"))
529
- return "workbook";
530
- return "rules";
531
- }
532
102
  const expectedDoc = resolveExpectedDocFamily(documentType);
533
103
  // Default AI output for draft generation. When AI files already exist and overwriteAi is false,
534
104
  // we validate and preserve the existing AI surface content instead of using this stub.
@@ -580,6 +150,19 @@ export async function generateDocument(args, ctx) {
580
150
  }
581
151
  };
582
152
  }
153
+ // Build normalized model now to keep parser/validator/normalizer wiring exercised.
154
+ const normalized = normalizeDocument(parseAiDocument(aiOutput));
155
+ void renderDocument(normalized, {
156
+ id: "runtime-preview",
157
+ version: 1,
158
+ docType: expectedDoc,
159
+ target: documentType,
160
+ profile: expectedDoc === "runbook" ? "runbook" : expectedDoc === "workbook" ? "workbook" : "core",
161
+ sections: [
162
+ { id: "meta", source: "meta", renderer: "renderMetaSection" },
163
+ { id: "rules", source: "rules", renderer: "renderRuleSection" }
164
+ ]
165
+ });
583
166
  let humanOutput = `# ${documentType}\n\nGenerated without template.`;
584
167
  if (templateFound) {
585
168
  const rendered = renderTemplate(templateContent);
@@ -686,76 +269,5 @@ export async function generateDocument(args, ctx) {
686
269
  };
687
270
  }
688
271
  export async function generateAllDocuments(args, ctx) {
689
- const config = await loadRuntimeConfig(ctx.workspacePath);
690
- const templatesDir = resolve(config.sourceRoot, config.templatesRoot);
691
- async function listTemplateFiles(dir, baseDir) {
692
- const entries = await readdir(dir, { withFileTypes: true });
693
- const files = [];
694
- for (const entry of entries) {
695
- const absPath = resolve(dir, entry.name);
696
- if (entry.isDirectory()) {
697
- files.push(...(await listTemplateFiles(absPath, baseDir)));
698
- continue;
699
- }
700
- if (!entry.isFile() || !entry.name.endsWith(".md")) {
701
- continue;
702
- }
703
- const relPath = absPath.slice(baseDir.length + 1).split("\\").join("/");
704
- files.push(relPath);
705
- }
706
- return files;
707
- }
708
- let templateFiles = [];
709
- try {
710
- templateFiles = (await listTemplateFiles(templatesDir, templatesDir)).sort();
711
- }
712
- catch {
713
- return {
714
- ok: false,
715
- results: [],
716
- summary: {
717
- total: 0,
718
- succeeded: 0,
719
- failed: 1,
720
- skipped: 0,
721
- timestamp: new Date().toISOString()
722
- }
723
- };
724
- }
725
- const results = [];
726
- let succeeded = 0;
727
- let failed = 0;
728
- let skipped = 0;
729
- const batchOptions = {
730
- ...args.options,
731
- overwriteAi: args.options?.overwriteAi ?? false,
732
- overwriteHuman: args.options?.overwriteHuman ?? true,
733
- strict: args.options?.strict ?? false,
734
- };
735
- for (const templateFile of templateFiles) {
736
- const result = await generateDocument({ documentType: templateFile, options: batchOptions }, ctx);
737
- results.push(result);
738
- if (result.ok) {
739
- if (result.evidence.filesWritten.length > 0) {
740
- succeeded++;
741
- }
742
- else {
743
- skipped++;
744
- }
745
- }
746
- else {
747
- failed++;
748
- }
749
- }
750
- return {
751
- ok: failed === 0,
752
- results,
753
- summary: {
754
- total: templateFiles.length,
755
- succeeded,
756
- failed,
757
- skipped,
758
- timestamp: new Date().toISOString()
759
- }
760
- };
272
+ return runGenerateAllDocuments(args, ctx, generateDocument);
761
273
  }