@alisio/core 0.1.1 → 0.2.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/agents/active.d.ts +15 -1
- package/dist/agents/active.js +48 -9
- package/dist/analysis/python/sources.js +7 -4
- package/dist/application.js +16 -1
- package/dist/artifacts/chart-lint.d.ts +8 -0
- package/dist/artifacts/chart-lint.js +84 -0
- package/dist/artifacts/publisher.js +3 -0
- package/dist/artifacts/store.d.ts +3 -1
- package/dist/artifacts/store.js +11 -1
- package/dist/changelog/data.js +32 -1
- package/dist/config.d.ts +14 -0
- package/dist/config.js +15 -0
- package/dist/core/contracts.d.ts +8 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/plan/diagrams.d.ts +76 -0
- package/dist/plan/diagrams.js +326 -0
- package/dist/plan/exit-plan.d.ts +5 -12
- package/dist/plan/exit-plan.js +199 -33
- package/dist/plan/manifest.d.ts +53 -0
- package/dist/plan/manifest.js +319 -0
- package/dist/plan/state.d.ts +21 -1
- package/dist/plan/state.js +6 -12
- package/dist/tools/analysis.js +5 -2
- package/package.json +2 -2
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { PlanDiagramStatus, PlanManifest } from "@alisio/sdk";
|
|
2
|
+
import { type PlanDiagram } from "./diagrams.ts";
|
|
3
|
+
export interface PlanHeading {
|
|
4
|
+
id: string;
|
|
5
|
+
title: string;
|
|
6
|
+
level: number;
|
|
7
|
+
/** Zero-based line of the heading. */
|
|
8
|
+
line: number;
|
|
9
|
+
}
|
|
10
|
+
/** What `PlanState` remembers about the diagrams of a revision (enough to diff the next one). */
|
|
11
|
+
export interface DiagramRecord {
|
|
12
|
+
id: string;
|
|
13
|
+
title: string;
|
|
14
|
+
hash: string;
|
|
15
|
+
}
|
|
16
|
+
export declare const MANIFEST_VERSION = 1;
|
|
17
|
+
/** Heading text → anchor id (lowercase letters and digits joined by hyphens). */
|
|
18
|
+
export declare function slugifyHeading(title: string): string;
|
|
19
|
+
/** ATX headings outside fenced code, with unique ids (`steps`, `steps-2`). */
|
|
20
|
+
export declare function parsePlanHeadings(markdown: string): PlanHeading[];
|
|
21
|
+
/** The heading a diagram's `section` names (by id or title), or undefined when none matches. */
|
|
22
|
+
export declare function resolveSection(headings: PlanHeading[], section: string | undefined): string | undefined;
|
|
23
|
+
/** Inline Markdown → plain text (emphasis, code, links and list markers removed). */
|
|
24
|
+
export declare function plainText(markdown: string): string;
|
|
25
|
+
export interface ManifestInput {
|
|
26
|
+
planId: string;
|
|
27
|
+
revision: number;
|
|
28
|
+
title: string;
|
|
29
|
+
hash: string;
|
|
30
|
+
markdown: string;
|
|
31
|
+
diagrams: PlanDiagram[];
|
|
32
|
+
/** Diagrams of the previous revision (from the plan state), when there was one. */
|
|
33
|
+
previous?: DiagramRecord[];
|
|
34
|
+
}
|
|
35
|
+
/** The diff of this revision's diagrams against the previous one. */
|
|
36
|
+
export declare function diffDiagrams(diagrams: Array<{
|
|
37
|
+
id: string;
|
|
38
|
+
title: string;
|
|
39
|
+
hash: string;
|
|
40
|
+
}>, previous: DiagramRecord[] | undefined): {
|
|
41
|
+
status: Map<string, PlanDiagramStatus>;
|
|
42
|
+
removed: Array<{
|
|
43
|
+
id: string;
|
|
44
|
+
title: string;
|
|
45
|
+
}>;
|
|
46
|
+
};
|
|
47
|
+
/** The plan manifest (`plan.json`). Deterministic: the same input gives the same manifest. */
|
|
48
|
+
export declare function buildPlanManifest(input: ManifestInput): PlanManifest;
|
|
49
|
+
/** The hash of a plan revision: the Markdown alone, or the Markdown plus its diagrams. */
|
|
50
|
+
export declare function planContentHash(markdown: string, diagrams: Array<{
|
|
51
|
+
id: string;
|
|
52
|
+
hash: string;
|
|
53
|
+
}>): string;
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `plan.json` manifest of a plan folder, generated deterministically from the plan Markdown
|
|
3
|
+
* and its accepted diagrams (never by the model). The viewer reads it instead of parsing free
|
|
4
|
+
* Markdown. Sections that the plan does not have simply come out empty: the viewer then falls back
|
|
5
|
+
* to the rendered `plan.md`.
|
|
6
|
+
*
|
|
7
|
+
* Pure functions: no I/O.
|
|
8
|
+
*/
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
10
|
+
import { diagramHash } from "./diagrams.js";
|
|
11
|
+
export const MANIFEST_VERSION = 1;
|
|
12
|
+
const SUMMARY_MAX = 400;
|
|
13
|
+
const ITEM_MAX = 160;
|
|
14
|
+
const DETAIL_MAX = 600;
|
|
15
|
+
const LIST_MAX = 30;
|
|
16
|
+
/** Heading text → anchor id (lowercase letters and digits joined by hyphens). */
|
|
17
|
+
export function slugifyHeading(title) {
|
|
18
|
+
const slug = title
|
|
19
|
+
.normalize("NFKD")
|
|
20
|
+
.replace(/[̀-ͯ]/g, "")
|
|
21
|
+
.toLowerCase()
|
|
22
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
23
|
+
.replace(/^-+|-+$/g, "")
|
|
24
|
+
.slice(0, 60)
|
|
25
|
+
.replace(/-+$/, "");
|
|
26
|
+
return slug || "section";
|
|
27
|
+
}
|
|
28
|
+
/** ATX headings outside fenced code, with unique ids (`steps`, `steps-2`). */
|
|
29
|
+
export function parsePlanHeadings(markdown) {
|
|
30
|
+
const headings = [];
|
|
31
|
+
const used = new Map();
|
|
32
|
+
let fence;
|
|
33
|
+
markdown
|
|
34
|
+
.replace(/\r\n?/g, "\n")
|
|
35
|
+
.split("\n")
|
|
36
|
+
.forEach((line, index) => {
|
|
37
|
+
const marker = /^\s{0,3}(`{3,}|~{3,})/.exec(line)?.[1];
|
|
38
|
+
if (marker) {
|
|
39
|
+
if (!fence)
|
|
40
|
+
fence = marker[0]?.repeat(marker.length);
|
|
41
|
+
else if (line.trim().startsWith(fence))
|
|
42
|
+
fence = undefined;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (fence)
|
|
46
|
+
return;
|
|
47
|
+
const match = /^\s{0,3}(#{1,6})\s+(.+?)\s*#*\s*$/.exec(line);
|
|
48
|
+
if (!match?.[1] || !match[2])
|
|
49
|
+
return;
|
|
50
|
+
const title = plainText(match[2]);
|
|
51
|
+
if (!title)
|
|
52
|
+
return;
|
|
53
|
+
const base = slugifyHeading(title);
|
|
54
|
+
const count = (used.get(base) ?? 0) + 1;
|
|
55
|
+
used.set(base, count);
|
|
56
|
+
headings.push({
|
|
57
|
+
id: count === 1 ? base : `${base}-${count}`,
|
|
58
|
+
title,
|
|
59
|
+
level: match[1].length,
|
|
60
|
+
line: index,
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
return headings;
|
|
64
|
+
}
|
|
65
|
+
/** The heading a diagram's `section` names (by id or title), or undefined when none matches. */
|
|
66
|
+
export function resolveSection(headings, section) {
|
|
67
|
+
const wanted = section?.trim().replace(/^#+\s*/, "");
|
|
68
|
+
if (!wanted)
|
|
69
|
+
return undefined;
|
|
70
|
+
const lower = wanted.toLowerCase();
|
|
71
|
+
const slug = slugifyHeading(wanted);
|
|
72
|
+
return (headings.find((h) => h.id === lower || h.id === slug)?.id ??
|
|
73
|
+
headings.find((h) => h.title.toLowerCase() === lower)?.id ??
|
|
74
|
+
headings.find((h) => h.title.toLowerCase().startsWith(lower))?.id);
|
|
75
|
+
}
|
|
76
|
+
/** Inline Markdown → plain text (emphasis, code, links and list markers removed). */
|
|
77
|
+
export function plainText(markdown) {
|
|
78
|
+
return markdown
|
|
79
|
+
.replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1")
|
|
80
|
+
.replace(/\[([^\]]*)\]\([^)]*\)/g, "$1")
|
|
81
|
+
.replace(/(\*\*|__)(.*?)\1/g, "$2")
|
|
82
|
+
.replace(/(^|[^\w*])([*_])(?!\s)(.+?)\2(?!\w)/g, "$1$3")
|
|
83
|
+
.replace(/`([^`]*)`/g, "$1")
|
|
84
|
+
.replace(/~~(.*?)~~/g, "$1")
|
|
85
|
+
.replace(/\s+/g, " ")
|
|
86
|
+
.trim();
|
|
87
|
+
}
|
|
88
|
+
const cut = (text, max) => text.length > max ? `${text.slice(0, max - 1).trimEnd()}…` : text;
|
|
89
|
+
const ROLES = [
|
|
90
|
+
["goal", /^(goals?|objectives?|purpose|objetivos?|meta|metas)$/],
|
|
91
|
+
["context", /^(context|background|findings|contexto|antecedentes)$/],
|
|
92
|
+
[
|
|
93
|
+
"steps",
|
|
94
|
+
/^(steps|implementation steps|implementation|plan|stages|phases|approach|pasos|etapas|fases|implementacion|enfoque)$/,
|
|
95
|
+
],
|
|
96
|
+
["risks", /^(risks?|risks and (open )?questions|considerations|riesgos?|consideraciones)$/],
|
|
97
|
+
["verification", /^(verification|testing|validation|tests?|verificacion|pruebas|validacion)$/],
|
|
98
|
+
["decisions", /^(decisions?|key decisions|design decisions|decisiones)$/],
|
|
99
|
+
];
|
|
100
|
+
function roleOf(title) {
|
|
101
|
+
const normalized = title
|
|
102
|
+
.normalize("NFKD")
|
|
103
|
+
.replace(/[̀-ͯ]/g, "")
|
|
104
|
+
.toLowerCase()
|
|
105
|
+
.replace(/^[\d.\s)-]+/, "")
|
|
106
|
+
.replace(/[^a-z\s]/g, " ")
|
|
107
|
+
.replace(/\s+/g, " ")
|
|
108
|
+
.trim();
|
|
109
|
+
return ROLES.find(([, pattern]) => pattern.test(normalized))?.[0];
|
|
110
|
+
}
|
|
111
|
+
/** Heading blocks: each heading with the lines up to the next heading. */
|
|
112
|
+
function blocksOf(markdown, headings) {
|
|
113
|
+
const lines = markdown.replace(/\r\n?/g, "\n").split("\n");
|
|
114
|
+
return headings.map((heading, index) => ({
|
|
115
|
+
heading,
|
|
116
|
+
lines: lines.slice(heading.line + 1, headings[index + 1]?.line ?? lines.length),
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* The role of a heading. A level-1 heading is the plan's title ("# Plan"), not a section, unless
|
|
121
|
+
* the plan has no section headings at all (everything written with `#`).
|
|
122
|
+
*/
|
|
123
|
+
function headingRole(headings, heading) {
|
|
124
|
+
if (heading.level === 1 && headings.some((h) => h.level >= 2 && roleOf(h.title)))
|
|
125
|
+
return undefined;
|
|
126
|
+
return roleOf(heading.title);
|
|
127
|
+
}
|
|
128
|
+
/** The lines of a role's section including its sub-sections (until a heading of the same level). */
|
|
129
|
+
function sectionLines(markdown, headings, role) {
|
|
130
|
+
const lines = markdown.replace(/\r\n?/g, "\n").split("\n");
|
|
131
|
+
const out = [];
|
|
132
|
+
headings.forEach((heading, index) => {
|
|
133
|
+
if (headingRole(headings, heading) !== role)
|
|
134
|
+
return;
|
|
135
|
+
const end = headings.slice(index + 1).find((next) => next.level <= heading.level)?.line ?? lines.length;
|
|
136
|
+
out.push(lines.slice(heading.line + 1, end));
|
|
137
|
+
});
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
const LIST_ITEM = /^(\s{0,3})(?:\d+[.)]|[-*+])\s+(.*)$/;
|
|
141
|
+
/** Top-level list items of some lines; indented continuation lines become the detail. */
|
|
142
|
+
function listItems(lines) {
|
|
143
|
+
const items = [];
|
|
144
|
+
let current;
|
|
145
|
+
let fence = false;
|
|
146
|
+
const flush = () => {
|
|
147
|
+
if (!current)
|
|
148
|
+
return;
|
|
149
|
+
const title = plainText(current.head);
|
|
150
|
+
if (title)
|
|
151
|
+
items.push({ title, detail: plainText(current.rest.join(" ")) });
|
|
152
|
+
current = undefined;
|
|
153
|
+
};
|
|
154
|
+
for (const line of lines) {
|
|
155
|
+
if (/^\s*(```|~~~)/.test(line)) {
|
|
156
|
+
fence = !fence;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (fence)
|
|
160
|
+
continue;
|
|
161
|
+
const match = LIST_ITEM.exec(line);
|
|
162
|
+
if (match && (match[1]?.length ?? 0) <= 1) {
|
|
163
|
+
flush();
|
|
164
|
+
current = { head: match[2] ?? "", rest: [] };
|
|
165
|
+
}
|
|
166
|
+
else if (current && line.trim()) {
|
|
167
|
+
current.rest.push(line.trim().replace(/^(?:\d+[.)]|[-*+])\s+/, ""));
|
|
168
|
+
}
|
|
169
|
+
else if (!line.trim() && current && current.rest.length === 0) {
|
|
170
|
+
// A blank line right after the item head: keep collecting until the next item.
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
flush();
|
|
174
|
+
return items;
|
|
175
|
+
}
|
|
176
|
+
/** The first paragraph of some lines as plain text. */
|
|
177
|
+
function firstParagraph(lines) {
|
|
178
|
+
const paragraph = [];
|
|
179
|
+
let fence = false;
|
|
180
|
+
for (const line of lines) {
|
|
181
|
+
if (/^\s*(```|~~~)/.test(line)) {
|
|
182
|
+
fence = !fence;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (fence)
|
|
186
|
+
continue;
|
|
187
|
+
if (!line.trim()) {
|
|
188
|
+
if (paragraph.length)
|
|
189
|
+
break;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (LIST_ITEM.test(line) || /^\s*(>|\||---)/.test(line)) {
|
|
193
|
+
if (paragraph.length)
|
|
194
|
+
break;
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
paragraph.push(line.trim());
|
|
198
|
+
}
|
|
199
|
+
return plainText(paragraph.join(" "));
|
|
200
|
+
}
|
|
201
|
+
/** Paragraphs and list items of some lines, as plain text entries. */
|
|
202
|
+
function entries(lines) {
|
|
203
|
+
const items = listItems(lines);
|
|
204
|
+
if (items.length)
|
|
205
|
+
return items.map((item) => (item.detail ? `${item.title}: ${item.detail}` : item.title));
|
|
206
|
+
const text = firstParagraph(lines);
|
|
207
|
+
return text ? [text] : [];
|
|
208
|
+
}
|
|
209
|
+
/** The diff of this revision's diagrams against the previous one. */
|
|
210
|
+
export function diffDiagrams(diagrams, previous) {
|
|
211
|
+
const before = new Map((previous ?? []).map((d) => [d.id, d]));
|
|
212
|
+
const status = new Map();
|
|
213
|
+
for (const diagram of diagrams) {
|
|
214
|
+
const old = before.get(diagram.id);
|
|
215
|
+
status.set(diagram.id, !old ? "new" : old.hash === diagram.hash ? "unchanged" : "updated");
|
|
216
|
+
}
|
|
217
|
+
const now = new Set(diagrams.map((d) => d.id));
|
|
218
|
+
const removed = (previous ?? [])
|
|
219
|
+
.filter((d) => !now.has(d.id))
|
|
220
|
+
.map((d) => ({ id: d.id, title: d.title }));
|
|
221
|
+
return { status, removed };
|
|
222
|
+
}
|
|
223
|
+
/** The plan manifest (`plan.json`). Deterministic: the same input gives the same manifest. */
|
|
224
|
+
export function buildPlanManifest(input) {
|
|
225
|
+
const headings = parsePlanHeadings(input.markdown);
|
|
226
|
+
const goalBlocks = sectionLines(input.markdown, headings, "goal");
|
|
227
|
+
const goalLines = goalBlocks.flat();
|
|
228
|
+
const preface = (() => {
|
|
229
|
+
const first = headings[0];
|
|
230
|
+
const lines = input.markdown.replace(/\r\n?/g, "\n").split("\n");
|
|
231
|
+
// Text after the title and before the first sub-heading, or the whole text with no headings.
|
|
232
|
+
if (!first)
|
|
233
|
+
return lines;
|
|
234
|
+
const second = headings[1];
|
|
235
|
+
return first.level === 1
|
|
236
|
+
? lines.slice(first.line + 1, second?.line ?? lines.length)
|
|
237
|
+
: lines.slice(0, first.line);
|
|
238
|
+
})();
|
|
239
|
+
const summary = cut(firstParagraph(goalLines) ||
|
|
240
|
+
firstParagraph(preface) ||
|
|
241
|
+
firstParagraph(blocksOf(input.markdown, headings).flatMap((b) => b.lines)), SUMMARY_MAX);
|
|
242
|
+
const goals = listItems(goalLines)
|
|
243
|
+
.map((item) => cut(item.detail ? `${item.title}: ${item.detail}` : item.title, ITEM_MAX))
|
|
244
|
+
.slice(0, LIST_MAX);
|
|
245
|
+
const stepBlocks = sectionLines(input.markdown, headings, "steps");
|
|
246
|
+
const stepHeadings = headings.filter((heading, index) => {
|
|
247
|
+
const parent = headings
|
|
248
|
+
.slice(0, index)
|
|
249
|
+
.reverse()
|
|
250
|
+
.find((candidate) => candidate.level < heading.level);
|
|
251
|
+
return !!parent && headingRole(headings, parent) === "steps";
|
|
252
|
+
});
|
|
253
|
+
let stages = listItems(stepBlocks.flat()).map((item) => ({
|
|
254
|
+
title: cut(item.title, ITEM_MAX),
|
|
255
|
+
...(item.detail ? { detail: cut(item.detail, DETAIL_MAX) } : {}),
|
|
256
|
+
}));
|
|
257
|
+
if (!stages.length && stepHeadings.length) {
|
|
258
|
+
const blocks = blocksOf(input.markdown, headings);
|
|
259
|
+
stages = stepHeadings.map((heading) => {
|
|
260
|
+
const body = blocks.find((block) => block.heading.id === heading.id)?.lines ?? [];
|
|
261
|
+
const detail = firstParagraph(body);
|
|
262
|
+
return {
|
|
263
|
+
title: cut(heading.title, ITEM_MAX),
|
|
264
|
+
...(detail ? { detail: cut(detail, DETAIL_MAX) } : {}),
|
|
265
|
+
};
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
stages = stages.slice(0, LIST_MAX);
|
|
269
|
+
const considerations = [];
|
|
270
|
+
for (const [role, kind] of [
|
|
271
|
+
["decisions", "decision"],
|
|
272
|
+
["risks", "risk"],
|
|
273
|
+
["verification", "verification"],
|
|
274
|
+
])
|
|
275
|
+
for (const lines of sectionLines(input.markdown, headings, role))
|
|
276
|
+
for (const text of entries(lines))
|
|
277
|
+
if (considerations.length < LIST_MAX * 2)
|
|
278
|
+
considerations.push({ kind, text: cut(text, DETAIL_MAX) });
|
|
279
|
+
const hashes = input.diagrams.map((diagram) => ({
|
|
280
|
+
id: diagram.id,
|
|
281
|
+
title: diagram.title,
|
|
282
|
+
hash: diagramHash(diagram),
|
|
283
|
+
}));
|
|
284
|
+
const { status, removed } = diffDiagrams(hashes, input.previous);
|
|
285
|
+
return {
|
|
286
|
+
version: MANIFEST_VERSION,
|
|
287
|
+
planId: input.planId,
|
|
288
|
+
revision: input.revision,
|
|
289
|
+
title: input.title,
|
|
290
|
+
hash: input.hash,
|
|
291
|
+
summary,
|
|
292
|
+
goals,
|
|
293
|
+
stages,
|
|
294
|
+
considerations,
|
|
295
|
+
sections: headings.map(({ id, title, level }) => ({ id, title, level })),
|
|
296
|
+
diagrams: input.diagrams.map((diagram, index) => {
|
|
297
|
+
const section = resolveSection(headings, diagram.section);
|
|
298
|
+
return {
|
|
299
|
+
id: diagram.id,
|
|
300
|
+
title: diagram.title,
|
|
301
|
+
explanation: diagram.explanation,
|
|
302
|
+
...(section ? { section } : {}),
|
|
303
|
+
type: diagram.type,
|
|
304
|
+
syntax: diagram.syntax,
|
|
305
|
+
file: `diagrams/${diagram.id}.mmd`,
|
|
306
|
+
hash: hashes[index]?.hash ?? "",
|
|
307
|
+
status: status.get(diagram.id) ?? "new",
|
|
308
|
+
};
|
|
309
|
+
}),
|
|
310
|
+
removed,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
/** The hash of a plan revision: the Markdown alone, or the Markdown plus its diagrams. */
|
|
314
|
+
export function planContentHash(markdown, diagrams) {
|
|
315
|
+
const hash = createHash("sha256").update(markdown, "utf8");
|
|
316
|
+
if (diagrams.length)
|
|
317
|
+
hash.update(`\n--diagrams--\n${diagrams.map((d) => `${d.id}:${d.hash}`).join("\n")}`, "utf8");
|
|
318
|
+
return hash.digest("hex");
|
|
319
|
+
}
|
package/dist/plan/state.d.ts
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan review state (`sessions.options.plan`) and the pure helpers around it. One entry per
|
|
3
|
+
* session: the latest `exit_plan` proposal. Every transition is a compare-and-set inside one
|
|
4
|
+
* `BEGIN IMMEDIATE` transaction, so it is safe across surfaces (and processes) that share the
|
|
5
|
+
* database: whatever happens twice (a double click, a reload, two tabs) changes the state once.
|
|
6
|
+
*
|
|
7
|
+
* pending ──approve──▶ approved ──claim──▶ implementing
|
|
8
|
+
* ├────skip──────▶ skipped └─discard─▶ cancelled (the run was cancelled)
|
|
9
|
+
* └────context───▶ feedback (a later `exit_plan` call starts a new revision)
|
|
10
|
+
*/
|
|
1
11
|
import type { QuestionOption } from "@alisio/sdk";
|
|
12
|
+
import { type DiagramRecord } from "./manifest.ts";
|
|
2
13
|
/** The decision screen title (web: localized; the TUI prints it as is). */
|
|
3
14
|
export declare const PLAN_REVIEW_TITLE = "Plan complete. What would you like to do?";
|
|
4
15
|
export declare const PLAN_QUESTION_ID = "plan";
|
|
@@ -20,6 +31,8 @@ export interface PlanState {
|
|
|
20
31
|
status: PlanStatus;
|
|
21
32
|
callId?: string;
|
|
22
33
|
artifactId?: string;
|
|
34
|
+
/** The accepted diagrams of this revision (absent without any): what the next one is diffed against. */
|
|
35
|
+
diagrams?: DiagramRecord[];
|
|
23
36
|
/** The approved snapshot; kept only while `approved` (afterwards it lives in the user message). */
|
|
24
37
|
plan?: string;
|
|
25
38
|
updatedAt: number;
|
|
@@ -34,7 +47,14 @@ export interface PlanStore {
|
|
|
34
47
|
options?: Record<string, unknown>;
|
|
35
48
|
};
|
|
36
49
|
}
|
|
37
|
-
|
|
50
|
+
/**
|
|
51
|
+
* The hash of a plan revision. Without diagrams it is the SHA-256 of the Markdown (as before);
|
|
52
|
+
* with diagrams it also covers them, so a change in a diagram alone is a different plan.
|
|
53
|
+
*/
|
|
54
|
+
export declare const planHash: (plan: string, diagrams?: Array<{
|
|
55
|
+
id: string;
|
|
56
|
+
hash: string;
|
|
57
|
+
}>) => string;
|
|
38
58
|
/** The plan state stored in `options`, or undefined when absent or malformed. */
|
|
39
59
|
export declare function readPlanState(options: Record<string, unknown> | undefined): PlanState | undefined;
|
|
40
60
|
export declare const currentPlan: (store: PlanStore, sessionId: string) => PlanState | undefined;
|
package/dist/plan/state.js
CHANGED
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Plan review state (`sessions.options.plan`) and the pure helpers around it. One entry per
|
|
3
|
-
* session: the latest `exit_plan` proposal. Every transition is a compare-and-set inside one
|
|
4
|
-
* `BEGIN IMMEDIATE` transaction, so it is safe across surfaces (and processes) that share the
|
|
5
|
-
* database: whatever happens twice (a double click, a reload, two tabs) changes the state once.
|
|
6
|
-
*
|
|
7
|
-
* pending ──approve──▶ approved ──claim──▶ implementing
|
|
8
|
-
* ├────skip──────▶ skipped └─discard─▶ cancelled (the run was cancelled)
|
|
9
|
-
* └────context───▶ feedback (a later `exit_plan` call starts a new revision)
|
|
10
|
-
*/
|
|
11
|
-
import { createHash } from "node:crypto";
|
|
1
|
+
import { planContentHash } from "./manifest.js";
|
|
12
2
|
/** The decision screen title (web: localized; the TUI prints it as is). */
|
|
13
3
|
export const PLAN_REVIEW_TITLE = "Plan complete. What would you like to do?";
|
|
14
4
|
export const PLAN_QUESTION_ID = "plan";
|
|
@@ -28,7 +18,11 @@ export const PLAN_OPTIONS = [
|
|
|
28
18
|
textInput: { placeholder: "Add context for the plan" },
|
|
29
19
|
},
|
|
30
20
|
];
|
|
31
|
-
|
|
21
|
+
/**
|
|
22
|
+
* The hash of a plan revision. Without diagrams it is the SHA-256 of the Markdown (as before);
|
|
23
|
+
* with diagrams it also covers them, so a change in a diagram alone is a different plan.
|
|
24
|
+
*/
|
|
25
|
+
export const planHash = (plan, diagrams = []) => planContentHash(plan, diagrams);
|
|
32
26
|
const STATUSES = new Set([
|
|
33
27
|
"pending",
|
|
34
28
|
"approved",
|
package/dist/tools/analysis.js
CHANGED
|
@@ -120,8 +120,11 @@ function pythonRunDescription(deps) {
|
|
|
120
120
|
"top-level folder with index.html becomes one multi-file dashboard. Optionally write",
|
|
121
121
|
'$ALISIO_OUTPUT_DIR/outputs.json = {"artifacts":[{"path":"…","title":"…"}]} to publish only',
|
|
122
122
|
"those. The cwd is $ALISIO_WORK_DIR (scratch); inputs are copied to $ALISIO_INPUT_DIR.",
|
|
123
|
-
`Available modules: ${modules}. alisio_runtime offers output_dir(), html.page/table/write
|
|
124
|
-
"
|
|
123
|
+
`Available modules: ${modules}. alisio_runtime offers output_dir(), html.page/table/write,`,
|
|
124
|
+
"charts (Chart.js bundled; pie/donut/bar/hbar/line/area/scatter(labels, values or {name: values}, title,",
|
|
125
|
+
"fmt='currency:USD', locale) in charts.card/grid/kpis, then charts.write(name, title, body): responsive, with",
|
|
126
|
+
"a data table) and svg.* (static, no script). For charts ALWAYS use them: never hand-write SVG arcs or fixed-size",
|
|
127
|
+
"SVGs, never load a CDN; one chart per card; pie only for 2-5 parts, else donut or hbar. inputs: [{ datasetId }] copies a dataset (see",
|
|
125
128
|
"data_inspect) to $ALISIO_INPUT_DIR/<name>.sqlite: alisio_runtime.datasets.open(name) returns a",
|
|
126
129
|
"read-only sqlite3 connection (table `data` or `s_<sheet>`). HTML is shown offline: embed data and scripts",
|
|
127
130
|
"inline (no fetch, no CDN). Nothing is published when the script fails unless publishOnError.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alisio/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Embeddable provider-agnostic Alisio agent core: tool loop, context compaction, local tools, MCP, plugin host and SQLite session store. Runs on Node >=22.16 and Bun.",
|
|
5
5
|
"author": "Gustavo Gutiérrez",
|
|
6
6
|
"license": "MIT",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"node": ">=22.16"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@alisio/sdk": "0.
|
|
44
|
+
"@alisio/sdk": "0.2.0",
|
|
45
45
|
"@mixmark-io/domino": "2.2.0",
|
|
46
46
|
"@modelcontextprotocol/client": "2.1.0",
|
|
47
47
|
"ajv": "8.20.0",
|