agentera 3.0.0-dev.74 → 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.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": "agentera.generatedBuildSource.v1",
3
- "commit": "15c2b8d04ef350ff9e09bc5f40a0d079aeedc399",
4
- "tree": "0e0c77c6fc7da5b3da9b6b92eaa6117e669658ef",
5
- "files": 6724,
6
- "workingTreeSha256": "634b717f526f20689aa5fa08cf3c268675617f59e56d77e1555b9e49cab07606",
7
- "identitySha256": "b94b49cdad8c10a7f99ee0189b1981a8d8f3217b345b7e18e29e1f9a8de3c7ed"
3
+ "commit": "70aed21badb985087fd79371fabb61b1ec31ddde",
4
+ "tree": "3baa4c8145944638cad38ae98346f98518d01884",
5
+ "files": 6750,
6
+ "workingTreeSha256": "57aa3b7d804724356e7a3bf9adf7ea9a54e29312cd19b0abcd23f4f9699dfe54",
7
+ "identitySha256": "f4acdd4621fd8110b1277f88be7f093929a887c6b4db6c3269e3a1af98a63b36"
8
8
  }
@@ -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
@@ -138,15 +138,15 @@ owners:
138
138
  minimum_ms: 307207
139
139
  note: Task 1 recorded a contention fingerprint, so the minimum is a historical ceiling rather than a direct target.
140
140
  controlled_measurement:
141
- measured_on: 2026-08-10
141
+ measured_on: 2026-08-14
142
142
  command: pnpm -C packages/cli run verify:package
143
- serial_runs_ms: [12376, 11666, 11047]
144
- owner_wall_time_runs_ms: [11415, 10789, 10202]
145
- minimum_ms: 11047
146
- median_ms: 11666
147
- maximum_ms: 12376
148
- test_files: 2
149
- passed_tests: 11
143
+ serial_runs_ms: [5558, 5489, 5471]
144
+ owner_wall_time_runs_ms: [4752, 4698, 4672]
145
+ minimum_ms: 5471
146
+ median_ms: 5489
147
+ maximum_ms: 5558
148
+ test_files: 3
149
+ passed_tests: 12
150
150
  fixture_construction_cycles: 2
151
151
  deterministic_pack_runs: 2
152
152
  extracted_smoke_runs: 1
@@ -156,10 +156,6 @@ owners:
156
156
  logical_cpus: 16
157
157
  node: 22.23.2
158
158
  pnpm: 10.30.3
159
- load_average_at_start:
160
- - [15.69, 16.16, 15.30]
161
- - [13.97, 15.76, 15.18]
162
- - [12.79, 15.38, 15.07]
163
159
  conditions: Serial commands with existing dependencies and caches, no intentional verification overlap, and two independent construction roots per command.
164
160
  remote_cold_measurement:
165
161
  measured_on: 2026-08-12
@@ -254,7 +250,6 @@ conservative_routing:
254
250
  - packages/cli/test/helpers/preCutoverBootstrapDispatcher.mjs
255
251
  - packages/cli/test/helpers/runtimeProofCliBoundary.mjs
256
252
  - packages/cli/test/integration/runtimeBootstrapMatrix.test.ts
257
- - packages/cli/test/integration/sourcePackageParity.test.ts
258
253
  - references/analysis/verification-policy.yaml
259
254
  - scripts/precommit-vitest.sh
260
255
  prefixes:
@@ -80,7 +80,7 @@ inventory:
80
80
  classification: current
81
81
  production_owner: { module: packages/cli/src/release/releaseMetadata.ts, symbol: readReleaseMetadata }
82
82
  consumers:
83
- - { kind: validator, module: packages/cli/src/release/releaseMetadata.ts, symbol: readReleaseMetadata, consumption: loads }
83
+ - { kind: validator, module: packages/cli/src/release/releaseMetadata.ts, symbol: readUpdateChannels, consumption: loads }
84
84
  - path: references/cli/vocabulary-index.yaml
85
85
  classification: current
86
86
  production_owner: { module: packages/cli/src/validate/vocabularyAuthority.ts, symbol: validateVocabularyAuthority }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": "agentera.generatedBuildSource.v1",
3
- "commit": "15c2b8d04ef350ff9e09bc5f40a0d079aeedc399",
4
- "tree": "0e0c77c6fc7da5b3da9b6b92eaa6117e669658ef",
5
- "files": 6724,
6
- "workingTreeSha256": "634b717f526f20689aa5fa08cf3c268675617f59e56d77e1555b9e49cab07606",
7
- "identitySha256": "b94b49cdad8c10a7f99ee0189b1981a8d8f3217b345b7e18e29e1f9a8de3c7ed"
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,
@@ -184,7 +184,7 @@ function readPackageJson(root) {
184
184
  }
185
185
  return readJson(candidate);
186
186
  }
187
- function readUpdateChannels(root) {
187
+ export function readUpdateChannels(root) {
188
188
  const candidate = path.join(root, "references/cli/update-channels.yaml");
189
189
  if (!fs.existsSync(candidate)) {
190
190
  return null;
@@ -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 = heading.includes("critical") ? "critical" : heading.includes("degraded") ? "degraded" : heading.includes("annoying") ? "annoying" : heading.includes("resolved") ? "resolved" : heading.includes("normal") ? "normal" : heading === "notes" ? null : 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: "unsafe",
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 === "resolved" ? "## ✓ Resolved" : `## → ${section[0].toUpperCase()}${section.slice(1)}`; }
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 ["critical", "degraded", "normal", "annoying", "resolved"]) {
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
- if (lines.at(-1)?.trim())
39
- lines.push("");
40
- lines.push(headingFor(section));
41
- heading = lines.length - 1;
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 inspection("unsafe_inactive", errorCounts(error));
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 inspection("unsafe_active", errorCounts(error));
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 = heading.includes("critical") ? "critical" : heading.includes("degraded") ? "degraded" : heading.includes("annoying") ? "annoying" : heading.includes("resolved") ? "resolved" : heading.includes("normal") ? "normal" : heading === "notes" ? null : 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.74",
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": "15c2b8d04ef350ff9e09bc5f40a0d079aeedc399"
83
+ "gitRef": "70aed21badb985087fd79371fabb61b1ec31ddde"
84
84
  }
85
85
  }