@workflow-cannon/workspace-kit 0.8.0 → 0.9.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 +2 -1
- package/dist/cli/run-command.d.ts +11 -0
- package/dist/cli/run-command.js +138 -0
- package/dist/cli.js +18 -145
- package/dist/core/config-cli.js +4 -4
- package/dist/core/config-metadata.js +3 -3
- package/dist/core/policy.d.ts +9 -1
- package/dist/core/policy.js +39 -22
- package/dist/core/transcript-completion-hook.js +41 -3
- package/dist/core/workspace-kit-config.d.ts +2 -1
- package/dist/core/workspace-kit-config.js +4 -29
- package/dist/modules/approvals/index.js +2 -2
- package/dist/modules/documentation/runtime.js +30 -6
- package/dist/modules/improvement/index.js +15 -3
- package/dist/modules/improvement/transcript-sync-runtime.d.ts +5 -0
- package/dist/modules/improvement/transcript-sync-runtime.js +17 -0
- package/package.json +2 -1
- package/src/modules/documentation/README.md +39 -0
- package/src/modules/documentation/RULES.md +70 -0
- package/src/modules/documentation/config.md +14 -0
- package/src/modules/documentation/index.ts +120 -0
- package/src/modules/documentation/instructions/document-project.md +44 -0
- package/src/modules/documentation/instructions/documentation-maintainer.md +81 -0
- package/src/modules/documentation/instructions/generate-document.md +44 -0
- package/src/modules/documentation/runtime.ts +870 -0
- package/src/modules/documentation/schemas/documentation-schema.md +54 -0
- package/src/modules/documentation/state.md +8 -0
- package/src/modules/documentation/templates/AGENTS.md +84 -0
- package/src/modules/documentation/templates/ARCHITECTURE.md +71 -0
- package/src/modules/documentation/templates/PRINCIPLES.md +122 -0
- package/src/modules/documentation/templates/RELEASING.md +96 -0
- package/src/modules/documentation/templates/ROADMAP.md +131 -0
- package/src/modules/documentation/templates/SECURITY.md +53 -0
- package/src/modules/documentation/templates/SUPPORT.md +40 -0
- package/src/modules/documentation/templates/TERMS.md +61 -0
- package/src/modules/documentation/templates/runbooks/consumer-cadence.md +55 -0
- package/src/modules/documentation/templates/runbooks/parity-validation-flow.md +68 -0
- package/src/modules/documentation/templates/runbooks/release-channels.md +30 -0
- package/src/modules/documentation/templates/workbooks/phase2-config-policy-workbook.md +42 -0
- package/src/modules/documentation/templates/workbooks/task-engine-workbook.md +42 -0
- package/src/modules/documentation/templates/workbooks/transcript-automation-baseline.md +68 -0
- package/src/modules/documentation/types.ts +51 -0
|
@@ -21,7 +21,7 @@ export const KIT_CONFIG_DEFAULTS = {
|
|
|
21
21
|
},
|
|
22
22
|
improvement: {
|
|
23
23
|
transcripts: {
|
|
24
|
-
sourcePath: "
|
|
24
|
+
sourcePath: "",
|
|
25
25
|
archivePath: "agent-transcripts",
|
|
26
26
|
maxFilesPerSync: 5000,
|
|
27
27
|
maxBytesPerFile: 50_000_000,
|
|
@@ -40,37 +40,12 @@ export const KIT_CONFIG_DEFAULTS = {
|
|
|
40
40
|
};
|
|
41
41
|
/**
|
|
42
42
|
* Static module-level defaults keyed by module id (merged in registry startup order).
|
|
43
|
-
*
|
|
43
|
+
* Keep true defaults in KIT_CONFIG_DEFAULTS as the canonical source; use this map
|
|
44
|
+
* only when a module contributes additional non-default config structure.
|
|
44
45
|
*/
|
|
45
46
|
export const MODULE_CONFIG_CONTRIBUTIONS = {
|
|
46
|
-
"task-engine": {
|
|
47
|
-
tasks: {
|
|
48
|
-
storeRelativePath: ".workspace-kit/tasks/state.json"
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
|
-
documentation: {
|
|
52
|
-
documentation: {}
|
|
53
|
-
},
|
|
54
47
|
approvals: {},
|
|
55
|
-
planning: {}
|
|
56
|
-
improvement: {
|
|
57
|
-
transcripts: {
|
|
58
|
-
sourcePath: ".cursor/agent-transcripts",
|
|
59
|
-
archivePath: "agent-transcripts",
|
|
60
|
-
maxFilesPerSync: 5000,
|
|
61
|
-
maxBytesPerFile: 50_000_000,
|
|
62
|
-
maxTotalScanBytes: 500_000_000,
|
|
63
|
-
discoveryPaths: []
|
|
64
|
-
},
|
|
65
|
-
cadence: {
|
|
66
|
-
minIntervalMinutes: 15,
|
|
67
|
-
skipIfNoNewTranscripts: true,
|
|
68
|
-
maxRecommendationCandidatesPerRun: 500
|
|
69
|
-
},
|
|
70
|
-
hooks: {
|
|
71
|
-
afterTaskCompleted: "off"
|
|
72
|
-
}
|
|
73
|
-
}
|
|
48
|
+
planning: {}
|
|
74
49
|
};
|
|
75
50
|
export function deepMerge(target, source) {
|
|
76
51
|
const out = { ...target };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveActorWithFallback } from "../../core/policy.js";
|
|
2
2
|
import { runReviewItem } from "./review-runtime.js";
|
|
3
3
|
export const approvalsModule = {
|
|
4
4
|
registration: {
|
|
@@ -40,7 +40,7 @@ export const approvalsModule = {
|
|
|
40
40
|
const args = command.args ?? {};
|
|
41
41
|
const actor = typeof args.actor === "string" && args.actor.trim().length > 0
|
|
42
42
|
? args.actor.trim()
|
|
43
|
-
: ctx.resolvedActor ??
|
|
43
|
+
: ctx.resolvedActor ?? (await resolveActorWithFallback(ctx.workspacePath, args, process.env));
|
|
44
44
|
const taskId = typeof args.taskId === "string" ? args.taskId : "";
|
|
45
45
|
const decision = args.decision;
|
|
46
46
|
if (decision !== "accept" && decision !== "decline" && decision !== "accept_edited") {
|
|
@@ -2,6 +2,7 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { dirname, resolve, sep } from "node:path";
|
|
4
4
|
import { readdir } from "node:fs/promises";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
5
6
|
function isPathWithinRoot(path, root) {
|
|
6
7
|
return path === root || path.startsWith(`${root}${sep}`);
|
|
7
8
|
}
|
|
@@ -12,8 +13,30 @@ function parseDefaultValue(fileContent, key, fallback) {
|
|
|
12
13
|
return match?.[1] ?? fallback;
|
|
13
14
|
}
|
|
14
15
|
async function loadRuntimeConfig(workspacePath) {
|
|
15
|
-
const
|
|
16
|
-
const
|
|
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
|
+
}
|
|
17
40
|
const aiRoot = parseDefaultValue(configContent, "sources.aiRoot", "/.ai");
|
|
18
41
|
const humanRoot = parseDefaultValue(configContent, "sources.humanRoot", "docs/maintainers");
|
|
19
42
|
const templatesRoot = parseDefaultValue(configContent, "sources.templatesRoot", "src/modules/documentation/templates");
|
|
@@ -27,7 +50,8 @@ async function loadRuntimeConfig(workspacePath) {
|
|
|
27
50
|
templatesRoot,
|
|
28
51
|
instructionsRoot,
|
|
29
52
|
schemasRoot,
|
|
30
|
-
maxValidationAttempts: Number.isFinite(maxValidationAttempts) ? maxValidationAttempts : 3
|
|
53
|
+
maxValidationAttempts: Number.isFinite(maxValidationAttempts) ? maxValidationAttempts : 3,
|
|
54
|
+
sourceRoot
|
|
31
55
|
};
|
|
32
56
|
}
|
|
33
57
|
function parseAiRecordLine(line) {
|
|
@@ -441,7 +465,7 @@ export async function generateDocument(args, ctx) {
|
|
|
441
465
|
const conflicts = [];
|
|
442
466
|
const aiRoot = resolve(ctx.workspacePath, config.aiRoot.replace(/^\//, ""));
|
|
443
467
|
const humanRoot = resolve(ctx.workspacePath, config.humanRoot.replace(/^\//, ""));
|
|
444
|
-
const templatePath = resolve(
|
|
468
|
+
const templatePath = resolve(config.sourceRoot, config.templatesRoot, documentType);
|
|
445
469
|
const aiOutputPath = resolve(aiRoot, documentType);
|
|
446
470
|
const humanOutputPath = resolve(humanRoot, documentType);
|
|
447
471
|
if (!isPathWithinRoot(aiOutputPath, aiRoot) || !isPathWithinRoot(humanOutputPath, humanRoot)) {
|
|
@@ -493,7 +517,7 @@ export async function generateDocument(args, ctx) {
|
|
|
493
517
|
};
|
|
494
518
|
}
|
|
495
519
|
}
|
|
496
|
-
const schemaPath = resolve(
|
|
520
|
+
const schemaPath = resolve(config.sourceRoot, config.schemasRoot, "documentation-schema.md");
|
|
497
521
|
if (existsSync(schemaPath)) {
|
|
498
522
|
filesRead.push(schemaPath);
|
|
499
523
|
await readFile(schemaPath, "utf8");
|
|
@@ -663,7 +687,7 @@ export async function generateDocument(args, ctx) {
|
|
|
663
687
|
}
|
|
664
688
|
export async function generateAllDocuments(args, ctx) {
|
|
665
689
|
const config = await loadRuntimeConfig(ctx.workspacePath);
|
|
666
|
-
const templatesDir = resolve(
|
|
690
|
+
const templatesDir = resolve(config.sourceRoot, config.templatesRoot);
|
|
667
691
|
async function listTemplateFiles(dir, baseDir) {
|
|
668
692
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
669
693
|
const files = [];
|
|
@@ -58,13 +58,25 @@ export const improvementModule = {
|
|
|
58
58
|
const transcriptsRoot = typeof args.transcriptsRoot === "string" ? args.transcriptsRoot : undefined;
|
|
59
59
|
const fromTag = typeof args.fromTag === "string" ? args.fromTag : undefined;
|
|
60
60
|
const toTag = typeof args.toTag === "string" ? args.toTag : undefined;
|
|
61
|
+
const syncArgs = {
|
|
62
|
+
sourcePath: typeof args.sourcePath === "string" ? args.sourcePath : undefined,
|
|
63
|
+
archivePath: transcriptsRoot
|
|
64
|
+
};
|
|
61
65
|
try {
|
|
62
|
-
const
|
|
66
|
+
const state = await loadImprovementState(ctx.workspacePath);
|
|
67
|
+
const sync = await runSyncTranscripts(ctx, syncArgs, state);
|
|
68
|
+
state.lastSyncRunAt = new Date().toISOString();
|
|
69
|
+
await saveImprovementState(ctx.workspacePath, state);
|
|
70
|
+
const result = await runGenerateRecommendations(ctx, {
|
|
71
|
+
transcriptsRoot: sync.archivePath,
|
|
72
|
+
fromTag,
|
|
73
|
+
toTag
|
|
74
|
+
});
|
|
63
75
|
return {
|
|
64
76
|
ok: true,
|
|
65
77
|
code: "recommendations-generated",
|
|
66
|
-
message: `
|
|
67
|
-
data: result
|
|
78
|
+
message: `After sync (${sync.copied} copied): created ${result.created.length} improvement task(s); skipped ${result.skipped} duplicate(s)`,
|
|
79
|
+
data: { sync, ...result }
|
|
68
80
|
};
|
|
69
81
|
}
|
|
70
82
|
catch (e) {
|
|
@@ -47,6 +47,11 @@ type ImprovementTranscriptConfig = {
|
|
|
47
47
|
discoveryPaths: string[];
|
|
48
48
|
};
|
|
49
49
|
export declare function resolveImprovementTranscriptConfig(ctx: ModuleLifecycleContext, args: TranscriptSyncArgs): ImprovementTranscriptConfig;
|
|
50
|
+
/**
|
|
51
|
+
* Cursor stores agent transcripts under `~/.cursor/projects/<slug>/agent-transcripts`, where `slug`
|
|
52
|
+
* is the workspace root with path separators replaced by hyphens (drive letter included on Windows).
|
|
53
|
+
*/
|
|
54
|
+
export declare function buildCursorProjectsAgentTranscriptsPath(workspacePath: string): string;
|
|
50
55
|
export declare function resolveTranscriptSourceRoot(workspacePath: string, cfg: ImprovementTranscriptConfig, args: TranscriptSyncArgs): Promise<{
|
|
51
56
|
root: string;
|
|
52
57
|
discoveredFrom: string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
5
|
const DEFAULT_DISCOVERY_PATHS = [".cursor/agent-transcripts", ".vscode/agent-transcripts"];
|
|
@@ -58,6 +59,16 @@ async function pathExists(abs) {
|
|
|
58
59
|
return false;
|
|
59
60
|
}
|
|
60
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Cursor stores agent transcripts under `~/.cursor/projects/<slug>/agent-transcripts`, where `slug`
|
|
64
|
+
* is the workspace root with path separators replaced by hyphens (drive letter included on Windows).
|
|
65
|
+
*/
|
|
66
|
+
export function buildCursorProjectsAgentTranscriptsPath(workspacePath) {
|
|
67
|
+
const home = os.homedir();
|
|
68
|
+
const resolved = path.resolve(workspacePath);
|
|
69
|
+
const slug = resolved.split(path.sep).filter((s) => s.length > 0).join("-");
|
|
70
|
+
return path.join(home, ".cursor", "projects", slug, "agent-transcripts");
|
|
71
|
+
}
|
|
61
72
|
export async function resolveTranscriptSourceRoot(workspacePath, cfg, args) {
|
|
62
73
|
const sourcePathArg = typeof args.sourcePath === "string" ? args.sourcePath.trim() : "";
|
|
63
74
|
if (sourcePathArg) {
|
|
@@ -76,6 +87,12 @@ export async function resolveTranscriptSourceRoot(workspacePath, cfg, args) {
|
|
|
76
87
|
return { root: abs, discoveredFrom: rel, tried };
|
|
77
88
|
}
|
|
78
89
|
}
|
|
90
|
+
const cursorGlobal = buildCursorProjectsAgentTranscriptsPath(workspacePath);
|
|
91
|
+
const cursorLabel = path.join("~", ".cursor", "projects", path.basename(path.dirname(cursorGlobal)), "agent-transcripts");
|
|
92
|
+
tried.push(cursorLabel);
|
|
93
|
+
if (await pathExists(cursorGlobal)) {
|
|
94
|
+
return { root: cursorGlobal, discoveredFrom: "cursor-global-project-agent-transcripts", tried };
|
|
95
|
+
}
|
|
79
96
|
const fallback = path.resolve(workspacePath, ".cursor/agent-transcripts");
|
|
80
97
|
return { root: fallback, discoveredFrom: ".cursor/agent-transcripts (fallback)", tried };
|
|
81
98
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workflow-cannon/workspace-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"packageManager": "pnpm@10.0.0",
|
|
6
6
|
"license": "MIT",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
},
|
|
43
43
|
"files": [
|
|
44
44
|
"dist",
|
|
45
|
+
"src/modules/documentation",
|
|
45
46
|
"package.json"
|
|
46
47
|
]
|
|
47
48
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Documentation Module
|
|
2
|
+
|
|
3
|
+
Translates between canonical AI-optimized documentation and human-readable maintainership docs.
|
|
4
|
+
|
|
5
|
+
Primary responsibilities:
|
|
6
|
+
|
|
7
|
+
- maintain parity between `/.ai` and configured human docs roots (default: `docs/maintainers`)
|
|
8
|
+
- execute instruction-file driven documentation generation
|
|
9
|
+
- record deterministic documentation generation state and evidence
|
|
10
|
+
|
|
11
|
+
See `src/modules/documentation/RULES.md` for the canonical usage order and validation rules.
|
|
12
|
+
|
|
13
|
+
## Callable commands
|
|
14
|
+
|
|
15
|
+
Registered on the documentation module and dispatched through `src/core/module-command-router.ts`:
|
|
16
|
+
|
|
17
|
+
- `document-project` — batch: generate **all** project docs from the template library. Outputs AI docs to `.ai/` (preserving existing) and human docs to `docs/maintainers/` (overwriting). Continues through failures; reports batch summary. See `instructions/document-project.md`.
|
|
18
|
+
- `generate-document` — single: generate **one** document by `documentType`. See `instructions/generate-document.md`.
|
|
19
|
+
|
|
20
|
+
## Shipped templates
|
|
21
|
+
|
|
22
|
+
Files under `templates/`; `documentType` is the filename (basename). Keep this list aligned with `instructions/document-project.md` **Inputs**:
|
|
23
|
+
|
|
24
|
+
- `AGENTS.md`
|
|
25
|
+
- `ARCHITECTURE.md`
|
|
26
|
+
- `PRINCIPLES.md`
|
|
27
|
+
- `RELEASING.md`
|
|
28
|
+
- `ROADMAP.md`
|
|
29
|
+
- `SECURITY.md`
|
|
30
|
+
- `SUPPORT.md`
|
|
31
|
+
- `TERMS.md`
|
|
32
|
+
- `runbooks/parity-validation-flow.md`
|
|
33
|
+
- `runbooks/consumer-cadence.md`
|
|
34
|
+
- `runbooks/release-channels.md`
|
|
35
|
+
- `workbooks/transcript-automation-baseline.md`
|
|
36
|
+
- `workbooks/phase2-config-policy-workbook.md`
|
|
37
|
+
- `workbooks/task-engine-workbook.md`
|
|
38
|
+
|
|
39
|
+
Adding a new template: add `templates/<Name>.md`, extend the **Inputs** list in `instructions/document-project.md`, and add the same line here.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Documentation Module Rules
|
|
2
|
+
|
|
3
|
+
Single entrypoint for how to use the documentation module.
|
|
4
|
+
|
|
5
|
+
## Precedence Order
|
|
6
|
+
|
|
7
|
+
When guidance conflicts, apply this order:
|
|
8
|
+
|
|
9
|
+
1. `/.ai/PRINCIPLES.md` (global governance and approval gates)
|
|
10
|
+
2. `/.ai/module-build.md` (module development and validation rules)
|
|
11
|
+
3. `src/modules/documentation/config.md` (module path policy and generation behavior)
|
|
12
|
+
4. `src/modules/documentation/instructions/document-project.md` (document generation workflow)
|
|
13
|
+
5. `src/modules/documentation/instructions/documentation-maintainer.md` (AI-doc generation policy)
|
|
14
|
+
6. `src/modules/documentation/schemas/documentation-schema.md` (record schema contract)
|
|
15
|
+
7. `src/modules/documentation/templates/*.md` (document-type generation templates)
|
|
16
|
+
8. `docs/maintainers/module-build-guide.md` (human-readable companion guidance)
|
|
17
|
+
|
|
18
|
+
## Usage Model
|
|
19
|
+
|
|
20
|
+
- Choose an instruction entry from `instructions/` for the operation you want.
|
|
21
|
+
- Discover available callable module operations through `src/core/module-command-router.ts` command listing.
|
|
22
|
+
- Load module config first and restrict writes to configured document paths.
|
|
23
|
+
- If a matching template exists, use it as the structure contract.
|
|
24
|
+
- For templates with `{{{ ... }}}` blocks, treat block content as generation instructions, not output text.
|
|
25
|
+
- Generate AI-surface content first, then generate human-surface content from that result plus project context.
|
|
26
|
+
|
|
27
|
+
## Command Contracts
|
|
28
|
+
|
|
29
|
+
### `document-project(options)` — batch
|
|
30
|
+
|
|
31
|
+
Generates all project docs by iterating every `.md` template in `sources.templatesRoot`.
|
|
32
|
+
|
|
33
|
+
- Default behavior: **preserve AI docs** (`overwriteAi: false`), **overwrite human docs** (`overwriteHuman: true`), continue on individual failure.
|
|
34
|
+
- Returns batch summary with total/succeeded/failed/skipped counts plus per-document results.
|
|
35
|
+
|
|
36
|
+
### `generate-document(documentType, options)` — single
|
|
37
|
+
|
|
38
|
+
Generates one document pair (AI + human) for the given `documentType`.
|
|
39
|
+
|
|
40
|
+
- `documentType`: required string basename resolving to `<templatesRoot>/<documentType>`.
|
|
41
|
+
- `options`:
|
|
42
|
+
- `dryRun?: boolean` (default `false`) - compute outputs/validations without writing files
|
|
43
|
+
- `overwrite?: boolean` (default `true`) - allow replacing existing files (both surfaces)
|
|
44
|
+
- `overwriteAi?: boolean` - override `overwrite` for AI surface only
|
|
45
|
+
- `overwriteHuman?: boolean` - override `overwrite` for human surface only
|
|
46
|
+
- `strict?: boolean` (default `true`) - fail on unresolved warnings (validation/conflict/coverage)
|
|
47
|
+
- `maxValidationAttempts?: number` (default from config) - override retry limit
|
|
48
|
+
- `allowWithoutTemplate?: boolean` (default `false`) - continue without template only when explicitly confirmed
|
|
49
|
+
- Shipped template basenames are listed in `instructions/generate-document.md` and `instructions/document-project.md`.
|
|
50
|
+
|
|
51
|
+
### Shared semantics
|
|
52
|
+
|
|
53
|
+
- Both commands read paths from module config (`sources.aiRoot`, `sources.humanRoot`, `sources.templatesRoot`, `sources.instructionsRoot`, `sources.schemasRoot`).
|
|
54
|
+
- Both enforce write boundaries to configured output roots only.
|
|
55
|
+
- Both execute AI generation first, then human generation from AI output plus project context.
|
|
56
|
+
- Both return evidence objects containing files read/written, validations, retries, warnings/conflicts, and timestamp.
|
|
57
|
+
|
|
58
|
+
## Required Validation
|
|
59
|
+
|
|
60
|
+
- Validate AI output against `schemas/documentation-schema.md`.
|
|
61
|
+
- Verify section coverage for templated documents and ensure no unresolved `{{{` blocks remain.
|
|
62
|
+
- Detect conflicts against higher-precedence sources and stop/prompt when required.
|
|
63
|
+
- Emit run evidence (inputs, outputs, validation results, timestamp).
|
|
64
|
+
|
|
65
|
+
## Missing Template Behavior
|
|
66
|
+
|
|
67
|
+
- If a template for the requested document type is missing:
|
|
68
|
+
- warn the user
|
|
69
|
+
- ask whether to continue without a template
|
|
70
|
+
- continue only with explicit confirmation
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Documentation Module Config
|
|
2
|
+
|
|
3
|
+
Defines module-level configuration keys used by the documentation module.
|
|
4
|
+
|
|
5
|
+
- `sources.aiRoot`: canonical AI docs root (default: `/.ai`)
|
|
6
|
+
- `sources.humanRoot`: human docs root (default: `docs/maintainers`)
|
|
7
|
+
- `sources.templatesRoot`: document template root (default: `src/modules/documentation/templates`)
|
|
8
|
+
- `sources.instructionsRoot`: instruction root (default: `src/modules/documentation/instructions`)
|
|
9
|
+
- `sources.schemasRoot`: schema root (default: `src/modules/documentation/schemas`)
|
|
10
|
+
- `generation.enforceParity`: fail generation when AI and human surfaces diverge
|
|
11
|
+
- `generation.enforceSectionCoverage`: fail when expected template sections are missing in output
|
|
12
|
+
- `generation.resolveOrRetryOnValidationError`: auto-resolve issues or retry generation before returning failure
|
|
13
|
+
- `generation.maxValidationAttempts`: maximum validate/retry attempts (default: `3`)
|
|
14
|
+
- `generation.allowTemplatelessFallback`: when template is missing, require user confirmation before continuing
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { WorkflowModule } from "../../contracts/module-contract.js";
|
|
2
|
+
import { generateDocument, generateAllDocuments } from "./runtime.js";
|
|
3
|
+
export type {
|
|
4
|
+
DocumentationBatchResult,
|
|
5
|
+
DocumentationConflict,
|
|
6
|
+
DocumentationGenerateOptions,
|
|
7
|
+
DocumentationGenerateResult,
|
|
8
|
+
DocumentationGenerationEvidence,
|
|
9
|
+
DocumentationValidationIssue
|
|
10
|
+
} from "./types.js";
|
|
11
|
+
import type { DocumentationGenerateOptions } from "./types.js";
|
|
12
|
+
|
|
13
|
+
function parseOptions(raw: Record<string, unknown>): DocumentationGenerateOptions {
|
|
14
|
+
return {
|
|
15
|
+
dryRun: typeof raw.dryRun === "boolean" ? raw.dryRun : undefined,
|
|
16
|
+
overwrite: typeof raw.overwrite === "boolean" ? raw.overwrite : undefined,
|
|
17
|
+
overwriteAi: typeof raw.overwriteAi === "boolean" ? raw.overwriteAi : undefined,
|
|
18
|
+
overwriteHuman: typeof raw.overwriteHuman === "boolean" ? raw.overwriteHuman : undefined,
|
|
19
|
+
strict: typeof raw.strict === "boolean" ? raw.strict : undefined,
|
|
20
|
+
maxValidationAttempts:
|
|
21
|
+
typeof raw.maxValidationAttempts === "number" ? raw.maxValidationAttempts : undefined,
|
|
22
|
+
allowWithoutTemplate:
|
|
23
|
+
typeof raw.allowWithoutTemplate === "boolean" ? raw.allowWithoutTemplate : undefined,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const documentationModule: WorkflowModule = {
|
|
28
|
+
registration: {
|
|
29
|
+
id: "documentation",
|
|
30
|
+
version: "0.2.0",
|
|
31
|
+
contractVersion: "1",
|
|
32
|
+
capabilities: ["documentation"],
|
|
33
|
+
dependsOn: [],
|
|
34
|
+
enabledByDefault: true,
|
|
35
|
+
config: {
|
|
36
|
+
path: "src/modules/documentation/config.md",
|
|
37
|
+
format: "md",
|
|
38
|
+
description: "Documentation module configuration contract."
|
|
39
|
+
},
|
|
40
|
+
state: {
|
|
41
|
+
path: "src/modules/documentation/state.md",
|
|
42
|
+
format: "md",
|
|
43
|
+
description: "Documentation module generation/runtime state contract."
|
|
44
|
+
},
|
|
45
|
+
instructions: {
|
|
46
|
+
directory: "src/modules/documentation/instructions",
|
|
47
|
+
entries: [
|
|
48
|
+
{
|
|
49
|
+
name: "document-project",
|
|
50
|
+
file: "document-project.md",
|
|
51
|
+
description: "Generate all project docs from templates to .ai and docs/maintainers surfaces."
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "generate-document",
|
|
55
|
+
file: "generate-document.md",
|
|
56
|
+
description: "Generate a single document by type for .ai and docs/maintainers surfaces."
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
async onCommand(command, ctx) {
|
|
62
|
+
const args = command.args ?? {};
|
|
63
|
+
const rawOptions: Record<string, unknown> =
|
|
64
|
+
typeof args.options === "object" && args.options !== null
|
|
65
|
+
? (args.options as Record<string, unknown>)
|
|
66
|
+
: {};
|
|
67
|
+
const options = parseOptions(rawOptions);
|
|
68
|
+
|
|
69
|
+
if (command.name === "document-project") {
|
|
70
|
+
const batchResult = await generateAllDocuments({ options }, ctx);
|
|
71
|
+
return {
|
|
72
|
+
ok: batchResult.ok,
|
|
73
|
+
code: batchResult.ok ? "documented-project" : "documentation-batch-failed",
|
|
74
|
+
message: batchResult.ok
|
|
75
|
+
? `Generated ${batchResult.summary.succeeded} documents (${batchResult.summary.skipped} skipped)`
|
|
76
|
+
: `Batch failed: ${batchResult.summary.failed} of ${batchResult.summary.total} documents failed`,
|
|
77
|
+
data: {
|
|
78
|
+
summary: batchResult.summary,
|
|
79
|
+
results: batchResult.results.map((r) => ({
|
|
80
|
+
documentType: r.evidence.documentType,
|
|
81
|
+
ok: r.ok,
|
|
82
|
+
aiOutputPath: r.aiOutputPath,
|
|
83
|
+
humanOutputPath: r.humanOutputPath,
|
|
84
|
+
filesWritten: r.evidence.filesWritten,
|
|
85
|
+
filesSkipped: r.evidence.filesSkipped,
|
|
86
|
+
}))
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (command.name === "generate-document") {
|
|
92
|
+
const result = await generateDocument(
|
|
93
|
+
{
|
|
94
|
+
documentType: typeof args.documentType === "string" ? args.documentType : undefined,
|
|
95
|
+
options
|
|
96
|
+
},
|
|
97
|
+
ctx
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
ok: result.ok,
|
|
102
|
+
code: result.ok ? "generated-document" : "generation-failed",
|
|
103
|
+
message: result.ok
|
|
104
|
+
? `Generated document '${args.documentType ?? "unknown"}'`
|
|
105
|
+
: `Failed to generate document '${args.documentType ?? "unknown"}'`,
|
|
106
|
+
data: {
|
|
107
|
+
aiOutputPath: result.aiOutputPath,
|
|
108
|
+
humanOutputPath: result.humanOutputPath,
|
|
109
|
+
evidence: result.evidence
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
ok: false,
|
|
116
|
+
code: "unsupported-command",
|
|
117
|
+
message: `Documentation module does not support command '${command.name}'`
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# document-project
|
|
2
|
+
|
|
3
|
+
Generate all project documentation by running `generate-document` for every template in the template library. Outputs AI-optimized docs to `.ai/` and human-readable docs to `docs/maintainers/`.
|
|
4
|
+
|
|
5
|
+
## Inputs
|
|
6
|
+
|
|
7
|
+
- `options` (all optional):
|
|
8
|
+
- `dryRun?: boolean` — compute outputs/validations without writing files
|
|
9
|
+
- `overwriteAi?: boolean` — overwrite existing AI docs (default `false`)
|
|
10
|
+
- `overwriteHuman?: boolean` — overwrite existing human docs (default `true`)
|
|
11
|
+
- `strict?: boolean` — fail on unresolved warnings (default `false` in batch mode)
|
|
12
|
+
- `maxValidationAttempts?: number` — override retry limit per document
|
|
13
|
+
|
|
14
|
+
## Shipped templates
|
|
15
|
+
|
|
16
|
+
All `.md` files under `sources.templatesRoot` (default `src/modules/documentation/templates`) are processed:
|
|
17
|
+
|
|
18
|
+
- `AGENTS.md`
|
|
19
|
+
- `ARCHITECTURE.md`
|
|
20
|
+
- `PRINCIPLES.md`
|
|
21
|
+
- `RELEASING.md`
|
|
22
|
+
- `ROADMAP.md`
|
|
23
|
+
- `SECURITY.md`
|
|
24
|
+
- `SUPPORT.md`
|
|
25
|
+
- `TERMS.md`
|
|
26
|
+
- `runbooks/parity-validation-flow.md`
|
|
27
|
+
- `runbooks/consumer-cadence.md`
|
|
28
|
+
- `runbooks/release-channels.md`
|
|
29
|
+
- `workbooks/transcript-automation-baseline.md`
|
|
30
|
+
- `workbooks/phase2-config-policy-workbook.md`
|
|
31
|
+
- `workbooks/task-engine-workbook.md`
|
|
32
|
+
|
|
33
|
+
## Required behavior
|
|
34
|
+
|
|
35
|
+
1. Discover all `.md` templates in `sources.templatesRoot`.
|
|
36
|
+
2. For each template, invoke `generate-document` with the template basename as `documentType`.
|
|
37
|
+
3. Default overwrite behavior: **preserve AI docs** (`overwriteAi: false`), **overwrite human docs** (`overwriteHuman: true`).
|
|
38
|
+
4. Continue through all templates on individual failure; do not stop the batch.
|
|
39
|
+
5. Collect per-document results and emit a batch summary with total/succeeded/failed/skipped counts.
|
|
40
|
+
6. Return `ok: true` only when zero documents failed.
|
|
41
|
+
|
|
42
|
+
## Skipped AI doc handling
|
|
43
|
+
|
|
44
|
+
When AI docs are skipped because they already exist (`filesSkipped` is non-empty in a document result), the calling agent **must** prompt the user before overwriting. Present the list of skipped AI doc paths and ask for explicit confirmation. If confirmed, re-run with `overwriteAi: true` for those documents.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
meta|v=1|doc=generator|truth=canonical|st=active
|
|
2
|
+
|
|
3
|
+
goal|produce=canonical_project_docs|opt=max_obedience_per_token|minimize=ambiguity,drift,token_waste
|
|
4
|
+
io|in=repo_files,code_config,existing_docs,core_schema|out=manifest,rules,map,workflows,commands,decisions,glossary,observed,planned,checks
|
|
5
|
+
authority|order=canonical_ai_docs>code_and_config_reality>generated_human_docs>narrative_docs
|
|
6
|
+
|
|
7
|
+
use|schema=src/modules/documentation/schemas/documentation-schema.md
|
|
8
|
+
create|files=.ai/00-manifest.md,.ai/01-rules.md,.ai/02-map.md,.ai/03-workflows.md,.ai/04-commands.md,.ai/05-decisions.md,.ai/06-glossary.md,.ai/07-observed.md,.ai/08-planned.md,.ai/09-checks.md
|
|
9
|
+
min_set|files=manifest,rules,map,workflows
|
|
10
|
+
|
|
11
|
+
author|one_record_per_line=true|meta_first_once=true|stable_order=true|omit_empty_optional=true|one_fact_per_record=true|exceptions_inline=true
|
|
12
|
+
author|explicit=level,scope,directive,trigger,done,approval,stop_prompt_behavior
|
|
13
|
+
author|forbid=vague_directives,undefined_shorthand,hidden_exceptions,softened_requirements,duplicate_meaning,manual_reordering_without_reason
|
|
14
|
+
|
|
15
|
+
classify|rule=intended_policy_for_future_action
|
|
16
|
+
classify|observed=current_reality_or_drift_not_policy
|
|
17
|
+
classify|planned=target_state_not_yet_true
|
|
18
|
+
classify|decision=compact_choice_plus_consequence
|
|
19
|
+
classify|check=validation_assertion
|
|
20
|
+
classify|term=project_specific_term_only
|
|
21
|
+
|
|
22
|
+
infer|allowed=project_identity,stack,repo_scope,path_roles,module_roles,commands,high_confidence_workflows,observed_facts
|
|
23
|
+
infer|forbid=unstated_policy,hidden_exceptions,preferred_style_without_evidence,intent_from_accidental_code_smells
|
|
24
|
+
infer|policy_from=explicit_docs,repeated_patterns_with_high_confidence,user_stated_preferences,active_decisions
|
|
25
|
+
infer|when_unclear=write_observed_or_draft_not_rule
|
|
26
|
+
|
|
27
|
+
rule|write=rule|RID|lvl|scope|directive|optional_fields
|
|
28
|
+
rule|good=add_migration,preserve_backward_compatibility,contain_business_logic,commit_secrets,manual_edit,add_or_update_tests
|
|
29
|
+
rule|bad=best_practices,clean_code,good_design,keep_it_simple,do_the_right_thing
|
|
30
|
+
rule|levels=must,must_not,should,may
|
|
31
|
+
rule|scope=concrete_and_stable
|
|
32
|
+
rule|exception=inline_unless
|
|
33
|
+
rule|interaction=ov=auto,warn,prompt,stop_when_needed
|
|
34
|
+
rule|new_id_if=meaning_changes
|
|
35
|
+
rule|keep_id_if=meaning_same_and_fields_refined
|
|
36
|
+
|
|
37
|
+
map|write=path_and_module_records_only
|
|
38
|
+
map|path_fields=role,has,xhas,deps,xdeps,check,st,refs
|
|
39
|
+
map|module_fields=role,owns,deps,xdeps,entry,tests,st,refs
|
|
40
|
+
map|require=ownership_boundaries_for_major_paths
|
|
41
|
+
map|forbid=generic_roles,misc_buckets
|
|
42
|
+
|
|
43
|
+
wf|write=wf|WID|name|when|do|done|optional_fields
|
|
44
|
+
wf|require=trigger_and_done_when_tasklike
|
|
45
|
+
wf|use=forbid,ask_if,halt_if,ap,risk_when_relevant
|
|
46
|
+
wf|common_first=true|risky_early=true|clarify_last=true
|
|
47
|
+
wf|stop_if=destructive_unapproved,policy_conflict_unresolved,unsafe_missing_info
|
|
48
|
+
wf|ask_if=multiple_valid_interpretations,required_choice_missing
|
|
49
|
+
|
|
50
|
+
cmd|write=canonical_commands_only
|
|
51
|
+
cmd|include=install,dev,test,lint,build_when_present
|
|
52
|
+
cmd|forbid=speculative_commands
|
|
53
|
+
|
|
54
|
+
decision|write=only_active_high_value_choices
|
|
55
|
+
decision|require=topic,choice
|
|
56
|
+
decision|prefer=why,then
|
|
57
|
+
decision|forbid=long_narrative
|
|
58
|
+
|
|
59
|
+
term|write=only_if_reduces_ambiguity
|
|
60
|
+
term|forbid=common_engineering_terms,indirection_debt
|
|
61
|
+
|
|
62
|
+
observed|write=for_drift_violations_legacy_patterns_notable_current_facts
|
|
63
|
+
planned|write=for_target_state_not_yet_implemented
|
|
64
|
+
check|write=for_required_validation_gates_and_done_assertions
|
|
65
|
+
|
|
66
|
+
order|manifest=meta,project,stack,prio,truth,ref
|
|
67
|
+
order|rules=global,risky,scoped,workflow,style
|
|
68
|
+
order|map=roots,major,special,legacy
|
|
69
|
+
order|workflows=common,risky,edge,clarify
|
|
70
|
+
|
|
71
|
+
validate|struct=allowed_record_types,required_fields,valid_enums,valid_ids,meta_first_once
|
|
72
|
+
validate|semantic=no_duplicate_active_ids,no_conflicting_active_rules_without_exception,no_vague_directives,no_observed_as_rule,no_planned_as_rule,no_unknown_paths_without_reason
|
|
73
|
+
validate|interaction=critical_secret_risk_stop,destructive_unapproved_stop_or_required,ambiguity_prompt_or_observed
|
|
74
|
+
|
|
75
|
+
stop|if=critical_secret_risk,destructive_change_without_approval,unresolved_policy_conflict,unsafe_missing_info
|
|
76
|
+
prompt|if=multiple_valid_interpretations,required_approval_missing,repo_intent_unclear_but_safe_to_ask
|
|
77
|
+
warn|if=should_violation,low_risk_uncertainty,minor_doc_drift
|
|
78
|
+
auto|if=low_risk_clear_routine_work
|
|
79
|
+
|
|
80
|
+
output|deterministic=true|preserve_existing_order_when_semantics_same=true|preserve_ids=true
|
|
81
|
+
output|never=soften_must_to_should,drop_unless,merge_distinct_rules_into_fuzzy_prose,invent_rationale
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# generate-document
|
|
2
|
+
|
|
3
|
+
Generate a single document for both canonical AI and human-readable surfaces using module config, schema, and templates.
|
|
4
|
+
|
|
5
|
+
## Inputs
|
|
6
|
+
|
|
7
|
+
- `documentType` (required): basename of the doc to generate; must match a file under `sources.templatesRoot` (default `src/modules/documentation/templates`). Known templates:
|
|
8
|
+
- `AGENTS.md`
|
|
9
|
+
- `ARCHITECTURE.md`
|
|
10
|
+
- `PRINCIPLES.md`
|
|
11
|
+
- `RELEASING.md`
|
|
12
|
+
- `ROADMAP.md`
|
|
13
|
+
- `SECURITY.md`
|
|
14
|
+
- `SUPPORT.md`
|
|
15
|
+
- `TERMS.md`
|
|
16
|
+
- `runbooks/parity-validation-flow.md`
|
|
17
|
+
- `runbooks/consumer-cadence.md`
|
|
18
|
+
- `runbooks/release-channels.md`
|
|
19
|
+
- `workbooks/transcript-automation-baseline.md`
|
|
20
|
+
- `workbooks/phase2-config-policy-workbook.md`
|
|
21
|
+
- `workbooks/task-engine-workbook.md`
|
|
22
|
+
- `options`:
|
|
23
|
+
- `dryRun?: boolean` — compute outputs/validations without writing files
|
|
24
|
+
- `overwrite?: boolean` — allow replacing existing files (default `true`)
|
|
25
|
+
- `overwriteAi?: boolean` — override `overwrite` for AI surface only
|
|
26
|
+
- `overwriteHuman?: boolean` — override `overwrite` for human surface only
|
|
27
|
+
- `strict?: boolean` — fail on unresolved warnings (default `true`)
|
|
28
|
+
- `maxValidationAttempts?: number` — override retry limit
|
|
29
|
+
- `allowWithoutTemplate?: boolean` — continue without template only when explicitly confirmed
|
|
30
|
+
|
|
31
|
+
## Required behavior
|
|
32
|
+
|
|
33
|
+
1. Read `src/modules/documentation/RULES.md` and apply precedence order before generation.
|
|
34
|
+
2. Load module config and resolve output roots from configured paths (`sources.aiRoot`, `sources.humanRoot`).
|
|
35
|
+
3. Restrict writes strictly to configured output roots; reject writes outside those roots.
|
|
36
|
+
4. Resolve template for `documentType` from `sources.templatesRoot`.
|
|
37
|
+
5. If template is missing, warn user and ask whether to continue without a template; continue only on explicit confirmation.
|
|
38
|
+
6. Generate AI output first at `<aiRoot>/<documentType>` using `documentation-maintainer.md` + `documentation-schema.md`.
|
|
39
|
+
7. Validate AI output against schema; on validation failure, auto-resolve/retry up to `generation.maxValidationAttempts` before failing.
|
|
40
|
+
8. Re-read generated AI output with project context, then generate human output at `<humanRoot>/<documentType>`.
|
|
41
|
+
9. For templates containing `{{{ ... }}}`, execute block contents as generation instructions and ensure no unresolved blocks remain in output.
|
|
42
|
+
10. Run section coverage validation (all required sections present, correct headings/order where required); retry/resolve on failure.
|
|
43
|
+
11. Detect conflicts with higher-precedence docs and stop/prompt when policy-sensitive or unresolved.
|
|
44
|
+
12. Emit run evidence: inputs, files read/written, validation results, retries used, conflict outcomes, timestamp.
|