zelari-code 1.42.0 → 1.44.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/dist/cli/main.bundled.js +1278 -688
- package/dist/cli/main.bundled.js.map +4 -4
- package/dist/cli/runHeadless.js +15 -0
- package/dist/cli/runHeadless.js.map +1 -1
- package/dist/cli/toolRegistry.js +16 -0
- package/dist/cli/toolRegistry.js.map +1 -1
- package/dist/cli/tools/planTaskTools.js +236 -0
- package/dist/cli/tools/planTaskTools.js.map +1 -0
- package/dist/cli/workspace/index.js +2 -0
- package/dist/cli/workspace/index.js.map +1 -1
- package/dist/cli/workspace/planDriftCheck.js +217 -0
- package/dist/cli/workspace/planDriftCheck.js.map +1 -0
- package/dist/cli/workspace/planDriftCheck.test.js +146 -0
- package/dist/cli/workspace/planDriftCheck.test.js.map +1 -0
- package/dist/cli/workspace/planStore.js +213 -0
- package/dist/cli/workspace/planStore.js.map +1 -0
- package/dist/cli/workspace/planStore.test.js +295 -0
- package/dist/cli/workspace/planStore.test.js.map +1 -0
- package/dist/cli/workspace/postCouncilHook.js +7 -0
- package/dist/cli/workspace/postCouncilHook.js.map +1 -1
- package/dist/cli/workspace/stubs.js +14 -4
- package/dist/cli/workspace/stubs.js.map +1 -1
- package/dist/cli/workspace/workspaceSummary.js +7 -1
- package/dist/cli/workspace/workspaceSummary.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* workspace/planDriftCheck.ts — deterministic drift check between
|
|
3
|
+
* `.zelari/plan.json` and the canonical plan doc
|
|
4
|
+
* (`.zelari/docs/plan-canonical*.md`).
|
|
5
|
+
*
|
|
6
|
+
* Why (v0.10 post-mortem): repeated council runs re-emitted the same
|
|
7
|
+
* concepts under new phase-id slugs (p0-parit-*, p0-safety-gate-*,
|
|
8
|
+
* p0-hook-lifecycle-*, …), multiplying phases/tasks/milestones in
|
|
9
|
+
* plan.json without ever consolidating. This check runs after every
|
|
10
|
+
* council run (postCouncilHook Step 2b) and reports drift instead of
|
|
11
|
+
* silently accumulating it.
|
|
12
|
+
*
|
|
13
|
+
* Checks:
|
|
14
|
+
* - DUPLICATE_MILESTONE (error): >1 milestone targeting the same version.
|
|
15
|
+
* - PHASE_IN_CANONICAL_BLOCKLIST (error) / TASK_IN_CANONICAL_BLOCKLIST
|
|
16
|
+
* (error): id matches a prefix the canonical doc marked as duplicate
|
|
17
|
+
* or descope (`` `p0-parit-*` `` style).
|
|
18
|
+
* - CANONICAL_PHASE_MISSING (error): canonical phase absent from plan.json.
|
|
19
|
+
* - DUPLICATE_TASK_TITLE (warning): same normalized title on >1 task.
|
|
20
|
+
* - TASK_IN_UNKNOWN_PHASE (warning): task.phaseId not in phases.
|
|
21
|
+
*
|
|
22
|
+
* Contract: fail-open — corrupt/missing inputs return `{ ran: false }`
|
|
23
|
+
* and never throw. A JSON report lands in `.zelari/drift-report.json`.
|
|
24
|
+
* Disable with ZELARI_DRIFT_CHECK=0.
|
|
25
|
+
*
|
|
26
|
+
* @since v1.15.0
|
|
27
|
+
*/
|
|
28
|
+
import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
|
|
29
|
+
import { join } from 'node:path';
|
|
30
|
+
/** Newest `plan-canonical*.md` under `<rootDir>/docs`, if present. */
|
|
31
|
+
function findCanonicalDoc(rootDir) {
|
|
32
|
+
const docsDir = join(rootDir, 'docs');
|
|
33
|
+
if (!existsSync(docsDir))
|
|
34
|
+
return null;
|
|
35
|
+
const candidates = readdirSync(docsDir)
|
|
36
|
+
.filter((f) => /^plan-canonical.*\.md$/i.test(f))
|
|
37
|
+
.map((f) => ({ f, mtime: statSync(join(docsDir, f)).mtimeMs }))
|
|
38
|
+
.sort((a, b) => b.mtime - a.mtime);
|
|
39
|
+
return candidates.length > 0 ? candidates[0].f : null;
|
|
40
|
+
}
|
|
41
|
+
/** Phase ids come from `` ### … `p0-…` `` headings; blocklist prefixes from `` `p0-…-*` `` tokens. */
|
|
42
|
+
function parseCanonicalDoc(text) {
|
|
43
|
+
const activePhases = new Set();
|
|
44
|
+
const blockedPrefixes = new Set();
|
|
45
|
+
const headingPhase = /^#{2,6}[^\n]*?`([a-z0-9][a-z0-9-]*)`/gm;
|
|
46
|
+
for (const m of text.matchAll(headingPhase))
|
|
47
|
+
activePhases.add(m[1]);
|
|
48
|
+
const blocked = /`([a-z0-9][a-z0-9-]*)-\*`/g;
|
|
49
|
+
for (const m of text.matchAll(blocked))
|
|
50
|
+
blockedPrefixes.add(`${m[1]}-`);
|
|
51
|
+
return { activePhases, blockedPrefixes };
|
|
52
|
+
}
|
|
53
|
+
/** Lowercase, accent-stripped, punctuation-collapsed comparison key. */
|
|
54
|
+
function normalizeTitle(value) {
|
|
55
|
+
if (typeof value !== 'string')
|
|
56
|
+
return '';
|
|
57
|
+
return value
|
|
58
|
+
.normalize('NFD')
|
|
59
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
60
|
+
.toLowerCase()
|
|
61
|
+
.replace(/[^a-z0-9]+/g, ' ')
|
|
62
|
+
.trim();
|
|
63
|
+
}
|
|
64
|
+
/** Milestone grouping key: version token ("v0.10.0" ≡ "0.10.0"). */
|
|
65
|
+
function versionKey(value) {
|
|
66
|
+
if (typeof value !== 'string')
|
|
67
|
+
return '';
|
|
68
|
+
const m = value.toLowerCase().match(/v?\d+(?:\.\d+)*/);
|
|
69
|
+
return m ? m[0].replace(/^v/, '') : value.trim().toLowerCase();
|
|
70
|
+
}
|
|
71
|
+
function firstString(v) {
|
|
72
|
+
return typeof v === 'string' && v.trim().length > 0 ? v : null;
|
|
73
|
+
}
|
|
74
|
+
/** Read a file, returning null instead of throwing (fail-open). */
|
|
75
|
+
function readFileSyncSafe(path) {
|
|
76
|
+
try {
|
|
77
|
+
return readFileSync(path, 'utf8');
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Run the drift check against `<rootDir>/plan.json`. Never throws:
|
|
85
|
+
* errors are captured into `reason` (fail-open, audit-style).
|
|
86
|
+
*/
|
|
87
|
+
export async function runPlanDriftCheck(rootDir) {
|
|
88
|
+
if (process.env['ZELARI_DRIFT_CHECK'] === '0') {
|
|
89
|
+
return { ran: false, reason: 'ZELARI_DRIFT_CHECK=0 (disabled)' };
|
|
90
|
+
}
|
|
91
|
+
const planPath = join(rootDir, 'plan.json');
|
|
92
|
+
if (!existsSync(planPath)) {
|
|
93
|
+
return { ran: false, reason: '.zelari/plan.json missing (not design-phase)' };
|
|
94
|
+
}
|
|
95
|
+
let plan;
|
|
96
|
+
try {
|
|
97
|
+
plan = JSON.parse(readFileSync(planPath, 'utf8'));
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return { ran: false, reason: '.zelari/plan.json corrupt' };
|
|
101
|
+
}
|
|
102
|
+
const findings = [];
|
|
103
|
+
const phases = Array.isArray(plan.phases) ? plan.phases : [];
|
|
104
|
+
const tasks = Array.isArray(plan.tasks) ? plan.tasks : [];
|
|
105
|
+
const milestones = Array.isArray(plan.milestones) ? plan.milestones : [];
|
|
106
|
+
const phaseIds = new Set(phases.map((p) => (typeof p.id === 'string' ? p.id : '')).filter(Boolean));
|
|
107
|
+
// ── Canonical cross-check ────────────────────────────────────────────
|
|
108
|
+
const canonicalName = findCanonicalDoc(rootDir);
|
|
109
|
+
const canonicalText = canonicalName
|
|
110
|
+
? readFileSyncSafe(join(rootDir, 'docs', canonicalName))
|
|
111
|
+
: null;
|
|
112
|
+
if (canonicalText !== null) {
|
|
113
|
+
const { activePhases, blockedPrefixes } = parseCanonicalDoc(canonicalText);
|
|
114
|
+
for (const id of activePhases) {
|
|
115
|
+
if (!phaseIds.has(id)) {
|
|
116
|
+
findings.push({
|
|
117
|
+
code: 'CANONICAL_PHASE_MISSING',
|
|
118
|
+
severity: 'error',
|
|
119
|
+
message: `canonical phase \`${id}\` (${canonicalName}) is missing from plan.json`,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
for (const id of phaseIds) {
|
|
124
|
+
const hit = [...blockedPrefixes].find((p) => id.startsWith(p));
|
|
125
|
+
if (hit) {
|
|
126
|
+
findings.push({
|
|
127
|
+
code: 'PHASE_IN_CANONICAL_BLOCKLIST',
|
|
128
|
+
severity: 'error',
|
|
129
|
+
message: `phase \`${id}\` matches canonical duplicate/descope prefix \`${hit}*\``,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
for (const t of tasks) {
|
|
134
|
+
const id = typeof t.id === 'string' ? t.id : '';
|
|
135
|
+
const hit = id ? [...blockedPrefixes].find((p) => id.startsWith(p)) : undefined;
|
|
136
|
+
if (hit) {
|
|
137
|
+
findings.push({
|
|
138
|
+
code: 'TASK_IN_CANONICAL_BLOCKLIST',
|
|
139
|
+
severity: 'error',
|
|
140
|
+
message: `task \`${id}\` matches canonical duplicate/descope prefix \`${hit}*\``,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// ── Structural checks (canonical-independent) ────────────────────────
|
|
146
|
+
const byVersion = new Map();
|
|
147
|
+
for (const m of milestones) {
|
|
148
|
+
const key = versionKey(m.targetVersion);
|
|
149
|
+
if (!key)
|
|
150
|
+
continue;
|
|
151
|
+
const id = typeof m.id === 'string' ? m.id : '(no id)';
|
|
152
|
+
byVersion.set(key, [...(byVersion.get(key) ?? []), id]);
|
|
153
|
+
}
|
|
154
|
+
for (const [version, ids] of byVersion) {
|
|
155
|
+
if (ids.length > 1) {
|
|
156
|
+
findings.push({
|
|
157
|
+
code: 'DUPLICATE_MILESTONE',
|
|
158
|
+
severity: 'error',
|
|
159
|
+
message: `${ids.length} milestones target ${version}: ${ids.join(', ')} — keep exactly one canonical`,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const byTitle = new Map();
|
|
164
|
+
for (const t of tasks) {
|
|
165
|
+
const title = normalizeTitle(firstString(t.title) ?? firstString(t.name) ?? firstString(t.description));
|
|
166
|
+
const id = typeof t.id === 'string' ? t.id : '(no id)';
|
|
167
|
+
if (title)
|
|
168
|
+
byTitle.set(title, [...(byTitle.get(title) ?? []), id]);
|
|
169
|
+
const phaseId = firstString(t.phaseId);
|
|
170
|
+
if (phaseId && !phaseIds.has(phaseId)) {
|
|
171
|
+
findings.push({
|
|
172
|
+
code: 'TASK_IN_UNKNOWN_PHASE',
|
|
173
|
+
severity: 'warning',
|
|
174
|
+
message: `task \`${id}\` references unknown phaseId \`${phaseId}\``,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
for (const [title, ids] of byTitle) {
|
|
179
|
+
if (ids.length > 1) {
|
|
180
|
+
findings.push({
|
|
181
|
+
code: 'DUPLICATE_TASK_TITLE',
|
|
182
|
+
severity: 'warning',
|
|
183
|
+
message: `duplicate task title "${title.slice(0, 60)}": ${ids.join(', ')}`,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const ok = findings.every((f) => f.severity !== 'error');
|
|
188
|
+
const canonicalParsed = canonicalName !== null && canonicalText !== null;
|
|
189
|
+
let reportPath;
|
|
190
|
+
try {
|
|
191
|
+
reportPath = join(rootDir, 'drift-report.json');
|
|
192
|
+
writeFileSync(reportPath, JSON.stringify({
|
|
193
|
+
generatedAt: new Date().toISOString(),
|
|
194
|
+
ok,
|
|
195
|
+
canonicalDoc: canonicalName ?? null,
|
|
196
|
+
counts: { phases: phases.length, tasks: tasks.length, milestones: milestones.length },
|
|
197
|
+
findings,
|
|
198
|
+
}, null, 2) + '\n', 'utf8');
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
reportPath = undefined;
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
ran: true,
|
|
205
|
+
ok,
|
|
206
|
+
findings,
|
|
207
|
+
...(canonicalParsed
|
|
208
|
+
? { canonicalDoc: canonicalName }
|
|
209
|
+
: {
|
|
210
|
+
reason: canonicalName
|
|
211
|
+
? `canonical doc ${canonicalName} unreadable (structural checks only)`
|
|
212
|
+
: 'no canonical doc (structural checks only)',
|
|
213
|
+
}),
|
|
214
|
+
...(reportPath ? { reportPath } : {}),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
//# sourceMappingURL=planDriftCheck.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"planDriftCheck.js","sourceRoot":"","sources":["../../../src/cli/workspace/planDriftCheck.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* planDriftCheck tests — P1.5 governance acceptance (v0.10 post-mortem).
|
|
3
|
+
*
|
|
4
|
+
* Covers: aligned plan (green), duplicate milestone, canonical blocklist
|
|
5
|
+
* hits (phase + task), canonical phase missing from plan.json, duplicate
|
|
6
|
+
* task titles, unknown phaseId, fail-open skips (missing plan.json,
|
|
7
|
+
* ZELARI_DRIFT_CHECK=0, no canonical doc), and the drift-report.json
|
|
8
|
+
* artifact.
|
|
9
|
+
*
|
|
10
|
+
* @since v1.15.0
|
|
11
|
+
*/
|
|
12
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
13
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
14
|
+
import { tmpdir } from 'node:os';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
import { runPlanDriftCheck } from './planDriftCheck.js';
|
|
17
|
+
let dir;
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
dir = mkdtempSync(join(tmpdir(), 'zelari-drift-'));
|
|
20
|
+
});
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
rmSync(dir, { recursive: true, force: true });
|
|
23
|
+
delete process.env['ZELARI_DRIFT_CHECK'];
|
|
24
|
+
});
|
|
25
|
+
function zelari() {
|
|
26
|
+
const z = join(dir, '.zelari');
|
|
27
|
+
mkdirSync(z, { recursive: true });
|
|
28
|
+
return z;
|
|
29
|
+
}
|
|
30
|
+
function seedPlan(phases, tasks, milestones) {
|
|
31
|
+
const z = zelari();
|
|
32
|
+
writeFileSync(join(z, 'plan.json'), JSON.stringify({ phases, tasks, milestones }), 'utf8');
|
|
33
|
+
return z;
|
|
34
|
+
}
|
|
35
|
+
function seedCanonical(text) {
|
|
36
|
+
mkdirSync(join(dir, '.zelari', 'docs'), { recursive: true });
|
|
37
|
+
writeFileSync(join(dir, '.zelari', 'docs', 'plan-canonical-v9-9-9.md'), text, 'utf8');
|
|
38
|
+
}
|
|
39
|
+
const CANONICAL = [
|
|
40
|
+
'# Piano canonico',
|
|
41
|
+
'## Regola',
|
|
42
|
+
'Duplicati: `p0-parit-*`, `p0-safety-gate-observability-*`.',
|
|
43
|
+
'### P0 — `p0-safety-observability`',
|
|
44
|
+
'### P1 — `p1-session-ux-extensibility`',
|
|
45
|
+
].join('\n');
|
|
46
|
+
function phase(id) {
|
|
47
|
+
return { kind: 'phase', id, name: id };
|
|
48
|
+
}
|
|
49
|
+
function task(id, name, phaseId) {
|
|
50
|
+
return { kind: 'task', id, name, phaseId, status: 'pending', priority: 'high' };
|
|
51
|
+
}
|
|
52
|
+
describe('runPlanDriftCheck', () => {
|
|
53
|
+
it('is green when plan.json matches the canonical doc', async () => {
|
|
54
|
+
const z = seedPlan([phase('p0-safety-observability'), phase('p1-session-ux-extensibility')], [
|
|
55
|
+
task('p0-safety-observability-lifecyclehookrunner-1', 'LifecycleHookRunner', 'p0-safety-observability'),
|
|
56
|
+
task('p1-session-ux-extensibility-fork-rewind-2', 'Fork e rewind', 'p1-session-ux-extensibility'),
|
|
57
|
+
], [{ kind: 'milestone', id: 'm-1', name: 'v0.10.0', targetVersion: 'v0.10.0' }]);
|
|
58
|
+
seedCanonical(CANONICAL);
|
|
59
|
+
const result = await runPlanDriftCheck(z);
|
|
60
|
+
expect(result.ran).toBe(true);
|
|
61
|
+
expect(result.ok).toBe(true);
|
|
62
|
+
expect(result.findings).toEqual([]);
|
|
63
|
+
expect(result.canonicalDoc).toBe('plan-canonical-v9-9-9.md');
|
|
64
|
+
expect(result.reportPath).toBe(join(z, 'drift-report.json'));
|
|
65
|
+
const report = JSON.parse(readFileSync(result.reportPath, 'utf8'));
|
|
66
|
+
expect(report.ok).toBe(true);
|
|
67
|
+
expect(report.counts).toEqual({ phases: 2, tasks: 2, milestones: 1 });
|
|
68
|
+
});
|
|
69
|
+
it('flags duplicate milestones targeting the same version', async () => {
|
|
70
|
+
const z = seedPlan([phase('p0-safety-observability')], [task('t-1', 'Solo task', 'p0-safety-observability')], [
|
|
71
|
+
{ kind: 'milestone', id: 'm-1', name: 'v0.10.0 A', targetVersion: 'v0.10.0' },
|
|
72
|
+
{ kind: 'milestone', id: 'm-2', name: 'v0.10.0 B', targetVersion: '0.10.0' },
|
|
73
|
+
]);
|
|
74
|
+
seedCanonical(CANONICAL);
|
|
75
|
+
const result = await runPlanDriftCheck(z);
|
|
76
|
+
expect(result.ok).toBe(false);
|
|
77
|
+
const dup = result.findings.filter((f) => f.code === 'DUPLICATE_MILESTONE');
|
|
78
|
+
expect(dup).toHaveLength(1);
|
|
79
|
+
expect(dup[0].severity).toBe('error');
|
|
80
|
+
expect(dup[0].message).toContain('m-1');
|
|
81
|
+
expect(dup[0].message).toContain('m-2');
|
|
82
|
+
});
|
|
83
|
+
it('flags phases and tasks matching a canonical duplicate/descope prefix', async () => {
|
|
84
|
+
const z = seedPlan([phase('p0-safety-observability'), phase('p0-parit-sicurezza')], [
|
|
85
|
+
task('p0-parit-sicurezza-hook-1', 'Hook runner', 'p0-parit-sicurezza'),
|
|
86
|
+
task('p0-safety-gate-observability-hook-2', 'Altro hook', 'p0-safety-observability'),
|
|
87
|
+
], []);
|
|
88
|
+
seedCanonical(CANONICAL);
|
|
89
|
+
const result = await runPlanDriftCheck(z);
|
|
90
|
+
expect(result.ok).toBe(false);
|
|
91
|
+
const codes = result.findings.map((f) => f.code);
|
|
92
|
+
expect(codes).toContain('PHASE_IN_CANONICAL_BLOCKLIST');
|
|
93
|
+
expect(codes).toContain('TASK_IN_CANONICAL_BLOCKLIST');
|
|
94
|
+
});
|
|
95
|
+
it('flags canonical phases missing from plan.json', async () => {
|
|
96
|
+
const z = seedPlan([phase('p0-safety-observability')], [task('t-1', 'Task', 'p0-safety-observability')], []);
|
|
97
|
+
seedCanonical(CANONICAL);
|
|
98
|
+
const result = await runPlanDriftCheck(z);
|
|
99
|
+
expect(result.ok).toBe(false);
|
|
100
|
+
const missing = result.findings.filter((f) => f.code === 'CANONICAL_PHASE_MISSING');
|
|
101
|
+
expect(missing).toHaveLength(1);
|
|
102
|
+
expect(missing[0].message).toContain('p1-session-ux-extensibility');
|
|
103
|
+
});
|
|
104
|
+
it('warns (non-blocking) on duplicate titles and unknown phaseId', async () => {
|
|
105
|
+
const z = seedPlan([phase('p0-safety-observability'), phase('p1-session-ux-extensibility')], [
|
|
106
|
+
task('a-1', 'Lifecycle hook runner', 'p0-safety-observability'),
|
|
107
|
+
task('b-2', 'Lifecycle Hook Runner!', 'p1-session-ux-extensibility'),
|
|
108
|
+
task('c-3', 'Task orfano', 'fase-inesistente'),
|
|
109
|
+
], []);
|
|
110
|
+
seedCanonical(CANONICAL);
|
|
111
|
+
const result = await runPlanDriftCheck(z);
|
|
112
|
+
expect(result.ok).toBe(true); // warnings only
|
|
113
|
+
const codes = result.findings.map((f) => f.code).sort();
|
|
114
|
+
expect(codes).toEqual(['DUPLICATE_TASK_TITLE', 'TASK_IN_UNKNOWN_PHASE']);
|
|
115
|
+
});
|
|
116
|
+
it('runs structural checks only when no canonical doc exists', async () => {
|
|
117
|
+
const z = seedPlan([phase('p0-safety-observability')], [task('t-1', 'Task', 'p0-safety-observability')], [{ kind: 'milestone', id: 'm-1', name: 'v1', targetVersion: 'v1.0.0' }]);
|
|
118
|
+
const result = await runPlanDriftCheck(z);
|
|
119
|
+
expect(result.ran).toBe(true);
|
|
120
|
+
expect(result.ok).toBe(true);
|
|
121
|
+
expect(result.canonicalDoc).toBeUndefined();
|
|
122
|
+
expect(result.reason).toContain('no canonical doc');
|
|
123
|
+
});
|
|
124
|
+
it('is skipped when plan.json is missing', async () => {
|
|
125
|
+
const z = zelari();
|
|
126
|
+
const result = await runPlanDriftCheck(z);
|
|
127
|
+
expect(result.ran).toBe(false);
|
|
128
|
+
expect(result.reason).toContain('missing');
|
|
129
|
+
});
|
|
130
|
+
it('is skipped when plan.json is corrupt', async () => {
|
|
131
|
+
const z = zelari();
|
|
132
|
+
writeFileSync(join(z, 'plan.json'), '{not json', 'utf8');
|
|
133
|
+
const result = await runPlanDriftCheck(z);
|
|
134
|
+
expect(result.ran).toBe(false);
|
|
135
|
+
expect(result.reason).toContain('corrupt');
|
|
136
|
+
});
|
|
137
|
+
it('is disabled via ZELARI_DRIFT_CHECK=0', async () => {
|
|
138
|
+
seedPlan([phase('p0')], [], []);
|
|
139
|
+
process.env['ZELARI_DRIFT_CHECK'] = '0';
|
|
140
|
+
const result = await runPlanDriftCheck(join(dir, '.zelari'));
|
|
141
|
+
expect(result.ran).toBe(false);
|
|
142
|
+
expect(result.reason).toContain('disabled');
|
|
143
|
+
expect(existsSync(join(dir, '.zelari', 'drift-report.json'))).toBe(false);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
//# sourceMappingURL=planDriftCheck.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"planDriftCheck.test.js","sourceRoot":"","sources":["../../../src/cli/workspace/planDriftCheck.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* workspace/planStore.ts — Durable workspace task store (ADR-0018, slice 3a).
|
|
3
|
+
*
|
|
4
|
+
* Canonical, versioned view of `.zelari/plan.json`, shared by the agent-loop
|
|
5
|
+
* `task_*` tools (src/cli/tools/planTaskTools.ts) and the council stubs
|
|
6
|
+
* (createPlan/createTask/updateTask — src/cli/workspace/stubs.ts).
|
|
7
|
+
*
|
|
8
|
+
* Contract (docs/decisions/0018-workspace-task-store-plan-json.md):
|
|
9
|
+
* - Envelope v1: root carries `schemaVersion`, `counter`, `tasks`; every
|
|
10
|
+
* other root field (`phases`, `milestones`, council metadata, …) is
|
|
11
|
+
* preserved verbatim in pass-through. Unknown fields on a task are kept
|
|
12
|
+
* unless explicitly overwritten.
|
|
13
|
+
* - Status: `pending | in_progress | completed | cancelled | blocked`.
|
|
14
|
+
* Legacy council `done` is normalized to `completed` on read; `task_*`
|
|
15
|
+
* writes use the v1 vocabulary. Council readers were patched to accept
|
|
16
|
+
* both (workspaceSummary.ts).
|
|
17
|
+
* - Ids: `t<N>` sequential via the persisted `counter` — never collides
|
|
18
|
+
* with council ids (`<phaseId>-<slug>-<N>`).
|
|
19
|
+
* - Concurrency: same in-process keyed mutex as the council stubs
|
|
20
|
+
* (`workspaceMutex`, key `<root>:plan`) + atomic tmp+rename write, with a
|
|
21
|
+
* `plan.json.bak` backup before rewriting an existing file. Cross-process
|
|
22
|
+
* races (two CLIs on the same cwd) remain best-effort until a lock file
|
|
23
|
+
* lands (ADR-0018 hardening, out of scope for 3a).
|
|
24
|
+
*
|
|
25
|
+
* @since v1.43.0
|
|
26
|
+
*/
|
|
27
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync, } from 'node:fs';
|
|
28
|
+
import { dirname, join } from 'node:path';
|
|
29
|
+
import { resolveWorkspaceRoot } from './paths.js';
|
|
30
|
+
import { Storage, workspaceMutex } from './storage.js';
|
|
31
|
+
export const PLAN_SCHEMA_VERSION = 1;
|
|
32
|
+
export const PLAN_MAX_TASKS = 100;
|
|
33
|
+
export const PLAN_TITLE_MAX = 200;
|
|
34
|
+
export const PLAN_NOTES_MAX = 2000;
|
|
35
|
+
export const PLAN_TAG_MAX = 64;
|
|
36
|
+
export const PLAN_TASK_STATUSES = [
|
|
37
|
+
'pending',
|
|
38
|
+
'in_progress',
|
|
39
|
+
'completed',
|
|
40
|
+
'cancelled',
|
|
41
|
+
'blocked',
|
|
42
|
+
];
|
|
43
|
+
/** Typed store error — corrupt file never gets overwritten silently. */
|
|
44
|
+
export class PlanStoreError extends Error {
|
|
45
|
+
code;
|
|
46
|
+
constructor(message, code) {
|
|
47
|
+
super(message);
|
|
48
|
+
this.code = code;
|
|
49
|
+
this.name = 'PlanStoreError';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** Resolved plan.json path for a project root (creates nothing). */
|
|
53
|
+
export function planJsonPathFor(projectRoot = process.cwd()) {
|
|
54
|
+
return join(resolveWorkspaceRoot(projectRoot), 'plan.json');
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Read-modify-write the plan store under the shared workspace mutex.
|
|
58
|
+
* Corrupt JSON → PlanStoreError('PLAN_CORRUPT') and the file is left intact.
|
|
59
|
+
*/
|
|
60
|
+
export async function withPlanStore(projectRoot, fn) {
|
|
61
|
+
const rootDir = resolveWorkspaceRoot(projectRoot);
|
|
62
|
+
return workspaceMutex.run(`${rootDir}:plan`, () => {
|
|
63
|
+
const handle = loadHandle(rootDir);
|
|
64
|
+
const out = fn(handle);
|
|
65
|
+
saveHandle(rootDir, handle);
|
|
66
|
+
return out;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/** Allocate the next `t<N>` id (increments the persisted counter). */
|
|
70
|
+
export function nextPlanTaskId(store) {
|
|
71
|
+
const maxExisting = store.tasks.reduce((max, t) => {
|
|
72
|
+
const m = /^t(\d+)$/.exec(t.id);
|
|
73
|
+
return m ? Math.max(max, parseInt(m[1], 10)) : max;
|
|
74
|
+
}, 0);
|
|
75
|
+
store.counter = Math.max(store.counter, maxExisting) + 1;
|
|
76
|
+
return `t${store.counter}`;
|
|
77
|
+
}
|
|
78
|
+
/** Write/refresh the per-task artifact the workspace summary links to. */
|
|
79
|
+
export function writePlanTaskArtifact(rootDir, task) {
|
|
80
|
+
const path = join(rootDir, 'plan-tasks', `${task.id}.md`);
|
|
81
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
82
|
+
const meta = {
|
|
83
|
+
kind: 'task',
|
|
84
|
+
id: task.id,
|
|
85
|
+
name: task.title,
|
|
86
|
+
phaseId: task.phaseId,
|
|
87
|
+
status: task.status,
|
|
88
|
+
priority: task.priority ?? 'medium',
|
|
89
|
+
updatedAt: task.updatedAt,
|
|
90
|
+
};
|
|
91
|
+
const body = [
|
|
92
|
+
`# Task ${task.id}: ${task.title}`,
|
|
93
|
+
'',
|
|
94
|
+
`- Status: **${task.status}**`,
|
|
95
|
+
`- Priority: ${task.priority ?? 'medium'}`,
|
|
96
|
+
task.phaseId ? `- Phase: ${task.phaseId}` : null,
|
|
97
|
+
task.agent ? `- Agent: ${task.agent}` : null,
|
|
98
|
+
'',
|
|
99
|
+
task.notes?.trim() ? task.notes.trim() : '_(no notes)_',
|
|
100
|
+
'',
|
|
101
|
+
]
|
|
102
|
+
.filter((l) => l !== null)
|
|
103
|
+
.join('\n');
|
|
104
|
+
new Storage().write(path, meta, body);
|
|
105
|
+
}
|
|
106
|
+
// ── internals ─────────────────────────────────────────────────────────
|
|
107
|
+
function loadHandle(rootDir) {
|
|
108
|
+
const jsonPath = join(rootDir, 'plan.json');
|
|
109
|
+
if (!existsSync(jsonPath)) {
|
|
110
|
+
return { rootDir, tasks: [], counter: 0, rootFields: {} };
|
|
111
|
+
}
|
|
112
|
+
let parsed;
|
|
113
|
+
try {
|
|
114
|
+
const raw = JSON.parse(readFileSync(jsonPath, 'utf8'));
|
|
115
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
116
|
+
throw new Error('root is not a JSON object');
|
|
117
|
+
}
|
|
118
|
+
parsed = raw;
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
throw new PlanStoreError(`PLAN_CORRUPT: ${jsonPath} is not valid JSON — refusing to overwrite a ` +
|
|
122
|
+
`possibly hand-edited or crashed write. Fix or remove the file (a ` +
|
|
123
|
+
`.bak may exist), then retry.`, 'PLAN_CORRUPT');
|
|
124
|
+
}
|
|
125
|
+
const { tasks: rawTasks, counter, schemaVersion: _schemaVersion, ...rootFields } = parsed;
|
|
126
|
+
const tasks = (Array.isArray(rawTasks) ? rawTasks : [])
|
|
127
|
+
.map(normalizeTask)
|
|
128
|
+
.filter((t) => typeof t.id === 'string' && t.id.length > 0);
|
|
129
|
+
const numericCounter = typeof counter === 'number' && Number.isFinite(counter) && counter >= 0
|
|
130
|
+
? Math.floor(counter)
|
|
131
|
+
: 0;
|
|
132
|
+
return { rootDir, tasks, counter: numericCounter, rootFields };
|
|
133
|
+
}
|
|
134
|
+
function saveHandle(rootDir, handle) {
|
|
135
|
+
if (handle.tasks.length > PLAN_MAX_TASKS) {
|
|
136
|
+
throw new PlanStoreError(`PLAN_TOO_MANY_TASKS: plan.json would exceed ${PLAN_MAX_TASKS} tasks ` +
|
|
137
|
+
`(${handle.tasks.length}) — cancel or complete tasks first.`, 'PLAN_TOO_MANY_TASKS');
|
|
138
|
+
}
|
|
139
|
+
const jsonPath = join(rootDir, 'plan.json');
|
|
140
|
+
mkdirSync(rootDir, { recursive: true });
|
|
141
|
+
if (existsSync(jsonPath)) {
|
|
142
|
+
copyFileSync(jsonPath, `${jsonPath}.bak`);
|
|
143
|
+
}
|
|
144
|
+
const file = {
|
|
145
|
+
...handle.rootFields,
|
|
146
|
+
schemaVersion: PLAN_SCHEMA_VERSION,
|
|
147
|
+
counter: handle.counter,
|
|
148
|
+
tasks: handle.tasks,
|
|
149
|
+
};
|
|
150
|
+
const tmp = `${jsonPath}.tmp-${process.pid}`;
|
|
151
|
+
writeFileSync(tmp, JSON.stringify(file, null, 2) + '\n', 'utf8');
|
|
152
|
+
renameSync(tmp, jsonPath);
|
|
153
|
+
}
|
|
154
|
+
/** Normalize a raw task record (council or v1) into the contract shape. */
|
|
155
|
+
function normalizeTask(raw) {
|
|
156
|
+
const t = (raw !== null && typeof raw === 'object'
|
|
157
|
+
? { ...raw }
|
|
158
|
+
: {});
|
|
159
|
+
if (typeof t.id === 'string') {
|
|
160
|
+
t.id = t.id.trim().slice(0, 64);
|
|
161
|
+
}
|
|
162
|
+
const titleSource = firstString(t.title) ??
|
|
163
|
+
firstString(t.name) ??
|
|
164
|
+
firstString(t.description) ??
|
|
165
|
+
'';
|
|
166
|
+
t.title = titleSource.slice(0, PLAN_TITLE_MAX);
|
|
167
|
+
// Council readers (buildPlanSummary) render `name` — keep the alias in
|
|
168
|
+
// sync so t<N> tasks display correctly there too.
|
|
169
|
+
if (t.title && !firstString(t.name)) {
|
|
170
|
+
t.name = t.title;
|
|
171
|
+
}
|
|
172
|
+
t.status = normalizeStatus(t.status);
|
|
173
|
+
if (typeof t.notes === 'string') {
|
|
174
|
+
t.notes = t.notes.slice(0, PLAN_NOTES_MAX);
|
|
175
|
+
}
|
|
176
|
+
if (typeof t.phaseId === 'string') {
|
|
177
|
+
t.phaseId = t.phaseId.slice(0, PLAN_TAG_MAX);
|
|
178
|
+
}
|
|
179
|
+
if (typeof t.agent === 'string') {
|
|
180
|
+
t.agent = t.agent.slice(0, PLAN_TAG_MAX);
|
|
181
|
+
}
|
|
182
|
+
return t;
|
|
183
|
+
}
|
|
184
|
+
function normalizeStatus(raw) {
|
|
185
|
+
const s = typeof raw === 'string' ? raw.trim().toLowerCase() : '';
|
|
186
|
+
switch (s) {
|
|
187
|
+
case 'in_progress':
|
|
188
|
+
case 'in-progress':
|
|
189
|
+
case 'doing':
|
|
190
|
+
case 'started':
|
|
191
|
+
case 'active':
|
|
192
|
+
return 'in_progress';
|
|
193
|
+
case 'completed':
|
|
194
|
+
case 'complete':
|
|
195
|
+
case 'done':
|
|
196
|
+
case 'finished':
|
|
197
|
+
return 'completed';
|
|
198
|
+
case 'cancelled':
|
|
199
|
+
case 'canceled':
|
|
200
|
+
case 'closed':
|
|
201
|
+
return 'cancelled';
|
|
202
|
+
case 'blocked':
|
|
203
|
+
case 'on-hold':
|
|
204
|
+
case 'on hold':
|
|
205
|
+
return 'blocked';
|
|
206
|
+
default:
|
|
207
|
+
return 'pending';
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function firstString(v) {
|
|
211
|
+
return typeof v === 'string' && v.trim().length > 0 ? v : null;
|
|
212
|
+
}
|
|
213
|
+
//# sourceMappingURL=planStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"planStore.js","sourceRoot":"","sources":["../../../src/cli/workspace/planStore.ts"],"names":[],"mappings":"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"}
|