agentera 3.0.0-dev.47 → 3.0.0-dev.51

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.
Files changed (35) hide show
  1. package/README.md +15 -4
  2. package/bundle/CHANGELOG.md +4 -4
  3. package/bundle/references/adapters/package-publication.json +4 -4
  4. package/bundle/references/adapters/package-registry.yaml +2 -1
  5. package/bundle/references/artifacts/state-storage-authority.yaml +51 -11
  6. package/bundle/skills/agentera/SKILL.md +11 -2
  7. package/bundle/skills/agentera/schemas/artifacts/plan.yaml +38 -4
  8. package/dist/capabilities/orchestrate/instructions.js +2 -0
  9. package/dist/capabilities/plan/instructions.js +1 -0
  10. package/dist/capabilities/status/instructions.js +5 -1
  11. package/dist/cli/commands/compact.js +125 -3
  12. package/dist/cli/commands/doctor.js +8 -1
  13. package/dist/cli/commands/prime/collectEntityOrientation.js +22 -7
  14. package/dist/cli/commands/prime/collectOrientationState.js +3 -3
  15. package/dist/cli/commands/prime/orientationOutput.js +2 -0
  16. package/dist/cli/commands/state/write.js +1 -5
  17. package/dist/cli/commands/validate.js +1 -1
  18. package/dist/cli/help.js +4 -0
  19. package/dist/cli/orientation/attention.js +4 -2
  20. package/dist/core/developmentInvocation.js +1 -1
  21. package/dist/registries/activationTuples.js +3 -2
  22. package/dist/registries/packagePublication.js +2 -2
  23. package/dist/state/entityStorage.js +9 -6
  24. package/dist/state/planEntities.js +485 -33
  25. package/dist/state/planLineageValidation.js +52 -0
  26. package/dist/state/planReplacementTransaction.js +475 -0
  27. package/dist/state/todoActivationSafety.js +58 -0
  28. package/dist/state/todoDocsEntities.js +22 -17
  29. package/dist/state/todoReconciliationActivation.js +12 -4
  30. package/dist/state/todoReconciliationInspection.js +8 -12
  31. package/dist/state/write/explain.js +32 -1
  32. package/dist/state/write/grammar.js +5 -0
  33. package/dist/state/write/operations.js +1 -0
  34. package/dist/state/write/runtimeOperations.js +6 -4
  35. package/package.json +2 -2
@@ -2,7 +2,7 @@ import path from "node:path";
2
2
  import { listProgressEntities } from "../../../state/progressEntities.js";
3
3
  import { listDecisionEntities } from "../../../state/decisionEntities.js";
4
4
  import { listHealthEntities } from "../../../state/healthEntities.js";
5
- import { listPlanEntities, listPlanTaskEntities } from "../../../state/planEntities.js";
5
+ import { listPlanEntities, listPlanTaskEntities, openPlanConflictDiagnostic } from "../../../state/planEntities.js";
6
6
  import { listObjectiveEntities, listExperimentEntities } from "../../../state/objectiveExperimentEntities.js";
7
7
  import { assertTodoReconciliationReadable, listTodoDocsEntities, projectTodoReadEntities } from "../../../state/todoDocsEntities.js";
8
8
  import { inspectTodoReconciliationState } from "../../../state/todoReconciliationInspection.js";
@@ -105,22 +105,31 @@ function projectedHistory(list, artifact, fullCount, summaryCount, degraded) {
105
105
  source_contract: list.source_contract,
106
106
  };
107
107
  }
