agentera 3.0.0-dev.75 → 3.0.0-dev.77
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 +23 -0
- package/bundle/.agentera-build-source.json +5 -5
- package/bundle/CHANGELOG.md +15 -1
- package/bundle/UPGRADE.md +24 -5
- package/bundle/references/adapters/package-publication.json +4 -4
- package/bundle/references/adapters/package-registry.yaml +153 -151
- package/bundle/references/adapters/product-v1-reset.yaml +101 -0
- package/bundle/references/artifacts/glossary-entry-contract.yaml +163 -9
- package/bundle/references/artifacts/state-storage-authority.yaml +0 -5
- package/bundle/references/cli/update-channels.yaml +1 -1
- package/bundle/references/cli/vocabulary-index.yaml +2 -2
- package/bundle/references/cli/vocabulary.md +2 -3
- package/bundle/references/meta/retained-reference-authority.yaml +5 -0
- package/bundle/skills/agentera/schemas/artifacts/decisions.yaml +1 -20
- package/bundle/skills/agentera/schemas/artifacts/docs.yaml +0 -31
- package/bundle/skills/agentera/schemas/artifacts/experiments.yaml +0 -25
- package/bundle/skills/agentera/schemas/artifacts/glossary.yaml +1 -1
- package/bundle/skills/agentera/schemas/artifacts/health.yaml +1 -36
- package/bundle/skills/agentera/schemas/artifacts/objective.yaml +0 -31
- package/bundle/skills/agentera/schemas/artifacts/plan.yaml +1 -35
- package/bundle/skills/agentera/schemas/artifacts/progress.yaml +1 -25
- package/bundle/skills/agentera/schemas/artifacts/vision.yaml +0 -19
- package/dist/.agentera-build-source.json +5 -5
- package/dist/analytics/personalGlossaryAdmission.js +1 -1
- package/dist/analytics/personalGlossaryCandidateProjection.js +96 -0
- package/dist/analytics/personalGlossaryCandidateProjectionModel.js +1 -0
- package/dist/analytics/personalGlossaryRefreshProjection.js +351 -0
- package/dist/capabilities/status/instructions.js +1 -1
- package/dist/cli/commands/doctor.js +1 -0
- package/dist/cli/commands/personalGlossaryCandidateReads.js +12 -1
- package/dist/cli/commands/prime/briefOrientation.js +1 -1
- package/dist/cli/commands/prime/collectOrientationState.js +0 -7
- package/dist/cli/commands/prime/orientationOutput.js +3 -9
- package/dist/cli/commands/report.js +98 -8
- package/dist/cli/commands/upgrade.js +30 -0
- package/dist/cli/dispatch/index.js +7 -0
- package/dist/cli/dispatch/lifecycle.js +20 -0
- package/dist/cli/help.js +10 -3
- package/dist/cli/orientation/attention.js +1 -4
- package/dist/cli/productV1Eol.js +55 -0
- package/dist/cli/startupCompletenessContract.js +0 -1
- package/dist/cli/stateQuery.js +1 -1
- package/dist/registries/activationTuples.js +11 -2
- package/dist/registries/glossaryCandidateProjectionAuthority.js +154 -6
- package/dist/registries/glossaryCandidateProjectionContract.js +4 -0
- package/dist/registries/glossaryEntryContract.js +3 -3
- package/dist/registries/packagePublication.js +2 -2
- package/dist/runtime/nativeResourceCleanup.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/dist/upgrade/legacyAgentCleanup.js +1 -1
- package/dist/upgrade/migrateArtifactsV2ToV3.js +1 -27
- package/dist/upgrade/nextMajorDoctor.js +0 -21
- package/dist/upgrade/productV1Reset.js +515 -0
- package/dist/upgrade/productV1ResetAuthority.js +139 -0
- package/dist/upgrade/projectIntegration.js +4 -6
- package/dist/upgrade/upgradeOrchestrator.js +4 -12
- package/dist/validate/{v1LegacyCruft.js → glossaryVariantGuard.js} +3 -38
- package/package.json +2 -2
- package/dist/cli/commands/prime/v1Migration.js +0 -38
|
@@ -12,13 +12,61 @@ function sameStrings(actual, expected) {
|
|
|
12
12
|
function nonEmpty(value) {
|
|
13
13
|
return typeof value === "string" && value.trim().length > 0;
|
|
14
14
|
}
|
|
15
|
+
const EXPLICIT_ABSTENTION_KEYS = [
|
|
16
|
+
"attributed_quotation",
|
|
17
|
+
"ambiguous_reference",
|
|
18
|
+
"conflicting_meaning",
|
|
19
|
+
"empty_meaning",
|
|
20
|
+
"empty_term",
|
|
21
|
+
"example_context",
|
|
22
|
+
"hypothetical_definition",
|
|
23
|
+
"malformed_span",
|
|
24
|
+
"meaning_bound_exceeded",
|
|
25
|
+
"negated_definition",
|
|
26
|
+
"indirect_question",
|
|
27
|
+
"future_definition",
|
|
28
|
+
"project_only_scope",
|
|
29
|
+
"question_definition",
|
|
30
|
+
"retracted_definition",
|
|
31
|
+
"sarcasm_marker",
|
|
32
|
+
"stale_anchor",
|
|
33
|
+
"structural_fragment",
|
|
34
|
+
"term_bound_exceeded",
|
|
35
|
+
"unsafe_syntax",
|
|
36
|
+
"uncertain_scope",
|
|
37
|
+
"unresolved_anchor",
|
|
38
|
+
"provenance_incomplete",
|
|
39
|
+
"user_authorship_required",
|
|
40
|
+
];
|
|
41
|
+
const RECURRING_ABSTENTION_KEYS = [
|
|
42
|
+
"agent_only_evidence",
|
|
43
|
+
"copied_content",
|
|
44
|
+
"provenance_missing",
|
|
45
|
+
"insufficient_independent_origins",
|
|
46
|
+
"insufficient_projects",
|
|
47
|
+
"insufficient_sessions",
|
|
48
|
+
"conversation_evidence_incomplete",
|
|
49
|
+
"conversation_evidence_bound_exceeded",
|
|
50
|
+
"project_only_scope",
|
|
51
|
+
"noise_term",
|
|
52
|
+
"too_many_qualifying_origins",
|
|
53
|
+
"user_authorship_required",
|
|
54
|
+
];
|
|
15
55
|
/** Validate the authority-owned bounds for the internal candidate projection. */
|
|
16
56
|
export function validatePersonalCandidateProjectionAuthority(authority) {
|
|
17
57
|
const mining = mapping(authority.personal_mining_authority);
|
|
18
58
|
const projection = mapping(mining?.candidate_projection);
|
|
59
|
+
const refreshProduction = mapping(projection?.refresh_production);
|
|
60
|
+
const refreshProjection = mapping(refreshProduction?.projection);
|
|
61
|
+
const refreshConcurrency = mapping(refreshProduction?.concurrency);
|
|
62
|
+
const refreshContender = mapping(refreshConcurrency?.contender);
|
|
63
|
+
const partialFailure = mapping(refreshProduction?.partial_failure);
|
|
64
|
+
const safeReplacement = mapping(refreshProduction?.safe_replacement);
|
|
19
65
|
const selection = mapping(projection?.selection);
|
|
20
66
|
const sourceFamilies = mapping(selection?.source_families);
|
|
21
67
|
const projectIdentity = mapping(selection?.project_identity);
|
|
68
|
+
const miningSummary = mapping(projection?.mining_summary);
|
|
69
|
+
const summaryReconciliation = mapping(miningSummary?.reconciliation);
|
|
22
70
|
const excerpts = mapping(projection?.excerpts);
|
|
23
71
|
const excerptCompatibility = mapping(excerpts?.compatibility);
|
|
24
72
|
const retention = mapping(projection?.retention);
|
|
@@ -35,6 +83,109 @@ export function validatePersonalCandidateProjectionAuthority(authority) {
|
|
|
35
83
|
const privacy = mapping(mining?.privacy);
|
|
36
84
|
const contentExclusion = mapping(privacy?.content_exclusion);
|
|
37
85
|
const storageFilesystem = mapping(mapping(privacy?.storage)?.filesystem);
|
|
86
|
+
const malformedProjectionReplacementException = mapping(storageFilesystem?.malformed_candidate_projection_replacement_exception);
|
|
87
|
+
const errors = [];
|
|
88
|
+
if (refreshProduction?.status !== "active" ||
|
|
89
|
+
refreshProduction?.runtime !==
|
|
90
|
+
"packages/cli/src/analytics/personalGlossaryRefreshProjection.ts#produceCurrentPersonalGlossaryProjection" ||
|
|
91
|
+
refreshProduction?.trigger !== "explicit_consented_evidence_refresh_success" ||
|
|
92
|
+
refreshProjection?.count !== "exactly_one" ||
|
|
93
|
+
refreshProjection?.generation_binding !== "exact_published_evidence_generation" ||
|
|
94
|
+
refreshProjection?.policy_binding !== "exact_personal_mining_policy_version" ||
|
|
95
|
+
!nonEmpty(refreshProduction?.rule)) {
|
|
96
|
+
errors.push("personal_mining_authority refresh production must require one projection bound to the exact evidence generation and mining policy");
|
|
97
|
+
}
|
|
98
|
+
if (refreshConcurrency?.serialization !== "profile_local_exclusive_refresh_commit" ||
|
|
99
|
+
refreshConcurrency?.lock_lifetime !==
|
|
100
|
+
"before_evidence_publication_through_projection_binding_verification" ||
|
|
101
|
+
refreshContender?.refresh_outcome !== "failure" ||
|
|
102
|
+
refreshContender?.process_exit !== "nonzero" ||
|
|
103
|
+
refreshContender?.local_history_read !== false ||
|
|
104
|
+
refreshContender?.tier_write !== false ||
|
|
105
|
+
refreshContender?.projection_status !== "failed" ||
|
|
106
|
+
refreshContender?.projection_write !== false ||
|
|
107
|
+
refreshContender?.evidence_status !== "current_generation_remains_readable" ||
|
|
108
|
+
refreshContender?.recovery !== "npx -y agentera@next report refresh --consent local-history") {
|
|
109
|
+
errors.push("personal_mining_authority refresh production must serialize evidence and projection commit and fail contenders without writes");
|
|
110
|
+
}
|
|
111
|
+
if (partialFailure?.refresh_outcome !== "failure" ||
|
|
112
|
+
partialFailure?.process_exit !== "nonzero" ||
|
|
113
|
+
partialFailure?.evidence_status !== "published" ||
|
|
114
|
+
partialFailure?.projection_status !== "failed" ||
|
|
115
|
+
partialFailure?.tier_write_reporting !== "evidence_published_projection_not_written" ||
|
|
116
|
+
partialFailure?.current_generation !== "published_evidence_remains_current") {
|
|
117
|
+
errors.push("personal_mining_authority refresh production must report evidence and projection outcomes separately and preserve published evidence on projection failure");
|
|
118
|
+
}
|
|
119
|
+
if (safeReplacement?.authorization !== "explicit_consented_refresh_only" ||
|
|
120
|
+
safeReplacement?.target !== "exact_configured_candidate_projection_file" ||
|
|
121
|
+
safeReplacement?.precondition !== "existing_owned_regular_file_is_malformed" ||
|
|
122
|
+
safeReplacement?.operation !== "atomic_regular_file_replacement" ||
|
|
123
|
+
!sameStrings(safeReplacement?.reject, [
|
|
124
|
+
"symlink",
|
|
125
|
+
"non_regular_file",
|
|
126
|
+
"configured_path_mismatch",
|
|
127
|
+
"path_escape",
|
|
128
|
+
]) ||
|
|
129
|
+
safeReplacement?.filesystem_exception !==
|
|
130
|
+
"personal_mining_authority.privacy.storage.filesystem.malformed_candidate_projection_replacement_exception" ||
|
|
131
|
+
!sameStrings(safeReplacement?.replaceable_state, ["candidate_projection"]) ||
|
|
132
|
+
!sameStrings(safeReplacement?.preserved_state, [
|
|
133
|
+
"current_evidence_generation",
|
|
134
|
+
"review_records",
|
|
135
|
+
"personal_profile",
|
|
136
|
+
"project_state",
|
|
137
|
+
"project_glossary",
|
|
138
|
+
])) {
|
|
139
|
+
errors.push("personal_mining_authority refresh replacement must target only the exact owned regular projection and preserve evidence, review, profile, and project state");
|
|
140
|
+
}
|
|
141
|
+
if (malformedProjectionReplacementException?.scope !==
|
|
142
|
+
"malformed_candidate_projection_replacement_only" ||
|
|
143
|
+
malformedProjectionReplacementException?.authorization !== "explicit_consented_refresh_only" ||
|
|
144
|
+
malformedProjectionReplacementException?.precondition !==
|
|
145
|
+
"existing_owned_regular_file_is_malformed" ||
|
|
146
|
+
malformedProjectionReplacementException?.target !==
|
|
147
|
+
"exact_configured_candidate_projection_file" ||
|
|
148
|
+
malformedProjectionReplacementException?.inspection !==
|
|
149
|
+
"lstat_exact_target_without_following_symlinks" ||
|
|
150
|
+
malformedProjectionReplacementException?.required_type !== "regular_file" ||
|
|
151
|
+
!sameStrings(malformedProjectionReplacementException?.reject, [
|
|
152
|
+
"symlink",
|
|
153
|
+
"non_regular_file",
|
|
154
|
+
"configured_path_mismatch",
|
|
155
|
+
"path_escape",
|
|
156
|
+
]) ||
|
|
157
|
+
malformedProjectionReplacementException?.operation !== "atomic_regular_file_replacement" ||
|
|
158
|
+
malformedProjectionReplacementException?.outside_scope !== "configured_path_use_as_provided") {
|
|
159
|
+
errors.push("personal_mining_authority filesystem exception must confine malformed projection replacement to the exact configured regular file with no-follow inspection");
|
|
160
|
+
}
|
|
161
|
+
if (miningSummary?.schema_version !== "agentera.personalGlossaryMiningSummary.v1" ||
|
|
162
|
+
miningSummary?.location !== "report.mining_summary" ||
|
|
163
|
+
!sameStrings(miningSummary?.family_fields, [
|
|
164
|
+
"candidate_count",
|
|
165
|
+
"abstention_count",
|
|
166
|
+
"abstentions_by_reason",
|
|
167
|
+
]) ||
|
|
168
|
+
!sameStrings(miningSummary?.explicit_abstention_keys, EXPLICIT_ABSTENTION_KEYS) ||
|
|
169
|
+
!sameStrings(miningSummary?.recurring_abstention_keys, RECURRING_ABSTENTION_KEYS) ||
|
|
170
|
+
summaryReconciliation?.fixed_keys !== "all_keys_present_as_nonnegative_integers" ||
|
|
171
|
+
summaryReconciliation?.family_abstentions !== "abstention_count_equals_sum_of_reason_counts" ||
|
|
172
|
+
summaryReconciliation?.total_candidates !==
|
|
173
|
+
"total_equals_explicit_plus_recurring_candidate_counts" ||
|
|
174
|
+
summaryReconciliation?.total_abstentions !==
|
|
175
|
+
"total_equals_explicit_plus_recurring_abstention_counts" ||
|
|
176
|
+
summaryReconciliation?.projection_input !== "report_input_count_equals_total_candidate_count" ||
|
|
177
|
+
!sameStrings(miningSummary?.forbidden_content, [
|
|
178
|
+
"terms",
|
|
179
|
+
"source_ids",
|
|
180
|
+
"evidence_anchors",
|
|
181
|
+
"project_ids",
|
|
182
|
+
"project_keys",
|
|
183
|
+
"paths",
|
|
184
|
+
"excerpts",
|
|
185
|
+
]) ||
|
|
186
|
+
!nonEmpty(miningSummary?.rule)) {
|
|
187
|
+
errors.push("personal_mining_authority candidate projection must retain reconciled fixed-key aggregate mining counts without sensitive identity or content");
|
|
188
|
+
}
|
|
38
189
|
if (projection?.status !== "active" ||
|
|
39
190
|
projection?.runtime !==
|
|
40
191
|
"packages/cli/src/analytics/personalGlossaryCandidateProjection.ts#projectPersonalGlossaryCandidates" ||
|
|
@@ -106,8 +257,7 @@ export function validatePersonalCandidateProjectionAuthority(authority) {
|
|
|
106
257
|
currentGeneration?.unavailable_behavior !== "current_generation_unavailable" ||
|
|
107
258
|
currentGeneration?.stale_projection_behavior !== "projection_stale" ||
|
|
108
259
|
!nonEmpty(retrieval?.rule) ||
|
|
109
|
-
retrievalCommand?.canonical !==
|
|
110
|
-
"npx -y agentera@next report personal-glossary-candidates" ||
|
|
260
|
+
retrievalCommand?.canonical !== "npx -y agentera@next report personal-glossary-candidates" ||
|
|
111
261
|
retrievalCommand?.namespace !== "report" ||
|
|
112
262
|
retrievalCommand?.format !== "json" ||
|
|
113
263
|
retrievalCommand?.project_checkout !== "not_required" ||
|
|
@@ -189,9 +339,7 @@ export function validatePersonalCandidateProjectionAuthority(authority) {
|
|
|
189
339
|
storageFilesystem?.symlink_confinement !== "not_claimed" ||
|
|
190
340
|
!nonEmpty(storageFilesystem?.rule) ||
|
|
191
341
|
!nonEmpty(persistence?.rule)) {
|
|
192
|
-
|
|
193
|
-
"personal_mining_authority candidate projection must bound deterministic allocation, private retrieval, content exclusion, safe excerpts, host-filesystem storage, retention, and user-local replay",
|
|
194
|
-
];
|
|
342
|
+
errors.push("personal_mining_authority candidate projection must bound deterministic allocation, private retrieval, content exclusion, safe excerpts, host-filesystem storage, retention, and user-local replay");
|
|
195
343
|
}
|
|
196
|
-
return
|
|
344
|
+
return errors;
|
|
197
345
|
}
|
|
@@ -23,6 +23,7 @@ export function personalGlossaryCandidateProjectionContract(pathname = glossaryE
|
|
|
23
23
|
const retention = mapping(privacy?.retention);
|
|
24
24
|
const projectIdentity = mapping(selection?.project_identity);
|
|
25
25
|
const persistence = mapping(projection?.persistence);
|
|
26
|
+
const miningSummary = mapping(projection?.mining_summary);
|
|
26
27
|
const families = mapping(selection?.source_families);
|
|
27
28
|
const sourceFamilies = Object.fromEntries(Object.entries(families ?? {}).flatMap(([family, values]) => Array.isArray(values) && values.every((value) => typeof value === "string")
|
|
28
29
|
? [[family, [...values]]]
|
|
@@ -53,6 +54,9 @@ export function personalGlossaryCandidateProjectionContract(pathname = glossaryE
|
|
|
53
54
|
? contentExclusion.candidate_reason
|
|
54
55
|
: "",
|
|
55
56
|
excerptSensitiveContentAction: typeof contentExclusion?.excerpt_action === "string" ? contentExclusion.excerpt_action : "",
|
|
57
|
+
miningSummarySchemaVersion: typeof miningSummary?.schema_version === "string" ? miningSummary.schema_version : "",
|
|
58
|
+
explicitAbstentionKeys: strings(miningSummary?.explicit_abstention_keys),
|
|
59
|
+
recurringAbstentionKeys: strings(miningSummary?.recurring_abstention_keys),
|
|
56
60
|
candidateReadCommand: projectGlossaryDevelopmentValue(retrievalCommand?.canonical, "candidate_retrieval.command"),
|
|
57
61
|
candidateReadSchemaVersion: typeof list?.schema_version === "string" ? list.schema_version : "",
|
|
58
62
|
candidateReadDefaultLimit: typeof list?.default_limit === "number" ? list.default_limit : 0,
|
|
@@ -388,8 +388,8 @@ export function validateGlossaryEntryContract(pathname = glossaryEntryAuthorityP
|
|
|
388
388
|
errors.push("project glossary digest and build publication must remain active under the declared consumer boundary");
|
|
389
389
|
}
|
|
390
390
|
if (confirmedVariantGuard?.owner !==
|
|
391
|
-
"packages/cli/src/validate/
|
|
392
|
-
confirmedVariantGuard?.validation_surface !== "packages/cli/test/cli/
|
|
391
|
+
"packages/cli/src/validate/glossaryVariantGuard.ts#scanConfirmedVariantViolations" ||
|
|
392
|
+
confirmedVariantGuard?.validation_surface !== "packages/cli/test/cli/glossaryVariantGuard.test.ts" ||
|
|
393
393
|
confirmedVariantGuard?.loader !==
|
|
394
394
|
"packages/cli/src/state/write/glossaryPublication.ts#loadProjectGlossaryDocument" ||
|
|
395
395
|
confirmedVariantGuard?.matching !== "exact_case_sensitive_boundary_aware_literal" ||
|
|
@@ -424,7 +424,7 @@ export function validateGlossaryEntryContract(pathname = glossaryEntryAuthorityP
|
|
|
424
424
|
"vendor",
|
|
425
425
|
]) ||
|
|
426
426
|
!nonEmpty(confirmedVariantGuard?.exclusion_rule)) {
|
|
427
|
-
errors.push("confirmed variant guard must reuse validated project glossary pairs and bounded
|
|
427
|
+
errors.push("confirmed variant guard must reuse validated project glossary pairs and bounded exclusions");
|
|
428
428
|
}
|
|
429
429
|
if (proposalValidation?.owner !== "audit" ||
|
|
430
430
|
proposalValidation?.shared_runtime !==
|
|
@@ -52,12 +52,12 @@ export const ACTIVATION_CENSUS_AUTHORITY = deepFreeze({
|
|
|
52
52
|
cli: { count: 27, sha256: "986a637f5c11c57ab7a897a01bd291d0811846e5339c3831c715932cf9659ad4" },
|
|
53
53
|
capability: { count: 12, sha256: "007e1157a892fec54182b1803aeaa442cf8e2e332e6a055c3bd020e2b0731867" },
|
|
54
54
|
runtime: { count: 81, sha256: "03fe600a3a27f05daf0d1b59c16b00bedc354a13c70ea60be0b457175fdf743c" },
|
|
55
|
-
reference: { count:
|
|
55
|
+
reference: { count: 26, sha256: "aa1ad513d89b66c9133a8572cef2dce760e76d6ee39d2ffa2d4e312578dcc847" },
|
|
56
56
|
state: { count: 38, sha256: "7ab0dd6ef1b1b1ce66bd9ea94d1de3d542528233e928a44102e289bf12416681" },
|
|
57
57
|
package: { count: 67, sha256: "d73cd86fed39057cf9ae73e462a59e842372392b1e88846f2b220378a2ff75cc" },
|
|
58
58
|
bootstrap: { count: 34, sha256: "71c2038744e2518a6adb722acbb5f9352bddfb5d1c92eeb0e347297ef2ca2f1e" },
|
|
59
59
|
},
|
|
60
|
-
total: { count:
|
|
60
|
+
total: { count: 285, sha256: "042700a26b34afba57b60eaf4a4529d9e02ce39d8574e6883dfb892eebc64fd5" },
|
|
61
61
|
});
|
|
62
62
|
/** Each dimension names the production contract it observes independently. */
|
|
63
63
|
export const ACTIVATION_EVIDENCE_SOURCES = deepFreeze({
|
|
@@ -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
|
+
}
|
|
@@ -204,7 +204,7 @@ export function scanLegacyCapabilityAgentPaths(agentsDir) {
|
|
|
204
204
|
}
|
|
205
205
|
return hits.sort();
|
|
206
206
|
}
|
|
207
|
-
/** Scan a tree for reintroduced Swedish-verb agents
|
|
207
|
+
/** Scan a tree for reintroduced Swedish-verb agents. */
|
|
208
208
|
export function scanLegacySwedishVerbAgentViolations(root) {
|
|
209
209
|
const resolved = resolvePath(root);
|
|
210
210
|
const violations = [];
|
|
@@ -19,14 +19,6 @@ import { applyInstalledHooksRetirementItems, planInstalledHooksRetirementItems,
|
|
|
19
19
|
* v2→v3 migration phases: artifacts (noop for YAML), runtime rewire, cleanup.
|
|
20
20
|
*/
|
|
21
21
|
export const MIGRATION_STATUSES = ["pending", "applied", "noop", "blocked", "failed"];
|
|
22
|
-
export const V1_ARTIFACT_PAIRS = [
|
|
23
|
-
[".agentera/PROGRESS.md", ".agentera/progress.yaml"],
|
|
24
|
-
[".agentera/PLAN.md", ".agentera/plan.yaml"],
|
|
25
|
-
[".agentera/DECISIONS.md", ".agentera/decisions.yaml"],
|
|
26
|
-
[".agentera/HEALTH.md", ".agentera/health.yaml"],
|
|
27
|
-
[".agentera/DOCS.md", ".agentera/docs.yaml"],
|
|
28
|
-
["VISION.md", ".agentera/vision.yaml"],
|
|
29
|
-
];
|
|
30
22
|
const PLAN_ARCHIVE_FILE = /^PLAN-.+\.ya?ml$/i;
|
|
31
23
|
const PLAN_LIFECYCLE_ACTION = "normalize-plan-lifecycle";
|
|
32
24
|
function isMapping(value) {
|
|
@@ -178,16 +170,6 @@ export function summarizePhase(name, items, message = "") {
|
|
|
178
170
|
}
|
|
179
171
|
return { name, status, summary, items, message };
|
|
180
172
|
}
|
|
181
|
-
export function detectV1ArtifactPairs(project) {
|
|
182
|
-
const root = resolvePath(project);
|
|
183
|
-
const found = [];
|
|
184
|
-
for (const [md, yaml] of V1_ARTIFACT_PAIRS) {
|
|
185
|
-
if (isFile(path.join(root, md)) && !isFile(path.join(root, yaml))) {
|
|
186
|
-
found.push(md);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
return found;
|
|
190
|
-
}
|
|
191
173
|
function listV2YamlArtifacts(project) {
|
|
192
174
|
const root = resolvePath(project);
|
|
193
175
|
const agenteraDir = path.join(root, ".agentera");
|
|
@@ -218,21 +200,13 @@ export function planArtifactsPhase(project) {
|
|
|
218
200
|
},
|
|
219
201
|
]);
|
|
220
202
|
}
|
|
221
|
-
const v1Pairs = detectV1ArtifactPairs(root);
|
|
222
|
-
const v1Items = v1Pairs.map((source) => ({
|
|
223
|
-
status: "blocked",
|
|
224
|
-
action: "manual-v1-handoff",
|
|
225
|
-
source,
|
|
226
|
-
message: "v1 Markdown state is unsupported; convert it with a v2 CLI before running the v2-to-v3 upgrade",
|
|
227
|
-
}));
|
|
228
203
|
const yamlArtifacts = listV2YamlArtifacts(root);
|
|
229
204
|
const lifecycleItems = lifecyclePlanArtifacts(root);
|
|
230
|
-
if (yamlArtifacts.length === 0 &&
|
|
205
|
+
if (yamlArtifacts.length === 0 && lifecycleItems.length === 0) {
|
|
231
206
|
return summarizePhase("artifacts", [], "no project artifacts found");
|
|
232
207
|
}
|
|
233
208
|
const lifecycleSources = new Set(lifecycleItems.flatMap((item) => item.source ? [item.source] : []));
|
|
234
209
|
const items = [
|
|
235
|
-
...v1Items,
|
|
236
210
|
...yamlArtifacts.filter((source) => !lifecycleSources.has(source)).map((source) => ({
|
|
237
211
|
status: "noop",
|
|
238
212
|
action: "preserve",
|
|
@@ -7,19 +7,6 @@ import { resolveRunningVersion } from "./versionResolution.js";
|
|
|
7
7
|
*/
|
|
8
8
|
export const NEXT_MAJOR_SECTION_HEADER = "Next major";
|
|
9
9
|
export const NEXT_MAJOR_LINE_CAP = 6;
|
|
10
|
-
/** v1 installs predate the channel authority; hardcoded stable-line successor mapping. */
|
|
11
|
-
export const V1_NEXT_MAJOR_FALLBACK = {
|
|
12
|
-
concept: "forward_successor_line",
|
|
13
|
-
currentVersion: "1.x",
|
|
14
|
-
currentChannel: "stable",
|
|
15
|
-
channel: "stable",
|
|
16
|
-
version: "2.x",
|
|
17
|
-
announced: true,
|
|
18
|
-
npmOnlyAdvisory: "",
|
|
19
|
-
guideUrl: "https://github.com/jgabor/agentera/blob/main/UPGRADE.md#recommended-upgrade-v1--v2-stable-channel",
|
|
20
|
-
previewCommand: "npx -y agentera@latest upgrade --dry-run",
|
|
21
|
-
irreversibleAdvisory: "Forward migration upgrades artifact format from Markdown to YAML; review the preview before applying.",
|
|
22
|
-
};
|
|
23
10
|
function parseNextMajorBlock(raw) {
|
|
24
11
|
if (raw === null || raw === undefined) {
|
|
25
12
|
return null;
|
|
@@ -120,14 +107,6 @@ export function resolveNextMajorDoctorLines(ctx) {
|
|
|
120
107
|
const displayChannel = install?.kind === "v2_managed_app_home" ? "stable" : currentChannel;
|
|
121
108
|
const runningDistributionMajor = ctx.runningDistributionMajor ??
|
|
122
109
|
(install?.kind === "v2_managed_app_home" ? 2 : cliDistributionMajor(sourceRoot));
|
|
123
|
-
if (runningDistributionMajor === 1) {
|
|
124
|
-
const block = V1_NEXT_MAJOR_FALLBACK;
|
|
125
|
-
return formatNextMajorDoctorLines({
|
|
126
|
-
currentVersion: block.currentVersion,
|
|
127
|
-
currentChannel: block.currentChannel,
|
|
128
|
-
block,
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
110
|
const authorityBlock = loadChannelNextMajor(sourceRoot, successorChannel);
|
|
132
111
|
if (!authorityBlock || !authorityBlock.announced) {
|
|
133
112
|
return null;
|