agentera 3.0.0-dev.75 → 3.0.0-dev.76
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/bundle/.agentera-build-source.json +5 -5
- package/bundle/CHANGELOG.md +5 -0
- package/dist/.agentera-build-source.json +5 -5
- package/dist/cli/commands/doctor.js +1 -0
- package/dist/state/entityMigrationTodo.js +1 -1
- package/dist/state/todoDocsEntities.js +8 -3
- package/dist/state/todoMarkdownProjection.js +18 -6
- package/dist/state/todoReconciliationInspection.js +15 -2
- package/dist/state/todoReconciliationRepair.js +3 -1
- package/dist/state/todoSeverityHeadings.js +71 -0
- package/package.json +2 -2
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "agentera.generatedBuildSource.v1",
|
|
3
|
-
"commit": "
|
|
4
|
-
"tree": "
|
|
5
|
-
"files":
|
|
6
|
-
"workingTreeSha256": "
|
|
7
|
-
"identitySha256": "
|
|
3
|
+
"commit": "70aed21badb985087fd79371fabb61b1ec31ddde",
|
|
4
|
+
"tree": "3baa4c8145944638cad38ae98346f98518d01884",
|
|
5
|
+
"files": 6750,
|
|
6
|
+
"workingTreeSha256": "57aa3b7d804724356e7a3bf9adf7ea9a54e29312cd19b0abcd23f4f9699dfe54",
|
|
7
|
+
"identitySha256": "f4acdd4621fd8110b1277f88be7f093929a887c6b4db6c3269e3a1af98a63b36"
|
|
8
8
|
}
|
package/bundle/CHANGELOG.md
CHANGED
|
@@ -92,6 +92,11 @@
|
|
|
92
92
|
|
|
93
93
|
### Fixed
|
|
94
94
|
|
|
95
|
+
- Fixed TODO reconciliation so typed writes use canonical severity headings and
|
|
96
|
+
insert missing bands before later sections. Duplicate, mismatched, or
|
|
97
|
+
out-of-order headings now return one bounded actionable diagnosis through
|
|
98
|
+
TODO reads, Prime, Doctor, and state validation, and block writes before effects.
|
|
99
|
+
Recognized v2 `## Resolved` headings normalize during entity cutover.
|
|
95
100
|
- Fixed large unsafe inactive TODO status startup to fit its 22,500-byte JSON limit with bounded recovery details.
|
|
96
101
|
- Fixed fresh projects so they can initialize Plan entity state without a legacy upgrade route.
|
|
97
102
|
- Fixed `agentera doctor --format json` to report bounded, read-only retirement
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "agentera.generatedBuildSource.v1",
|
|
3
|
-
"commit": "
|
|
4
|
-
"tree": "
|
|
5
|
-
"files":
|
|
6
|
-
"workingTreeSha256": "
|
|
7
|
-
"identitySha256": "
|
|
3
|
+
"commit": "70aed21badb985087fd79371fabb61b1ec31ddde",
|
|
4
|
+
"tree": "3baa4c8145944638cad38ae98346f98518d01884",
|
|
5
|
+
"files": 6750,
|
|
6
|
+
"workingTreeSha256": "57aa3b7d804724356e7a3bf9adf7ea9a54e29312cd19b0abcd23f4f9699dfe54",
|
|
7
|
+
"identitySha256": "f4acdd4621fd8110b1277f88be7f093929a887c6b4db6c3269e3a1af98a63b36"
|
|
8
8
|
}
|
|
@@ -181,6 +181,7 @@ export function cmdDoctor(args, io = {}) {
|
|
|
181
181
|
reconciliationState: todoReconciliation.state,
|
|
182
182
|
reconciliationCounts: todoReconciliation.counts,
|
|
183
183
|
reconciliationOmittedCount: todoReconciliation.omitted_count,
|
|
184
|
+
...(todoReconciliation.risks ? { reconciliationRisks: todoReconciliation.risks } : {}),
|
|
184
185
|
previewCommand: todoReconciliation.preview_command,
|
|
185
186
|
applyCommand: todoReconciliation.apply_command,
|
|
186
187
|
recoveryCommand: todoReconciliation.recovery_command,
|
|
@@ -110,7 +110,7 @@ export function todoReconciliationMigrationPlan(todoPath, files, entries) {
|
|
|
110
110
|
}));
|
|
111
111
|
if (byLine.size !== rows.length)
|
|
112
112
|
return undefined;
|
|
113
|
-
const lines = todo.bytes.toString("utf8").split(/\r?\n/);
|
|
113
|
+
const lines = todo.bytes.toString("utf8").split(/\r?\n/).map((line) => line === "## Resolved" ? "## ✓ Resolved" : line);
|
|
114
114
|
for (const row of rows) {
|
|
115
115
|
const entry = byLine.get(row.index);
|
|
116
116
|
if (!entry?.proposed_target)
|
|
@@ -22,6 +22,7 @@ import { loadTodoReconciliationActivation, todoLegacyRowFingerprint, todoReconci
|
|
|
22
22
|
import { normalizeTodoOwnerCorrectionEvidence, planTodoOwnerCorrection, planTodoRepair } from "./todoReconciliationRepair.js";
|
|
23
23
|
import { readTodoMarkdown, renderManagedMarkdown } from "./todoMarkdownProjection.js";
|
|
24
24
|
import { inactiveTodoActivationSafety, rejectUnsafeInactiveTodoActivation, unsafeInactiveDuplicateDiagnosis } from "./todoActivationSafety.js";
|
|
25
|
+
import { assertTodoSeverityHeadingStructure, todoSeveritySectionForHeading } from "./todoSeverityHeadings.js";
|
|
25
26
|
const ID = /^[a-z]{10}$/;
|
|
26
27
|
const SHA256 = /^[a-f0-9]{64}$/;
|
|
27
28
|
const TODO = { artifact: "todo", boundary: "todo_item", order: "severity_then_status_then_markdown_order_then_id" };
|
|
@@ -139,6 +140,7 @@ function todoAuthority() {
|
|
|
139
140
|
};
|
|
140
141
|
}
|
|
141
142
|
export function managedRows(markdown, activation, entities) {
|
|
143
|
+
assertTodoSeverityHeadingStructure(markdown);
|
|
142
144
|
const result = new Map();
|
|
143
145
|
const order = new Map();
|
|
144
146
|
const legacy = [];
|
|
@@ -146,7 +148,7 @@ export function managedRows(markdown, activation, entities) {
|
|
|
146
148
|
markdown.split(/\r?\n/).forEach((line, index) => {
|
|
147
149
|
const heading = line.trim().match(/^##\s+(.+)$/)?.[1]?.toLowerCase();
|
|
148
150
|
if (heading)
|
|
149
|
-
section =
|
|
151
|
+
section = todoSeveritySectionForHeading(line) ?? (heading === "notes" ? null : section);
|
|
150
152
|
const item = parseTodoMarkdownListItem(line.trim());
|
|
151
153
|
if (!item)
|
|
152
154
|
return;
|
|
@@ -291,16 +293,17 @@ function inspectTodoReadView(root, sourceRoot, entities) {
|
|
|
291
293
|
catch (error) {
|
|
292
294
|
if (error instanceof StateWriteInputError && /Markdown/i.test(error.body.message))
|
|
293
295
|
throw error;
|
|
296
|
+
const diagnosis = error instanceof StateWriteInputError && mapping(error.body.diagnosis) ? error.body.diagnosis : undefined;
|
|
294
297
|
return {
|
|
295
298
|
rows: new Map(),
|
|
296
299
|
drift: {
|
|
297
300
|
schema_version: RECONCILIATION_VERSION,
|
|
298
|
-
status: "
|
|
301
|
+
status: "conflict", action_required: true,
|
|
299
302
|
read_effect: "none",
|
|
300
303
|
next_write_boundary: "atomic_reconciliation",
|
|
301
304
|
authority: todoAuthority(),
|
|
302
305
|
counts: { managed: 0, drifted: 0, conflicts: 1 },
|
|
303
|
-
items: [],
|
|
306
|
+
items: [], ...(diagnosis ? { diagnosis } : {}),
|
|
304
307
|
},
|
|
305
308
|
};
|
|
306
309
|
}
|
|
@@ -669,6 +672,8 @@ export function mutateTodoDocsEntity(req, options = {}) {
|
|
|
669
672
|
const correctingOwners = artifact === "todo" && req.spec.verb === "correct-owners";
|
|
670
673
|
const ownerEvidence = correctingOwners ? normalizeTodoOwnerCorrectionEvidence(req.input ?? {}) : null;
|
|
671
674
|
const pending = todoBinding ? inspectTodoReconciliation(pinnedRoot, todoBinding) : [];
|
|
675
|
+
if (artifact === "todo")
|
|
676
|
+
assertTodoSeverityHeadingStructure(readTodoMarkdown(todoPublicPath(pinnedRoot, sourceRoot)).text);
|
|
672
677
|
const initialActivation = artifact === "todo" ? loadTodoReconciliationActivation(pinnedRoot) : null;
|
|
673
678
|
if (artifact === "todo" && ["activate", "repair", "correct-owners"].includes(req.spec.verb)) {
|
|
674
679
|
const repairing = req.spec.verb === "repair";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import { renderTodoPublicRecord } from "../cli/todoMarkdown.js";
|
|
3
3
|
import { reject } from "./write/errors.js";
|
|
4
|
+
import { TODO_SEVERITY_HEADINGS, todoSeveritySectionForHeading } from "./todoSeverityHeadings.js";
|
|
4
5
|
const TODO_MARKDOWN_MAX_BYTES = 1024 * 1024;
|
|
5
6
|
export function readTodoMarkdown(target) {
|
|
6
7
|
if (!fs.existsSync(target))
|
|
@@ -21,7 +22,7 @@ export function readTodoMarkdown(target) {
|
|
|
21
22
|
return { bytes, text: text };
|
|
22
23
|
}
|
|
23
24
|
function sectionFor(record) { return record.status === "resolved" ? "resolved" : String(record.severity); }
|
|
24
|
-
function headingFor(section) { return section ===
|
|
25
|
+
function headingFor(section) { return TODO_SEVERITY_HEADINGS.find((entry) => entry.section === section).heading; }
|
|
25
26
|
function rowFor(id, record) { return `- [${record.status === "resolved" ? "x" : " "}] [id:${id}] ${renderTodoPublicRecord(record)}`; }
|
|
26
27
|
export function renderManagedMarkdown(markdown, records, existing) {
|
|
27
28
|
const byLine = new Map([...existing.values()].map((row) => [row.line, row]));
|
|
@@ -29,16 +30,27 @@ export function renderManagedMarkdown(markdown, records, existing) {
|
|
|
29
30
|
const lines = markdown.split(/\r?\n/).flatMap((line, index) => { const row = byLine.get(index); if (!row)
|
|
30
31
|
return [line]; const record = records.get(row.id); if (!record || sectionFor(record) !== row.section)
|
|
31
32
|
return []; retained.add(row.id); return [rowFor(row.id, record)]; });
|
|
32
|
-
for (const section of
|
|
33
|
+
for (const { section } of TODO_SEVERITY_HEADINGS) {
|
|
33
34
|
const ids = [...records.entries()].filter(([id, record]) => !retained.has(id) && sectionFor(record) === section).sort(([left], [right]) => { const a = existing.get(left); const b = existing.get(right); return (a?.section === section ? a.snapshot.order : Number.MAX_SAFE_INTEGER) - (b?.section === section ? b.snapshot.order : Number.MAX_SAFE_INTEGER) || left.localeCompare(right); }).map(([id]) => id);
|
|
34
35
|
if (!ids.length)
|
|
35
36
|
continue;
|
|
36
37
|
let heading = lines.findIndex((line) => line.trim().toLowerCase() === headingFor(section).toLowerCase());
|
|
37
38
|
if (heading < 0) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
const order = TODO_SEVERITY_HEADINGS.findIndex((entry) => entry.section === section);
|
|
40
|
+
const later = lines.findIndex((line) => {
|
|
41
|
+
const found = todoSeveritySectionForHeading(line);
|
|
42
|
+
return found !== null && TODO_SEVERITY_HEADINGS.findIndex((entry) => entry.section === found) > order;
|
|
43
|
+
});
|
|
44
|
+
if (later >= 0) {
|
|
45
|
+
lines.splice(later, 0, headingFor(section), "");
|
|
46
|
+
heading = later;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
if (lines.at(-1)?.trim())
|
|
50
|
+
lines.push("");
|
|
51
|
+
lines.push(headingFor(section));
|
|
52
|
+
heading = lines.length - 1;
|
|
53
|
+
}
|
|
42
54
|
}
|
|
43
55
|
let insert = heading + 1;
|
|
44
56
|
while (insert < lines.length && !/^##\s+/.test(lines[insert].trim()))
|
|
@@ -76,6 +76,19 @@ function errorCounts(error) {
|
|
|
76
76
|
...(diagnosis.omitted_count !== undefined ? { omitted_count: diagnosis.omitted_count } : {}),
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
|
+
function errorRisks(error) {
|
|
80
|
+
return error instanceof StateWriteInputError && mapping(error.body.diagnosis) ? error.body.diagnosis : undefined;
|
|
81
|
+
}
|
|
82
|
+
function errorInspection(state, error) {
|
|
83
|
+
const risks = errorRisks(error);
|
|
84
|
+
const result = inspection(state, errorCounts(error), risks);
|
|
85
|
+
if (risks?.classification === "todo_severity_heading_structure" && error instanceof StateWriteInputError) {
|
|
86
|
+
result.preview_command = null;
|
|
87
|
+
result.apply_command = null;
|
|
88
|
+
result.recovery_command = error.body.recovery ?? result.recovery_command;
|
|
89
|
+
}
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
79
92
|
/** Read-only classification shared by prime, doctor, and whole-state validation. */
|
|
80
93
|
export function inspectTodoReconciliationState(root, sourceRoot = resolveSourceRoot(), discovery) {
|
|
81
94
|
try {
|
|
@@ -112,7 +125,7 @@ export function inspectTodoReconciliationState(root, sourceRoot = resolveSourceR
|
|
|
112
125
|
return inspection(safety.safe ? "inactive" : "unsafe_inactive", safety.counts, safety.safe ? undefined : safety.risks);
|
|
113
126
|
}
|
|
114
127
|
catch (error) {
|
|
115
|
-
return
|
|
128
|
+
return errorInspection("unsafe_inactive", error);
|
|
116
129
|
}
|
|
117
130
|
}
|
|
118
131
|
try {
|
|
@@ -122,7 +135,7 @@ export function inspectTodoReconciliationState(root, sourceRoot = resolveSourceR
|
|
|
122
135
|
return inspection(unsafe ? "unsafe_active" : "healthy_active", counts);
|
|
123
136
|
}
|
|
124
137
|
catch (error) {
|
|
125
|
-
return
|
|
138
|
+
return errorInspection("unsafe_active", error);
|
|
126
139
|
}
|
|
127
140
|
}
|
|
128
141
|
function publicSnapshot(record, order) {
|
|
@@ -3,6 +3,7 @@ import { parseTodoMarkdownListItem, renderTodoPublicRecord } from "../cli/todoMa
|
|
|
3
3
|
import { canonicalRecordJson } from "./archiveDiscovery.js";
|
|
4
4
|
import { reject } from "./write/errors.js";
|
|
5
5
|
import { todoLegacyRowFingerprint, TODO_OWNER_CORRECTION_INPUT_VERSION, TODO_OWNER_CORRECTION_PREVIEW_COMMAND, TODO_REPAIR_PREVIEW_COMMAND } from "./todoReconciliationActivation.js";
|
|
6
|
+
import { assertTodoSeverityHeadingStructure, todoSeveritySectionForHeading } from "./todoSeverityHeadings.js";
|
|
6
7
|
const RECONCILIATION_VERSION = "agentera.todoReconciliation.v1";
|
|
7
8
|
const DIAGNOSTIC_LIMIT = 20;
|
|
8
9
|
function mapping(value) { return value !== null && typeof value === "object" && !Array.isArray(value); }
|
|
@@ -91,6 +92,7 @@ export function normalizeTodoOwnerCorrectionEvidence(input) {
|
|
|
91
92
|
};
|
|
92
93
|
}
|
|
93
94
|
function scanRows(markdown) {
|
|
95
|
+
assertTodoSeverityHeadingStructure(markdown);
|
|
94
96
|
const managed = new Map();
|
|
95
97
|
const legacy = [];
|
|
96
98
|
const order = new Map();
|
|
@@ -98,7 +100,7 @@ function scanRows(markdown) {
|
|
|
98
100
|
markdown.split(/\r?\n/).forEach((line, index) => {
|
|
99
101
|
const heading = line.trim().match(/^##\s+(.+)$/)?.[1]?.toLowerCase();
|
|
100
102
|
if (heading)
|
|
101
|
-
section =
|
|
103
|
+
section = todoSeveritySectionForHeading(line) ?? (heading === "notes" ? null : section);
|
|
102
104
|
const item = parseTodoMarkdownListItem(line.trim());
|
|
103
105
|
if (!item)
|
|
104
106
|
return;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { reject } from "./write/errors.js";
|
|
2
|
+
export const TODO_SEVERITY_HEADINGS = [
|
|
3
|
+
{ section: "critical", name: "Critical", heading: "## ⇶ Critical" },
|
|
4
|
+
{ section: "degraded", name: "Degraded", heading: "## ⇉ Degraded" },
|
|
5
|
+
{ section: "normal", name: "Normal", heading: "## → Normal" },
|
|
6
|
+
{ section: "annoying", name: "Annoying", heading: "## ⇢ Annoying" },
|
|
7
|
+
{ section: "resolved", name: "Resolved", heading: "## ✓ Resolved" },
|
|
8
|
+
];
|
|
9
|
+
const DIAGNOSTIC_LIMIT = 20;
|
|
10
|
+
const byName = new Map(TODO_SEVERITY_HEADINGS.map((entry) => [entry.name.toLowerCase(), entry]));
|
|
11
|
+
function candidate(line) {
|
|
12
|
+
const content = line.trim().match(/^##\s+(.+?)\s*$/)?.[1];
|
|
13
|
+
if (!content)
|
|
14
|
+
return null;
|
|
15
|
+
const name = content.match(/^(?:[^\p{L}\p{N}\s]+\s+)?(Critical|Degraded|Normal|Annoying|Resolved)$/iu)?.[1];
|
|
16
|
+
return name ? byName.get(name.toLowerCase()) ?? null : null;
|
|
17
|
+
}
|
|
18
|
+
export function todoSeveritySectionForHeading(line) {
|
|
19
|
+
const entry = candidate(line);
|
|
20
|
+
return entry && line.trim() === entry.heading ? entry.section : null;
|
|
21
|
+
}
|
|
22
|
+
export function inspectTodoSeverityHeadings(markdown) {
|
|
23
|
+
const diagnostics = [];
|
|
24
|
+
const seen = new Set();
|
|
25
|
+
let greatestOrder = -1;
|
|
26
|
+
for (const [index, line] of markdown.split(/\r?\n/).entries()) {
|
|
27
|
+
const entry = candidate(line);
|
|
28
|
+
if (!entry)
|
|
29
|
+
continue;
|
|
30
|
+
const order = TODO_SEVERITY_HEADINGS.indexOf(entry);
|
|
31
|
+
const add = (diagnostic) => { diagnostics.push(diagnostic); };
|
|
32
|
+
if (line.trim() !== entry.heading)
|
|
33
|
+
add({
|
|
34
|
+
code: "todo_severity_heading_mismatch",
|
|
35
|
+
classification: "glyph_name_mismatch",
|
|
36
|
+
line: index + 1,
|
|
37
|
+
expected_heading: entry.heading,
|
|
38
|
+
recovery: `Replace TODO.md line ${index + 1} with '${entry.heading}'.`,
|
|
39
|
+
});
|
|
40
|
+
if (seen.has(entry.section))
|
|
41
|
+
add({
|
|
42
|
+
code: "todo_severity_heading_duplicate",
|
|
43
|
+
classification: "duplicate",
|
|
44
|
+
line: index + 1,
|
|
45
|
+
expected_heading: entry.heading,
|
|
46
|
+
recovery: `Keep exactly one '${entry.heading}' section and move its rows there.`,
|
|
47
|
+
});
|
|
48
|
+
if (order < greatestOrder)
|
|
49
|
+
add({
|
|
50
|
+
code: "todo_severity_heading_out_of_order",
|
|
51
|
+
classification: "out_of_order",
|
|
52
|
+
line: index + 1,
|
|
53
|
+
expected_heading: entry.heading,
|
|
54
|
+
recovery: "Order managed TODO sections as Critical, Degraded, Normal, Annoying, then Resolved; missing sections are allowed.",
|
|
55
|
+
});
|
|
56
|
+
seen.add(entry.section);
|
|
57
|
+
greatestOrder = Math.max(greatestOrder, order);
|
|
58
|
+
}
|
|
59
|
+
return { diagnostics: diagnostics.slice(0, DIAGNOSTIC_LIMIT), omitted_count: Math.max(0, diagnostics.length - DIAGNOSTIC_LIMIT) };
|
|
60
|
+
}
|
|
61
|
+
export function assertTodoSeverityHeadingStructure(markdown) {
|
|
62
|
+
const diagnosis = inspectTodoSeverityHeadings(markdown);
|
|
63
|
+
if (!diagnosis.diagnostics.length)
|
|
64
|
+
return;
|
|
65
|
+
reject({
|
|
66
|
+
class: "conflict",
|
|
67
|
+
message: "TODO.md managed severity section structure is malformed",
|
|
68
|
+
diagnosis: { classification: "todo_severity_heading_structure", ...diagnosis },
|
|
69
|
+
recovery: "Apply every bounded TODO.md heading correction exactly as reported, then retry; no state was changed.",
|
|
70
|
+
});
|
|
71
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentera",
|
|
3
|
-
"version": "3.0.0-dev.
|
|
3
|
+
"version": "3.0.0-dev.76",
|
|
4
4
|
"description": "One CLI, many capabilities: project state, artifact validation, and capability routing for AI coding agents",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agentera",
|
|
@@ -80,6 +80,6 @@
|
|
|
80
80
|
},
|
|
81
81
|
"agentera": {
|
|
82
82
|
"suiteVersion": "3.0.0",
|
|
83
|
-
"gitRef": "
|
|
83
|
+
"gitRef": "70aed21badb985087fd79371fabb61b1ec31ddde"
|
|
84
84
|
}
|
|
85
85
|
}
|