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.
- package/README.md +15 -4
- package/bundle/CHANGELOG.md +4 -4
- package/bundle/references/adapters/package-publication.json +4 -4
- package/bundle/references/adapters/package-registry.yaml +2 -1
- package/bundle/references/artifacts/state-storage-authority.yaml +51 -11
- package/bundle/skills/agentera/SKILL.md +11 -2
- package/bundle/skills/agentera/schemas/artifacts/plan.yaml +38 -4
- package/dist/capabilities/orchestrate/instructions.js +2 -0
- package/dist/capabilities/plan/instructions.js +1 -0
- package/dist/capabilities/status/instructions.js +5 -1
- package/dist/cli/commands/compact.js +125 -3
- package/dist/cli/commands/doctor.js +8 -1
- package/dist/cli/commands/prime/collectEntityOrientation.js +22 -7
- package/dist/cli/commands/prime/collectOrientationState.js +3 -3
- package/dist/cli/commands/prime/orientationOutput.js +2 -0
- package/dist/cli/commands/state/write.js +1 -5
- package/dist/cli/commands/validate.js +1 -1
- package/dist/cli/help.js +4 -0
- package/dist/cli/orientation/attention.js +4 -2
- package/dist/core/developmentInvocation.js +1 -1
- package/dist/registries/activationTuples.js +3 -2
- package/dist/registries/packagePublication.js +2 -2
- package/dist/state/entityStorage.js +9 -6
- package/dist/state/planEntities.js +485 -33
- package/dist/state/planLineageValidation.js +52 -0
- package/dist/state/planReplacementTransaction.js +475 -0
- package/dist/state/todoActivationSafety.js +58 -0
- package/dist/state/todoDocsEntities.js +22 -17
- package/dist/state/todoReconciliationActivation.js +12 -4
- package/dist/state/todoReconciliationInspection.js +8 -12
- package/dist/state/write/explain.js +32 -1
- package/dist/state/write/grammar.js +5 -0
- package/dist/state/write/operations.js +1 -0
- package/dist/state/write/runtimeOperations.js +6 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -38,18 +38,29 @@ bytes). Status startup returns
|
|
|
38
38
|
bytes). Omitted detail names its authoritative recovery command. `doctor`
|
|
39
39
|
returns detailed read-only evidence and exact user actions. Entity-mode projects
|
|
40
40
|
with an absent or unsafe TODO reconciliation marker return `action_required`
|
|
41
|
-
instead of `ok` or `up_to_date
|
|
42
|
-
preview
|
|
41
|
+
instead of `ok` or `up_to_date`. A safe inactive project reports an activation
|
|
42
|
+
preview and its exact effect-bound apply command:
|
|
43
43
|
|
|
44
44
|
```bash
|
|
45
45
|
npx -y agentera@next state todo activate --dry-run --format json
|
|
46
46
|
npx -y agentera@next state todo activate --effect-sha256 EFFECT_SHA256 --yes --format json
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
An unsafe active project with an existing marker reports the separate repair
|
|
50
|
+
preview and effect-bound apply path:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
47
53
|
npx -y agentera@next state todo repair --dry-run --format json
|
|
48
54
|
npx -y agentera@next state todo repair --effect-sha256 EFFECT_SHA256 --yes --format json
|
|
49
55
|
```
|
|
50
56
|
|
|
51
|
-
|
|
52
|
-
|
|
57
|
+
Unsafe inactive evidence (unmatched projections, duplicate public work, stale
|
|
58
|
+
entity status, or prospective resurrection) reports a bounded, content-private
|
|
59
|
+
diagnosis but no effect digest or apply command. It has no repair path before
|
|
60
|
+
activation. Correct the Markdown-owned public rows and Agentera-owned entity
|
|
61
|
+
state, then replan any required mutation. `check validate state` reports the
|
|
62
|
+
same read-only diagnosis. Healthy active TODO projections keep the existing
|
|
63
|
+
output.
|
|
53
64
|
|
|
54
65
|
## Shared-skill integration
|
|
55
66
|
|
package/bundle/CHANGELOG.md
CHANGED
|
@@ -146,9 +146,9 @@
|
|
|
146
146
|
outside the contract. One bounded activation transaction rejects duplicate
|
|
147
147
|
ID-less rows before requiring canonical IDs for all newly managed rows.
|
|
148
148
|
- Fixed TODO cutover and activation to require a complete one-to-one inventory,
|
|
149
|
-
preserve Markdown-owned status and order, and block implicit mutation.
|
|
150
|
-
|
|
151
|
-
|
|
149
|
+
preserve Markdown-owned status and order, and block implicit mutation. Unsafe inactive
|
|
150
|
+
`agentera state todo activate --dry-run` and confirmed activation reject
|
|
151
|
+
resurrection or duplicate effects before an effect digest, apply command, or project mutation.
|
|
152
152
|
- Fixed unsafe TODO reconciliation recovery and reporting. `agentera state todo
|
|
153
153
|
repair` collapses only proven duplicate or stale projections without reopening
|
|
154
154
|
completed work, while `agentera prime`, `agentera doctor`, and `agentera check
|
|
@@ -213,7 +213,7 @@
|
|
|
213
213
|
- Added persisted plan identity across active-to-archive transitions, deterministic read-only identity for legacy plans, explicit lifecycle provenance, mirrored-path reporting, and structured ambiguity failures for identity collisions.
|
|
214
214
|
- Added `agentera state plan tasks list` and `agentera state plan tasks get --task N` for bounded active-plan task retrieval with declared task-number order, opaque snapshot cursors, structured not-found and compatibility errors, and exact omission recovery in text and JSON plan output.
|
|
215
215
|
- Added `agentera state decisions amend --id ID --base-sha256 HASH [--question ... --context ... --alternative-chosen ... --alternative-rejected ... --choice ... --reasoning ... --confidence firm|provisional|exploratory --feeds-into ...] [--dry-run] --format json`: validates and publishes one immutable decision revision entity. Effective reads compose the base, ordered revisions, and current satisfaction entity; stale-base and same-base conflicts fail before publication, and identical retries converge.
|
|
216
|
-
|
|
216
|
+
- Added `agentera state plan replace --predecessor PREDECESSOR_ID --successor SUCCESSOR_ID --format json` for targeted recovery of competing open plans; it atomically selects the named successor, rejects invalid requests without effects, and recovers interruptions without partial state.
|
|
217
217
|
- Added an executable bounded-retrieval authority and reserved CLI grammar for plan tasks, plans, and objective-scoped experiments, including stable and legacy identity rules, experiment `0`, opaque cursors, explicit omissions, and structured compatibility errors; retrieval execution remains staged for subsequent tasks.
|
|
218
218
|
- Added one shared skill at `~/.agents/skills/agentera` plus the CLI and entity-backed project state as the active integration contract; host-native plugin, hook, agent, and package surfaces are not shipped.
|
|
219
219
|
- Defined native-resource cleanup evidence for supported hosts and explicit `agentera upgrade --legacy-cleanup RESOURCE_ID --dry-run|--yes` selection. Cleanup retains only ledger-proven whole resources; Codex descriptor reports preserve shared configuration keys as report-only action-required items without durable key-level ownership.
|
|
@@ -284,8 +284,8 @@
|
|
|
284
284
|
"sha256": "2b48c82bb7ee10388d9ebb4fa7ea3743ea82a7e9aaedfcd5a858eeedb9d891ae"
|
|
285
285
|
},
|
|
286
286
|
"state": {
|
|
287
|
-
"count":
|
|
288
|
-
"sha256": "
|
|
287
|
+
"count": 37,
|
|
288
|
+
"sha256": "ca87acdb0fda04ba46a52163fc30ec6578c077e61fcb64597f861a36fdce1dc4"
|
|
289
289
|
},
|
|
290
290
|
"package": {
|
|
291
291
|
"count": 66,
|
|
@@ -297,8 +297,8 @@
|
|
|
297
297
|
}
|
|
298
298
|
},
|
|
299
299
|
"total": {
|
|
300
|
-
"count":
|
|
301
|
-
"sha256": "
|
|
300
|
+
"count": 279,
|
|
301
|
+
"sha256": "b92cd469f858e9584682d05b4d9bb03c2fc4b905f3485475723786c0a978c138"
|
|
302
302
|
}
|
|
303
303
|
}
|
|
304
304
|
},
|
|
@@ -130,6 +130,7 @@ records:
|
|
|
130
130
|
- {"path":"CHANGELOG.md","region":"line:212","category":"argument_bearing","classification":"exact_exemption","normalized_sha256":"35f2268ae3281a70b7c584c23e4e57a07fc9ee6625eed48ae252d167e8ba83ea","reason":"Changelog line:212 records exact release history for that scalar; it does not direct current execution."}
|
|
131
131
|
- {"path":"CHANGELOG.md","region":"line:214","category":"argument_bearing","classification":"exact_exemption","normalized_sha256":"67d177100a3c9892643202a255523b9a84f57aa4c28b8c5dd21973c13cc06c7d","reason":"Changelog line:214 records exact release history for that scalar; it does not direct current execution."}
|
|
132
132
|
- {"path":"CHANGELOG.md","region":"line:215","category":"argument_bearing","classification":"exact_exemption","normalized_sha256":"39f085892a7b3c6e14905d90ab0c3f4cc58c66e0b5ffc2ab6ce0f88e8182cf98","reason":"Changelog line:215 records exact release history for that scalar; it does not direct current execution."}
|
|
133
|
+
- {"path":"CHANGELOG.md","region":"line:216","category":"argument_bearing","classification":"exact_exemption","normalized_sha256":"a2f0254ae0226ce04de0917832fd1911cf27b1b69a832abcfbfe34c01c8d0d41","reason":"Changelog line:216 records targeted plan-replacement behavior in release history; it does not direct current execution."}
|
|
133
134
|
- {"path":"CHANGELOG.md","region":"line:219","category":"argument_bearing","classification":"exact_exemption","normalized_sha256":"6093e60d6c57f5bee694523c7fe380cd6a2ebaaff36d13e7ef153f1439789e99","reason":"Changelog line:219 records exact release history for that scalar; it does not direct current execution."}
|
|
134
135
|
- {"path":"CHANGELOG.md","region":"line:220","category":"argument_bearing","classification":"exact_exemption","normalized_sha256":"3dc02d2ca69c6d89705932cf557ffaa2bc089a294b518c0eef1a449dfb3d5173","reason":"Changelog line:220 records exact release history for that scalar; it does not direct current execution."}
|
|
135
136
|
- {"path":"CHANGELOG.md","region":"line:224","category":"argument_bearing","classification":"exact_exemption","normalized_sha256":"2aac4a56f628aaca71f8003fd0678fed24771a7b10141759f7192808af70215c","reason":"Changelog line:224 records exact release history for that scalar; it does not direct current execution."}
|
|
@@ -247,7 +248,7 @@ records:
|
|
|
247
248
|
- {"path":"CHANGELOG.md","region":"line:1058","category":"argument_bearing","classification":"exact_exemption","normalized_sha256":"79e767fd2e4baa00cdfc763c6814870dbefe20b6745d181f82bdba5139c0272f","reason":"Changelog line:1058 records exact release history for that scalar; it does not direct current execution."}
|
|
248
249
|
- {"path":"CHANGELOG.md","region":"line:1065","category":"argument_bearing","classification":"exact_exemption","normalized_sha256":"d57016b29585bb34604fc36cfce740c4e6b83a2c0fa753467730d6840ecebd34","reason":"Changelog line:1065 records exact release history for that scalar; it does not direct current execution."}
|
|
249
250
|
- {"path":"CHANGELOG.md","region":"line:1084","category":"argument_bearing","classification":"exact_exemption","normalized_sha256":"42cbb848be0de98624a2d008843e13c40efc7c77a02d7c4f489348d0e73b5b60","reason":"Changelog line:1084 records exact release history for that scalar; it does not direct current execution."}
|
|
250
|
-
- {"path":"CHANGELOG.md","region":"line:150","category":"argument_bearing","classification":"exact_exemption","normalized_sha256":"
|
|
251
|
+
- {"path":"CHANGELOG.md","region":"line:150","category":"argument_bearing","classification":"exact_exemption","normalized_sha256":"273d7dae87662352c49e5723aaa528efa89c9a7dbc7d43d3cbc54c1a1b9771da","reason":"Changelog line:150 records exact release history for that scalar; it does not direct current execution."}
|
|
251
252
|
- {"path":"CHANGELOG.md","region":"line:152","category":"argument_bearing","classification":"exact_exemption","normalized_sha256":"6d02d187f8e271cc6acc4b6966344a841615326891969dbec67815e40a51fe81","reason":"Changelog line:152 records exact release history for that scalar; it does not direct current execution."}
|
|
252
253
|
- {"path":"CHANGELOG.md","region":"line:154","category":"argument_bearing","classification":"exact_exemption","normalized_sha256":"a5dd4c79ebfefc03bd1c6e3d159f362fd2c5f584d6ff8de7928edf88414133a9","reason":"Changelog line:154 records exact release history for that scalar; it does not direct current execution."}
|
|
253
254
|
- {"path":"DESIGN.md","region":"line:41","category":"argument_bearing","classification":"exact_exemption","normalized_sha256":"650f6966f3048b7a15748d4ff652fff31c696e5c2b29e2268a0d60889251ebf1","reason":"Suite-design authority table names the exact prime context label as a source role; it is vocabulary, not an execution step."}
|
|
@@ -620,13 +620,18 @@ entity_target:
|
|
|
620
620
|
- boundary: plan
|
|
621
621
|
artifact: plan
|
|
622
622
|
independently_mutable: true
|
|
623
|
-
relationships: []
|
|
623
|
+
relationships: [previous_plan_archived]
|
|
624
624
|
implementation: implemented
|
|
625
|
-
publication:
|
|
625
|
+
publication: replace_owned_entity_with_targeted_replacement_journal
|
|
626
|
+
targeted_replacement_journal:
|
|
627
|
+
path: .agentera/.entity-recovery/plan-replacement/<content-digest>.json
|
|
628
|
+
identity: canonical_operation_identity_and_complete_target_before_after_bytes
|
|
629
|
+
visibility: pending_journal_blocks_plan_reads_until_exact_retry_recovers_or_finishes
|
|
630
|
+
replay: exact_existing_roles_or_create_input_sha256_only
|
|
626
631
|
record:
|
|
627
632
|
required_fields: [header, what, why, scope]
|
|
628
633
|
required_paths: [header.title, header.created, header.status]
|
|
629
|
-
forbidden_fields: [id, artifact, stable_id, plan_id, tasks
|
|
634
|
+
forbidden_fields: [id, artifact, stable_id, plan_id, tasks]
|
|
630
635
|
identity: >-
|
|
631
636
|
The canonical plan identity is the bare ten-letter entity envelope id.
|
|
632
637
|
A composite header.id belongs only to legacy migration source and is
|
|
@@ -775,6 +780,16 @@ entity_target:
|
|
|
775
780
|
canonical ten-letter ID; each retained legacy exemption admits at
|
|
776
781
|
most one exact row occurrence per reconciliation and cannot
|
|
777
782
|
authorize a changed or duplicate row.
|
|
783
|
+
unsafe_inactive: >-
|
|
784
|
+
One read-only pre-publication decision gates both activation preview
|
|
785
|
+
and confirmed apply. Every canonical entity must have one exact
|
|
786
|
+
public projection. Unmatched projections, duplicate public work,
|
|
787
|
+
stale entity public fields, or a prospective resurrection reject
|
|
788
|
+
before effects with bounded, content-private risk. Unsafe inactive
|
|
789
|
+
state emits neither an effect digest nor an apply command. Repair
|
|
790
|
+
requires an activation marker and is not pre-activation recovery;
|
|
791
|
+
owners must correct their source data, and any required mutation
|
|
792
|
+
must be replanned.
|
|
778
793
|
managed_row_identity: every_activated_managed_checkbox_row_has_one_ten_letter_id
|
|
779
794
|
unmanaged_regions: checkbox_rows_outside_managed_headings_remain_ordinary_markdown
|
|
780
795
|
public_path_binding: exact_docs_mapped_relative_path_plus_docs_yaml_snapshot_sha256
|
|
@@ -877,6 +892,7 @@ entity_target:
|
|
|
877
892
|
relationships:
|
|
878
893
|
representation: bare target entity ID in the named record field
|
|
879
894
|
integrity: every non-null target must resolve to exactly one entity with the declared target boundary
|
|
895
|
+
plan_lineage: An optional plan.previous_plan_archived points to one distinct archived predecessor, and that predecessor has at most one successor; reverse lineage is derived from the canonical source record rather than stored in a second mutable field.
|
|
880
896
|
unresolved_behavior: whole-state validation and migration cutover fail without creating a target
|
|
881
897
|
declarations:
|
|
882
898
|
- source: decision_satisfaction
|
|
@@ -891,6 +907,10 @@ entity_target:
|
|
|
891
907
|
field: plan
|
|
892
908
|
target: plan
|
|
893
909
|
cardinality: exactly_one
|
|
910
|
+
- source: plan
|
|
911
|
+
field: previous_plan_archived
|
|
912
|
+
target: plan
|
|
913
|
+
cardinality: zero_or_one
|
|
894
914
|
- source: plan_task
|
|
895
915
|
field: depends_on
|
|
896
916
|
target: plan_task
|
|
@@ -2713,11 +2733,11 @@ mutation_grammar:
|
|
|
2713
2733
|
verb: archive
|
|
2714
2734
|
class: simple_transition
|
|
2715
2735
|
selectors: [--plan]
|
|
2716
|
-
preconditions: [entity_mode_marker_is_valid,
|
|
2736
|
+
preconditions: [entity_mode_marker_is_valid, lifecycle_effects_are_derived_from_locked_canonical_entities, selected_plan_is_complete_or_archived_or_force_archivable]
|
|
2717
2737
|
owned_fields: [id, artifact, plan, header.status]
|
|
2718
2738
|
input: { mode: none, sources: [], cli_owned_fields: [] }
|
|
2719
|
-
recovery:
|
|
2720
|
-
examples: ['npx -y agentera@next state plan archive --dry-run --format json']
|
|
2739
|
+
recovery: Archive a complete plan normally. With --force, archive the selected open plan unchanged only after the locked canonical snapshot identifies it; multiple implicit open-plan candidates are rejected without effects.
|
|
2740
|
+
examples: ['npx -y agentera@next state plan archive --dry-run --format json', 'npx -y agentera@next state plan archive --force --dry-run --format json']
|
|
2721
2741
|
bounds: { max_input_utf8_bytes: 0, max_collection_items: 100 }
|
|
2722
2742
|
allow_force: true
|
|
2723
2743
|
fields: [{ flag: --plan, field: plan, kind: string }]
|
|
@@ -2726,19 +2746,39 @@ mutation_grammar:
|
|
|
2726
2746
|
verb: create
|
|
2727
2747
|
class: batch_transaction
|
|
2728
2748
|
selectors: []
|
|
2729
|
-
preconditions: [entity_mode_marker_is_valid, input_is_one_complete_plan_document, create_local_task_ordinals_are_sequential, dependency_graph_is_valid]
|
|
2749
|
+
preconditions: [entity_mode_marker_is_valid, lifecycle_effects_are_derived_from_locked_canonical_entities, input_is_one_complete_plan_document, create_local_task_ordinals_are_sequential, dependency_graph_is_valid]
|
|
2730
2750
|
owned_fields: [id, artifact, header.id, previous_plan_archived, task_ids]
|
|
2731
2751
|
input:
|
|
2732
2752
|
mode: structured
|
|
2733
2753
|
root: complete plan document
|
|
2734
2754
|
sources: [file, stdin]
|
|
2735
2755
|
cli_owned_fields: [id, artifact, header.id, previous_plan_archived, task_ids]
|
|
2736
|
-
recovery: Run npx -y agentera@next state plan explain --verb create --format json, keep task ordinals and dependencies local to this atomic input, remove CLI-owned fields, and
|
|
2737
|
-
examples: ['npx -y agentera@next state plan create --input plan.yaml --format json']
|
|
2756
|
+
recovery: Run npx -y agentera@next state plan explain --verb create --format json, keep task ordinals and dependencies local to this atomic input, remove CLI-owned fields, and use --force only when the locked canonical snapshot has exactly one open predecessor to archive unchanged.
|
|
2757
|
+
examples: ['npx -y agentera@next state plan create --input plan.yaml --format json', 'npx -y agentera@next state plan create --force --input plan.yaml --format json']
|
|
2738
2758
|
bounds: { max_input_utf8_bytes: 32768, max_collection_items: 100 }
|
|
2739
2759
|
allow_force: true
|
|
2740
2760
|
fields: []
|
|
2741
2761
|
|
|
2762
|
+
- artifact: plan
|
|
2763
|
+
verb: replace
|
|
2764
|
+
class: batch_transaction
|
|
2765
|
+
selectors: [--predecessor, --successor]
|
|
2766
|
+
preconditions: [entity_mode_marker_is_valid, lifecycle_effects_are_derived_from_locked_canonical_entities, predecessor_and_existing_successor_are_explicit_or_one_complete_successor_input_is_supplied, named_roles_are_distinct_bare_plan_ids, replacement_leaves_no_unnamed_open_plan, exact_lineage_or_logical_create_input_replay_matches]
|
|
2767
|
+
owned_fields: [id, artifact, header.status, header.id, previous_plan_archived, replacement_input_sha256, task_ids]
|
|
2768
|
+
input:
|
|
2769
|
+
mode: structured
|
|
2770
|
+
root: complete plan document when creating a successor
|
|
2771
|
+
optional: true
|
|
2772
|
+
sources: [file, stdin]
|
|
2773
|
+
structured_sources: [file, stdin]
|
|
2774
|
+
cli_owned_fields: [id, artifact, header.id, previous_plan_archived, replacement_input_sha256, task_ids]
|
|
2775
|
+
recovery: Name one bare predecessor and either one existing bare successor or one complete successor plan input. The operation archives only the named predecessor, derives reverse lineage from the successor, and rejects divergent retries before effects. Competing-open diagnostics retain bounded bare IDs without assigning roles and recover through npx -y agentera@next state plan replace --predecessor PREDECESSOR_ID --successor SUCCESSOR_ID --format json.
|
|
2776
|
+
examples: ['npx -y agentera@next state plan replace --predecessor abcdefghij --successor klmnopqrst --format json', 'npx -y agentera@next state plan replace --predecessor abcdefghij --input plan.yaml --format json']
|
|
2777
|
+
bounds: { max_input_utf8_bytes: 32768, max_collection_items: 100 }
|
|
2778
|
+
fields:
|
|
2779
|
+
- { flag: --predecessor, field: predecessor, kind: string, required: true, description: Bare plan ID to archive as the explicit predecessor. }
|
|
2780
|
+
- { flag: --successor, field: successor, kind: string, description: Existing bare open plan ID to retain as the explicit successor. }
|
|
2781
|
+
|
|
2742
2782
|
- artifact: health
|
|
2743
2783
|
verb: append
|
|
2744
2784
|
class: record_payload
|
|
@@ -2811,10 +2851,10 @@ mutation_grammar:
|
|
|
2811
2851
|
verb: activate
|
|
2812
2852
|
class: batch_transaction
|
|
2813
2853
|
selectors: []
|
|
2814
|
-
preconditions: [entity_mode_marker_is_valid, activation_is_absent_or_replay, todo_reconciliation_is_valid, confirmed_effect_sha256_is_authorized_for_preview_or_replay]
|
|
2854
|
+
preconditions: [entity_mode_marker_is_valid, activation_is_absent_or_replay, todo_reconciliation_is_valid, inactive_projection_is_complete_one_to_one_and_safe, confirmed_effect_sha256_is_authorized_for_preview_or_replay]
|
|
2815
2855
|
owned_fields: [reconciliation, public_document, activation]
|
|
2816
2856
|
input: { mode: none, sources: [], cli_owned_fields: [] }
|
|
2817
|
-
recovery: Preview and review every reported activation effect before explicit confirmed apply;
|
|
2857
|
+
recovery: Preview and review every reported safe activation effect before explicit confirmed apply; unsafe inactive evidence requires non-mutating owner correction and replanning.
|
|
2818
2858
|
examples: ['npx -y agentera@next state todo activate --dry-run --format json', 'npx -y agentera@next state todo activate --effect-sha256 EFFECT_SHA256 --yes --format json']
|
|
2819
2859
|
bounds: { max_input_utf8_bytes: 0, max_collection_items: 256 }
|
|
2820
2860
|
compacts: true
|
|
@@ -229,12 +229,13 @@ Common mutations:
|
|
|
229
229
|
- `npx -y agentera@next state decisions append --input <path|-> --format json`
|
|
230
230
|
- `npx -y agentera@next state decisions amend --id ID --base-sha256 HASH --input <path|-> --format json`
|
|
231
231
|
- `npx -y agentera@next state decisions update --id ID ... --format json`
|
|
232
|
-
- `npx -y agentera@next state plan create --input plan.yaml --format json`
|
|
232
|
+
- `npx -y agentera@next state plan create [--force] --input plan.yaml --format json`
|
|
233
233
|
- `npx -y agentera@next state plan append [--plan PLAN_ID] --input task.yaml --format json`
|
|
234
234
|
- `npx -y agentera@next state plan update --id TASK_ID [--plan PLAN_ID] --input task-patch.yaml --format json`
|
|
235
235
|
- `npx -y agentera@next state plan set-status --id TASK_ID --status STATUS --format json`
|
|
236
236
|
- `npx -y agentera@next state plan set-plan-status [--plan PLAN_ID] --status complete --format json`
|
|
237
|
-
- `npx -y agentera@next state plan archive --format json`
|
|
237
|
+
- `npx -y agentera@next state plan archive [--plan ID] [--force] --format json`
|
|
238
|
+
- `npx -y agentera@next state plan replace --predecessor PREDECESSOR_ID --successor SUCCESSOR_ID --format json`
|
|
238
239
|
- `npx -y agentera@next state health append --input audit.yaml --format json`
|
|
239
240
|
- `npx -y agentera@next state todo create --input todo.yaml --format json`
|
|
240
241
|
- `npx -y agentera@next state todo update --id ID --input todo-patch.yaml --format json`
|
|
@@ -255,6 +256,14 @@ ordinals inside one atomic input document; the writer removes them before
|
|
|
255
256
|
publishing bare ten-letter plan and task envelope IDs. A legacy composite
|
|
256
257
|
`header.id` is migration-only and is never a public selector. Post-publication
|
|
257
258
|
task append/update payloads use only mutable task content and bare task IDs.
|
|
259
|
+
Without `--force`, an open plan blocks creation. With exactly one canonical open
|
|
260
|
+
predecessor, forced create archives that plan without changing task, evaluation,
|
|
261
|
+
or completion history, then records its bare ID in the successor's
|
|
262
|
+
writer-owned `previous_plan_archived` field. Forced archive preserves the same
|
|
263
|
+
history. Multiple implicit open candidates reject before effects. When they
|
|
264
|
+
block a reader or implicit writer, use the targeted replacement command only
|
|
265
|
+
after canonical evidence names both roles; list order never assigns predecessor
|
|
266
|
+
or successor roles.
|
|
258
267
|
|
|
259
268
|
Add `--dry-run` to preview any mutation without publishing it. Artifacts not
|
|
260
269
|
listed above are outside the typed writer contract and remain governed by their
|
|
@@ -260,11 +260,22 @@ PLAN:
|
|
|
260
260
|
7:
|
|
261
261
|
id: PL22
|
|
262
262
|
field: previous_plan_archived
|
|
263
|
-
type:
|
|
263
|
+
type: bare_ten_letter_id
|
|
264
|
+
required: false
|
|
265
|
+
description: >-
|
|
266
|
+
Writer-owned bare ID of the predecessor archived when this successor was
|
|
267
|
+
created. The predecessor's archived lifecycle and this successor reference
|
|
268
|
+
provide bidirectional lineage without changing task, evaluation, or
|
|
269
|
+
completion history.
|
|
270
|
+
8:
|
|
271
|
+
id: PL23
|
|
272
|
+
field: replacement_input_sha256
|
|
273
|
+
type: lowercase_sha256_hex
|
|
264
274
|
required: false
|
|
265
275
|
description: >-
|
|
266
|
-
|
|
267
|
-
|
|
276
|
+
Writer-owned immutable logical identity for a targeted replacement
|
|
277
|
+
successor. It excludes mutable task lifecycle and evaluation fields, so an
|
|
278
|
+
exact replacement input can replay after task execution changes.
|
|
268
279
|
|
|
269
280
|
UNKNOWN:
|
|
270
281
|
1:
|
|
@@ -545,9 +556,31 @@ LIFECYCLE_CONTRACT:
|
|
|
545
556
|
execution:
|
|
546
557
|
eligible: Only a plan at the current-plan path can supply executable work.
|
|
547
558
|
archived: An archived plan is historical and non-executable regardless of status.
|
|
559
|
+
lineage:
|
|
560
|
+
field: previous_plan_archived
|
|
561
|
+
representation: bare predecessor plan entity ID
|
|
562
|
+
publication: The writer assigns the field only while atomically archiving a predecessor and publishing its successor.
|
|
563
|
+
reverse: A predecessor's successor is derived from the unique canonical plan record that names its ID in previous_plan_archived; no second mutable lineage field exists.
|
|
564
|
+
replacement_input_identity:
|
|
565
|
+
field: replacement_input_sha256
|
|
566
|
+
representation: lowercase SHA-256 of the normalized logical replacement input
|
|
567
|
+
ownership: Writer-owned at targeted replacement publication and immutable afterward.
|
|
568
|
+
exclusions: [task.status, task.evaluation, task.superseded_by, task.superseded_reason]
|
|
548
569
|
forced_archive:
|
|
549
570
|
unfinished_status: archived
|
|
550
|
-
effect:
|
|
571
|
+
effect: Change the plan lifecycle to archived without claiming completion or changing tasks, evaluations, or completion history.
|
|
572
|
+
forced_create:
|
|
573
|
+
no_open_predecessor: Publish the successor normally.
|
|
574
|
+
one_open_predecessor: --force archives the predecessor unchanged and publishes the successor with previous_plan_archived set to the predecessor bare ID.
|
|
575
|
+
multiple_open_predecessors: Reject before effects because implicit creation cannot choose a predecessor.
|
|
576
|
+
targeted_replacement:
|
|
577
|
+
command: state plan replace --predecessor PREDECESSOR (--successor SUCCESSOR | --input PLAN.yaml)
|
|
578
|
+
existing_successor: Names both bare plan IDs, archives only the named predecessor, and assigns previous_plan_archived on the named open successor.
|
|
579
|
+
create_successor: Names the bare predecessor and supplies one complete plan document when no successor exists.
|
|
580
|
+
selection: Reject before effects if an unnamed open plan would remain, roles are invalid, or the requested successor conflicts with canonical lineage.
|
|
581
|
+
diagnostics: Competing-open readers and implicit writers expose bounded bare candidate IDs and the state plan replace --predecessor PREDECESSOR_ID --successor SUCCESSOR_ID recovery grammar. Canonical state never infers predecessor or successor roles from list order.
|
|
582
|
+
replay: The archived predecessor and unique derived successor identify an existing-successor replay; create retries compare the exact input identity retained on the successor rather than mutable task state.
|
|
583
|
+
atomicity: The complete predecessor, successor, and created-task target set is durable under the private entity recovery authority before any target is visible. Pending journals block plan reads until the exact retry finishes or restores the operation.
|
|
551
584
|
replacement_evidence_transition:
|
|
552
585
|
predicate: A replacement is complete and its latest persisted evaluation.last_verdict is pass.
|
|
553
586
|
supersession: Every superseded_by replacement must satisfy the predicate before a new supersession is published.
|
|
@@ -720,6 +753,7 @@ LIFECYCLE_CONTRACT:
|
|
|
720
753
|
documented_external_commitments:
|
|
721
754
|
- packages/cli/src/capabilities/*/instructions.ts
|
|
722
755
|
- packages/cli/README.md
|
|
756
|
+
- .opencode/skills/*/SKILL.md
|
|
723
757
|
- skills/agentera/SKILL.md
|
|
724
758
|
- skills/agentera/references/*
|
|
725
759
|
- references/**/*.md
|
|
@@ -8,6 +8,8 @@ Plans, tasks, progress cycles, health audits, and decisions use entity authority
|
|
|
8
8
|
|
|
9
9
|
Advance tasks with \`agentera state plan set-status --id ID\` and persist evaluator outcomes with \`agentera state plan record-evaluation --id ID\`. A superseded task is terminal, satisfies dependencies, and stays out of ready and blocked queues; record it only through \`agentera state plan supersede --id ID --by COMPLETED_ID --reason "..." --format json\`. Every replacement must be complete with latest persisted PASS before supersession.
|
|
10
10
|
|
|
11
|
+
When competing open plans block startup, do not infer predecessor or successor roles from list order. Use \`agentera state plan replace --predecessor PREDECESSOR_ID --successor SUCCESSOR_ID --format json\` only after canonical evidence establishes the complete recovery pair.
|
|
12
|
+
|
|
11
13
|
## The orchestration loop`)
|
|
12
14
|
.replaceAll("--number N", "--id ID")
|
|
13
15
|
.replaceAll("--task N", "--id ID")
|
|
@@ -28,5 +28,6 @@ Advice, clarification, tension, and handoff intent are transient and never write
|
|
|
28
28
|
## Workflow phases`)
|
|
29
29
|
.replaceAll("--number N", "--id ID")
|
|
30
30
|
.replaceAll("--task N", "--id ID")
|
|
31
|
+
.replace("Archive completed plan entities with `agentera state plan archive --format json`.", "Archive completed plan entities with `agentera state plan archive --format json`. With exactly one unfinished canonical predecessor, `agentera state plan create --force --input PATH --format json` archives it unchanged, publishes the successor, and records its bare ID in the writer-owned `previous_plan_archived` field. `agentera state plan archive --force` likewise preserves unfinished task, evaluation, and completion history; multiple implicit open candidates reject before effects. When competing open plans block selection, do not infer roles from list order: use `agentera state plan replace --predecessor PREDECESSOR_ID --successor SUCCESSOR_ID --format json` only after canonical evidence establishes the complete recovery pair.")
|
|
31
32
|
.replace("served via `planning_context.profile.path` — read directly when `status: loaded`; if missing or stale, proceed without persona grounding", "served through `agentera report profile-grounding --format json` — use only returned `content` when shared validity is `valid`; if absent or repair_needed, proceed without persona grounding and preserve exact recovery")
|
|
32
33
|
.replaceAll("planning_context.profile.path", "the shared profile validity summary");
|
|
@@ -8,4 +8,8 @@ export default `${instructions
|
|
|
8
8
|
|
|
9
9
|
### Profile validity
|
|
10
10
|
|
|
11
|
-
Render the shared profile validity exactly as \`valid\`, \`absent\`, or \`repair_needed\`. Freshness is separate and may be \`current\`, \`stale\`, or \`unknown\`; only valid stale input may carry stale guidance. Repair-needed input preserves its repair recovery and MUST NOT receive refresh advice. Never render profile path or content
|
|
11
|
+
Render the shared profile validity exactly as \`valid\`, \`absent\`, or \`repair_needed\`. Freshness is separate and may be \`current\`, \`stale\`, or \`unknown\`; only valid stale input may carry stale guidance. Repair-needed input preserves its repair recovery and MUST NOT receive refresh advice. Never render profile path or content.
|
|
12
|
+
|
|
13
|
+
### Plan selection
|
|
14
|
+
|
|
15
|
+
Render \`status_context.plan.id\` as the canonical selected plan identity. If competing open plans block startup, preserve the failure and its recovery verbatim. Do not infer predecessor or successor roles from a title or list order; only run \`agentera state plan replace --predecessor PREDECESSOR_ID --successor SUCCESSOR_ID --format json\` after canonical evidence establishes both roles.`;
|
|
@@ -1,7 +1,108 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import YAML, { isMap, isScalar, isSeq } from "yaml";
|
|
1
4
|
import { resolvePath } from "../../core/paths.js";
|
|
2
5
|
import { runCompaction } from "../../hooks/compaction/index.js";
|
|
3
6
|
import { emitStructured } from "../structured.js";
|
|
4
7
|
import { boundStructuredProjection } from "../../state/projectionPolicy.js";
|
|
8
|
+
const TODO_REFERENCE_DIAGNOSTIC_LIMIT = 20;
|
|
9
|
+
function compareText(left, right) {
|
|
10
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
11
|
+
}
|
|
12
|
+
function isDirectory(directory) {
|
|
13
|
+
try {
|
|
14
|
+
const stat = fs.lstatSync(directory);
|
|
15
|
+
return stat.isDirectory() && !stat.isSymbolicLink();
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function yamlFilesBelow(directory) {
|
|
22
|
+
if (!isDirectory(directory))
|
|
23
|
+
return [];
|
|
24
|
+
return fs.readdirSync(directory, { withFileTypes: true })
|
|
25
|
+
.sort((left, right) => compareText(left.name, right.name))
|
|
26
|
+
.flatMap((entry) => {
|
|
27
|
+
const candidate = path.join(directory, entry.name);
|
|
28
|
+
if (entry.isDirectory() && !entry.isSymbolicLink())
|
|
29
|
+
return yamlFilesBelow(candidate);
|
|
30
|
+
return entry.isFile() && path.extname(entry.name) === ".yaml" ? [candidate] : [];
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
function activeStateYamlFiles(project) {
|
|
34
|
+
const stateRoot = path.join(project, ".agentera");
|
|
35
|
+
if (!isDirectory(stateRoot))
|
|
36
|
+
return [];
|
|
37
|
+
const topLevel = fs.readdirSync(stateRoot, { withFileTypes: true })
|
|
38
|
+
.filter((entry) => entry.isFile() && path.extname(entry.name) === ".yaml")
|
|
39
|
+
.map((entry) => path.join(stateRoot, entry.name));
|
|
40
|
+
return [...topLevel, ...yamlFilesBelow(path.join(stateRoot, "entities"))]
|
|
41
|
+
.sort(compareText);
|
|
42
|
+
}
|
|
43
|
+
function stringScalarValues(node) {
|
|
44
|
+
if (isScalar(node))
|
|
45
|
+
return typeof node.value === "string" ? [node.value] : [];
|
|
46
|
+
if (isMap(node))
|
|
47
|
+
return node.items.flatMap((pair) => stringScalarValues(pair.value));
|
|
48
|
+
if (isSeq(node))
|
|
49
|
+
return node.items.flatMap(stringScalarValues);
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
function volatileTodoReferenceDiagnostics(project) {
|
|
53
|
+
const matches = [];
|
|
54
|
+
for (const file of activeStateYamlFiles(project)) {
|
|
55
|
+
const relativePath = path.relative(project, file).split(path.sep).join("/");
|
|
56
|
+
const contents = fs.readFileSync(file, "utf8");
|
|
57
|
+
let order = 0;
|
|
58
|
+
for (const document of YAML.parseAllDocuments(contents)) {
|
|
59
|
+
for (const value of stringScalarValues(document.contents)) {
|
|
60
|
+
for (const match of value.matchAll(/TODO line \d+|TODO\.md:\d+/g)) {
|
|
61
|
+
matches.push({ path: relativePath, reference: match[0], order: order++ });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
matches.sort((left, right) => compareText(left.path, right.path) || left.order - right.order);
|
|
67
|
+
return {
|
|
68
|
+
diagnostics: matches.slice(0, TODO_REFERENCE_DIAGNOSTIC_LIMIT).map(({ path, reference }) => ({ path, reference })),
|
|
69
|
+
omitted_count: Math.max(0, matches.length - TODO_REFERENCE_DIAGNOSTIC_LIMIT),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function volatileTodoReferenceGate(project, mode) {
|
|
73
|
+
const { diagnostics, omitted_count } = volatileTodoReferenceDiagnostics(project);
|
|
74
|
+
if (!diagnostics.length)
|
|
75
|
+
return null;
|
|
76
|
+
const count = diagnostics.length + omitted_count;
|
|
77
|
+
return {
|
|
78
|
+
status: {
|
|
79
|
+
artifact: "state_todo_references",
|
|
80
|
+
path: path.join(project, ".agentera"),
|
|
81
|
+
classification: "hygiene",
|
|
82
|
+
active_count: count,
|
|
83
|
+
archive_count: 0,
|
|
84
|
+
total_count: count,
|
|
85
|
+
over_limit_count: 0,
|
|
86
|
+
protected_overflow_count: 0,
|
|
87
|
+
exists: true,
|
|
88
|
+
reason: `${count} active state TODO reference${count === 1 ? "" : "s"} use volatile line syntax`,
|
|
89
|
+
},
|
|
90
|
+
mode,
|
|
91
|
+
action: "volatile_todo_reference",
|
|
92
|
+
changed: false,
|
|
93
|
+
result: null,
|
|
94
|
+
message: "replace TODO line references with stable TODO anchors",
|
|
95
|
+
diagnostics,
|
|
96
|
+
omitted_count,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function gatedCompactionOperations(project, mode) {
|
|
100
|
+
const hygiene = volatileTodoReferenceGate(project, mode);
|
|
101
|
+
if (mode === "fix" && hygiene)
|
|
102
|
+
return [hygiene];
|
|
103
|
+
const operations = runCompaction(project, mode);
|
|
104
|
+
return hygiene ? [...operations, hygiene] : operations;
|
|
105
|
+
}
|
|
5
106
|
function pyStr(value) {
|
|
6
107
|
if (value === null || value === undefined)
|
|
7
108
|
return "None";
|
|
@@ -33,6 +134,12 @@ function compactionOperationPayload(op) {
|
|
|
33
134
|
message: op.message,
|
|
34
135
|
reason: status.reason,
|
|
35
136
|
...(status.projection_recovery ? { recovery: status.projection_recovery } : {}),
|
|
137
|
+
...(op.diagnostics
|
|
138
|
+
? {
|
|
139
|
+
diagnostics: op.diagnostics.map(({ path, reference }) => ({ path, reference })),
|
|
140
|
+
omitted_count: op.omitted_count ?? 0,
|
|
141
|
+
}
|
|
142
|
+
: {}),
|
|
36
143
|
};
|
|
37
144
|
if (op.result !== null) {
|
|
38
145
|
const recovery = op.result.recovery;
|
|
@@ -57,11 +164,15 @@ function compactionGuidance(mode, operations) {
|
|
|
57
164
|
const protectedOps = operations.filter((op) => op.action === "protected_overflow");
|
|
58
165
|
const errors = operations.filter((op) => op.action === "error");
|
|
59
166
|
const refused = operations.filter((op) => op.action === "refused");
|
|
167
|
+
const volatileTodoReferences = operations.filter((op) => op.action === "volatile_todo_reference");
|
|
60
168
|
const checkCommand = "npx -y agentera check compact --mode check --format json";
|
|
61
169
|
const fixCommand = "npx -y agentera check compact --mode fix --format json";
|
|
62
170
|
if (errors.length > 0) {
|
|
63
171
|
return `Inspect the reported errors, repair invalid artifacts, then rerun \`${checkCommand}\`.`;
|
|
64
172
|
}
|
|
173
|
+
if (volatileTodoReferences.length > 0) {
|
|
174
|
+
return `Replace volatile TODO line references in active state with stable anchors, then rerun \`${checkCommand}\`.`;
|
|
175
|
+
}
|
|
65
176
|
if (refused.length > 0) {
|
|
66
177
|
return "Projection changes were safely refused until each full entry has a verified numbered archive; inspect recovery metadata and retry after recovery.";
|
|
67
178
|
}
|
|
@@ -97,8 +208,9 @@ function compactionSummary(mode, operations) {
|
|
|
97
208
|
const formatting = operations.filter((op) => op.action === "formatting" || op.action === "pending_formatting").length;
|
|
98
209
|
const protectedOverflow = operations.filter((op) => op.action === "protected_overflow").length;
|
|
99
210
|
const errors = operations.filter((op) => op.action === "error").length;
|
|
211
|
+
const volatileTodoReferences = operations.filter((op) => op.action === "volatile_todo_reference").length;
|
|
100
212
|
const changed = operations.filter((op) => op.changed).length;
|
|
101
|
-
const status = errors || (mode === "check" && (overLimit || formatting)) ? "fail" : "pass";
|
|
213
|
+
const status = errors || volatileTodoReferences || (mode === "check" && (overLimit || formatting)) ? "fail" : "pass";
|
|
102
214
|
return {
|
|
103
215
|
status,
|
|
104
216
|
mode,
|
|
@@ -116,6 +228,8 @@ function compactionSummary(mode, operations) {
|
|
|
116
228
|
function compactionExitCode(mode, operations) {
|
|
117
229
|
if (operations.some((op) => op.action === "error"))
|
|
118
230
|
return 2;
|
|
231
|
+
if (operations.some((op) => op.action === "volatile_todo_reference"))
|
|
232
|
+
return 1;
|
|
119
233
|
if (mode === "check" &&
|
|
120
234
|
operations.some((op) => op.action === "over_limit" || op.action === "formatting"))
|
|
121
235
|
return 1;
|
|
@@ -154,6 +268,14 @@ function emitCompactionPayload(payload, mode, format, out) {
|
|
|
154
268
|
`pending_summarization=${pyStr(item.pending_summarization_count)} | ` +
|
|
155
269
|
`protected_overflow=${item.protected_overflow_count} | ` +
|
|
156
270
|
`message=${item.message}\n`);
|
|
271
|
+
if (Array.isArray(item.diagnostics)) {
|
|
272
|
+
for (const diagnostic of item.diagnostics) {
|
|
273
|
+
if (!diagnostic || typeof diagnostic !== "object" || Array.isArray(diagnostic))
|
|
274
|
+
continue;
|
|
275
|
+
out(` diagnostic=${String(diagnostic.path)}: ${String(diagnostic.reference)}\n`);
|
|
276
|
+
}
|
|
277
|
+
out(` omitted_count=${pyStr(item.omitted_count)}\n`);
|
|
278
|
+
}
|
|
157
279
|
}
|
|
158
280
|
out(`guidance=${summary.guidance}\n`);
|
|
159
281
|
}
|
|
@@ -161,7 +283,7 @@ export function cmdCompact(args, io = {}) {
|
|
|
161
283
|
const out = io.out ?? ((t) => process.stdout.write(t));
|
|
162
284
|
const mode = args.mode ?? "check";
|
|
163
285
|
const project = resolvePath(args.project ?? process.cwd());
|
|
164
|
-
const operations =
|
|
286
|
+
const operations = gatedCompactionOperations(project, mode);
|
|
165
287
|
const payload = compactionPayload("check compact", project, mode, operations);
|
|
166
288
|
emitCompactionPayload(payload, mode, args.format ?? "text", out);
|
|
167
289
|
return compactionExitCode(mode, operations);
|
|
@@ -169,7 +291,7 @@ export function cmdCompact(args, io = {}) {
|
|
|
169
291
|
export function cmdGate(args, io = {}) {
|
|
170
292
|
const out = io.out ?? ((t) => process.stdout.write(t));
|
|
171
293
|
const project = resolvePath(args.project ?? process.cwd());
|
|
172
|
-
const operations =
|
|
294
|
+
const operations = gatedCompactionOperations(project, "check");
|
|
173
295
|
const payload = compactionPayload("check compact", project, "check", operations);
|
|
174
296
|
emitCompactionPayload(payload, "check", args.format ?? "text", out);
|
|
175
297
|
return compactionExitCode("check", operations);
|
|
@@ -87,11 +87,14 @@ export function renderDoctorStatus(status, retiredResources) {
|
|
|
87
87
|
lines.push(` Preview: ${signal.previewCommand}`);
|
|
88
88
|
if (signal.applyCommand)
|
|
89
89
|
lines.push(` Apply: ${signal.applyCommand}`);
|
|
90
|
+
if (signal.reconciliationState === "unsafe_inactive" && signal.recoveryCommand)
|
|
91
|
+
lines.push(` Recovery: ${signal.recoveryCommand}`);
|
|
90
92
|
}
|
|
91
93
|
}
|
|
92
94
|
}
|
|
93
95
|
const resources = Array.isArray(retiredResources?.resources) ? retiredResources.resources : [];
|
|
94
96
|
const todoReconciliation = status.signals?.some((signal) => signal.kind === "todo_reconciliation") ?? false;
|
|
97
|
+
const unsafeInactiveTodo = status.signals?.some((signal) => signal.kind === "todo_reconciliation" && signal.reconciliationState === "unsafe_inactive") ?? false;
|
|
95
98
|
if (resources.length > 0) {
|
|
96
99
|
lines.push("");
|
|
97
100
|
lines.push("Retired native resources:");
|
|
@@ -116,6 +119,10 @@ export function renderDoctorStatus(status, retiredResources) {
|
|
|
116
119
|
lines.push(" 3. Then retry Agentera once a retry command is available.");
|
|
117
120
|
}
|
|
118
121
|
}
|
|
122
|
+
else if (unsafeInactiveTodo) {
|
|
123
|
+
lines.push("");
|
|
124
|
+
lines.push("Next: complete the owner correction and replan before any TODO mutation.");
|
|
125
|
+
}
|
|
119
126
|
else if (todoReconciliation) {
|
|
120
127
|
lines.push("");
|
|
121
128
|
lines.push("Next: run the reported TODO reconciliation preview, review its bounded effect, then use its exact apply_command.");
|
|
@@ -170,7 +177,7 @@ export function cmdDoctor(args, io = {}) {
|
|
|
170
177
|
status.signals.push({
|
|
171
178
|
status: APP_MANUAL_REVIEW_NEEDED,
|
|
172
179
|
kind: "todo_reconciliation",
|
|
173
|
-
message: `action-required: TODO reconciliation is ${todoReconciliation.state === "inactive" ? "inactive" : todoReconciliation.state === "unsafe_active" ? "unsafe active" : "in an invalid lifecycle state"}`,
|
|
180
|
+
message: `action-required: TODO reconciliation is ${todoReconciliation.state === "inactive" ? "inactive" : todoReconciliation.state === "unsafe_inactive" ? "unsafe inactive" : todoReconciliation.state === "unsafe_active" ? "unsafe active" : "in an invalid lifecycle state"}`,
|
|
174
181
|
reconciliationState: todoReconciliation.state,
|
|
175
182
|
reconciliationCounts: todoReconciliation.counts,
|
|
176
183
|
reconciliationOmittedCount: todoReconciliation.omitted_count,
|