@hivehub/rulebook 5.2.0 → 5.3.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/.claude/commands/analysis.md +35 -0
- package/.claude/commands/rulebook-task-apply.md +7 -25
- package/.claude/commands/rulebook-task-archive.md +10 -19
- package/.claude/commands/rulebook-task-create.md +1 -1
- package/README.md +354 -965
- package/dist/cli/commands/analysis.d.ts +8 -0
- package/dist/cli/commands/analysis.d.ts.map +1 -0
- package/dist/cli/commands/analysis.js +78 -0
- package/dist/cli/commands/analysis.js.map +1 -0
- package/dist/cli/commands/context-intelligence.d.ts +33 -0
- package/dist/cli/commands/context-intelligence.d.ts.map +1 -0
- package/dist/cli/commands/context-intelligence.js +181 -0
- package/dist/cli/commands/context-intelligence.js.map +1 -0
- package/dist/cli/commands/index.d.ts +19 -0
- package/dist/cli/commands/index.d.ts.map +1 -0
- package/dist/cli/commands/index.js +19 -0
- package/dist/cli/commands/index.js.map +1 -0
- package/dist/cli/commands/init.d.ts +15 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +608 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/mcp.d.ts +10 -0
- package/dist/cli/commands/mcp.d.ts.map +1 -0
- package/dist/cli/commands/mcp.js +128 -0
- package/dist/cli/commands/mcp.js.map +1 -0
- package/dist/cli/commands/memory.d.ts +24 -0
- package/dist/cli/commands/memory.d.ts.map +1 -0
- package/dist/cli/commands/memory.js +265 -0
- package/dist/cli/commands/memory.js.map +1 -0
- package/dist/cli/commands/misc.d.ts +33 -0
- package/dist/cli/commands/misc.d.ts.map +1 -0
- package/dist/cli/commands/misc.js +576 -0
- package/dist/cli/commands/misc.js.map +1 -0
- package/dist/cli/commands/plans.d.ts +15 -0
- package/dist/cli/commands/plans.d.ts.map +1 -0
- package/dist/cli/commands/plans.js +266 -0
- package/dist/cli/commands/plans.js.map +1 -0
- package/dist/cli/commands/ralph.d.ts +45 -0
- package/dist/cli/commands/ralph.d.ts.map +1 -0
- package/dist/cli/commands/ralph.js +694 -0
- package/dist/cli/commands/ralph.js.map +1 -0
- package/dist/cli/commands/skills.d.ts +9 -0
- package/dist/cli/commands/skills.d.ts.map +1 -0
- package/dist/cli/commands/skills.js +249 -0
- package/dist/cli/commands/skills.js.map +1 -0
- package/dist/cli/commands/task.d.ts +16 -0
- package/dist/cli/commands/task.d.ts.map +1 -0
- package/dist/cli/commands/task.js +256 -0
- package/dist/cli/commands/task.js.map +1 -0
- package/dist/cli/commands/update.d.ts +14 -0
- package/dist/cli/commands/update.d.ts.map +1 -0
- package/dist/cli/commands/update.js +636 -0
- package/dist/cli/commands/update.js.map +1 -0
- package/dist/cli/commands/workspace.d.ts +6 -0
- package/dist/cli/commands/workspace.d.ts.map +1 -0
- package/dist/cli/commands/workspace.js +141 -0
- package/dist/cli/commands/workspace.js.map +1 -0
- package/dist/core/agent-template-engine.js +28 -28
- package/dist/core/analysis-manager.d.ts +56 -0
- package/dist/core/analysis-manager.d.ts.map +1 -0
- package/dist/core/analysis-manager.js +218 -0
- package/dist/core/analysis-manager.js.map +1 -0
- package/dist/core/claude-md-generator.d.ts +52 -0
- package/dist/core/claude-md-generator.d.ts.map +1 -0
- package/dist/core/claude-md-generator.js +104 -0
- package/dist/core/claude-md-generator.js.map +1 -0
- package/dist/core/claude-settings-manager.d.ts +37 -0
- package/dist/core/claude-settings-manager.d.ts.map +1 -0
- package/dist/core/claude-settings-manager.js +168 -0
- package/dist/core/claude-settings-manager.js.map +1 -0
- package/dist/core/compact-context-manager.d.ts +34 -0
- package/dist/core/compact-context-manager.d.ts.map +1 -0
- package/dist/core/compact-context-manager.js +60 -0
- package/dist/core/compact-context-manager.js.map +1 -0
- package/dist/core/doctor.d.ts +19 -0
- package/dist/core/doctor.d.ts.map +1 -0
- package/dist/core/doctor.js +163 -0
- package/dist/core/doctor.js.map +1 -0
- package/dist/core/generator.js +28 -28
- package/dist/core/mcp-reference-generator.d.ts +13 -0
- package/dist/core/mcp-reference-generator.d.ts.map +1 -0
- package/dist/core/mcp-reference-generator.js +66 -0
- package/dist/core/mcp-reference-generator.js.map +1 -0
- package/dist/core/merger.d.ts +35 -0
- package/dist/core/merger.d.ts.map +1 -1
- package/dist/core/merger.js +120 -0
- package/dist/core/merger.js.map +1 -1
- package/dist/core/prd-generator.d.ts.map +1 -1
- package/dist/core/prd-generator.js +7 -1
- package/dist/core/prd-generator.js.map +1 -1
- package/dist/core/ralph-manager.d.ts.map +1 -1
- package/dist/core/ralph-manager.js +17 -0
- package/dist/core/ralph-manager.js.map +1 -1
- package/dist/core/rules-generator.d.ts +73 -0
- package/dist/core/rules-generator.d.ts.map +1 -0
- package/dist/core/rules-generator.js +201 -0
- package/dist/core/rules-generator.js.map +1 -0
- package/dist/core/state-writer.d.ts +35 -0
- package/dist/core/state-writer.d.ts.map +1 -0
- package/dist/core/state-writer.js +81 -0
- package/dist/core/state-writer.js.map +1 -0
- package/dist/core/task-manager.d.ts +35 -0
- package/dist/core/task-manager.d.ts.map +1 -1
- package/dist/core/task-manager.js +137 -38
- package/dist/core/task-manager.js.map +1 -1
- package/dist/core/telemetry.d.ts +29 -0
- package/dist/core/telemetry.d.ts.map +1 -0
- package/dist/core/telemetry.js +57 -0
- package/dist/core/telemetry.js.map +1 -0
- package/dist/core/workflow-generator.d.ts.map +1 -1
- package/dist/core/workflow-generator.js +2 -177
- package/dist/core/workflow-generator.js.map +1 -1
- package/dist/index.js +28 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/rulebook-server.d.ts.map +1 -1
- package/dist/mcp/rulebook-server.js +190 -7
- package/dist/mcp/rulebook-server.js.map +1 -1
- package/dist/memory/memory-store.js +91 -91
- package/dist/types.d.ts +11 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/gitignore.d.ts +10 -0
- package/dist/utils/gitignore.d.ts.map +1 -0
- package/dist/utils/gitignore.js +38 -0
- package/dist/utils/gitignore.js.map +1 -0
- package/package.json +1 -1
- package/templates/compact-context/_default.md +23 -0
- package/templates/compact-context/cpp.md +26 -0
- package/templates/compact-context/go.md +26 -0
- package/templates/compact-context/python.md +26 -0
- package/templates/compact-context/rust.md +28 -0
- package/templates/compact-context/typescript.md +29 -0
- package/templates/core/CLAUDE_MD_v2.md +71 -0
- package/templates/hooks/check-context-and-handoff.ps1 +50 -0
- package/templates/hooks/check-context-and-handoff.sh +69 -0
- package/templates/hooks/enforce-mcp-for-tasks.sh +31 -0
- package/templates/hooks/enforce-no-deferred.sh +21 -0
- package/templates/hooks/enforce-no-shortcuts.sh +31 -0
- package/templates/hooks/enforce-team-for-background-agents.ps1 +63 -0
- package/templates/hooks/enforce-team-for-background-agents.sh +55 -0
- package/templates/hooks/on-compact-reinject.sh +34 -0
- package/templates/hooks/resume-from-handoff.ps1 +33 -0
- package/templates/hooks/resume-from-handoff.sh +55 -0
- package/templates/rules/consult-analysis-before-implementing.md +23 -0
- package/templates/rules/cpp.md +46 -0
- package/templates/rules/csharp.md +44 -0
- package/templates/rules/diagnostic-first.md +39 -0
- package/templates/rules/fail-twice-escalate.md +46 -0
- package/templates/rules/go.md +40 -0
- package/templates/rules/java.md +43 -0
- package/templates/rules/javascript.md +39 -0
- package/templates/rules/multi-agent-teams.md +75 -0
- package/templates/rules/python.md +43 -0
- package/templates/rules/respect-handoff-trigger.md +41 -0
- package/templates/rules/rust.md +40 -0
- package/templates/rules/typescript.md +40 -0
- package/templates/skills/dev/analysis/SKILL.md +19 -0
- package/templates/skills/dev/handoff/SKILL.md +27 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rules-generator.d.ts","sourceRoot":"","sources":["../../src/core/rules-generator.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGnD;;;;;;;;;;;;;;GAcG;AAEH,eAAO,MAAM,SAAS,kBAAkB,CAAC;AACzC,eAAO,MAAM,kBAAkB,0CAA0C,CAAC;AAE1E;;;GAGG;AACH,eAAO,MAAM,wBAAwB,wFAS3B,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,eAAe,8IAMlB,CAAC;AAEX,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC;AAkB9E,MAAM,WAAW,qBAAqB;IACpC,wDAAwD;IACxD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,4EAA4E;IAC5E,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,4DAA4D;IAC5D,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,wBAAgB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,wBAAgB,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,qBAAqB,GAAG,MAAM,CAExF;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,qBAAqB,GAAG,IAAI,CAMlF;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,qBAAqB,GAAG,OAAO,CAAC,MAAM,CAAC,CAMvF;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE7D;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,IAAI,CAAC,eAAe,EAAE,WAAW,CAAC,GAC5C,OAAO,CAAC,qBAAqB,CAAC,CA2DhC;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,GAAG,MAAM,CAAC;IAC7B,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;CACxB;AAED,wBAAsB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAuBpF"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { readFile, writeFile, fileExists, ensureDir } from '../utils/file-system.js';
|
|
3
|
+
import { getTemplatesDir } from './generator.js';
|
|
4
|
+
/**
|
|
5
|
+
* v5.3.0 `.claude/rules/` generator.
|
|
6
|
+
*
|
|
7
|
+
* For every detected language that rulebook ships a template for, emit
|
|
8
|
+
* `.claude/rules/<language>.md` with YAML `paths:` frontmatter. These are
|
|
9
|
+
* Anthropic's path-scoped rules (loaded only when Claude reads a file
|
|
10
|
+
* matching the glob), so they cost zero context budget outside of the
|
|
11
|
+
* relevant file types.
|
|
12
|
+
*
|
|
13
|
+
* Files generated by rulebook carry a sentinel comment immediately after
|
|
14
|
+
* the frontmatter. On `rulebook update`, any `.claude/rules/*.md` without
|
|
15
|
+
* the sentinel is treated as user-authored and left alone. This lets users
|
|
16
|
+
* adopt a generated rule file as their own by simply deleting the sentinel
|
|
17
|
+
* comment line.
|
|
18
|
+
*/
|
|
19
|
+
export const RULES_DIR = '.claude/rules';
|
|
20
|
+
export const GENERATED_SENTINEL = 'Generated by @hivehub/rulebook v5.3.0';
|
|
21
|
+
/**
|
|
22
|
+
* Languages with shipped rule templates. Adding a new entry here requires a
|
|
23
|
+
* matching `templates/rules/<slug>.md` file.
|
|
24
|
+
*/
|
|
25
|
+
export const SUPPORTED_RULE_LANGUAGES = [
|
|
26
|
+
'typescript',
|
|
27
|
+
'javascript',
|
|
28
|
+
'rust',
|
|
29
|
+
'python',
|
|
30
|
+
'go',
|
|
31
|
+
'cpp',
|
|
32
|
+
'java',
|
|
33
|
+
'csharp',
|
|
34
|
+
];
|
|
35
|
+
/**
|
|
36
|
+
* Generic (non-language) rules shipped by rulebook v5.3.0.
|
|
37
|
+
* These are always emitted regardless of detection result.
|
|
38
|
+
*/
|
|
39
|
+
export const ALWAYS_ON_RULES = [
|
|
40
|
+
'multi-agent-teams',
|
|
41
|
+
'consult-analysis-before-implementing',
|
|
42
|
+
'respect-handoff-trigger',
|
|
43
|
+
'diagnostic-first',
|
|
44
|
+
'fail-twice-escalate',
|
|
45
|
+
];
|
|
46
|
+
/**
|
|
47
|
+
* Detected languages from the `detector.ts` pipeline may use aliases
|
|
48
|
+
* (e.g. `"c"` — which rulebook treats as C/C++ and routes to the `cpp`
|
|
49
|
+
* template). Map them here.
|
|
50
|
+
*/
|
|
51
|
+
const LANGUAGE_ALIASES = {
|
|
52
|
+
c: 'cpp',
|
|
53
|
+
'c++': 'cpp',
|
|
54
|
+
cs: 'csharp',
|
|
55
|
+
'c#': 'csharp',
|
|
56
|
+
ts: 'typescript',
|
|
57
|
+
js: 'javascript',
|
|
58
|
+
py: 'python',
|
|
59
|
+
rs: 'rust',
|
|
60
|
+
};
|
|
61
|
+
export function getRulesDir(projectRoot) {
|
|
62
|
+
return path.join(projectRoot, RULES_DIR);
|
|
63
|
+
}
|
|
64
|
+
export function getRulePath(projectRoot, language) {
|
|
65
|
+
return path.join(getRulesDir(projectRoot), `${language}.md`);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Normalize a detected language string to a supported rule template slug.
|
|
69
|
+
* Returns null if rulebook does not ship a template for it.
|
|
70
|
+
*/
|
|
71
|
+
export function resolveRuleLanguage(detected) {
|
|
72
|
+
const lower = detected.toLowerCase().trim();
|
|
73
|
+
if (SUPPORTED_RULE_LANGUAGES.includes(lower)) {
|
|
74
|
+
return lower;
|
|
75
|
+
}
|
|
76
|
+
return LANGUAGE_ALIASES[lower] ?? null;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Read a language rule template from the package's `templates/rules/`
|
|
80
|
+
* directory.
|
|
81
|
+
*/
|
|
82
|
+
export async function readRuleTemplate(language) {
|
|
83
|
+
const templatePath = path.join(getTemplatesDir(), 'rules', `${language}.md`);
|
|
84
|
+
if (!(await fileExists(templatePath))) {
|
|
85
|
+
throw new Error(`Rule template not found at ${templatePath}`);
|
|
86
|
+
}
|
|
87
|
+
return await readFile(templatePath);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Check whether an existing rule file was generated by rulebook. Returns
|
|
91
|
+
* true only if the canonical sentinel comment is present. User files that
|
|
92
|
+
* the user "adopted" by deleting the sentinel return false and are
|
|
93
|
+
* preserved across updates.
|
|
94
|
+
*/
|
|
95
|
+
export function hasGeneratedSentinel(content) {
|
|
96
|
+
return content.includes(GENERATED_SENTINEL);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Generate `.claude/rules/<language>.md` for every detected language that
|
|
100
|
+
* has a shipped template. User-authored rules (no sentinel) are preserved.
|
|
101
|
+
*/
|
|
102
|
+
export async function generateRules(projectRoot, detection) {
|
|
103
|
+
const rulesDir = getRulesDir(projectRoot);
|
|
104
|
+
await ensureDir(rulesDir);
|
|
105
|
+
const result = {
|
|
106
|
+
written: [],
|
|
107
|
+
preserved: [],
|
|
108
|
+
unsupported: [],
|
|
109
|
+
};
|
|
110
|
+
// Deduplicate: a project might detect both "typescript" and "javascript";
|
|
111
|
+
// we emit both rules since both apply. But if two detection entries map
|
|
112
|
+
// to the same template slug, only write once.
|
|
113
|
+
const seen = new Set();
|
|
114
|
+
for (const entry of detection.languages) {
|
|
115
|
+
const slug = resolveRuleLanguage(entry.language);
|
|
116
|
+
if (!slug) {
|
|
117
|
+
result.unsupported.push(entry.language);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (seen.has(slug))
|
|
121
|
+
continue;
|
|
122
|
+
seen.add(slug);
|
|
123
|
+
const targetPath = getRulePath(projectRoot, slug);
|
|
124
|
+
if (await fileExists(targetPath)) {
|
|
125
|
+
const existing = await readFile(targetPath);
|
|
126
|
+
if (!hasGeneratedSentinel(existing)) {
|
|
127
|
+
// User-owned file — never touch it.
|
|
128
|
+
result.preserved.push(targetPath);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const template = await readRuleTemplate(slug);
|
|
133
|
+
await writeFile(targetPath, template);
|
|
134
|
+
result.written.push(targetPath);
|
|
135
|
+
}
|
|
136
|
+
// Always-on rules (not language-scoped) — emit if sentinel-based skip allows
|
|
137
|
+
for (const ruleName of ALWAYS_ON_RULES) {
|
|
138
|
+
const targetPath = path.join(rulesDir, `${ruleName}.md`);
|
|
139
|
+
if (await fileExists(targetPath)) {
|
|
140
|
+
const existing = await readFile(targetPath);
|
|
141
|
+
if (!hasGeneratedSentinel(existing)) {
|
|
142
|
+
result.preserved.push(targetPath);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const templatePath = path.join(getTemplatesDir(), 'rules', `${ruleName}.md`);
|
|
147
|
+
if (await fileExists(templatePath)) {
|
|
148
|
+
const template = await readFile(templatePath);
|
|
149
|
+
await writeFile(targetPath, template);
|
|
150
|
+
result.written.push(targetPath);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return result;
|
|
154
|
+
}
|
|
155
|
+
export async function listRulesWithSource(projectRoot) {
|
|
156
|
+
const rulesDir = getRulesDir(projectRoot);
|
|
157
|
+
if (!(await fileExists(rulesDir)))
|
|
158
|
+
return [];
|
|
159
|
+
const { promises: fs } = await import('fs');
|
|
160
|
+
const entries = await fs.readdir(rulesDir, { withFileTypes: true });
|
|
161
|
+
const results = [];
|
|
162
|
+
for (const entry of entries) {
|
|
163
|
+
if (!entry.isFile() || !entry.name.endsWith('.md'))
|
|
164
|
+
continue;
|
|
165
|
+
const filePath = path.join(rulesDir, entry.name);
|
|
166
|
+
const content = await readFile(filePath);
|
|
167
|
+
const source = hasGeneratedSentinel(content) ? 'generated' : 'user';
|
|
168
|
+
results.push({
|
|
169
|
+
name: entry.name.replace(/\.md$/, ''),
|
|
170
|
+
path: filePath,
|
|
171
|
+
source,
|
|
172
|
+
paths: extractPathsFrontmatter(content),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
results.sort((a, b) => a.name.localeCompare(b.name));
|
|
176
|
+
return results;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Extract the `paths:` field from a YAML frontmatter block at the top of a
|
|
180
|
+
* markdown file. Returns null if no frontmatter or no `paths:` field.
|
|
181
|
+
* Intentionally simple — handles the exact shape the shipped templates use,
|
|
182
|
+
* not arbitrary YAML. Callers that need full YAML support should use a real
|
|
183
|
+
* parser.
|
|
184
|
+
*/
|
|
185
|
+
function extractPathsFrontmatter(content) {
|
|
186
|
+
// Handle both LF and CRLF line endings (Windows writes CRLF)
|
|
187
|
+
const normalized = content.replace(/\r\n/g, '\n');
|
|
188
|
+
const match = normalized.match(/^---\n([\s\S]*?)\n---/);
|
|
189
|
+
if (!match)
|
|
190
|
+
return null;
|
|
191
|
+
const frontmatter = match[1];
|
|
192
|
+
const pathsMatch = frontmatter.match(/^paths:\s*\n((?:\s*-\s+.+\n?)+)/m);
|
|
193
|
+
if (!pathsMatch)
|
|
194
|
+
return null;
|
|
195
|
+
const lines = pathsMatch[1].split('\n').filter((l) => l.trim().startsWith('-'));
|
|
196
|
+
return lines.map((l) => l
|
|
197
|
+
.replace(/^\s*-\s+/, '')
|
|
198
|
+
.replace(/^["']|["']$/g, '')
|
|
199
|
+
.trim());
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=rules-generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rules-generator.js","sourceRoot":"","sources":["../../src/core/rules-generator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v5.3.0 F3 — `.rulebook/STATE.md` auto-regeneration.
|
|
3
|
+
*
|
|
4
|
+
* Renders a short (<40 line) machine-written status file imported by
|
|
5
|
+
* CLAUDE.md via `@.rulebook/STATE.md`. Updated on every task state
|
|
6
|
+
* change or Ralph iteration. Users can opt out by adding a
|
|
7
|
+
* `manual: true` YAML frontmatter flag.
|
|
8
|
+
*/
|
|
9
|
+
export declare const STATE_FILE = ".rulebook/STATE.md";
|
|
10
|
+
export interface StateSnapshot {
|
|
11
|
+
activeTask?: {
|
|
12
|
+
id: string;
|
|
13
|
+
phase: string;
|
|
14
|
+
progress: string;
|
|
15
|
+
} | null;
|
|
16
|
+
lastRalphIteration?: number | null;
|
|
17
|
+
lastQualityGate?: string | null;
|
|
18
|
+
healthScore?: number | null;
|
|
19
|
+
updatedAt: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function getStatePath(projectRoot: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Check whether the STATE.md has `manual: true` frontmatter, meaning
|
|
24
|
+
* the user maintains it by hand and rulebook should not touch it.
|
|
25
|
+
*/
|
|
26
|
+
export declare function isManuallyMaintained(projectRoot: string): Promise<boolean>;
|
|
27
|
+
/**
|
|
28
|
+
* Write or overwrite `.rulebook/STATE.md` with the given snapshot.
|
|
29
|
+
* No-op if the file has `manual: true` frontmatter.
|
|
30
|
+
*/
|
|
31
|
+
export declare function writeState(projectRoot: string, snapshot: StateSnapshot): Promise<{
|
|
32
|
+
path: string;
|
|
33
|
+
written: boolean;
|
|
34
|
+
}>;
|
|
35
|
+
//# sourceMappingURL=state-writer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-writer.d.ts","sourceRoot":"","sources":["../../src/core/state-writer.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AAEH,eAAO,MAAM,UAAU,uBAAuB,CAAC;AAE/C,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACpE,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAKhF;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAC9B,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,aAAa,GACtB,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAU7C"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { writeFile, fileExists, readFile, ensureDir } from '../utils/file-system.js';
|
|
3
|
+
/**
|
|
4
|
+
* v5.3.0 F3 — `.rulebook/STATE.md` auto-regeneration.
|
|
5
|
+
*
|
|
6
|
+
* Renders a short (<40 line) machine-written status file imported by
|
|
7
|
+
* CLAUDE.md via `@.rulebook/STATE.md`. Updated on every task state
|
|
8
|
+
* change or Ralph iteration. Users can opt out by adding a
|
|
9
|
+
* `manual: true` YAML frontmatter flag.
|
|
10
|
+
*/
|
|
11
|
+
export const STATE_FILE = '.rulebook/STATE.md';
|
|
12
|
+
export function getStatePath(projectRoot) {
|
|
13
|
+
return path.join(projectRoot, STATE_FILE);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Check whether the STATE.md has `manual: true` frontmatter, meaning
|
|
17
|
+
* the user maintains it by hand and rulebook should not touch it.
|
|
18
|
+
*/
|
|
19
|
+
export async function isManuallyMaintained(projectRoot) {
|
|
20
|
+
const target = getStatePath(projectRoot);
|
|
21
|
+
if (!(await fileExists(target)))
|
|
22
|
+
return false;
|
|
23
|
+
const content = await readFile(target);
|
|
24
|
+
return /^---\n[\s\S]*?manual:\s*true/m.test(content);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Write or overwrite `.rulebook/STATE.md` with the given snapshot.
|
|
28
|
+
* No-op if the file has `manual: true` frontmatter.
|
|
29
|
+
*/
|
|
30
|
+
export async function writeState(projectRoot, snapshot) {
|
|
31
|
+
if (await isManuallyMaintained(projectRoot)) {
|
|
32
|
+
return { path: getStatePath(projectRoot), written: false };
|
|
33
|
+
}
|
|
34
|
+
const target = getStatePath(projectRoot);
|
|
35
|
+
await ensureDir(path.dirname(target));
|
|
36
|
+
const content = renderState(snapshot);
|
|
37
|
+
await writeFile(target, content);
|
|
38
|
+
return { path: target, written: true };
|
|
39
|
+
}
|
|
40
|
+
function renderState(s) {
|
|
41
|
+
const lines = [
|
|
42
|
+
'<!-- Auto-generated by rulebook v5.3.0. Do not edit — changes will be overwritten. -->',
|
|
43
|
+
'<!-- Add `manual: true` frontmatter to opt out of auto-generation. -->',
|
|
44
|
+
'',
|
|
45
|
+
'# Project state',
|
|
46
|
+
'',
|
|
47
|
+
`**Last updated**: ${s.updatedAt}`,
|
|
48
|
+
'',
|
|
49
|
+
];
|
|
50
|
+
if (s.activeTask) {
|
|
51
|
+
lines.push(`## Active task`);
|
|
52
|
+
lines.push('');
|
|
53
|
+
lines.push(`- **ID**: \`${s.activeTask.id}\``);
|
|
54
|
+
lines.push(`- **Phase**: ${s.activeTask.phase}`);
|
|
55
|
+
lines.push(`- **Progress**: ${s.activeTask.progress}`);
|
|
56
|
+
lines.push('');
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
lines.push('## Active task');
|
|
60
|
+
lines.push('');
|
|
61
|
+
lines.push('No active task.');
|
|
62
|
+
lines.push('');
|
|
63
|
+
}
|
|
64
|
+
if (s.lastRalphIteration != null) {
|
|
65
|
+
lines.push(`## Ralph`);
|
|
66
|
+
lines.push('');
|
|
67
|
+
lines.push(`- **Last iteration**: ${s.lastRalphIteration}`);
|
|
68
|
+
if (s.lastQualityGate) {
|
|
69
|
+
lines.push(`- **Quality gate**: ${s.lastQualityGate}`);
|
|
70
|
+
}
|
|
71
|
+
lines.push('');
|
|
72
|
+
}
|
|
73
|
+
if (s.healthScore != null) {
|
|
74
|
+
lines.push(`## Health`);
|
|
75
|
+
lines.push('');
|
|
76
|
+
lines.push(`- **Score**: ${s.healthScore}/100`);
|
|
77
|
+
lines.push('');
|
|
78
|
+
}
|
|
79
|
+
return lines.join('\n');
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=state-writer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-writer.js","sourceRoot":"","sources":["../../src/core/state-writer.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,6 +15,37 @@ export interface TaskValidationResult {
|
|
|
15
15
|
errors: string[];
|
|
16
16
|
warnings: string[];
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* v5.3.0 F-NEW-3 — mandatory task tail.
|
|
20
|
+
*
|
|
21
|
+
* Every task MUST end with these three items. `createTask()` appends them
|
|
22
|
+
* automatically; `validateTask()` refuses any task that does not have them
|
|
23
|
+
* all checked; `archiveTask()` propagates that refusal.
|
|
24
|
+
*
|
|
25
|
+
* Idempotent: `renderMandatoryTail` is deterministic and `checkMandatoryTail`
|
|
26
|
+
* is case-insensitive on the leading verb so a user who rewords the items
|
|
27
|
+
* slightly (e.g. "Update docs covering the change") is still recognized.
|
|
28
|
+
*/
|
|
29
|
+
export declare const MANDATORY_TAIL_ITEMS: readonly [{
|
|
30
|
+
readonly label: "Update or create documentation covering the implementation";
|
|
31
|
+
readonly match: RegExp;
|
|
32
|
+
}, {
|
|
33
|
+
readonly label: "Write tests covering the new behavior";
|
|
34
|
+
readonly match: RegExp;
|
|
35
|
+
}, {
|
|
36
|
+
readonly label: "Run tests and confirm they pass";
|
|
37
|
+
readonly match: RegExp;
|
|
38
|
+
}];
|
|
39
|
+
export declare function renderMandatoryTail(sectionNumber: number): string;
|
|
40
|
+
export interface TailCheckResult {
|
|
41
|
+
/** True when all three tail items are present in the file. */
|
|
42
|
+
present: boolean;
|
|
43
|
+
/** Labels of items that are entirely missing from the file. */
|
|
44
|
+
missing: string[];
|
|
45
|
+
/** Labels of items that are present but unchecked (`- [ ]`). */
|
|
46
|
+
unchecked: string[];
|
|
47
|
+
}
|
|
48
|
+
export declare function checkMandatoryTail(tasksContent: string): TailCheckResult;
|
|
18
49
|
export declare class TaskManager {
|
|
19
50
|
private rulebookPath;
|
|
20
51
|
private tasksPath;
|
|
@@ -75,6 +106,10 @@ export declare class TaskManager {
|
|
|
75
106
|
* Update task status
|
|
76
107
|
*/
|
|
77
108
|
updateTaskStatus(taskId: string, status: RulebookTask['status']): Promise<void>;
|
|
109
|
+
/**
|
|
110
|
+
* v5.3.0 F3: refresh .rulebook/STATE.md after task state changes.
|
|
111
|
+
*/
|
|
112
|
+
private refreshState;
|
|
78
113
|
/**
|
|
79
114
|
* Get raw task metadata (including blocks/blockedBy/cascadeImpact for v5 blocker tracking)
|
|
80
115
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-manager.d.ts","sourceRoot":"","sources":["../../src/core/task-manager.ts"],"names":[],"mappings":"AAsBA,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,MAAM,EAAE,SAAS,GAAG,aAAa,GAAG,WAAW,GAAG,SAAS,CAAC;IAC5D,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,WAAW,CAAS;gBAEhB,WAAW,EAAE,MAAM,EAAE,WAAW,GAAE,MAAoB;IAMlE;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"task-manager.d.ts","sourceRoot":"","sources":["../../src/core/task-manager.ts"],"names":[],"mappings":"AAsBA,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,MAAM,EAAE,SAAS,GAAG,aAAa,GAAG,WAAW,GAAG,SAAS,CAAC;IAC5D,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;EAUvB,CAAC;AAEX,wBAAgB,mBAAmB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAQjE;AAED,MAAM,WAAW,eAAe;IAC9B,8DAA8D;IAC9D,OAAO,EAAE,OAAO,CAAC;IACjB,+DAA+D;IAC/D,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,gEAAgE;IAChE,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,eAAe,CAsBxE;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,WAAW,CAAS;gBAEhB,WAAW,EAAE,MAAM,EAAE,WAAW,GAAE,MAAoB;IAMlE;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAcjC;;OAEG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAInC;;OAEG;IACH,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;IAUlE;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE;IAMlE;;OAEG;IACG,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA6D/C;;;OAGG;IACG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAqEnC;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC;IAkCxC;;OAEG;IACG,SAAS,CAAC,eAAe,GAAE,OAAe,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAyC1E;;OAEG;IACG,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAE,OAAe,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAsFvF;;OAEG;IACG,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;IA8EjE;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,GAAE,OAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAqCjF;;OAEG;IACG,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IA0BrF;;OAEG;YACW,YAAY;IAgC1B;;OAEG;IACG,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAW9E;;OAEG;IACG,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAiB5D;;OAEG;IACG,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAchD;AAED,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,MAAM,EACnB,WAAW,GAAE,MAAoB,GAChC,WAAW,CAEb"}
|
|
@@ -12,6 +12,58 @@ const README_FILE = 'README.md';
|
|
|
12
12
|
* Examples: phase0_3.1.0-volumetric-clouds, phase3a_3.24.0-lens-flare
|
|
13
13
|
*/
|
|
14
14
|
const PHASE_PREFIX_REGEX = /^phase\d+[a-z]?_/;
|
|
15
|
+
/**
|
|
16
|
+
* v5.3.0 F-NEW-3 — mandatory task tail.
|
|
17
|
+
*
|
|
18
|
+
* Every task MUST end with these three items. `createTask()` appends them
|
|
19
|
+
* automatically; `validateTask()` refuses any task that does not have them
|
|
20
|
+
* all checked; `archiveTask()` propagates that refusal.
|
|
21
|
+
*
|
|
22
|
+
* Idempotent: `renderMandatoryTail` is deterministic and `checkMandatoryTail`
|
|
23
|
+
* is case-insensitive on the leading verb so a user who rewords the items
|
|
24
|
+
* slightly (e.g. "Update docs covering the change") is still recognized.
|
|
25
|
+
*/
|
|
26
|
+
export const MANDATORY_TAIL_ITEMS = [
|
|
27
|
+
{
|
|
28
|
+
label: 'Update or create documentation covering the implementation',
|
|
29
|
+
match: /^\s*-\s*\[[ x]\]\s.*(update|create).*documentation/i,
|
|
30
|
+
},
|
|
31
|
+
{ label: 'Write tests covering the new behavior', match: /^\s*-\s*\[[ x]\]\s.*write.*tests?/i },
|
|
32
|
+
{
|
|
33
|
+
label: 'Run tests and confirm they pass',
|
|
34
|
+
match: /^\s*-\s*\[[ x]\]\s.*run.*tests?.*(pass|confirm)/i,
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
export function renderMandatoryTail(sectionNumber) {
|
|
38
|
+
return [
|
|
39
|
+
`## ${sectionNumber}. Tail (mandatory — enforced by rulebook v5.3.0)`,
|
|
40
|
+
`- [ ] ${sectionNumber}.1 Update or create documentation covering the implementation`,
|
|
41
|
+
`- [ ] ${sectionNumber}.2 Write tests covering the new behavior`,
|
|
42
|
+
`- [ ] ${sectionNumber}.3 Run tests and confirm they pass`,
|
|
43
|
+
'',
|
|
44
|
+
].join('\n');
|
|
45
|
+
}
|
|
46
|
+
export function checkMandatoryTail(tasksContent) {
|
|
47
|
+
const lines = tasksContent.split('\n');
|
|
48
|
+
const missing = [];
|
|
49
|
+
const unchecked = [];
|
|
50
|
+
for (const item of MANDATORY_TAIL_ITEMS) {
|
|
51
|
+
const matched = lines.find((line) => item.match.test(line));
|
|
52
|
+
if (!matched) {
|
|
53
|
+
missing.push(item.label);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
// Checked if the line contains `[x]` or `[X]`; unchecked otherwise.
|
|
57
|
+
if (!/\[x\]/i.test(matched)) {
|
|
58
|
+
unchecked.push(item.label);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
present: missing.length === 0,
|
|
63
|
+
missing,
|
|
64
|
+
unchecked,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
15
67
|
export class TaskManager {
|
|
16
68
|
rulebookPath;
|
|
17
69
|
tasksPath;
|
|
@@ -34,6 +86,8 @@ export class TaskManager {
|
|
|
34
86
|
if (!existsSync(this.archivePath)) {
|
|
35
87
|
mkdirSync(this.archivePath, { recursive: true });
|
|
36
88
|
}
|
|
89
|
+
// Auto-migrate legacy archive from tasks/archive to .rulebook/archive
|
|
90
|
+
await this.migrateArchive();
|
|
37
91
|
}
|
|
38
92
|
/**
|
|
39
93
|
* Get the path to a task directory
|
|
@@ -79,34 +133,30 @@ export class TaskManager {
|
|
|
79
133
|
mkdirSync(taskPath, { recursive: true });
|
|
80
134
|
mkdirSync(join(taskPath, SPECS_DIR), { recursive: true });
|
|
81
135
|
// Create proposal.md template
|
|
82
|
-
const proposalContent = `# Proposal: ${taskId}
|
|
83
|
-
|
|
84
|
-
## Why
|
|
85
|
-
[Explain why this change is needed - minimum 20 characters]
|
|
86
|
-
|
|
87
|
-
## What Changes
|
|
88
|
-
[Describe what will change]
|
|
89
|
-
|
|
90
|
-
## Impact
|
|
91
|
-
- Affected specs: [list]
|
|
92
|
-
- Affected code: [list]
|
|
93
|
-
- Breaking change: YES/NO
|
|
94
|
-
- User benefit: [describe]
|
|
136
|
+
const proposalContent = `# Proposal: ${taskId}
|
|
137
|
+
|
|
138
|
+
## Why
|
|
139
|
+
[Explain why this change is needed - minimum 20 characters]
|
|
140
|
+
|
|
141
|
+
## What Changes
|
|
142
|
+
[Describe what will change]
|
|
143
|
+
|
|
144
|
+
## Impact
|
|
145
|
+
- Affected specs: [list]
|
|
146
|
+
- Affected code: [list]
|
|
147
|
+
- Breaking change: YES/NO
|
|
148
|
+
- User benefit: [describe]
|
|
95
149
|
`;
|
|
96
150
|
await writeFileAsync(join(taskPath, 'proposal.md'), proposalContent);
|
|
97
|
-
// Create tasks.md template
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
- [ ] 2
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
## 3. Documentation
|
|
107
|
-
- [ ] 3.1 Update README
|
|
108
|
-
- [ ] 3.2 Update CHANGELOG
|
|
109
|
-
`;
|
|
151
|
+
// Create tasks.md template — the MANDATORY tail items (v5.3.0 F-NEW-3)
|
|
152
|
+
// are appended automatically here AND enforced by validateTask() /
|
|
153
|
+
// archiveTask(): a task cannot be closed unless docs are updated,
|
|
154
|
+
// tests are written, and tests pass.
|
|
155
|
+
const tasksContent = `## 1. Implementation
|
|
156
|
+
- [ ] 1.1 First task
|
|
157
|
+
- [ ] 1.2 Second task
|
|
158
|
+
|
|
159
|
+
${renderMandatoryTail(2)}`;
|
|
110
160
|
await writeFileAsync(join(taskPath, 'tasks.md'), tasksContent);
|
|
111
161
|
// Create .metadata.json with initial status
|
|
112
162
|
const now = new Date().toISOString();
|
|
@@ -116,8 +166,9 @@ export class TaskManager {
|
|
|
116
166
|
updatedAt: now,
|
|
117
167
|
};
|
|
118
168
|
await writeFileAsync(join(taskPath, '.metadata.json'), JSON.stringify(metadata, null, 2));
|
|
119
|
-
// Update tasks README index
|
|
169
|
+
// Update tasks README index + STATE.md
|
|
120
170
|
await this.updateReadme();
|
|
171
|
+
await this.refreshState();
|
|
121
172
|
}
|
|
122
173
|
/**
|
|
123
174
|
* Generate and update README.md index in the tasks root directory.
|
|
@@ -154,17 +205,21 @@ export class TaskManager {
|
|
|
154
205
|
};
|
|
155
206
|
const statusIcon = (status) => {
|
|
156
207
|
switch (status) {
|
|
157
|
-
case 'completed':
|
|
158
|
-
|
|
159
|
-
case '
|
|
160
|
-
|
|
208
|
+
case 'completed':
|
|
209
|
+
return '✅';
|
|
210
|
+
case 'in-progress':
|
|
211
|
+
return '🔄';
|
|
212
|
+
case 'blocked':
|
|
213
|
+
return '🚫';
|
|
214
|
+
default:
|
|
215
|
+
return '⬚';
|
|
161
216
|
}
|
|
162
217
|
};
|
|
163
218
|
let readme = `# Tasks Index\n\n`;
|
|
164
219
|
readme += `> Auto-generated by rulebook. Do not edit manually.\n\n`;
|
|
165
220
|
const totalTasks = tasks.length;
|
|
166
|
-
const completedTasks = tasks.filter(t => t.status === 'completed').length;
|
|
167
|
-
const inProgressTasks = tasks.filter(t => t.status === 'in-progress').length;
|
|
221
|
+
const completedTasks = tasks.filter((t) => t.status === 'completed').length;
|
|
222
|
+
const inProgressTasks = tasks.filter((t) => t.status === 'in-progress').length;
|
|
168
223
|
readme += `**Total**: ${totalTasks} tasks | **Completed**: ${completedTasks} | **In Progress**: ${inProgressTasks} | **Pending**: ${totalTasks - completedTasks - inProgressTasks}\n\n`;
|
|
169
224
|
for (const [phaseKey, phaseTasks] of phases) {
|
|
170
225
|
readme += `## ${phaseKey}\n\n`;
|
|
@@ -172,9 +227,7 @@ export class TaskManager {
|
|
|
172
227
|
readme += `|--------|------|----------|-------------|\n`;
|
|
173
228
|
for (const task of phaseTasks) {
|
|
174
229
|
const progress = getProgress(task);
|
|
175
|
-
const progressStr = progress.total > 0
|
|
176
|
-
? `${progress.done}/${progress.total}`
|
|
177
|
-
: '-';
|
|
230
|
+
const progressStr = progress.total > 0 ? `${progress.done}/${progress.total}` : '-';
|
|
178
231
|
// Extract short description from task ID (after phase prefix)
|
|
179
232
|
const desc = task.id.replace(/^phase\d+[a-z]?_/, '').replace(/-/g, ' ');
|
|
180
233
|
readme += `| ${statusIcon(task.status)} | ${task.id} | ${progressStr} | ${desc} |\n`;
|
|
@@ -362,6 +415,16 @@ export class TaskManager {
|
|
|
362
415
|
errors.push('Purpose section (## Why) must have at least 20 characters');
|
|
363
416
|
}
|
|
364
417
|
}
|
|
418
|
+
// v5.3.0 F-NEW-3: mandatory task tail (docs + tests + verify)
|
|
419
|
+
const tail = task.tasks
|
|
420
|
+
? checkMandatoryTail(task.tasks)
|
|
421
|
+
: { present: false, missing: MANDATORY_TAIL_ITEMS.map((i) => i.label), unchecked: [] };
|
|
422
|
+
if (!tail.present) {
|
|
423
|
+
errors.push(`Mandatory task tail missing from tasks.md (required in v5.3.0): ${tail.missing.join(', ')}`);
|
|
424
|
+
}
|
|
425
|
+
else if (tail.unchecked.length > 0) {
|
|
426
|
+
errors.push(`Mandatory task tail items are still unchecked: ${tail.unchecked.join(', ')}`);
|
|
427
|
+
}
|
|
365
428
|
// Validate specs
|
|
366
429
|
if (!task.specs || Object.keys(task.specs).length === 0) {
|
|
367
430
|
warnings.push('No spec files found (specs/*/spec.md)');
|
|
@@ -430,8 +493,9 @@ export class TaskManager {
|
|
|
430
493
|
// Move task to archive using cross-platform filesystem operations
|
|
431
494
|
const { renameSync } = await import('fs');
|
|
432
495
|
renameSync(taskPath, archiveTaskPath);
|
|
433
|
-
// Update tasks README index
|
|
496
|
+
// Update tasks README index + STATE.md
|
|
434
497
|
await this.updateReadme();
|
|
498
|
+
await this.refreshState();
|
|
435
499
|
}
|
|
436
500
|
/**
|
|
437
501
|
* Update task status
|
|
@@ -452,8 +516,43 @@ export class TaskManager {
|
|
|
452
516
|
updatedAt: task.updatedAt,
|
|
453
517
|
};
|
|
454
518
|
await writeFileUtil(metadataPath, JSON.stringify(metadata, null, 2));
|
|
455
|
-
// Update tasks README index
|
|
519
|
+
// Update tasks README index + STATE.md
|
|
456
520
|
await this.updateReadme();
|
|
521
|
+
await this.refreshState();
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* v5.3.0 F3: refresh .rulebook/STATE.md after task state changes.
|
|
525
|
+
*/
|
|
526
|
+
async refreshState() {
|
|
527
|
+
try {
|
|
528
|
+
const { writeState } = await import('./state-writer.js');
|
|
529
|
+
const tasks = await this.listTasks(false);
|
|
530
|
+
const active = tasks.find((t) => t.status === 'in-progress') ??
|
|
531
|
+
tasks.find((t) => t.status === 'pending') ??
|
|
532
|
+
null;
|
|
533
|
+
const totalItems = tasks.reduce((n, t) => {
|
|
534
|
+
const m = t.tasks?.match(/- \[x\]/gi);
|
|
535
|
+
const u = t.tasks?.match(/- \[ \]/g);
|
|
536
|
+
return n + (m?.length ?? 0) + (u?.length ?? 0);
|
|
537
|
+
}, 0);
|
|
538
|
+
const checkedItems = tasks.reduce((n, t) => {
|
|
539
|
+
const m = t.tasks?.match(/- \[x\]/gi);
|
|
540
|
+
return n + (m?.length ?? 0);
|
|
541
|
+
}, 0);
|
|
542
|
+
await writeState(join(this.rulebookPath, '..'), {
|
|
543
|
+
activeTask: active
|
|
544
|
+
? {
|
|
545
|
+
id: active.id,
|
|
546
|
+
phase: active.id.match(/^(phase\d+[a-z]?)_/)?.[1] ?? '?',
|
|
547
|
+
progress: `${checkedItems}/${totalItems} items`,
|
|
548
|
+
}
|
|
549
|
+
: null,
|
|
550
|
+
updatedAt: new Date().toISOString(),
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
catch {
|
|
554
|
+
// Non-fatal — STATE.md refresh must never break task operations
|
|
555
|
+
}
|
|
457
556
|
}
|
|
458
557
|
/**
|
|
459
558
|
* Get raw task metadata (including blocks/blockedBy/cascadeImpact for v5 blocker tracking)
|