@workflow-cannon/workspace-kit 0.18.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 +61 -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 +8 -0
- 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/index.js +384 -50
- package/dist/modules/planning/question-engine.d.ts +2 -0
- package/dist/modules/planning/question-engine.js +8 -1
- 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
|
@@ -27,21 +27,18 @@ function parseOptions(raw: Record<string, unknown>): DocumentationGenerateOption
|
|
|
27
27
|
export const documentationModule: WorkflowModule = {
|
|
28
28
|
registration: {
|
|
29
29
|
id: "documentation",
|
|
30
|
-
version: "0.
|
|
30
|
+
version: "0.3.0",
|
|
31
31
|
contractVersion: "1",
|
|
32
|
+
stateSchema: 1,
|
|
32
33
|
capabilities: ["documentation"],
|
|
33
34
|
dependsOn: [],
|
|
35
|
+
optionalPeers: [],
|
|
34
36
|
enabledByDefault: true,
|
|
35
37
|
config: {
|
|
36
38
|
path: "src/modules/documentation/config.md",
|
|
37
39
|
format: "md",
|
|
38
40
|
description: "Documentation module configuration contract."
|
|
39
41
|
},
|
|
40
|
-
state: {
|
|
41
|
-
path: "src/modules/documentation/state.md",
|
|
42
|
-
format: "md",
|
|
43
|
-
description: "Documentation module generation/runtime state contract."
|
|
44
|
-
},
|
|
45
42
|
instructions: {
|
|
46
43
|
directory: "src/modules/documentation/instructions",
|
|
47
44
|
entries: [
|
|
@@ -66,50 +63,58 @@ export const documentationModule: WorkflowModule = {
|
|
|
66
63
|
: {};
|
|
67
64
|
const options = parseOptions(rawOptions);
|
|
68
65
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
66
|
+
const handlers: Record<string, () => Promise<{
|
|
67
|
+
ok: boolean;
|
|
68
|
+
code: string;
|
|
69
|
+
message?: string;
|
|
70
|
+
data?: Record<string, unknown>;
|
|
71
|
+
}>> = {
|
|
72
|
+
"document-project": async () => {
|
|
73
|
+
const batchResult = await generateAllDocuments({ options }, ctx);
|
|
74
|
+
return {
|
|
75
|
+
ok: batchResult.ok,
|
|
76
|
+
code: batchResult.ok ? "documented-project" : "documentation-batch-failed",
|
|
77
|
+
message: batchResult.ok
|
|
78
|
+
? `Generated ${batchResult.summary.succeeded} documents (${batchResult.summary.skipped} skipped)`
|
|
79
|
+
: `Batch failed: ${batchResult.summary.failed} of ${batchResult.summary.total} documents failed`,
|
|
80
|
+
data: {
|
|
81
|
+
summary: batchResult.summary,
|
|
82
|
+
results: batchResult.results.map((r) => ({
|
|
83
|
+
documentType: r.evidence.documentType,
|
|
84
|
+
ok: r.ok,
|
|
85
|
+
aiOutputPath: r.aiOutputPath,
|
|
86
|
+
humanOutputPath: r.humanOutputPath,
|
|
87
|
+
filesWritten: r.evidence.filesWritten,
|
|
88
|
+
filesSkipped: r.evidence.filesSkipped
|
|
89
|
+
}))
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
"generate-document": async () => {
|
|
94
|
+
const result = await generateDocument(
|
|
95
|
+
{
|
|
96
|
+
documentType: typeof args.documentType === "string" ? args.documentType : undefined,
|
|
97
|
+
options
|
|
98
|
+
},
|
|
99
|
+
ctx
|
|
100
|
+
);
|
|
99
101
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
102
|
+
return {
|
|
103
|
+
ok: result.ok,
|
|
104
|
+
code: result.ok ? "generated-document" : "generation-failed",
|
|
105
|
+
message: result.ok
|
|
106
|
+
? `Generated document '${args.documentType ?? "unknown"}'`
|
|
107
|
+
: `Failed to generate document '${args.documentType ?? "unknown"}'`,
|
|
108
|
+
data: {
|
|
109
|
+
aiOutputPath: result.aiOutputPath,
|
|
110
|
+
humanOutputPath: result.humanOutputPath,
|
|
111
|
+
evidence: result.evidence
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
const handler = handlers[command.name];
|
|
117
|
+
if (handler) return handler();
|
|
113
118
|
|
|
114
119
|
return {
|
|
115
120
|
ok: false,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# document-project
|
|
2
2
|
|
|
3
|
-
Generate all project documentation by running `generate-document` for every
|
|
3
|
+
Generate all project documentation by running `generate-document` for every view model in `src/modules/documentation/views`. Outputs AI-optimized docs to `.ai/` and human-readable docs to `docs/maintainers/`.
|
|
4
4
|
|
|
5
5
|
## Inputs
|
|
6
6
|
|
|
@@ -11,9 +11,9 @@ Generate all project documentation by running `generate-document` for every temp
|
|
|
11
11
|
- `strict?: boolean` — fail on unresolved warnings (default `false` in batch mode)
|
|
12
12
|
- `maxValidationAttempts?: number` — override retry limit per document
|
|
13
13
|
|
|
14
|
-
## Shipped
|
|
14
|
+
## Shipped targets
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
View models in `src/modules/documentation/views` map to these output targets:
|
|
17
17
|
|
|
18
18
|
- `AGENTS.md`
|
|
19
19
|
- `ARCHITECTURE.md`
|
|
@@ -33,10 +33,10 @@ All `.md` files under `sources.templatesRoot` (default `src/modules/documentatio
|
|
|
33
33
|
|
|
34
34
|
## Required behavior
|
|
35
35
|
|
|
36
|
-
1. Discover all `.
|
|
37
|
-
2. For each
|
|
36
|
+
1. Discover all `.view.yaml` view models (fallback: discover templates in fixture workspaces without `views/`).
|
|
37
|
+
2. For each view model, invoke `generate-document` using the view model `target` as `documentType`.
|
|
38
38
|
3. Default overwrite behavior: **preserve AI docs** (`overwriteAi: false`), **overwrite human docs** (`overwriteHuman: true`).
|
|
39
|
-
4. Continue through all
|
|
39
|
+
4. Continue through all targets on individual failure; do not stop the batch.
|
|
40
40
|
5. Collect per-document results and emit a batch summary with total/succeeded/failed/skipped counts.
|
|
41
41
|
6. Return `ok: true` only when zero documents failed.
|
|
42
42
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# generate-document
|
|
2
2
|
|
|
3
|
-
Generate a single document for both canonical AI and human-readable surfaces using module config, schema, and
|
|
3
|
+
Generate a single document for both canonical AI and human-readable surfaces using module config, schema, view models, and deterministic renderers.
|
|
4
4
|
|
|
5
5
|
## Inputs
|
|
6
6
|
|
|
7
|
-
- `documentType` (required): basename of the doc to generate;
|
|
7
|
+
- `documentType` (required): basename of the doc to generate; should match a `target` declared in `src/modules/documentation/views/*.view.yaml`. Known targets:
|
|
8
8
|
- `AGENTS.md`
|
|
9
9
|
- `ARCHITECTURE.md`
|
|
10
10
|
- `PRINCIPLES.md`
|
|
@@ -34,11 +34,11 @@ Generate a single document for both canonical AI and human-readable surfaces usi
|
|
|
34
34
|
1. Read `src/modules/documentation/RULES.md` and apply precedence order before generation.
|
|
35
35
|
2. Load module config and resolve output roots from configured paths (`sources.aiRoot`, `sources.humanRoot`).
|
|
36
36
|
3. Restrict writes strictly to configured output roots; reject writes outside those roots.
|
|
37
|
-
4. Resolve template for `documentType` from `sources.templatesRoot
|
|
37
|
+
4. Resolve template for `documentType` from `sources.templatesRoot` (for section/coverage checks) and resolve matching view model target from `views/`.
|
|
38
38
|
5. If template is missing, warn user and ask whether to continue without a template; continue only on explicit confirmation.
|
|
39
39
|
6. Generate AI output first at `<aiRoot>/<documentType>` using `documentation-maintainer.md` + `documentation-schema.md`.
|
|
40
40
|
7. Validate AI output against schema; on validation failure, auto-resolve/retry up to `generation.maxValidationAttempts` before failing.
|
|
41
|
-
8.
|
|
41
|
+
8. Parse AI output into keyed records, validate schema, normalize typed model, then render human output via named renderer functions from the matched view model.
|
|
42
42
|
9. For templates containing `{{{ ... }}}`, execute block contents as generation instructions and ensure no unresolved blocks remain in output.
|
|
43
43
|
10. Run section coverage validation (all required sections present, correct headings/order where required); retry/resolve on failure.
|
|
44
44
|
11. Detect conflicts with higher-precedence docs and stop/prompt when policy-sensitive or unresolved.
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import type { AiRecord } from "./parser.js";
|
|
2
|
+
import type {
|
|
3
|
+
NormalizedBaseRecord,
|
|
4
|
+
NormalizedCadence,
|
|
5
|
+
NormalizedCheck,
|
|
6
|
+
NormalizedCommand,
|
|
7
|
+
NormalizedConfig,
|
|
8
|
+
NormalizedDecision,
|
|
9
|
+
NormalizedDocument,
|
|
10
|
+
NormalizedExample,
|
|
11
|
+
NormalizedGuardrail,
|
|
12
|
+
NormalizedMeta,
|
|
13
|
+
NormalizedRef,
|
|
14
|
+
NormalizedRollback,
|
|
15
|
+
NormalizedRule,
|
|
16
|
+
NormalizedState,
|
|
17
|
+
NormalizedTerm,
|
|
18
|
+
NormalizedWorkflow,
|
|
19
|
+
NormalizedRunbook,
|
|
20
|
+
NormalizedWorkbook,
|
|
21
|
+
NormalizedChain,
|
|
22
|
+
NormalizedArtifact,
|
|
23
|
+
NormalizedPromotion,
|
|
24
|
+
NormalizedTransition
|
|
25
|
+
} from "./types.js";
|
|
26
|
+
|
|
27
|
+
function asStatus(v?: string): NormalizedBaseRecord["status"] {
|
|
28
|
+
if (v === "active" || v === "deprecated" || v === "draft" || v === "observed" || v === "planned") return v;
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function asList(v?: string): string[] {
|
|
33
|
+
if (!v) return [];
|
|
34
|
+
return v
|
|
35
|
+
.split(",")
|
|
36
|
+
.map((x) => x.trim())
|
|
37
|
+
.filter(Boolean);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function normalizeDocument(records: AiRecord[]): NormalizedDocument {
|
|
41
|
+
const refs: NormalizedRef[] = [];
|
|
42
|
+
const rules: NormalizedRule[] = [];
|
|
43
|
+
const checks: NormalizedCheck[] = [];
|
|
44
|
+
const decisions: NormalizedDecision[] = [];
|
|
45
|
+
const examples: NormalizedExample[] = [];
|
|
46
|
+
const terms: NormalizedTerm[] = [];
|
|
47
|
+
const commands: NormalizedCommand[] = [];
|
|
48
|
+
const workflows: NormalizedWorkflow[] = [];
|
|
49
|
+
const runbooks: NormalizedRunbook[] = [];
|
|
50
|
+
const workbooks: NormalizedWorkbook[] = [];
|
|
51
|
+
const chains: NormalizedChain[] = [];
|
|
52
|
+
const states: NormalizedState[] = [];
|
|
53
|
+
const transitions: NormalizedTransition[] = [];
|
|
54
|
+
const promotions: NormalizedPromotion[] = [];
|
|
55
|
+
const rollbacks: NormalizedRollback[] = [];
|
|
56
|
+
const artifacts: NormalizedArtifact[] = [];
|
|
57
|
+
const configs: NormalizedConfig[] = [];
|
|
58
|
+
const cadences: NormalizedCadence[] = [];
|
|
59
|
+
const guardrails: NormalizedGuardrail[] = [];
|
|
60
|
+
const refsById = new Map<string, NormalizedRef>();
|
|
61
|
+
const examplesByParent = new Map<string, NormalizedExample[]>();
|
|
62
|
+
const profileRecords = new Map<"core" | "runbook" | "workbook", Array<NormalizedBaseRecord>>([
|
|
63
|
+
["core", []],
|
|
64
|
+
["runbook", []],
|
|
65
|
+
["workbook", []]
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
let meta: NormalizedMeta | null = null;
|
|
69
|
+
for (const rec of records) {
|
|
70
|
+
const status = asStatus(rec.kv["status"] ?? rec.kv["st"]);
|
|
71
|
+
if (rec.type === "meta") {
|
|
72
|
+
meta = {
|
|
73
|
+
schema: "base.v2",
|
|
74
|
+
doc: rec.kv["doc"] ?? "rules",
|
|
75
|
+
truth: (rec.kv["truth"] as NormalizedMeta["truth"]) ?? "canonical",
|
|
76
|
+
profile: rec.kv["profile"] as NormalizedMeta["profile"],
|
|
77
|
+
status,
|
|
78
|
+
title: rec.kv["title"],
|
|
79
|
+
owner: rec.kv["owner"],
|
|
80
|
+
tags: asList(rec.kv["tags"]),
|
|
81
|
+
refs: asList(rec.kv["refs"])
|
|
82
|
+
};
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (rec.type === "ref") {
|
|
87
|
+
const ref: NormalizedRef = {
|
|
88
|
+
id: rec.kv["id"] ?? rec.kv["name"] ?? "",
|
|
89
|
+
type: (rec.kv["type"] as NormalizedRef["type"]) ?? "doc",
|
|
90
|
+
target: rec.kv["target"] ?? rec.kv["path"] ?? "",
|
|
91
|
+
anchor: rec.kv["anchor"],
|
|
92
|
+
label: rec.kv["label"] ?? rec.kv["name"],
|
|
93
|
+
note: rec.kv["note"],
|
|
94
|
+
status
|
|
95
|
+
};
|
|
96
|
+
refs.push(ref);
|
|
97
|
+
if (ref.id) refsById.set(ref.id, ref);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (rec.type === "rule") {
|
|
102
|
+
rules.push({
|
|
103
|
+
id: rec.kv["id"] ?? rec.kv["slot1"] ?? "",
|
|
104
|
+
level: (rec.kv["level"] as NormalizedRule["level"]) ?? "should",
|
|
105
|
+
scope: rec.kv["scope"] ?? "",
|
|
106
|
+
scope_kind: rec.kv["scope_kind"],
|
|
107
|
+
kind: rec.kv["kind"],
|
|
108
|
+
directive: rec.kv["directive"] ?? rec.kv["slot3"] ?? "",
|
|
109
|
+
why: rec.kv["why"] ?? "",
|
|
110
|
+
unless: rec.kv["unless"],
|
|
111
|
+
also: asList(rec.kv["also"]),
|
|
112
|
+
risk: rec.kv["risk"] as NormalizedRule["risk"],
|
|
113
|
+
approval: rec.kv["approval"] as NormalizedRule["approval"],
|
|
114
|
+
override: rec.kv["override"] as NormalizedRule["override"],
|
|
115
|
+
status,
|
|
116
|
+
refs: asList(rec.kv["refs"])
|
|
117
|
+
});
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (rec.type === "example") {
|
|
122
|
+
const ex: NormalizedExample = {
|
|
123
|
+
id: rec.kv["id"] ?? "",
|
|
124
|
+
for: rec.kv["for"] ?? "",
|
|
125
|
+
kind: (rec.kv["kind"] as NormalizedExample["kind"]) ?? "edge",
|
|
126
|
+
text: rec.kv["text"] ?? "",
|
|
127
|
+
status,
|
|
128
|
+
refs: asList(rec.kv["refs"])
|
|
129
|
+
};
|
|
130
|
+
examples.push(ex);
|
|
131
|
+
const list = examplesByParent.get(ex.for) ?? [];
|
|
132
|
+
list.push(ex);
|
|
133
|
+
examplesByParent.set(ex.for, list);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (rec.type === "check") checks.push({ id: rec.kv["id"] ?? "", scope: rec.kv["scope"] ?? "", assertion: rec.kv["assertion"] ?? rec.kv["assert"] ?? "", when: rec.kv["when"], onFail: rec.kv["onFail"] as NormalizedCheck["onFail"], status, refs: asList(rec.kv["refs"]) });
|
|
138
|
+
if (rec.type === "decision") decisions.push({ id: rec.kv["id"] ?? "", topic: rec.kv["topic"] ?? "", choice: rec.kv["choice"] ?? "", why: rec.kv["why"] ?? "", consequence: rec.kv["consequence"] ?? rec.kv["then"], status, refs: asList(rec.kv["refs"]) });
|
|
139
|
+
if (rec.type === "term") terms.push({ name: rec.kv["name"] ?? "", definition: rec.kv["definition"] ?? rec.kv["def"] ?? "", status, refs: asList(rec.kv["refs"]) });
|
|
140
|
+
if (rec.type === "command" || rec.type === "cmd") commands.push({ id: rec.kv["id"] ?? rec.kv["slot1"] ?? "", name: rec.kv["name"] ?? "", use: rec.kv["use"] ?? "", scope: rec.kv["scope"] ?? "", expectation: rec.kv["expectation"] ?? rec.kv["expect"] ?? "", risk: rec.kv["risk"] as NormalizedCommand["risk"], sensitivity: rec.kv["sensitivity"] as NormalizedCommand["sensitivity"], status, refs: asList(rec.kv["refs"]) });
|
|
141
|
+
if (rec.type === "workflow" || rec.type === "wf") workflows.push({ id: rec.kv["id"] ?? rec.kv["slot1"] ?? "", name: rec.kv["name"] ?? "", when: rec.kv["when"] ?? "", steps: asList(rec.kv["steps"] ?? rec.kv["do"]), done: asList(rec.kv["done"]), forbid: asList(rec.kv["forbid"]), askIf: rec.kv["askIf"] ?? rec.kv["ask_if"], haltIf: rec.kv["haltIf"] ?? rec.kv["halt_if"], approval: rec.kv["approval"] as NormalizedWorkflow["approval"], risk: rec.kv["risk"] as NormalizedWorkflow["risk"], status, refs: asList(rec.kv["refs"]) });
|
|
142
|
+
if (rec.type === "runbook") runbooks.push({ name: rec.kv["name"] ?? "", scope: rec.kv["scope"] ?? "", owner: rec.kv["owner"] ?? "", status, refs: asList(rec.kv["refs"]) });
|
|
143
|
+
if (rec.type === "workbook") workbooks.push({ name: rec.kv["name"] ?? "", phase: rec.kv["phase"] ?? "", state: rec.kv["state"] ?? "", status, refs: asList(rec.kv["refs"]) });
|
|
144
|
+
if (rec.type === "chain") chains.push({ step: rec.kv["step"] ?? "", command: rec.kv["command"] ?? "", expectExit: Number.parseInt(rec.kv["expectExit"] ?? rec.kv["expect_exit"] ?? "0", 10), status, refs: asList(rec.kv["refs"]) });
|
|
145
|
+
if (rec.type === "state") states.push({ name: rec.kv["name"] ?? "", distTag: rec.kv["distTag"] ?? rec.kv["dist_tag"] ?? "", intent: rec.kv["intent"] ?? "", status, refs: asList(rec.kv["refs"]) });
|
|
146
|
+
if (rec.type === "transition") transitions.push({ from: rec.kv["from"] ?? "", to: rec.kv["to"] ?? "", requires: asList(rec.kv["requires"]), status, refs: asList(rec.kv["refs"]) });
|
|
147
|
+
if (rec.type === "promotion") promotions.push({ from: rec.kv["from"] ?? "", to: rec.kv["to"] ?? "", requires: asList(rec.kv["requires"]), status, refs: asList(rec.kv["refs"]) });
|
|
148
|
+
if (rec.type === "rollback") rollbacks.push({ strategy: rec.kv["strategy"] ?? "", note: rec.kv["note"] ?? "", status, refs: asList(rec.kv["refs"]) });
|
|
149
|
+
if (rec.type === "artifact") artifacts.push({ path: rec.kv["path"] ?? "", schema: rec.kv["schema"] ?? "", status, refs: asList(rec.kv["refs"]) });
|
|
150
|
+
if (rec.type === "config") configs.push({ key: rec.kv["key"] ?? "", default: rec.kv["default"] ?? "", status, refs: asList(rec.kv["refs"]) });
|
|
151
|
+
if (rec.type === "cadence") cadences.push({ rule: rec.kv["rule"] ?? "", status, refs: asList(rec.kv["refs"]) });
|
|
152
|
+
if (rec.type === "guardrail") guardrails.push({ id: rec.kv["id"] ?? "", level: (rec.kv["level"] as NormalizedGuardrail["level"]) ?? "should", directive: rec.kv["directive"] ?? "", why: rec.kv["why"] ?? "", status, refs: asList(rec.kv["refs"]) });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const core: NormalizedBaseRecord[] = [...refs, ...rules, ...checks, ...decisions, ...examples, ...terms, ...commands, ...workflows];
|
|
156
|
+
const runbook: NormalizedBaseRecord[] = [...runbooks, ...chains, ...states, ...transitions, ...promotions, ...rollbacks, ...artifacts, ...configs, ...cadences, ...guardrails];
|
|
157
|
+
const workbook: NormalizedBaseRecord[] = [...workbooks, ...states, ...transitions, ...artifacts, ...guardrails];
|
|
158
|
+
profileRecords.set("core", core);
|
|
159
|
+
profileRecords.set("runbook", runbook);
|
|
160
|
+
profileRecords.set("workbook", workbook);
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
meta,
|
|
164
|
+
refs,
|
|
165
|
+
rules,
|
|
166
|
+
checks,
|
|
167
|
+
decisions,
|
|
168
|
+
examples,
|
|
169
|
+
terms,
|
|
170
|
+
commands,
|
|
171
|
+
workflows,
|
|
172
|
+
runbooks,
|
|
173
|
+
workbooks,
|
|
174
|
+
chains,
|
|
175
|
+
states,
|
|
176
|
+
transitions,
|
|
177
|
+
promotions,
|
|
178
|
+
rollbacks,
|
|
179
|
+
artifacts,
|
|
180
|
+
configs,
|
|
181
|
+
cadences,
|
|
182
|
+
guardrails,
|
|
183
|
+
refsById,
|
|
184
|
+
examplesByParent,
|
|
185
|
+
profileRecords
|
|
186
|
+
};
|
|
187
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export type AiRecord = {
|
|
2
|
+
type: string;
|
|
3
|
+
kv: Record<string, string>;
|
|
4
|
+
raw: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export function parseAiRecordLine(line: string): AiRecord | null {
|
|
8
|
+
const trimmed = line.trim();
|
|
9
|
+
if (!trimmed || trimmed.startsWith("#")) return null;
|
|
10
|
+
const parts = trimmed.split("|");
|
|
11
|
+
if (parts.length < 2) return null;
|
|
12
|
+
const type = parts[0]?.trim() ?? "";
|
|
13
|
+
if (!type) return null;
|
|
14
|
+
|
|
15
|
+
const kv: Record<string, string> = {};
|
|
16
|
+
let slotIndex = 1;
|
|
17
|
+
for (const token of parts.slice(1)) {
|
|
18
|
+
const piece = token.trim();
|
|
19
|
+
if (!piece) continue;
|
|
20
|
+
const idx = piece.indexOf("=");
|
|
21
|
+
if (idx >= 0) {
|
|
22
|
+
const key = piece.slice(0, idx).trim();
|
|
23
|
+
const value = piece.slice(idx + 1).trim();
|
|
24
|
+
if (key) kv[key] = value;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
// Transitional support: retain unkeyed tokens as synthetic slots.
|
|
28
|
+
kv[`slot${slotIndex}`] = piece;
|
|
29
|
+
slotIndex += 1;
|
|
30
|
+
}
|
|
31
|
+
return { type, kv, raw: line };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function parseAiDocument(text: string): AiRecord[] {
|
|
35
|
+
const records: AiRecord[] = [];
|
|
36
|
+
for (const line of text.split("\n")) {
|
|
37
|
+
const rec = parseAiRecordLine(line);
|
|
38
|
+
if (rec) records.push(rec);
|
|
39
|
+
}
|
|
40
|
+
return records;
|
|
41
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Policy-gated `workspace-kit run` commands owned by the documentation module.
|
|
3
|
+
* Keep in sync with instruction names in `documentation/index.ts`.
|
|
4
|
+
*/
|
|
5
|
+
export const DOCUMENTATION_POLICY_COMMAND_NAMES = [
|
|
6
|
+
["document-project", "doc.document-project"],
|
|
7
|
+
["generate-document", "doc.generate-document"]
|
|
8
|
+
] as const;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
NormalizedCheck,
|
|
3
|
+
NormalizedCommand,
|
|
4
|
+
NormalizedDecision,
|
|
5
|
+
NormalizedDocument,
|
|
6
|
+
NormalizedRule,
|
|
7
|
+
NormalizedTerm,
|
|
8
|
+
NormalizedWorkflow,
|
|
9
|
+
ViewModelDefinition,
|
|
10
|
+
ViewModelSection
|
|
11
|
+
} from "./types.js";
|
|
12
|
+
|
|
13
|
+
function stableSort(values: string[]): string[] {
|
|
14
|
+
return [...values].sort((a, b) => a.localeCompare(b));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function brief_summary(input: string[]): string {
|
|
18
|
+
if (input.length === 0) return "No summary records.";
|
|
19
|
+
return input.map((line) => `- ${line}`).join("\n");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function ordered_list(input: string[]): string {
|
|
23
|
+
if (input.length === 0) return "1. No entries";
|
|
24
|
+
return input.map((line, idx) => `${idx + 1}. ${line}`).join("\n");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function rule_table(rules: NormalizedRule[]): string {
|
|
28
|
+
if (rules.length === 0) return "_No rules_";
|
|
29
|
+
const rows = [...rules].sort((a, b) => a.id.localeCompare(b.id));
|
|
30
|
+
const body = rows
|
|
31
|
+
.map((r) => `| ${r.id} | ${r.level} | ${r.scope || "-"} | ${r.directive || "-"} | ${r.why || "-"} |`)
|
|
32
|
+
.join("\n");
|
|
33
|
+
return `| ID | Level | Scope | Directive | Why |\n|---|---|---|---|---|\n${body}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function check_table(checks: NormalizedCheck[]): string {
|
|
37
|
+
if (checks.length === 0) return "_No checks_";
|
|
38
|
+
const rows = [...checks].sort((a, b) => a.id.localeCompare(b.id));
|
|
39
|
+
const body = rows
|
|
40
|
+
.map((c) => `| ${c.id} | ${c.scope || "-"} | ${c.assertion || "-"} | ${c.onFail || "-"} |`)
|
|
41
|
+
.join("\n");
|
|
42
|
+
return `| ID | Scope | Assertion | On Fail |\n|---|---|---|---|\n${body}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function command_reference(commands: NormalizedCommand[]): string {
|
|
46
|
+
if (commands.length === 0) return "_No commands_";
|
|
47
|
+
const rows = [...commands].sort((a, b) => a.name.localeCompare(b.name));
|
|
48
|
+
return rows.map((c) => `- \`${c.name}\`: ${c.expectation || c.use || "No expectation"}`).join("\n");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function decision_section(decisions: NormalizedDecision[]): string {
|
|
52
|
+
if (decisions.length === 0) return "_No decisions_";
|
|
53
|
+
const rows = [...decisions].sort((a, b) => a.id.localeCompare(b.id));
|
|
54
|
+
return rows.map((d) => `### ${d.id}: ${d.topic}\n- Choice: ${d.choice}\n- Why: ${d.why}`).join("\n\n");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function term_list(terms: NormalizedTerm[]): string {
|
|
58
|
+
if (terms.length === 0) return "_No terms_";
|
|
59
|
+
return [...terms]
|
|
60
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
61
|
+
.map((t) => `- **${t.name}**: ${t.definition}`)
|
|
62
|
+
.join("\n");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function workflow_steps(workflows: NormalizedWorkflow[]): string {
|
|
66
|
+
if (workflows.length === 0) return "_No workflows_";
|
|
67
|
+
return workflows
|
|
68
|
+
.sort((a, b) => a.id.localeCompare(b.id))
|
|
69
|
+
.map((wf) => `### ${wf.id}: ${wf.name}\n${ordered_list(stableSort(wf.steps))}`)
|
|
70
|
+
.join("\n\n");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function chain_steps(chains: Array<{ step: string; command: string; expectExit: number }>): string {
|
|
74
|
+
if (chains.length === 0) return "_No chain steps_";
|
|
75
|
+
return chains
|
|
76
|
+
.map((c, idx) => `${idx + 1}. ${c.step} -> \`${c.command}\` (expect ${c.expectExit})`)
|
|
77
|
+
.join("\n");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function ref_table(refs: Array<{ id: string; type: string; target: string }>): string {
|
|
81
|
+
if (refs.length === 0) return "_No refs_";
|
|
82
|
+
const body = [...refs]
|
|
83
|
+
.sort((a, b) => a.id.localeCompare(b.id))
|
|
84
|
+
.map((r) => `| ${r.id} | ${r.type} | ${r.target} |`)
|
|
85
|
+
.join("\n");
|
|
86
|
+
return `| ID | Type | Target |\n|---|---|---|\n${body}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export const renderMetaSection = (doc: NormalizedDocument) =>
|
|
90
|
+
brief_summary([
|
|
91
|
+
`doc=${doc.meta?.doc ?? "unknown"}`,
|
|
92
|
+
`truth=${doc.meta?.truth ?? "unknown"}`,
|
|
93
|
+
`profile=${doc.meta?.profile ?? "core"}`
|
|
94
|
+
]);
|
|
95
|
+
export const renderRuleSection = (doc: NormalizedDocument) => rule_table(doc.rules);
|
|
96
|
+
export const renderDecisionSection = (doc: NormalizedDocument) => decision_section(doc.decisions);
|
|
97
|
+
|
|
98
|
+
function renderSection(doc: NormalizedDocument, section: ViewModelSection): string {
|
|
99
|
+
const byName: Record<string, (d: NormalizedDocument) => string> = {
|
|
100
|
+
renderMetaSection,
|
|
101
|
+
renderRuleSection,
|
|
102
|
+
renderDecisionSection,
|
|
103
|
+
brief_summary: (d) => brief_summary(d.examples.map((e) => e.text)),
|
|
104
|
+
ordered_list: (d) => ordered_list(d.commands.map((c) => c.name)),
|
|
105
|
+
rule_table: (d) => rule_table(d.rules),
|
|
106
|
+
check_table: (d) => check_table(d.checks),
|
|
107
|
+
command_reference: (d) => command_reference(d.commands),
|
|
108
|
+
decision_section: (d) => decision_section(d.decisions),
|
|
109
|
+
term_list: (d) => term_list(d.terms),
|
|
110
|
+
workflow_steps: (d) => workflow_steps(d.workflows),
|
|
111
|
+
chain_steps: (d) => chain_steps(d.chains),
|
|
112
|
+
ref_table: (d) => ref_table(d.refs)
|
|
113
|
+
};
|
|
114
|
+
const fn = byName[section.renderer] ?? (() => "_No renderer_");
|
|
115
|
+
const title = section.title ?? section.id;
|
|
116
|
+
return `## ${title}\n\n${fn(doc)}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function renderDocument(doc: NormalizedDocument, view: ViewModelDefinition): string {
|
|
120
|
+
return view.sections.map((s) => renderSection(doc, s)).join("\n\n").trim() + "\n";
|
|
121
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { ModuleLifecycleContext } from "../../contracts/module-contract.js";
|
|
2
|
+
import type { DocumentationBatchResult, DocumentationGenerateOptions, DocumentationGenerateResult } from "./types.js";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { readdir } from "node:fs/promises";
|
|
5
|
+
import { resolve } from "node:path";
|
|
6
|
+
import { listViewModels, loadViewModel } from "./view-models.js";
|
|
7
|
+
import { loadRuntimeConfig } from "./runtime-config.js";
|
|
8
|
+
|
|
9
|
+
type GenerateAllDocumentsArgs = {
|
|
10
|
+
options?: DocumentationGenerateOptions;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export async function runGenerateAllDocuments(
|
|
14
|
+
args: GenerateAllDocumentsArgs,
|
|
15
|
+
ctx: ModuleLifecycleContext,
|
|
16
|
+
generateOne: (args: { documentType?: string; options?: DocumentationGenerateOptions }, ctx: ModuleLifecycleContext) => Promise<DocumentationGenerateResult>
|
|
17
|
+
): Promise<DocumentationBatchResult> {
|
|
18
|
+
const config = await loadRuntimeConfig(ctx.workspacePath);
|
|
19
|
+
const workspaceViewsRoot = resolve(ctx.workspacePath, "src/modules/documentation/views");
|
|
20
|
+
const useWorkspaceViews = existsSync(workspaceViewsRoot);
|
|
21
|
+
const workItems: Array<{ documentType: string }> = [];
|
|
22
|
+
if (useWorkspaceViews) {
|
|
23
|
+
const viewFiles = await listViewModels(ctx.workspacePath);
|
|
24
|
+
for (const viewFile of viewFiles) {
|
|
25
|
+
const view = await loadViewModel(ctx.workspacePath, viewFile);
|
|
26
|
+
workItems.push({ documentType: view.target });
|
|
27
|
+
}
|
|
28
|
+
} else {
|
|
29
|
+
const templatesDir = resolve(config.sourceRoot, config.templatesRoot);
|
|
30
|
+
const listTemplateFiles = async (dir: string, baseDir: string): Promise<string[]> => {
|
|
31
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
32
|
+
const files: string[] = [];
|
|
33
|
+
for (const entry of entries) {
|
|
34
|
+
const absPath = resolve(dir, entry.name);
|
|
35
|
+
if (entry.isDirectory()) files.push(...(await listTemplateFiles(absPath, baseDir)));
|
|
36
|
+
if (entry.isFile() && entry.name.endsWith(".md")) files.push(absPath.slice(baseDir.length + 1).split("\\").join("/"));
|
|
37
|
+
}
|
|
38
|
+
return files;
|
|
39
|
+
};
|
|
40
|
+
for (const templateFile of (await listTemplateFiles(templatesDir, templatesDir)).sort()) {
|
|
41
|
+
workItems.push({ documentType: templateFile });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const results: DocumentationGenerateResult[] = [];
|
|
45
|
+
let succeeded = 0;
|
|
46
|
+
let failed = 0;
|
|
47
|
+
let skipped = 0;
|
|
48
|
+
const batchOptions: DocumentationGenerateOptions = {
|
|
49
|
+
...args.options,
|
|
50
|
+
overwriteAi: args.options?.overwriteAi ?? false,
|
|
51
|
+
overwriteHuman: args.options?.overwriteHuman ?? true,
|
|
52
|
+
strict: args.options?.strict ?? false
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
for (const item of workItems) {
|
|
56
|
+
const result = await generateOne({ documentType: item.documentType, options: batchOptions }, ctx);
|
|
57
|
+
results.push(result);
|
|
58
|
+
if (!result.ok) failed += 1;
|
|
59
|
+
else if (result.evidence.filesWritten.length > 0) succeeded += 1;
|
|
60
|
+
else skipped += 1;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
ok: failed === 0,
|
|
65
|
+
results,
|
|
66
|
+
summary: {
|
|
67
|
+
total: workItems.length,
|
|
68
|
+
succeeded,
|
|
69
|
+
failed,
|
|
70
|
+
skipped,
|
|
71
|
+
timestamp: new Date().toISOString()
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|