108
- function selected(entries, artifact) {
109
- if (entries.length < 2)
108
+ function entityPlanStatus(entry) {
109
+ const plan = entry.record ?? {};
110
+ const planHeader = plan.header && typeof plan.header === "object" && !Array.isArray(plan.header)
111
+ ? plan.header
112
+ : {};
113
+ return String(planHeader.status ?? plan.status ?? "");
114
+ }
115
+ function selected(entries, artifact, candidateIds = entries.map((entry) => String(entry.id)), sourceRoot) {
116
+ if (candidateIds.length < 2)
110
117
  return entries[0];
111
- const ids = entries.map((entry) => String(entry.id)).sort().join(", ");
118
+ const ids = candidateIds.slice().sort();
112
119
  const noun = artifact === "plan" ? "open plans" : "active objectives";
113
120
  const list = preCutoverCommand(`state ${artifact} list --format json`);
121
+ const planConflict = artifact === "plan" ? openPlanConflictDiagnostic(ids, sourceRoot) : undefined;
114
122
  throw new StateRetrievalFailure({
115
123
  schemaVersion: "agentera.stateFailure.v1",
116
124
  status: "fail",
117
125
  error: {
118
126
  class: "ambiguous",
119
127
  artifact,
120
- message: `multiple ${noun} require explicit selection: ${ids}`,
128
+ message: planConflict?.message ?? `multiple ${noun} require explicit selection: ${ids.join(", ")}`,
121
129
  syntax: preCutoverCommand(`state ${artifact} get --id ID --format json`),
122
130
  example: preCutoverCommand(`state ${artifact} get --id ${String(entries[0]?.id)} --format json`),
123
- recovery: `Run ${list}, resolve the competing ${noun}, and retry prime.`,
131
+ recovery: planConflict?.recovery ?? `Run ${list}, resolve the competing ${noun}, and retry prime.`,
132
+ ...(planConflict ? { details: planConflict.details } : {}),
124
133
  },
125
134
  }, 1);
126
135
  }
@@ -163,7 +172,13 @@ export function collectEntityOrientation(projectRoot, sourceRoot) {
163
172
  const objectiveEntries = entries(objectiveList);
164
173
  const todoEntries = entries(todoList);
165
174
  const docsEntries = entries(docsList);
166
- const selectedPlan = selected(planEntries, "plan");
175
+ // Startup detail is bounded to two plans, but ambiguous diagnostics must use
176
+ // the complete canonical candidate set so their ID sample and omission count
177
+ // match direct plan selection without creating another selector.
178
+ const openPlanCandidateIds = discovery.entities
179
+ .filter((entry) => entry.boundary === "plan" && entry.classification === "valid" && entry.id && ["open", "active"].includes(entityPlanStatus(entry)))
180
+ .map((entry) => entry.id);
181
+ const selectedPlan = selected(planEntries, "plan", openPlanCandidateIds, sourceRoot);
167
182
  const taskPage = selectedPlan
168
183
  ? listPlanTaskEntities(projectRoot, String(selectedPlan.id), 100, undefined, { sourceRoot, format: "json", discovery })
169
184
  : null;
@@ -99,10 +99,10 @@ export function collectOrientationState(opts) {
99
99
  const readiness = selectStatusReadiness(plan, health, objective, todoItems, decision, savedContext, entity.todoReadiness);
100
100
  const reconciliationReadiness = entity.todoReconciliation?.status === "action_required"
101
101
  ? withRecommended(readiness, {
102
- object: entity.todoReconciliation.state === "inactive" ? "Activate TODO reconciliation" : "Repair TODO reconciliation",
103
- capability: "build",
102
+ object: entity.todoReconciliation.state === "unsafe_inactive" ? "Replan unsafe TODO activation correction" : entity.todoReconciliation.state === "inactive" ? "Activate TODO reconciliation" : "Repair TODO reconciliation",
103
+ capability: entity.todoReconciliation.state === "unsafe_inactive" ? "plan" : "build",
104
104
  reason: entity.todoReconciliation.recovery_command,
105
- phase: entity.todoReconciliation.state === "invalid_lifecycle" ? "audit" : "build",
105
+ phase: entity.todoReconciliation.state === "unsafe_inactive" ? "plan" : entity.todoReconciliation.state === "invalid_lifecycle" ? "audit" : "build",
106
106
  })
107
107
  : readiness;
108
108
  const nextAction = cutover.status !== "complete"
@@ -214,6 +214,8 @@ export function buildStatusContextState(state, _command = "prime", _options = {}
214
214
  plan: {
215
215
  exists: plan.exists,
216
216
  active: plan.active ?? false,
217
+ id: plan.id ?? null,
218
+ artifact: plan.exists ? plan.artifact ?? "plan" : null,
217
219
  status: plan.status,
218
220
  title: plan.title ?? null,
219
221
  complete: plan.complete ?? 0,
@@ -272,7 +272,7 @@ function parseWrite(artifactRaw, argv) {
272
272
  message: `${artifact} ${verb} accepts field flags, not --input`,
273
273
  example: exampleFor(artifact, verb),
274
274
  });
275
- if (spec.inputRoot && !inputSource)
275
+ if (spec.inputRoot && !inputSource && !spec.inputOptional)
276
276
  invalid({
277
277
  class: "missing_argument",
278
278
  message: `--input is required for ${artifact} ${verb}`,
@@ -311,11 +311,7 @@ function parseWrite(artifactRaw, argv) {
311
311
  const id = mappingPath(values, "id");
312
312
  if (taskVerb && id === undefined)
313
313
  invalid({ class: "missing_argument", message: `--id is required for plan ${verb} in entity mode` });
314
- if (force)
315
- invalid({ class: "unrecognized_argument", message: "--force is unavailable for entity plans; incomplete plans remain canonical history" });
316
314
  }
317
- if (artifact === "plan" && verb === "create" && force)
318
- invalid({ class: "unrecognized_argument", message: "--force is unavailable for entity plan create because multiple open plans coexist" });
319
315
  return { artifact, spec, format, projectRoot, dryRun, force, values, callerPayload, inputSource };
320
316
  }
321
317
  function mappingPath(entry, field) {
@@ -457,7 +457,7 @@ export function cmdValidateState(args, io) {
457
457
  return payload.valid ? 0 : 1;
458
458
  }
459
459
  function todoReconciliationValidationIssue(diagnosis) {
460
- const label = diagnosis.state === "inactive" ? "inactive" : diagnosis.state === "unsafe_active" ? "unsafe active" : "invalid lifecycle";
460
+ const label = diagnosis.state === "inactive" ? "inactive" : diagnosis.state === "unsafe_inactive" ? "unsafe inactive" : diagnosis.state === "unsafe_active" ? "unsafe active" : "invalid lifecycle";
461
461
  return {
462
462
  code: `todo_reconciliation_${diagnosis.state}`,
463
463
  path: "TODO.md",
package/dist/cli/help.js CHANGED
@@ -146,6 +146,10 @@ export function printStateHelp(sub) {
146
146
  "Invalid historical archives remain non-fatal compatibility diagnostics unless selected.",
147
147
  "Task list accepts an optional bare plan ID and otherwise defaults to the sole open plan; task get requires --id.",
148
148
  "Only the displayed bare-ID selectors are accepted.",
149
+ "Plan create rejects an open predecessor unless --force can archive exactly one unchanged; create --force records that predecessor's bare ID in successor.previous_plan_archived.",
150
+ "Targeted replacement is explicit: state plan replace --predecessor ID --successor ID, or --predecessor ID --input PLAN.yaml to create the successor. It changes only the named predecessor lifecycle and derived successor lineage.",
151
+ "Competing open-plan diagnostics retain bounded bare IDs and require explicit roles: state plan replace --predecessor PREDECESSOR_ID --successor SUCCESSOR_ID --format json. They never infer a role from list order.",
152
+ "Archive completed plans normally. Archive an unfinished selected plan with --force; an implicit archive with multiple open plans rejects without effects.",
149
153
  "List limits are 1 through 100; structured pages are at most 32,768 UTF-8 bytes and omit whole entries only.",
150
154
  "Legacy plan identity collisions return a structured ambiguous error.",
151
155
  "",
@@ -6,8 +6,10 @@ export function buildOrientationAttention(state) {
6
6
  const { v1_migration: v1Migration, project_integration: projectIntegration, health, plan, decision_attention: decisionAttention, glossary_caveat_attention: glossaryCaveatAttention, corpus_coverage: corpusCoverage, todo_items: todoItems, todo_reconciliation: todoReconciliation, } = state;
7
7
  const attention = [];
8
8
  if (todoReconciliation?.status === "action_required") {
9
- const label = todoReconciliation.state === "inactive" ? "inactive" : todoReconciliation.state === "unsafe_active" ? "unsafe active" : "invalid lifecycle";
10
- attention.push(`action-required: TODO reconciliation is ${label}; preview \`${todoReconciliation.preview_command ?? "n/a"}\`; apply \`${todoReconciliation.apply_command ?? "n/a"}\``);
9
+ const label = todoReconciliation.state === "inactive" ? "inactive" : todoReconciliation.state === "unsafe_inactive" ? "unsafe inactive" : todoReconciliation.state === "unsafe_active" ? "unsafe active" : "invalid lifecycle";
10
+ attention.push(todoReconciliation.state === "unsafe_inactive"
11
+ ? `action-required: TODO reconciliation is ${label}; ${todoReconciliation.recovery_command}`
12
+ : `action-required: TODO reconciliation is ${label}; preview \`${todoReconciliation.preview_command ?? "n/a"}\`; apply \`${todoReconciliation.apply_command ?? "n/a"}\``);
11
13
  }
12
14
  const skillDivergenceSignals = (state.app.signals ?? []).filter((s) => s.kind === "skill_root_divergence");
13
15
  for (const signal of skillDivergenceSignals) {
@@ -284,7 +284,7 @@ function validateOperationExample(spec, template) {
284
284
  }
285
285
  if (!format)
286
286
  invalid(`${spec.artifact}.${spec.verb} example omits --format`);
287
- if (spec.inputMode === "structured" && !input)
287
+ if (spec.inputMode === "structured" && !spec.inputOptional && !input)
288
288
  invalid(`${spec.artifact}.${spec.verb} example omits --input`);
289
289
  for (const field of spec.fields) {
290
290
  if (field.required && !seen.has(field.flag))
@@ -74,6 +74,7 @@ const decodedCatalog = gunzipSync(Buffer.from(CATALOG, "base64")).toString("utf8
74
74
  const emittedReasons = JSON.parse(gunzipSync(Buffer.from(EMITTED_REASON_CATALOG, "base64")).toString("utf8"));
75
75
  const rawTuples = JSON.parse(decodedCatalog.slice(decodedCatalog.indexOf("[", 2)).replace(/,\s*]$/, "]"));
76
76
  const addedTuples = [
77
+ { class: "state", surface_id: "write:plan.replace", owner_path: "packages/cli/src/state/write/runtimeOperations.ts", owner_symbol_or_selector: "runtimeOperationSpecs", owner_selector: "plan.replace", semantic_selector_if_any: null, canonical_correction: "node packages/cli/dist/bin/agentera.js check validate state --format json" },
77
78
  { class: "state", surface_id: "write:todo.activate", owner_path: "packages/cli/src/state/write/runtimeOperations.ts", owner_symbol_or_selector: "runtimeOperationSpecs", owner_selector: "todo.activate", semantic_selector_if_any: null, canonical_correction: "node packages/cli/dist/bin/agentera.js check validate state --format json" },
78
79
  { class: "state", surface_id: "write:todo.repair", owner_path: "packages/cli/src/state/write/runtimeOperations.ts", owner_symbol_or_selector: "runtimeOperationSpecs", owner_selector: "todo.repair", semantic_selector_if_any: null, canonical_correction: "node packages/cli/dist/bin/agentera.js check validate state --format json" },
79
80
  { class: "package", surface_id: "emitted:packages/cli/src/cli/commands/doctor.ts", owner_path: "packages/cli/src/registries/packageRegistry.ts", owner_symbol_or_selector: "loadRegistry", owner_selector: "packages/cli/src/cli/commands/doctor.ts", semantic_selector_if_any: JSON.stringify({ path: "packages/cli/src/cli/commands/doctor.ts", selector: null, format: null, classification: null, reason: "Doctor project-state signals publish bounded reconciliation preview and apply guidance." }), canonical_correction: "pnpm -C packages/cli run verify:package" },
@@ -94,11 +95,11 @@ export const ACTIVATION_TUPLE_AUTHORITY = Object.freeze({
94
95
  capability: { count: 12, sha256: "892e6e5e2a57b41064bc44fa2946453225f1b1195aff77aad05365fd0a1071c2" },
95
96
  runtime: { count: 81, sha256: "99b2abff3ebff889b54b1781c563ab4b32609a479c4e90d6aad854f48fba7edc" },
96
97
  reference: { count: 22, sha256: "243fd3c317553f8924c56a52c61af90ccf2793b7b0018af1373b73c69b6c3afb" },
97
- state: { count: 36, sha256: "c3366cd6d538a47b5554f2e142eb255ac371bfc77e89128aceb59dfd8376c20a" },
98
+ state: { count: 37, sha256: "210bd831c5d62beac149efd888ee4a99af49a9469cf373031f8db9893904d0c1" },
98
99
  package: { count: 66, sha256: "83e6971af7f7564e42369aaccc445b32a0793bfffade843d149b6abd4fd3dbbc" },
99
100
  bootstrap: { count: 34, sha256: "9a7dd7e27110d85cf5c08835fdd8f08119e75579858e63bc6d396c733961d0bc" },
100
101
  },
101
- total: { count: 278, sha256: "52a349aa3901626cba0bef4e06d380067722d73d3039fea95f2d806a8018fe54" },
102
+ total: { count: 279, sha256: "c2d7a66bc9af04a5511348825995317a5968357c6cacbfd87eb2cc4fbe77156c" },
102
103
  });
103
104
  export function canonicalTupleJson(value) { return JSON.stringify(value); }
104
105
  export function digestCanonicalTuples(values) {
@@ -53,11 +53,11 @@ export const ACTIVATION_CENSUS_AUTHORITY = deepFreeze({
53
53
  capability: { count: 12, sha256: "007e1157a892fec54182b1803aeaa442cf8e2e332e6a055c3bd020e2b0731867" },
54
54
  runtime: { count: 81, sha256: "03fe600a3a27f05daf0d1b59c16b00bedc354a13c70ea60be0b457175fdf743c" },
55
55
  reference: { count: 22, sha256: "2b48c82bb7ee10388d9ebb4fa7ea3743ea82a7e9aaedfcd5a858eeedb9d891ae" },
56
- state: { count: 36, sha256: "a9f371e2f6d49e7cb890767d7dfe26edee11d4a7d28ee35ab6c491c0abafbbf0" },
56
+ state: { count: 37, sha256: "ca87acdb0fda04ba46a52163fc30ec6578c077e61fcb64597f861a36fdce1dc4" },
57
57
  package: { count: 66, sha256: "3548af7e84151c90690a6eb3d1cb6c7847f39b290161ecab599d8cb1bf0d2cb0" },
58
58
  bootstrap: { count: 34, sha256: "71c2038744e2518a6adb722acbb5f9352bddfb5d1c92eeb0e347297ef2ca2f1e" },
59
59
  },
60
- total: { count: 278, sha256: "5767645b4d78d8716e7ea1a283125bcfd43e13634311b33b62bc0a307d1abdb3" },
60
+ total: { count: 279, sha256: "b92cd469f858e9584682d05b4d9bb03c2fc4b905f3485475723786c0a978c138" },
61
61
  });
62
62
  /** Each dimension names the production contract it observes independently. */
63
63
  export const ACTIVATION_EVIDENCE_SOURCES = deepFreeze({
@@ -13,6 +13,7 @@ import { summaryMigrationProvenanceDeclaration, summaryMigrationProvenanceViolat
13
13
  import { validateCompactedSummarySourceRowAuthority } from "./summarySourceRowAuthority.js";
14
14
  import { acquireWriterLock } from "./write/lock.js";
15
15
  import { planTaskRecordViolations } from "./write/planEvaluation.js";
16
+ import { planLineageIssues } from "./planLineageValidation.js";
16
17
  import { todoDocsRecordViolations } from "./todoDocsEntityValidation.js";
17
18
  import { applyGlossaryCaveatLifecycleValidation, validateProgressGlossaryCaveat } from "./progressGlossaryCaveat.js";
18
19
  import { progressPublicationOrderViolations } from "./progressPublicationOrder.js";
@@ -63,8 +64,8 @@ function canonicalEntityEnvelopeAgainstModel(bytes, expected, model, sourceRoot,
63
64
  violations.push(...healthEntityViolations(record));
64
65
  if (expected.boundary === "plan") {
65
66
  const header = mapping(record.header) ? record.header : {};
66
- if (!mapping(record.header) || typeof header.title !== "string" || typeof header.created !== "string" || !["open", "complete", "archived"].includes(String(header.status)))
67
- violations.push("invalid plan lifecycle fields");
67
+ if (!mapping(record.header) || typeof header.title !== "string" || typeof header.created !== "string" || !["open", "complete", "archived"].includes(String(header.status)) || (record.replacement_input_sha256 !== undefined && !/^[a-f0-9]{64}$/.test(String(record.replacement_input_sha256))))
68
+ violations.push("invalid plan lifecycle or replacement identity fields");
68
69
  }
69
70
  if (expected.boundary === "plan_task")
70
71
  violations.push(...planTaskRecordViolations(record));
@@ -430,9 +431,9 @@ function discoverFile(projectRoot, file, pathArtifact, boundary, model, issues,
430
431
  }
431
432
  if (!malformed && boundary === "plan" && record) {
432
433
  const header = mapping(record.header) ? record.header : {};
433
- if (!mapping(record.header) || typeof header.title !== "string" || typeof header.created !== "string" || !["open", "complete", "archived"].includes(String(header.status))) {
434
+ if (!mapping(record.header) || typeof header.title !== "string" || typeof header.created !== "string" || !["open", "complete", "archived"].includes(String(header.status)) || (record.replacement_input_sha256 !== undefined && !/^[a-f0-9]{64}$/.test(String(record.replacement_input_sha256)))) {
434
435
  malformed = true;
435
- issues.push({ code: "malformed_entity", path: relativePath, id, artifact, boundary, message: `entity '${relativePath}' has invalid plan lifecycle fields`, recovery: recovery(projectRoot, `repair '${relativePath}' using the current plan header schema and open|complete|archived lifecycle`) });
436
+ issues.push({ code: "malformed_entity", path: relativePath, id, artifact, boundary, message: `entity '${relativePath}' has invalid plan lifecycle or replacement identity fields`, recovery: recovery(projectRoot, `repair '${relativePath}' using the current plan header schema, open|complete|archived lifecycle, and lowercase SHA-256 replacement identity`) });
436
437
  }
437
438
  }
438
439
  if (!malformed && boundary === "plan_task" && record) {
@@ -550,8 +551,8 @@ export function assertEntityDiscoveryOrigin(projectRoot, sourceRoot, discovery)
550
551
  }
551
552
  function relationTargets(entity, relation) {
552
553
  const value = entity.record?.[relation.field];
553
- if (relation.cardinality === "exactly_one")
554
- return typeof value === "string" ? [value] : null;
554
+ if (relation.cardinality === "exactly_one" || relation.cardinality === "zero_or_one")
555
+ return typeof value === "string" ? [value] : relation.cardinality === "zero_or_one" && (value === undefined || value === null) ? [] : null;
555
556
  if (value === undefined || value === null)
556
557
  return [];
557
558
  return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : null;
@@ -649,6 +650,8 @@ export function validateEntityDiscovery(projectRoot, sourceRoot, discovery, boun
649
650
  });
650
651
  }
651
652
  }
653
+ const canonicalPlans = discovery.entities.filter((entity) => entity.boundary === "plan" && entity.classification === "valid" && entity.id && entity.record);
654
+ issues.push(...planLineageIssues(canonicalPlans, (action) => recovery(projectRoot, action)));
652
655
  for (const definition of model.entities) {
653
656
  if (!definition.ownership || definition.ownership.cardinality !== "zero_or_one")
654
657
  continue;