@hivehub/rulebook 5.2.1 → 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 +135 -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;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;
|
|
1
|
+
{"version":3,"file":"task-manager.d.ts","sourceRoot":"","sources":["../../src/core/task-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -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;
|
|
@@ -81,34 +133,30 @@ export class TaskManager {
|
|
|
81
133
|
mkdirSync(taskPath, { recursive: true });
|
|
82
134
|
mkdirSync(join(taskPath, SPECS_DIR), { recursive: true });
|
|
83
135
|
// Create proposal.md template
|
|
84
|
-
const proposalContent = `# Proposal: ${taskId}
|
|
85
|
-
|
|
86
|
-
## Why
|
|
87
|
-
[Explain why this change is needed - minimum 20 characters]
|
|
88
|
-
|
|
89
|
-
## What Changes
|
|
90
|
-
[Describe what will change]
|
|
91
|
-
|
|
92
|
-
## Impact
|
|
93
|
-
- Affected specs: [list]
|
|
94
|
-
- Affected code: [list]
|
|
95
|
-
- Breaking change: YES/NO
|
|
96
|
-
- 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]
|
|
97
149
|
`;
|
|
98
150
|
await writeFileAsync(join(taskPath, 'proposal.md'), proposalContent);
|
|
99
|
-
// Create tasks.md template
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
- [ ] 2
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
## 3. Documentation
|
|
109
|
-
- [ ] 3.1 Update README
|
|
110
|
-
- [ ] 3.2 Update CHANGELOG
|
|
111
|
-
`;
|
|
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)}`;
|
|
112
160
|
await writeFileAsync(join(taskPath, 'tasks.md'), tasksContent);
|
|
113
161
|
// Create .metadata.json with initial status
|
|
114
162
|
const now = new Date().toISOString();
|
|
@@ -118,8 +166,9 @@ export class TaskManager {
|
|
|
118
166
|
updatedAt: now,
|
|
119
167
|
};
|
|
120
168
|
await writeFileAsync(join(taskPath, '.metadata.json'), JSON.stringify(metadata, null, 2));
|
|
121
|
-
// Update tasks README index
|
|
169
|
+
// Update tasks README index + STATE.md
|
|
122
170
|
await this.updateReadme();
|
|
171
|
+
await this.refreshState();
|
|
123
172
|
}
|
|
124
173
|
/**
|
|
125
174
|
* Generate and update README.md index in the tasks root directory.
|
|
@@ -156,17 +205,21 @@ export class TaskManager {
|
|
|
156
205
|
};
|
|
157
206
|
const statusIcon = (status) => {
|
|
158
207
|
switch (status) {
|
|
159
|
-
case 'completed':
|
|
160
|
-
|
|
161
|
-
case '
|
|
162
|
-
|
|
208
|
+
case 'completed':
|
|
209
|
+
return '✅';
|
|
210
|
+
case 'in-progress':
|
|
211
|
+
return '🔄';
|
|
212
|
+
case 'blocked':
|
|
213
|
+
return '🚫';
|
|
214
|
+
default:
|
|
215
|
+
return '⬚';
|
|
163
216
|
}
|
|
164
217
|
};
|
|
165
218
|
let readme = `# Tasks Index\n\n`;
|
|
166
219
|
readme += `> Auto-generated by rulebook. Do not edit manually.\n\n`;
|
|
167
220
|
const totalTasks = tasks.length;
|
|
168
|
-
const completedTasks = tasks.filter(t => t.status === 'completed').length;
|
|
169
|
-
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;
|
|
170
223
|
readme += `**Total**: ${totalTasks} tasks | **Completed**: ${completedTasks} | **In Progress**: ${inProgressTasks} | **Pending**: ${totalTasks - completedTasks - inProgressTasks}\n\n`;
|
|
171
224
|
for (const [phaseKey, phaseTasks] of phases) {
|
|
172
225
|
readme += `## ${phaseKey}\n\n`;
|
|
@@ -174,9 +227,7 @@ export class TaskManager {
|
|
|
174
227
|
readme += `|--------|------|----------|-------------|\n`;
|
|
175
228
|
for (const task of phaseTasks) {
|
|
176
229
|
const progress = getProgress(task);
|
|
177
|
-
const progressStr = progress.total > 0
|
|
178
|
-
? `${progress.done}/${progress.total}`
|
|
179
|
-
: '-';
|
|
230
|
+
const progressStr = progress.total > 0 ? `${progress.done}/${progress.total}` : '-';
|
|
180
231
|
// Extract short description from task ID (after phase prefix)
|
|
181
232
|
const desc = task.id.replace(/^phase\d+[a-z]?_/, '').replace(/-/g, ' ');
|
|
182
233
|
readme += `| ${statusIcon(task.status)} | ${task.id} | ${progressStr} | ${desc} |\n`;
|
|
@@ -364,6 +415,16 @@ export class TaskManager {
|
|
|
364
415
|
errors.push('Purpose section (## Why) must have at least 20 characters');
|
|
365
416
|
}
|
|
366
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
|
+
}
|
|
367
428
|
// Validate specs
|
|
368
429
|
if (!task.specs || Object.keys(task.specs).length === 0) {
|
|
369
430
|
warnings.push('No spec files found (specs/*/spec.md)');
|
|
@@ -432,8 +493,9 @@ export class TaskManager {
|
|
|
432
493
|
// Move task to archive using cross-platform filesystem operations
|
|
433
494
|
const { renameSync } = await import('fs');
|
|
434
495
|
renameSync(taskPath, archiveTaskPath);
|
|
435
|
-
// Update tasks README index
|
|
496
|
+
// Update tasks README index + STATE.md
|
|
436
497
|
await this.updateReadme();
|
|
498
|
+
await this.refreshState();
|
|
437
499
|
}
|
|
438
500
|
/**
|
|
439
501
|
* Update task status
|
|
@@ -454,8 +516,43 @@ export class TaskManager {
|
|
|
454
516
|
updatedAt: task.updatedAt,
|
|
455
517
|
};
|
|
456
518
|
await writeFileUtil(metadataPath, JSON.stringify(metadata, null, 2));
|
|
457
|
-
// Update tasks README index
|
|
519
|
+
// Update tasks README index + STATE.md
|
|
458
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
|
+
}
|
|
459
556
|
}
|
|
460
557
|
/**
|
|
461
558
|
* Get raw task metadata (including blocks/blockedBy/cascadeImpact for v5 blocker tracking)
|