@eddacraft/anvil-aps 0.1.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/AGENTS.md +155 -0
- package/LICENSE +14 -0
- package/README.md +57 -0
- package/TODO.md +40 -0
- package/dist/filter/context-bundle.d.ts +81 -0
- package/dist/filter/context-bundle.d.ts.map +1 -0
- package/dist/filter/context-bundle.js +230 -0
- package/dist/filter/index.d.ts +85 -0
- package/dist/filter/index.d.ts.map +1 -0
- package/dist/filter/index.js +169 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/loader/index.d.ts +80 -0
- package/dist/loader/index.d.ts.map +1 -0
- package/dist/loader/index.js +253 -0
- package/dist/parser/index.d.ts +24 -0
- package/dist/parser/index.d.ts.map +1 -0
- package/dist/parser/index.js +22 -0
- package/dist/parser/parse-document.d.ts +17 -0
- package/dist/parser/parse-document.d.ts.map +1 -0
- package/dist/parser/parse-document.js +219 -0
- package/dist/parser/parse-index.d.ts +31 -0
- package/dist/parser/parse-index.d.ts.map +1 -0
- package/dist/parser/parse-index.js +251 -0
- package/dist/parser/parse-task.d.ts +30 -0
- package/dist/parser/parse-task.d.ts.map +1 -0
- package/dist/parser/parse-task.js +261 -0
- package/dist/state/index.d.ts +307 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +689 -0
- package/dist/templates/generator.d.ts +71 -0
- package/dist/templates/generator.d.ts.map +1 -0
- package/dist/templates/generator.js +723 -0
- package/dist/templates/index.d.ts +5 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/index.js +4 -0
- package/dist/types/index.d.ts +131 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +107 -0
- package/dist/validator/index.d.ts +83 -0
- package/dist/validator/index.d.ts.map +1 -0
- package/dist/validator/index.js +611 -0
- package/docs/APS-Anvil-Integration.md +750 -0
- package/docs/APS-Conventions.md +635 -0
- package/docs/APS-NonGoals.md +455 -0
- package/docs/APS-Planning-Spec-v0.1.md +362 -0
- package/examples/README.md +170 -0
- package/examples/feature-auth.aps.md +87 -0
- package/examples/refactor-error-handling.aps.md +119 -0
- package/examples/system-ecommerce/APS.md +57 -0
- package/examples/system-ecommerce/modules/auth.aps.md +38 -0
- package/examples/system-ecommerce/modules/cart.aps.md +53 -0
- package/examples/system-ecommerce/modules/payments.aps.md +68 -0
- package/examples/system-ecommerce/modules/products.aps.md +53 -0
- package/package.json +34 -0
- package/project.json +37 -0
- package/scripts/generate-templates.js +33 -0
- package/src/filter/context-bundle.ts +312 -0
- package/src/filter/filter.test.ts +317 -0
- package/src/filter/index.ts +249 -0
- package/src/index.ts +16 -0
- package/src/loader/index.ts +364 -0
- package/src/loader/loader.test.ts +224 -0
- package/src/parser/__fixtures__/invalid-task-id-not-padded.aps.md +7 -0
- package/src/parser/__fixtures__/invalid-task-id.aps.md +8 -0
- package/src/parser/__fixtures__/minimal-task.aps.md +7 -0
- package/src/parser/__fixtures__/non-scope-hyphenated.aps.md +10 -0
- package/src/parser/__fixtures__/simple-index.aps.md +35 -0
- package/src/parser/__fixtures__/simple-plan.aps.md +19 -0
- package/src/parser/index.ts +30 -0
- package/src/parser/parse-document.test.ts +603 -0
- package/src/parser/parse-document.ts +262 -0
- package/src/parser/parse-index.test.ts +316 -0
- package/src/parser/parse-index.ts +298 -0
- package/src/parser/parse-task.test.ts +476 -0
- package/src/parser/parse-task.ts +325 -0
- package/src/state/__fixtures__/invalid-plan.aps.md +9 -0
- package/src/state/__fixtures__/test-plan.aps.md +20 -0
- package/src/state/index.ts +879 -0
- package/src/state/state.test.ts +645 -0
- package/src/templates/generator.test.ts +378 -0
- package/src/templates/generator.ts +776 -0
- package/src/templates/index.ts +5 -0
- package/src/types/index.ts +168 -0
- package/src/validator/__fixtures__/broken-links.aps.md +10 -0
- package/src/validator/__fixtures__/circular-deps-index.aps.md +26 -0
- package/src/validator/__fixtures__/circular-modules/module-a.aps.md +9 -0
- package/src/validator/__fixtures__/circular-modules/module-b.aps.md +9 -0
- package/src/validator/__fixtures__/circular-modules/module-c.aps.md +9 -0
- package/src/validator/__fixtures__/dup-modules/module-a.aps.md +9 -0
- package/src/validator/__fixtures__/dup-modules/module-b.aps.md +9 -0
- package/src/validator/__fixtures__/duplicate-ids-index.aps.md +15 -0
- package/src/validator/__fixtures__/invalid-task-id.aps.md +17 -0
- package/src/validator/__fixtures__/missing-confidence.aps.md +9 -0
- package/src/validator/__fixtures__/missing-h1.aps.md +5 -0
- package/src/validator/__fixtures__/missing-intent.aps.md +9 -0
- package/src/validator/__fixtures__/missing-modules-section.aps.md +7 -0
- package/src/validator/__fixtures__/missing-tasks-section.aps.md +7 -0
- package/src/validator/__fixtures__/modules/auth.aps.md +17 -0
- package/src/validator/__fixtures__/modules/payments.aps.md +13 -0
- package/src/validator/__fixtures__/scope-mismatch.aps.md +14 -0
- package/src/validator/__fixtures__/valid-index.aps.md +24 -0
- package/src/validator/__fixtures__/valid-leaf.aps.md +22 -0
- package/src/validator/index.ts +776 -0
- package/src/validator/validator.test.ts +269 -0
- package/templates/index-full.md +94 -0
- package/templates/index-minimal.md +16 -0
- package/templates/index-template.md +63 -0
- package/templates/leaf-full.md +76 -0
- package/templates/leaf-minimal.md +14 -0
- package/templates/leaf-template.md +55 -0
- package/templates/simple-full.md +56 -0
- package/templates/simple-minimal.md +14 -0
- package/templates/simple-template.md +30 -0
- package/tsconfig.json +19 -0
- package/tsconfig.lib.json +14 -0
- package/tsconfig.lib.tsbuildinfo +1 -0
- package/tsconfig.spec.json +9 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vitest.config.ts +15 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task parsing utilities
|
|
3
|
+
* Extracts task information from Markdown AST nodes
|
|
4
|
+
*/
|
|
5
|
+
import { ParseError } from '../types/index.js';
|
|
6
|
+
/**
|
|
7
|
+
* Extract task ID and title from H3 heading text
|
|
8
|
+
* Format: "SCOPE-NUMBER: Task title"
|
|
9
|
+
* - Scope: 1-10 uppercase alphanumeric characters
|
|
10
|
+
* - Number: 3-digit zero-padded (001-999)
|
|
11
|
+
*/
|
|
12
|
+
export function parseTaskHeading(heading) {
|
|
13
|
+
if (heading.depth !== 3) {
|
|
14
|
+
throw new ParseError('Task headings must be H3 (###)', undefined, undefined, 'parseTaskHeading');
|
|
15
|
+
}
|
|
16
|
+
const text = extractPlainText(heading);
|
|
17
|
+
// Extract ID and title - ID must match TASK_ID_REGEX format
|
|
18
|
+
const match = text.match(/^([A-Z0-9]{1,10}-\d{3}):\s*(.+)$/);
|
|
19
|
+
if (!match) {
|
|
20
|
+
throw new ParseError(`Invalid task heading format. Expected "SCOPE-NNN: Title" (e.g., AUTH-001), got: "${text}"`, undefined, undefined, 'parseTaskHeading');
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
id: match[1],
|
|
24
|
+
title: match[2].trim(),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Extract plain text from AST node (handles inline formatting)
|
|
29
|
+
*/
|
|
30
|
+
function extractPlainText(node) {
|
|
31
|
+
if ('value' in node && typeof node.value === 'string') {
|
|
32
|
+
return node.value;
|
|
33
|
+
}
|
|
34
|
+
if ('children' in node && Array.isArray(node.children)) {
|
|
35
|
+
return node.children.map((child) => extractPlainText(child)).join('');
|
|
36
|
+
}
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Parse task fields from paragraph and list nodes
|
|
41
|
+
* Fields are in format: **FieldName:** value
|
|
42
|
+
*/
|
|
43
|
+
export function parseTaskFields(paragraphs, lists) {
|
|
44
|
+
const fields = {};
|
|
45
|
+
let lastField = null;
|
|
46
|
+
let inlineInputs = null;
|
|
47
|
+
// First pass: extract all field key-value pairs from paragraphs
|
|
48
|
+
for (const para of paragraphs) {
|
|
49
|
+
const fieldMatches = extractFieldsFromParagraph(para);
|
|
50
|
+
for (const [key, value] of Object.entries(fieldMatches)) {
|
|
51
|
+
// Handle Inputs specially - may be inline text or followed by a list
|
|
52
|
+
if (key === 'Inputs') {
|
|
53
|
+
if (value.trim() === '') {
|
|
54
|
+
// Empty value - expect a list to follow
|
|
55
|
+
lastField = key;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
// Inline text value - store for later (list takes precedence if present)
|
|
59
|
+
inlineInputs = value.trim();
|
|
60
|
+
lastField = key;
|
|
61
|
+
}
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
assignField(fields, key, value);
|
|
65
|
+
lastField = key;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Second pass: handle Inputs field
|
|
69
|
+
// Lists take precedence over inline text
|
|
70
|
+
if (lastField === 'Inputs' && lists.length > 0) {
|
|
71
|
+
fields.inputs = extractListItems(lists[0]);
|
|
72
|
+
}
|
|
73
|
+
else if (inlineInputs !== null) {
|
|
74
|
+
// Use inline text as a single-item array
|
|
75
|
+
fields.inputs = [inlineInputs];
|
|
76
|
+
}
|
|
77
|
+
return fields;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Extract field key-value pairs from a paragraph containing bold markers
|
|
81
|
+
*/
|
|
82
|
+
function extractFieldsFromParagraph(para) {
|
|
83
|
+
const fields = {};
|
|
84
|
+
let currentKey = '';
|
|
85
|
+
let currentValue = '';
|
|
86
|
+
let inField = false;
|
|
87
|
+
for (const child of para.children) {
|
|
88
|
+
if (child.type === 'strong') {
|
|
89
|
+
// Check if this strong node contains a field name
|
|
90
|
+
const strongText = extractPlainText(child);
|
|
91
|
+
const fieldMatch = strongText.match(/^([\w-]+(?:\s+[\w-]+)*):$/);
|
|
92
|
+
if (fieldMatch) {
|
|
93
|
+
// Save previous field if exists (even if value is empty)
|
|
94
|
+
if (currentKey) {
|
|
95
|
+
fields[currentKey] = currentValue.trim().replace(/\s+/g, ' ');
|
|
96
|
+
}
|
|
97
|
+
currentKey = fieldMatch[1];
|
|
98
|
+
currentValue = '';
|
|
99
|
+
inField = true;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else if (inField) {
|
|
103
|
+
// Extract text from any phrasing content node (text, inlineCode, etc.)
|
|
104
|
+
// This handles validation commands in backticks and other inline formatting
|
|
105
|
+
if (child.type === 'break') {
|
|
106
|
+
// Convert breaks to spaces to handle multi-line values
|
|
107
|
+
currentValue += ' ';
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
currentValue += extractPlainText(child);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Save last field (even if value is empty)
|
|
115
|
+
if (currentKey) {
|
|
116
|
+
fields[currentKey] = currentValue.trim().replace(/\s+/g, ' ');
|
|
117
|
+
}
|
|
118
|
+
return fields;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Assign a parsed field value to the appropriate Task property
|
|
122
|
+
*/
|
|
123
|
+
function assignField(task, key, value) {
|
|
124
|
+
const normalizedKey = key.replace(/\s+/g, '');
|
|
125
|
+
switch (normalizedKey) {
|
|
126
|
+
case 'Intent':
|
|
127
|
+
task.intent = value;
|
|
128
|
+
break;
|
|
129
|
+
case 'ExpectedOutcome':
|
|
130
|
+
task.expectedOutcome = value;
|
|
131
|
+
break;
|
|
132
|
+
case 'Validation':
|
|
133
|
+
case 'Test':
|
|
134
|
+
// Support both "Validation:" and "Test:" field names per APS spec
|
|
135
|
+
task.validation = value;
|
|
136
|
+
break;
|
|
137
|
+
case 'Confidence':
|
|
138
|
+
task.confidence = parseConfidence(value);
|
|
139
|
+
break;
|
|
140
|
+
case 'Scopes':
|
|
141
|
+
task.scopes = parseCommaSeparated(value);
|
|
142
|
+
break;
|
|
143
|
+
case 'NonScope':
|
|
144
|
+
case 'Non-scope':
|
|
145
|
+
task.nonScope = parseCommaSeparated(value);
|
|
146
|
+
break;
|
|
147
|
+
case 'Files':
|
|
148
|
+
task.files = parseCommaSeparated(value);
|
|
149
|
+
break;
|
|
150
|
+
case 'Tags':
|
|
151
|
+
task.tags = parseCommaSeparated(value);
|
|
152
|
+
break;
|
|
153
|
+
case 'Dependencies':
|
|
154
|
+
task.dependencies = parseCommaSeparated(value);
|
|
155
|
+
break;
|
|
156
|
+
case 'Risks':
|
|
157
|
+
task.risks = parseCommaSeparated(value);
|
|
158
|
+
break;
|
|
159
|
+
case 'Packages': {
|
|
160
|
+
// Monorepo support: list of affected packages
|
|
161
|
+
const trimmed = value.trim();
|
|
162
|
+
if (!trimmed || trimmed.toLowerCase() === '(none)') {
|
|
163
|
+
task.packages = [];
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
task.packages = parseCommaSeparated(value);
|
|
167
|
+
}
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
case 'Link':
|
|
171
|
+
task.link = value;
|
|
172
|
+
break;
|
|
173
|
+
case 'Status':
|
|
174
|
+
task.status = parseStatus(value);
|
|
175
|
+
break;
|
|
176
|
+
// 'Inputs' is handled separately as a list
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Parse confidence value from string
|
|
181
|
+
*/
|
|
182
|
+
function parseConfidence(value) {
|
|
183
|
+
const normalized = value.toLowerCase().trim();
|
|
184
|
+
if (normalized === 'low' || normalized === 'medium' || normalized === 'high') {
|
|
185
|
+
return normalized;
|
|
186
|
+
}
|
|
187
|
+
return 'medium'; // default
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Parse task status from string
|
|
191
|
+
*/
|
|
192
|
+
function parseStatus(value) {
|
|
193
|
+
const normalized = value.toLowerCase().trim();
|
|
194
|
+
if (normalized === 'open' ||
|
|
195
|
+
normalized === 'locked' ||
|
|
196
|
+
normalized === 'completed' ||
|
|
197
|
+
normalized === 'cancelled') {
|
|
198
|
+
return normalized;
|
|
199
|
+
}
|
|
200
|
+
return 'open'; // default
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Parse comma-separated list into array
|
|
204
|
+
*/
|
|
205
|
+
function parseCommaSeparated(value) {
|
|
206
|
+
return value
|
|
207
|
+
.split(',')
|
|
208
|
+
.map((item) => item.trim())
|
|
209
|
+
.filter((item) => item.length > 0);
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Extract list items as strings
|
|
213
|
+
*/
|
|
214
|
+
function extractListItems(list) {
|
|
215
|
+
const items = [];
|
|
216
|
+
for (const item of list.children) {
|
|
217
|
+
if (item.type === 'listItem' && item.children.length > 0) {
|
|
218
|
+
const firstChild = item.children[0];
|
|
219
|
+
if (firstChild && firstChild.type === 'paragraph') {
|
|
220
|
+
items.push(extractPlainText(firstChild));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return items;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Parse a complete task from AST nodes
|
|
228
|
+
* @param heading - H3 heading node with task ID and title
|
|
229
|
+
* @param content - Array of paragraph and list nodes containing task fields
|
|
230
|
+
* @param sourcePath - Optional source file path
|
|
231
|
+
* @param lineNumber - Optional line number where task starts
|
|
232
|
+
*/
|
|
233
|
+
export function parseTask(heading, content, sourcePath, lineNumber) {
|
|
234
|
+
const { id, title } = parseTaskHeading(heading);
|
|
235
|
+
const paragraphs = content.filter((node) => node.type === 'paragraph');
|
|
236
|
+
const lists = content.filter((node) => node.type === 'list');
|
|
237
|
+
const fields = parseTaskFields(paragraphs, lists);
|
|
238
|
+
if (!fields.intent) {
|
|
239
|
+
throw new ParseError(`Task ${id} is missing required field: Intent`, sourcePath, lineNumber, 'parseTask');
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
id,
|
|
243
|
+
title,
|
|
244
|
+
intent: fields.intent,
|
|
245
|
+
expectedOutcome: fields.expectedOutcome,
|
|
246
|
+
validation: fields.validation,
|
|
247
|
+
confidence: fields.confidence ?? 'medium',
|
|
248
|
+
scopes: fields.scopes,
|
|
249
|
+
nonScope: fields.nonScope,
|
|
250
|
+
files: fields.files,
|
|
251
|
+
tags: fields.tags,
|
|
252
|
+
dependencies: fields.dependencies,
|
|
253
|
+
inputs: fields.inputs,
|
|
254
|
+
risks: fields.risks,
|
|
255
|
+
packages: fields.packages,
|
|
256
|
+
link: fields.link,
|
|
257
|
+
status: fields.status,
|
|
258
|
+
sourcePath,
|
|
259
|
+
sourceLineNumber: lineNumber,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State module - Task state management and locking functionality
|
|
3
|
+
*
|
|
4
|
+
* Manages `.anvil/state.json` for tracking task execution states.
|
|
5
|
+
* Provides TaskLocker for locking tasks for execution with:
|
|
6
|
+
* - First-lock-wins concurrent lock handling
|
|
7
|
+
* - Execution plan JSON generation with hash and provenance
|
|
8
|
+
* - Lock/unlock/status operations
|
|
9
|
+
*/
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
import { type Task, type TaskStatus } from '../types/index.js';
|
|
12
|
+
/**
|
|
13
|
+
* Source location of a task in a planning document
|
|
14
|
+
*/
|
|
15
|
+
export declare const TaskSourceSchema: z.ZodObject<{
|
|
16
|
+
file: z.ZodString;
|
|
17
|
+
line: z.ZodOptional<z.ZodNumber>;
|
|
18
|
+
}, z.core.$strip>;
|
|
19
|
+
export type TaskSource = z.infer<typeof TaskSourceSchema>;
|
|
20
|
+
/**
|
|
21
|
+
* State of a single task
|
|
22
|
+
*/
|
|
23
|
+
export declare const TaskStateSchema: z.ZodObject<{
|
|
24
|
+
status: z.ZodEnum<{
|
|
25
|
+
open: "open";
|
|
26
|
+
locked: "locked";
|
|
27
|
+
completed: "completed";
|
|
28
|
+
cancelled: "cancelled";
|
|
29
|
+
}>;
|
|
30
|
+
locked_at: z.ZodOptional<z.ZodString>;
|
|
31
|
+
locked_by: z.ZodOptional<z.ZodString>;
|
|
32
|
+
execution_file: z.ZodOptional<z.ZodString>;
|
|
33
|
+
source: z.ZodOptional<z.ZodObject<{
|
|
34
|
+
file: z.ZodString;
|
|
35
|
+
line: z.ZodOptional<z.ZodNumber>;
|
|
36
|
+
}, z.core.$strip>>;
|
|
37
|
+
completed_at: z.ZodOptional<z.ZodString>;
|
|
38
|
+
cancelled_at: z.ZodOptional<z.ZodString>;
|
|
39
|
+
}, z.core.$strip>;
|
|
40
|
+
export type TaskState = z.infer<typeof TaskStateSchema>;
|
|
41
|
+
/**
|
|
42
|
+
* Full state file schema (.anvil/state.json)
|
|
43
|
+
*/
|
|
44
|
+
export declare const StateFileSchema: z.ZodObject<{
|
|
45
|
+
version: z.ZodDefault<z.ZodString>;
|
|
46
|
+
tasks: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
47
|
+
status: z.ZodEnum<{
|
|
48
|
+
open: "open";
|
|
49
|
+
locked: "locked";
|
|
50
|
+
completed: "completed";
|
|
51
|
+
cancelled: "cancelled";
|
|
52
|
+
}>;
|
|
53
|
+
locked_at: z.ZodOptional<z.ZodString>;
|
|
54
|
+
locked_by: z.ZodOptional<z.ZodString>;
|
|
55
|
+
execution_file: z.ZodOptional<z.ZodString>;
|
|
56
|
+
source: z.ZodOptional<z.ZodObject<{
|
|
57
|
+
file: z.ZodString;
|
|
58
|
+
line: z.ZodOptional<z.ZodNumber>;
|
|
59
|
+
}, z.core.$strip>>;
|
|
60
|
+
completed_at: z.ZodOptional<z.ZodString>;
|
|
61
|
+
cancelled_at: z.ZodOptional<z.ZodString>;
|
|
62
|
+
}, z.core.$strip>>;
|
|
63
|
+
}, z.core.$strip>;
|
|
64
|
+
export type StateFile = z.infer<typeof StateFileSchema>;
|
|
65
|
+
/**
|
|
66
|
+
* Provenance information for execution plans
|
|
67
|
+
*/
|
|
68
|
+
export declare const ProvenanceSchema: z.ZodObject<{
|
|
69
|
+
locked_by: z.ZodString;
|
|
70
|
+
locked_at: z.ZodString;
|
|
71
|
+
git_commit: z.ZodOptional<z.ZodString>;
|
|
72
|
+
git_branch: z.ZodOptional<z.ZodString>;
|
|
73
|
+
source_file: z.ZodString;
|
|
74
|
+
source_line: z.ZodOptional<z.ZodNumber>;
|
|
75
|
+
}, z.core.$strip>;
|
|
76
|
+
export type Provenance = z.infer<typeof ProvenanceSchema>;
|
|
77
|
+
/**
|
|
78
|
+
* Execution plan JSON schema (per-task, written to .anvil/executions/)
|
|
79
|
+
*/
|
|
80
|
+
export declare const ExecutionPlanSchema: z.ZodObject<{
|
|
81
|
+
version: z.ZodDefault<z.ZodString>;
|
|
82
|
+
task_id: z.ZodString;
|
|
83
|
+
title: z.ZodString;
|
|
84
|
+
intent: z.ZodString;
|
|
85
|
+
expected_outcome: z.ZodOptional<z.ZodString>;
|
|
86
|
+
confidence: z.ZodEnum<{
|
|
87
|
+
low: "low";
|
|
88
|
+
medium: "medium";
|
|
89
|
+
high: "high";
|
|
90
|
+
}>;
|
|
91
|
+
scopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
92
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
93
|
+
dependencies: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
94
|
+
inputs: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
95
|
+
content_hash: z.ZodString;
|
|
96
|
+
provenance: z.ZodObject<{
|
|
97
|
+
locked_by: z.ZodString;
|
|
98
|
+
locked_at: z.ZodString;
|
|
99
|
+
git_commit: z.ZodOptional<z.ZodString>;
|
|
100
|
+
git_branch: z.ZodOptional<z.ZodString>;
|
|
101
|
+
source_file: z.ZodString;
|
|
102
|
+
source_line: z.ZodOptional<z.ZodNumber>;
|
|
103
|
+
}, z.core.$strip>;
|
|
104
|
+
}, z.core.$strip>;
|
|
105
|
+
export type ExecutionPlan = z.infer<typeof ExecutionPlanSchema>;
|
|
106
|
+
/**
|
|
107
|
+
* Get the path to the state file
|
|
108
|
+
*/
|
|
109
|
+
export declare function getStateFilePath(projectRoot: string): string;
|
|
110
|
+
/**
|
|
111
|
+
* Get the path to the executions directory
|
|
112
|
+
*/
|
|
113
|
+
export declare function getExecutionsDir(projectRoot: string): string;
|
|
114
|
+
/**
|
|
115
|
+
* Read the state file, returning empty state if it doesn't exist
|
|
116
|
+
*/
|
|
117
|
+
export declare function readStateFile(projectRoot: string): Promise<StateFile>;
|
|
118
|
+
/**
|
|
119
|
+
* Write the state file, creating directories if needed
|
|
120
|
+
*/
|
|
121
|
+
export declare function writeStateFile(projectRoot: string, state: StateFile): Promise<void>;
|
|
122
|
+
/**
|
|
123
|
+
* Get the state of a specific task
|
|
124
|
+
*/
|
|
125
|
+
export declare function getTaskState(projectRoot: string, taskId: string): Promise<TaskState | undefined>;
|
|
126
|
+
/**
|
|
127
|
+
* Update the state of a specific task
|
|
128
|
+
*/
|
|
129
|
+
export declare function updateTaskState(projectRoot: string, taskId: string, taskState: TaskState): Promise<void>;
|
|
130
|
+
/**
|
|
131
|
+
* Generate execution plan JSON for a task
|
|
132
|
+
*/
|
|
133
|
+
export declare function createExecutionPlan(task: Task, provenance: Provenance): ExecutionPlan;
|
|
134
|
+
/**
|
|
135
|
+
* Compute SHA-256 hash of task content
|
|
136
|
+
*/
|
|
137
|
+
export declare function computeTaskHash(task: Task): string;
|
|
138
|
+
/**
|
|
139
|
+
* Get execution plan file path for a task
|
|
140
|
+
*/
|
|
141
|
+
export declare function getExecutionPlanPath(projectRoot: string, taskId: string): string;
|
|
142
|
+
/**
|
|
143
|
+
* Write execution plan to file
|
|
144
|
+
*/
|
|
145
|
+
export declare function writeExecutionPlan(projectRoot: string, plan: ExecutionPlan): Promise<string>;
|
|
146
|
+
/**
|
|
147
|
+
* Read execution plan from file.
|
|
148
|
+
* Verifies the stored content_hash matches the recomputed hash.
|
|
149
|
+
* If the hash does not match, the plan is still returned but a
|
|
150
|
+
* `hashMismatch` flag is set on the result.
|
|
151
|
+
*/
|
|
152
|
+
export declare function readExecutionPlan(projectRoot: string, taskId: string): Promise<(ExecutionPlan & {
|
|
153
|
+
hashMismatch?: boolean;
|
|
154
|
+
}) | undefined>;
|
|
155
|
+
/**
|
|
156
|
+
* Delete execution plan file
|
|
157
|
+
*/
|
|
158
|
+
export declare function deleteExecutionPlan(projectRoot: string, taskId: string): Promise<void>;
|
|
159
|
+
/**
|
|
160
|
+
* Get current user name
|
|
161
|
+
*/
|
|
162
|
+
export declare function getCurrentUser(): string;
|
|
163
|
+
/**
|
|
164
|
+
* Get git commit hash (if in a git repo)
|
|
165
|
+
*/
|
|
166
|
+
export declare function getGitCommit(projectRoot: string): string | undefined;
|
|
167
|
+
/**
|
|
168
|
+
* Get git branch name (if in a git repo)
|
|
169
|
+
*/
|
|
170
|
+
export declare function getGitBranch(projectRoot: string): string | undefined;
|
|
171
|
+
/**
|
|
172
|
+
* Create provenance info for a task
|
|
173
|
+
*/
|
|
174
|
+
export declare function createProvenance(task: Task, projectRoot: string, user?: string): Provenance;
|
|
175
|
+
/**
|
|
176
|
+
* Error thrown by state operations
|
|
177
|
+
*/
|
|
178
|
+
export declare class StateError extends Error {
|
|
179
|
+
readonly path?: string | undefined;
|
|
180
|
+
readonly taskId?: string | undefined;
|
|
181
|
+
constructor(message: string, path?: string | undefined, taskId?: string | undefined);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Result of a lock operation
|
|
185
|
+
*/
|
|
186
|
+
export interface LockResult {
|
|
187
|
+
success: boolean;
|
|
188
|
+
taskId: string;
|
|
189
|
+
executionPlanPath?: string;
|
|
190
|
+
error?: string;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Result of an unlock operation
|
|
194
|
+
*/
|
|
195
|
+
export interface UnlockResult {
|
|
196
|
+
success: boolean;
|
|
197
|
+
taskId: string;
|
|
198
|
+
previousStatus?: TaskStatus;
|
|
199
|
+
error?: string;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Task status information
|
|
203
|
+
*/
|
|
204
|
+
export interface TaskStatusInfo {
|
|
205
|
+
taskId: string;
|
|
206
|
+
status: TaskStatus;
|
|
207
|
+
lockedAt?: string;
|
|
208
|
+
lockedBy?: string;
|
|
209
|
+
executionFile?: string;
|
|
210
|
+
source?: TaskSource;
|
|
211
|
+
completedAt?: string;
|
|
212
|
+
cancelledAt?: string;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Options for TaskLocker
|
|
216
|
+
*/
|
|
217
|
+
export interface TaskLockerOptions {
|
|
218
|
+
/** Project root directory */
|
|
219
|
+
projectRoot: string;
|
|
220
|
+
/** Path to the planning document */
|
|
221
|
+
planPath: string;
|
|
222
|
+
/** User name for provenance (defaults to current user) */
|
|
223
|
+
user?: string;
|
|
224
|
+
/** Skip validation before locking */
|
|
225
|
+
skipValidation?: boolean;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* TaskLocker - Manages task locking for execution
|
|
229
|
+
*
|
|
230
|
+
* @example
|
|
231
|
+
* ```typescript
|
|
232
|
+
* const locker = new TaskLocker({
|
|
233
|
+
* projectRoot: '/path/to/project',
|
|
234
|
+
* planPath: 'docs/planning/APS.md',
|
|
235
|
+
* });
|
|
236
|
+
*
|
|
237
|
+
* // Lock a task
|
|
238
|
+
* const result = await locker.lock('AUTH-001');
|
|
239
|
+
* if (result.success) {
|
|
240
|
+
* console.log(`Task locked, execution plan: ${result.executionPlanPath}`);
|
|
241
|
+
* }
|
|
242
|
+
*
|
|
243
|
+
* // Check status
|
|
244
|
+
* const status = await locker.getStatus('AUTH-001');
|
|
245
|
+
*
|
|
246
|
+
* // Unlock (cancel) a task
|
|
247
|
+
* await locker.unlock('AUTH-001');
|
|
248
|
+
* ```
|
|
249
|
+
*/
|
|
250
|
+
export declare class TaskLocker {
|
|
251
|
+
private projectRoot;
|
|
252
|
+
private planPath;
|
|
253
|
+
private user;
|
|
254
|
+
private skipValidation;
|
|
255
|
+
private plan;
|
|
256
|
+
constructor(options: TaskLockerOptions);
|
|
257
|
+
/**
|
|
258
|
+
* Load the plan (validates first unless skipValidation is true)
|
|
259
|
+
*/
|
|
260
|
+
private loadPlan;
|
|
261
|
+
/**
|
|
262
|
+
* Find a task by ID in the loaded plan
|
|
263
|
+
*/
|
|
264
|
+
private findTask;
|
|
265
|
+
/**
|
|
266
|
+
* Lock a task for execution
|
|
267
|
+
*
|
|
268
|
+
* - Validates planning doc first (unless skipValidation)
|
|
269
|
+
* - Snapshots task definition
|
|
270
|
+
* - Generates execution plan JSON with hash and provenance
|
|
271
|
+
* - Updates state.json
|
|
272
|
+
* - First lock wins (fails if already locked)
|
|
273
|
+
*/
|
|
274
|
+
lock(taskId: string): Promise<LockResult>;
|
|
275
|
+
/**
|
|
276
|
+
* Unlock (cancel) a locked task
|
|
277
|
+
*
|
|
278
|
+
* - Moves task to 'cancelled' status
|
|
279
|
+
* - Removes execution plan file
|
|
280
|
+
*/
|
|
281
|
+
unlock(taskId: string): Promise<UnlockResult>;
|
|
282
|
+
/**
|
|
283
|
+
* Mark a task as completed
|
|
284
|
+
*/
|
|
285
|
+
complete(taskId: string): Promise<UnlockResult>;
|
|
286
|
+
/**
|
|
287
|
+
* Get the status of a specific task
|
|
288
|
+
*/
|
|
289
|
+
getStatus(taskId: string): Promise<TaskStatusInfo | undefined>;
|
|
290
|
+
/**
|
|
291
|
+
* Get status of all tasks in the plan
|
|
292
|
+
*/
|
|
293
|
+
getAllStatus(): Promise<TaskStatusInfo[]>;
|
|
294
|
+
/**
|
|
295
|
+
* Get summary of task statuses
|
|
296
|
+
*/
|
|
297
|
+
getStatusSummary(): Promise<Record<TaskStatus, number>>;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Format task status for display
|
|
301
|
+
*/
|
|
302
|
+
export declare function formatTaskStatus(status: TaskStatusInfo): string;
|
|
303
|
+
/**
|
|
304
|
+
* Format all task statuses for display
|
|
305
|
+
*/
|
|
306
|
+
export declare function formatAllTaskStatus(statuses: TaskStatusInfo[]): string;
|
|
307
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/state/index.ts"],"names":[],"mappings":"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"}
|