agentera 0.0.0 → 3.0.0-dev.1
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 +6 -45
- package/bundle/.agentera-npx-bundle.json +4 -0
- package/bundle/references/adapters/cursor.md +213 -0
- package/bundle/references/adapters/opencode.md +530 -0
- package/bundle/references/adapters/package-manifest-interface-model.yaml +337 -0
- package/bundle/references/adapters/package-registry.yaml +247 -0
- package/bundle/references/adapters/package-surface-characterization.md +48 -0
- package/bundle/references/adapters/runtime-adapter-characterization.md +79 -0
- package/bundle/references/adapters/runtime-adapter-interface-model.yaml +200 -0
- package/bundle/references/adapters/runtime-adapter-registry.yaml +548 -0
- package/bundle/references/adapters/runtime-feature-parity.md +189 -0
- package/bundle/references/analysis/benchmark.md +267 -0
- package/bundle/references/analysis/startup-measurement-contract.yaml +424 -0
- package/bundle/references/artifacts/artifact-registry-interface-model.yaml +288 -0
- package/bundle/references/cli/agent-ready-state-contract.yaml +950 -0
- package/bundle/references/cli/app-lifecycle-vocabulary.yaml +241 -0
- package/bundle/references/cli/audience-namespace-cli-migration.yaml +355 -0
- package/bundle/references/cli/bundle-skill-vocabulary.yaml +278 -0
- package/bundle/references/cli/capability-instruction-contract.yaml +123 -0
- package/bundle/references/cli/capability-tool-classification.yaml +53 -0
- package/bundle/references/cli/routing-execution-vocabulary.yaml +281 -0
- package/bundle/references/cli/update-channels.yaml +147 -0
- package/bundle/references/cli/vocabulary-index.yaml +160 -0
- package/bundle/references/cli/vocabulary.md +566 -0
- package/bundle/references/meta/documentation-inventory.md +43 -0
- package/bundle/references/v1-section-mapping.md +47 -0
- package/bundle/registry.json +39 -0
- package/bundle/skills/agentera/.claude-plugin/plugin.json +27 -0
- package/bundle/skills/agentera/SKILL.md +470 -0
- package/bundle/skills/agentera/agents/dokumentera.toml +6 -0
- package/bundle/skills/agentera/agents/hej.toml +6 -0
- package/bundle/skills/agentera/agents/inspektera.toml +6 -0
- package/bundle/skills/agentera/agents/inspirera.toml +6 -0
- package/bundle/skills/agentera/agents/optimera.toml +6 -0
- package/bundle/skills/agentera/agents/orkestrera.toml +6 -0
- package/bundle/skills/agentera/agents/planera.toml +6 -0
- package/bundle/skills/agentera/agents/profilera.toml +6 -0
- package/bundle/skills/agentera/agents/realisera.toml +6 -0
- package/bundle/skills/agentera/agents/resonera.toml +6 -0
- package/bundle/skills/agentera/agents/visionera.toml +6 -0
- package/bundle/skills/agentera/agents/visualisera.toml +6 -0
- package/bundle/skills/agentera/capabilities/dokumentera/instructions.md +428 -0
- package/bundle/skills/agentera/capabilities/dokumentera/schemas/artifacts.yaml +73 -0
- package/bundle/skills/agentera/capabilities/dokumentera/schemas/exit.yaml +35 -0
- package/bundle/skills/agentera/capabilities/dokumentera/schemas/triggers.yaml +35 -0
- package/bundle/skills/agentera/capabilities/dokumentera/schemas/validation.yaml +139 -0
- package/bundle/skills/agentera/capabilities/hej/instructions.md +331 -0
- package/bundle/skills/agentera/capabilities/hej/schemas/artifacts.yaml +69 -0
- package/bundle/skills/agentera/capabilities/hej/schemas/exit.yaml +32 -0
- package/bundle/skills/agentera/capabilities/hej/schemas/triggers.yaml +58 -0
- package/bundle/skills/agentera/capabilities/hej/schemas/validation.yaml +55 -0
- package/bundle/skills/agentera/capabilities/inspektera/instructions.md +514 -0
- package/bundle/skills/agentera/capabilities/inspektera/schemas/artifacts.yaml +76 -0
- package/bundle/skills/agentera/capabilities/inspektera/schemas/exit.yaml +36 -0
- package/bundle/skills/agentera/capabilities/inspektera/schemas/triggers.yaml +38 -0
- package/bundle/skills/agentera/capabilities/inspektera/schemas/validation.yaml +113 -0
- package/bundle/skills/agentera/capabilities/inspirera/instructions.md +280 -0
- package/bundle/skills/agentera/capabilities/inspirera/schemas/artifacts.yaml +24 -0
- package/bundle/skills/agentera/capabilities/inspirera/schemas/exit.yaml +33 -0
- package/bundle/skills/agentera/capabilities/inspirera/schemas/triggers.yaml +34 -0
- package/bundle/skills/agentera/capabilities/inspirera/schemas/validation.yaml +58 -0
- package/bundle/skills/agentera/capabilities/optimera/instructions.md +437 -0
- package/bundle/skills/agentera/capabilities/optimera/schemas/artifacts.yaml +69 -0
- package/bundle/skills/agentera/capabilities/optimera/schemas/exit.yaml +35 -0
- package/bundle/skills/agentera/capabilities/optimera/schemas/triggers.yaml +39 -0
- package/bundle/skills/agentera/capabilities/optimera/schemas/validation.yaml +91 -0
- package/bundle/skills/agentera/capabilities/orkestrera/instructions.md +433 -0
- package/bundle/skills/agentera/capabilities/orkestrera/schemas/artifacts.yaml +64 -0
- package/bundle/skills/agentera/capabilities/orkestrera/schemas/exit.yaml +34 -0
- package/bundle/skills/agentera/capabilities/orkestrera/schemas/triggers.yaml +42 -0
- package/bundle/skills/agentera/capabilities/orkestrera/schemas/validation.yaml +107 -0
- package/bundle/skills/agentera/capabilities/planera/instructions.md +368 -0
- package/bundle/skills/agentera/capabilities/planera/schemas/artifacts.yaml +62 -0
- package/bundle/skills/agentera/capabilities/planera/schemas/exit.yaml +33 -0
- package/bundle/skills/agentera/capabilities/planera/schemas/triggers.yaml +34 -0
- package/bundle/skills/agentera/capabilities/planera/schemas/validation.yaml +61 -0
- package/bundle/skills/agentera/capabilities/profilera/instructions.md +419 -0
- package/bundle/skills/agentera/capabilities/profilera/schemas/artifacts.yaml +18 -0
- package/bundle/skills/agentera/capabilities/profilera/schemas/exit.yaml +34 -0
- package/bundle/skills/agentera/capabilities/profilera/schemas/triggers.yaml +45 -0
- package/bundle/skills/agentera/capabilities/profilera/schemas/validation.yaml +57 -0
- package/bundle/skills/agentera/capabilities/realisera/instructions.md +403 -0
- package/bundle/skills/agentera/capabilities/realisera/schemas/artifacts.yaml +80 -0
- package/bundle/skills/agentera/capabilities/realisera/schemas/exit.yaml +35 -0
- package/bundle/skills/agentera/capabilities/realisera/schemas/triggers.yaml +39 -0
- package/bundle/skills/agentera/capabilities/realisera/schemas/validation.yaml +110 -0
- package/bundle/skills/agentera/capabilities/resonera/instructions.md +329 -0
- package/bundle/skills/agentera/capabilities/resonera/schemas/artifacts.yaml +47 -0
- package/bundle/skills/agentera/capabilities/resonera/schemas/exit.yaml +35 -0
- package/bundle/skills/agentera/capabilities/resonera/schemas/triggers.yaml +46 -0
- package/bundle/skills/agentera/capabilities/resonera/schemas/validation.yaml +77 -0
- package/bundle/skills/agentera/capabilities/visionera/instructions.md +309 -0
- package/bundle/skills/agentera/capabilities/visionera/schemas/artifacts.yaml +57 -0
- package/bundle/skills/agentera/capabilities/visionera/schemas/exit.yaml +35 -0
- package/bundle/skills/agentera/capabilities/visionera/schemas/triggers.yaml +41 -0
- package/bundle/skills/agentera/capabilities/visionera/schemas/validation.yaml +74 -0
- package/bundle/skills/agentera/capabilities/visualisera/instructions.md +400 -0
- package/bundle/skills/agentera/capabilities/visualisera/schemas/artifacts.yaml +44 -0
- package/bundle/skills/agentera/capabilities/visualisera/schemas/exit.yaml +34 -0
- package/bundle/skills/agentera/capabilities/visualisera/schemas/triggers.yaml +33 -0
- package/bundle/skills/agentera/capabilities/visualisera/schemas/validation.yaml +80 -0
- package/bundle/skills/agentera/capability_schema_contract.yaml +385 -0
- package/bundle/skills/agentera/protocol.yaml +463 -0
- package/bundle/skills/agentera/references/contract.md +1039 -0
- package/bundle/skills/agentera/schemas/artifacts/changelog.yaml +60 -0
- package/bundle/skills/agentera/schemas/artifacts/decisions.yaml +461 -0
- package/bundle/skills/agentera/schemas/artifacts/design.yaml +55 -0
- package/bundle/skills/agentera/schemas/artifacts/docs.yaml +402 -0
- package/bundle/skills/agentera/schemas/artifacts/experiments.yaml +373 -0
- package/bundle/skills/agentera/schemas/artifacts/health.yaml +484 -0
- package/bundle/skills/agentera/schemas/artifacts/objective.yaml +399 -0
- package/bundle/skills/agentera/schemas/artifacts/plan.yaml +342 -0
- package/bundle/skills/agentera/schemas/artifacts/progress.yaml +325 -0
- package/bundle/skills/agentera/schemas/artifacts/todo.yaml +110 -0
- package/bundle/skills/agentera/schemas/artifacts/vision.yaml +262 -0
- package/bundle/skills/hej/.claude-plugin/plugin.json +6 -0
- package/bundle/skills/hej/SKILL.md +69 -0
- package/bundle/skills/hej/agents/hej.toml +11 -0
- package/bundle/skills/hej/agents/openai.yaml +8 -0
- package/dist/analytics/extractCorpus.js +1791 -0
- package/dist/analytics/extractCorpus.js.map +1 -0
- package/dist/analytics/usageStats.js +487 -0
- package/dist/analytics/usageStats.js.map +1 -0
- package/dist/bin/agentera.js +4 -0
- package/dist/bin/agentera.js.map +1 -0
- package/dist/cli/appContext.js +226 -0
- package/dist/cli/appContext.js.map +1 -0
- package/dist/cli/argvalidate.js +41 -0
- package/dist/cli/argvalidate.js.map +1 -0
- package/dist/cli/capabilityContext.js +2421 -0
- package/dist/cli/capabilityContext.js.map +1 -0
- package/dist/cli/commands/backfill.js +84 -0
- package/dist/cli/commands/backfill.js.map +1 -0
- package/dist/cli/commands/capability.js +44 -0
- package/dist/cli/commands/capability.js.map +1 -0
- package/dist/cli/commands/compact.js +148 -0
- package/dist/cli/commands/compact.js.map +1 -0
- package/dist/cli/commands/doctor.js +180 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/lint.js +179 -0
- package/dist/cli/commands/lint.js.map +1 -0
- package/dist/cli/commands/prime.js +544 -0
- package/dist/cli/commands/prime.js.map +1 -0
- package/dist/cli/commands/query.js +346 -0
- package/dist/cli/commands/query.js.map +1 -0
- package/dist/cli/commands/report.js +210 -0
- package/dist/cli/commands/report.js.map +1 -0
- package/dist/cli/commands/schema.js +306 -0
- package/dist/cli/commands/schema.js.map +1 -0
- package/dist/cli/commands/state.js +1012 -0
- package/dist/cli/commands/state.js.map +1 -0
- package/dist/cli/commands/upgrade.js +48 -0
- package/dist/cli/commands/upgrade.js.map +1 -0
- package/dist/cli/commands/validate.js +519 -0
- package/dist/cli/commands/validate.js.map +1 -0
- package/dist/cli/commands/verify.js +204 -0
- package/dist/cli/commands/verify.js.map +1 -0
- package/dist/cli/dispatch.js +958 -0
- package/dist/cli/dispatch.js.map +1 -0
- package/dist/cli/orientation.js +595 -0
- package/dist/cli/orientation.js.map +1 -0
- package/dist/cli/prime-blob.js +3 -0
- package/dist/cli/prime-blob.js.map +1 -0
- package/dist/cli/stateQuery.js +292 -0
- package/dist/cli/stateQuery.js.map +1 -0
- package/dist/cli/structured.js +18 -0
- package/dist/cli/structured.js.map +1 -0
- package/dist/core/difflib.js +274 -0
- package/dist/core/difflib.js.map +1 -0
- package/dist/core/git.js +43 -0
- package/dist/core/git.js.map +1 -0
- package/dist/core/paths.js +50 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/pyjson.js +101 -0
- package/dist/core/pyjson.js.map +1 -0
- package/dist/core/sourceRoot.js +72 -0
- package/dist/core/sourceRoot.js.map +1 -0
- package/dist/core/toml.js +11 -0
- package/dist/core/toml.js.map +1 -0
- package/dist/core/yaml.js +25 -0
- package/dist/core/yaml.js.map +1 -0
- package/dist/eval/evalSkills.js +258 -0
- package/dist/eval/evalSkills.js.map +1 -0
- package/dist/eval/semanticEval.js +148 -0
- package/dist/eval/semanticEval.js.map +1 -0
- package/dist/eval/semanticFixtures.js +227 -0
- package/dist/eval/semanticFixtures.js.map +1 -0
- package/dist/hooks/common.js +160 -0
- package/dist/hooks/common.js.map +1 -0
- package/dist/hooks/compaction.js +935 -0
- package/dist/hooks/compaction.js.map +1 -0
- package/dist/hooks/cursorPreToolUse.js +19 -0
- package/dist/hooks/cursorPreToolUse.js.map +1 -0
- package/dist/hooks/cursorSessionStart.js +71 -0
- package/dist/hooks/cursorSessionStart.js.map +1 -0
- package/dist/hooks/sessionStart.js +209 -0
- package/dist/hooks/sessionStart.js.map +1 -0
- package/dist/hooks/sessionStop.js +212 -0
- package/dist/hooks/sessionStop.js.map +1 -0
- package/dist/hooks/validateArtifact.js +933 -0
- package/dist/hooks/validateArtifact.js.map +1 -0
- package/dist/registries/artifactRegistry.js +206 -0
- package/dist/registries/artifactRegistry.js.map +1 -0
- package/dist/registries/capabilityContract.js +310 -0
- package/dist/registries/capabilityContract.js.map +1 -0
- package/dist/registries/packageRegistry.js +641 -0
- package/dist/registries/packageRegistry.js.map +1 -0
- package/dist/registries/runtimeAdapterRegistry.js +315 -0
- package/dist/registries/runtimeAdapterRegistry.js.map +1 -0
- package/dist/setup/codex.js +1056 -0
- package/dist/setup/codex.js.map +1 -0
- package/dist/setup/copilot.js +227 -0
- package/dist/setup/copilot.js.map +1 -0
- package/dist/setup/cursor.js +127 -0
- package/dist/setup/cursor.js.map +1 -0
- package/dist/setup/doctor.js +1276 -0
- package/dist/setup/doctor.js.map +1 -0
- package/dist/state/installRoot.js +279 -0
- package/dist/state/installRoot.js.map +1 -0
- package/dist/state/progressCommit.js +289 -0
- package/dist/state/progressCommit.js.map +1 -0
- package/dist/state/startupAnalysis.js +1953 -0
- package/dist/state/startupAnalysis.js.map +1 -0
- package/dist/upgrade/appModel.js +189 -0
- package/dist/upgrade/appModel.js.map +1 -0
- package/dist/upgrade/channels.js +208 -0
- package/dist/upgrade/channels.js.map +1 -0
- package/dist/upgrade/compatibility.js +201 -0
- package/dist/upgrade/compatibility.js.map +1 -0
- package/dist/upgrade/doctor.js +373 -0
- package/dist/upgrade/doctor.js.map +1 -0
- package/dist/upgrade/migrateArtifactsV2ToV3.js +332 -0
- package/dist/upgrade/migrateArtifactsV2ToV3.js.map +1 -0
- package/dist/upgrade/runtimeMigration.js +484 -0
- package/dist/upgrade/runtimeMigration.js.map +1 -0
- package/dist/upgrade/upgradeCommands.js +36 -0
- package/dist/upgrade/upgradeCommands.js.map +1 -0
- package/dist/upgrade/upgradeOrchestrator.js +299 -0
- package/dist/upgrade/upgradeOrchestrator.js.map +1 -0
- package/dist/upgrade/versionResolution.js +179 -0
- package/dist/upgrade/versionResolution.js.map +1 -0
- package/dist/validate/appHomeContract.js +150 -0
- package/dist/validate/appHomeContract.js.map +1 -0
- package/dist/validate/capability.js +412 -0
- package/dist/validate/capability.js.map +1 -0
- package/dist/validate/crossCapability.js +145 -0
- package/dist/validate/crossCapability.js.map +1 -0
- package/dist/validate/lifecycleAdapters.js +772 -0
- package/dist/validate/lifecycleAdapters.js.map +1 -0
- package/dist/validate/selfAudit.js +107 -0
- package/dist/validate/selfAudit.js.map +1 -0
- package/package.json +28 -8
- package/LICENSE +0 -201
- package/bin/agentera.mjs +0 -50
- package/lib/exec.mjs +0 -116
- package/lib/resolve.mjs +0 -129
|
@@ -0,0 +1,935 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import YAML from "yaml";
|
|
4
|
+
import { loadYamlMapping } from "../core/yaml.js";
|
|
5
|
+
import { DEFAULT_ARTIFACT_PATHS, MAX_FULL_ENTRIES, MAX_ONELINE_ENTRIES, MAX_TOTAL_ENTRIES, applyRetentionCaps, parseDocsYamlMapping, } from "./common.js";
|
|
6
|
+
// --- body field helpers ----------------------------------------------------
|
|
7
|
+
function extractField(body, label) {
|
|
8
|
+
const pattern = new RegExp(`^\\*\\*${escapeRe(label)}\\*\\*:\\s*(.+?)$`, "m");
|
|
9
|
+
const m = pattern.exec(body);
|
|
10
|
+
return m ? m[1].trim() : "";
|
|
11
|
+
}
|
|
12
|
+
function escapeRe(s) {
|
|
13
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
14
|
+
}
|
|
15
|
+
function firstNonEmpty(body) {
|
|
16
|
+
for (const line of body.split(/\r\n|\r|\n/)) {
|
|
17
|
+
if (line.trim())
|
|
18
|
+
return line.trim();
|
|
19
|
+
}
|
|
20
|
+
return "";
|
|
21
|
+
}
|
|
22
|
+
function truncateWords(text, limit = 15) {
|
|
23
|
+
const words = text.split(/\s+/).filter((w) => w.length > 0);
|
|
24
|
+
// Match Python str.split() token count but preserve original spacing on no-trunc.
|
|
25
|
+
const tokenCount = (text.match(/\S+/g) ?? []).length;
|
|
26
|
+
if (tokenCount <= limit)
|
|
27
|
+
return text;
|
|
28
|
+
return words.slice(0, limit).join(" ") + "...";
|
|
29
|
+
}
|
|
30
|
+
const HEADER_NUM_RE = /(?:Cycle|Decision|Audit|Experiment)\s+(\d+)/;
|
|
31
|
+
const HEADER_DATE_RE = /(\d{4}-\d{2}-\d{2})/;
|
|
32
|
+
const NUMBER_RE = /(?:Cycle|Decision|Audit|Experiment|EXP-)\s*(\d+)/;
|
|
33
|
+
function parseHeader(header) {
|
|
34
|
+
const numMatch = HEADER_NUM_RE.exec(header);
|
|
35
|
+
const dateMatch = HEADER_DATE_RE.exec(header);
|
|
36
|
+
const number = numMatch ? numMatch[1] : "";
|
|
37
|
+
const date = dateMatch ? dateMatch[1] : "";
|
|
38
|
+
const parts = header.split(" · ");
|
|
39
|
+
let title = parts.length >= 2 ? parts[parts.length - 1].trim() : "";
|
|
40
|
+
if (title === date) {
|
|
41
|
+
title = parts.length >= 3 ? parts[parts.length - 2].trim() : "";
|
|
42
|
+
}
|
|
43
|
+
return [number, date, title];
|
|
44
|
+
}
|
|
45
|
+
// --- one-line formatters ---------------------------------------------------
|
|
46
|
+
function formatProgressOneline(entry) {
|
|
47
|
+
let [number, date, title] = parseHeader(entry.header);
|
|
48
|
+
if (!title) {
|
|
49
|
+
title = extractField(entry.body, "What") || firstNonEmpty(entry.body);
|
|
50
|
+
}
|
|
51
|
+
title = truncateWords(title || "(no summary)", 20);
|
|
52
|
+
const numberPart = number ? `Cycle ${number}` : "Cycle ?";
|
|
53
|
+
const datePart = date ? ` (${date})` : "";
|
|
54
|
+
return `- ${numberPart}${datePart}: ${title}`;
|
|
55
|
+
}
|
|
56
|
+
function formatDecisionOneline(entry) {
|
|
57
|
+
const [number, date] = parseHeader(entry.header);
|
|
58
|
+
let chosen = extractField(entry.body, "Chosen alternative");
|
|
59
|
+
if (!chosen)
|
|
60
|
+
chosen = extractField(entry.body, "Chosen");
|
|
61
|
+
if (!chosen)
|
|
62
|
+
chosen = firstNonEmpty(entry.body);
|
|
63
|
+
chosen = truncateWords(chosen || "(no rationale)", 20);
|
|
64
|
+
const numberPart = number ? `Decision ${number}` : "Decision ?";
|
|
65
|
+
const datePart = date ? ` (${date})` : "";
|
|
66
|
+
return `- ${numberPart}${datePart}: ${chosen}`;
|
|
67
|
+
}
|
|
68
|
+
function formatHealthOneline(entry) {
|
|
69
|
+
const [number, date] = parseHeader(entry.header);
|
|
70
|
+
let grade = extractField(entry.body, "Overall");
|
|
71
|
+
if (!grade)
|
|
72
|
+
grade = extractField(entry.body, "Grade");
|
|
73
|
+
let trajectory = extractField(entry.body, "Overall trajectory");
|
|
74
|
+
if (!trajectory)
|
|
75
|
+
trajectory = "";
|
|
76
|
+
const summaryBits = [];
|
|
77
|
+
if (grade)
|
|
78
|
+
summaryBits.push(truncateWords(grade, 10));
|
|
79
|
+
if (trajectory && trajectory !== grade)
|
|
80
|
+
summaryBits.push(truncateWords(trajectory, 10));
|
|
81
|
+
const summary = summaryBits.length > 0 ? summaryBits.join(" | ") : "no summary";
|
|
82
|
+
const numberPart = number ? `Audit ${number}` : "Audit ?";
|
|
83
|
+
const datePart = date ? ` · ${date}` : "";
|
|
84
|
+
return `### ${numberPart}${datePart} (${summary})`;
|
|
85
|
+
}
|
|
86
|
+
function formatExperimentOneline(entry) {
|
|
87
|
+
const [number] = parseHeader(entry.header);
|
|
88
|
+
let summary = extractField(entry.body, "Metric");
|
|
89
|
+
if (!summary)
|
|
90
|
+
summary = extractField(entry.body, "Conclusion");
|
|
91
|
+
if (!summary)
|
|
92
|
+
summary = extractField(entry.body, "Result");
|
|
93
|
+
if (!summary)
|
|
94
|
+
summary = firstNonEmpty(entry.body);
|
|
95
|
+
summary = truncateWords(summary || "(no result)", 15);
|
|
96
|
+
const numberPart = number ? `EXP-${number}` : "EXP-?";
|
|
97
|
+
return `- ${numberPart}: ${summary}`;
|
|
98
|
+
}
|
|
99
|
+
const TODO_CHECKBOX_RE = /^-\s*\[x\]\s*/;
|
|
100
|
+
const TODO_ISS_LABEL_RE = /ISS-\d+:?\s*/;
|
|
101
|
+
function isTodoOnelinePassthrough(entry) {
|
|
102
|
+
return entry.kind === "oneline" && String(entry.header).includes("~~");
|
|
103
|
+
}
|
|
104
|
+
function stripTodoMetadata(header) {
|
|
105
|
+
let stripped = header.replace(TODO_CHECKBOX_RE, "");
|
|
106
|
+
stripped = stripped.replace(/~~/g, "").trim();
|
|
107
|
+
stripped = stripped.split(" · ")[0].trim();
|
|
108
|
+
stripped = stripped.replace(TODO_ISS_LABEL_RE, "").trim();
|
|
109
|
+
return stripped;
|
|
110
|
+
}
|
|
111
|
+
function formatTodoOneline(entry) {
|
|
112
|
+
const header = String(entry.header).trim();
|
|
113
|
+
if (isTodoOnelinePassthrough(entry)) {
|
|
114
|
+
return header.startsWith("- ") ? header : `- ${header}`;
|
|
115
|
+
}
|
|
116
|
+
const summary = truncateWords(stripTodoMetadata(header) || "(resolved)", 15);
|
|
117
|
+
return `- [x] ~~${summary}~~`;
|
|
118
|
+
}
|
|
119
|
+
export const SPECS = {
|
|
120
|
+
progress: {
|
|
121
|
+
name: "progress",
|
|
122
|
+
entryHeadingRe: /^■?\s*##\s+Cycle\s+\d+/m,
|
|
123
|
+
onelineHeadingRe: /^-\s+Cycle\s+\d+/m,
|
|
124
|
+
archiveHeading: "## Archived Cycles",
|
|
125
|
+
formatOneline: formatProgressOneline,
|
|
126
|
+
scopedSection: null,
|
|
127
|
+
},
|
|
128
|
+
decisions: {
|
|
129
|
+
name: "decisions",
|
|
130
|
+
entryHeadingRe: /^##\s+Decision\s+\d+/m,
|
|
131
|
+
onelineHeadingRe: /^-\s+Decision\s+\d+/m,
|
|
132
|
+
archiveHeading: "## Archived Decisions",
|
|
133
|
+
formatOneline: formatDecisionOneline,
|
|
134
|
+
scopedSection: null,
|
|
135
|
+
},
|
|
136
|
+
health: {
|
|
137
|
+
name: "health",
|
|
138
|
+
entryHeadingRe: /^##\s+Audit\s+\d+/m,
|
|
139
|
+
onelineHeadingRe: /^###\s+Audit\s+\d+/m,
|
|
140
|
+
archiveHeading: "## Archived Audits",
|
|
141
|
+
formatOneline: formatHealthOneline,
|
|
142
|
+
scopedSection: null,
|
|
143
|
+
},
|
|
144
|
+
experiments: {
|
|
145
|
+
name: "experiments",
|
|
146
|
+
entryHeadingRe: /^##\s+Experiment\s+\d+/m,
|
|
147
|
+
onelineHeadingRe: /^-\s+EXP-\d+/m,
|
|
148
|
+
archiveHeading: "## Archived Experiments",
|
|
149
|
+
formatOneline: formatExperimentOneline,
|
|
150
|
+
scopedSection: null,
|
|
151
|
+
},
|
|
152
|
+
"todo-resolved": {
|
|
153
|
+
name: "todo-resolved",
|
|
154
|
+
entryHeadingRe: /^-\s+\[x\]\s+/m,
|
|
155
|
+
onelineHeadingRe: null,
|
|
156
|
+
archiveHeading: null,
|
|
157
|
+
formatOneline: formatTodoOneline,
|
|
158
|
+
scopedSection: "## Resolved",
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
export const COMPACTABLE_YAML_ARTIFACTS = {
|
|
162
|
+
"PROGRESS.md": ["cycles", "archive"],
|
|
163
|
+
"DECISIONS.md": ["decisions", "archive"],
|
|
164
|
+
"HEALTH.md": ["audits", "archive"],
|
|
165
|
+
};
|
|
166
|
+
const YAML_SPEC_BY_ARTIFACT = {
|
|
167
|
+
"PROGRESS.md": "progress",
|
|
168
|
+
"DECISIONS.md": "decisions",
|
|
169
|
+
"HEALTH.md": "health",
|
|
170
|
+
};
|
|
171
|
+
const NON_COMPACTABLE_ARTIFACTS = {
|
|
172
|
+
"CHANGELOG.md": ["exempt", "public release history is not compacted"],
|
|
173
|
+
"PLAN.md": ["unsupported", "active plan is lifecycle state, not a uniform retained-entry log"],
|
|
174
|
+
"DOCS.md": ["unsupported", "docs owns artifact mapping and schema metadata"],
|
|
175
|
+
"VISION.md": ["protected", "vision state is protected during execution cycles"],
|
|
176
|
+
"DESIGN.md": ["unsupported", "design is a human-facing identity artifact, not a uniform retained-entry log"],
|
|
177
|
+
};
|
|
178
|
+
function overLimitCount(activeCount, archiveCount) {
|
|
179
|
+
const totalCount = activeCount + archiveCount;
|
|
180
|
+
return Math.max(Math.max(activeCount - MAX_FULL_ENTRIES, 0), Math.max(archiveCount - MAX_ONELINE_ENTRIES, 0), Math.max(totalCount - MAX_TOTAL_ENTRIES, 0));
|
|
181
|
+
}
|
|
182
|
+
function artifactPaths(projectRoot) {
|
|
183
|
+
const paths = { ...DEFAULT_ARTIFACT_PATHS };
|
|
184
|
+
const docsPath = path.join(projectRoot, ".agentera", "docs.yaml");
|
|
185
|
+
if (fs.existsSync(docsPath)) {
|
|
186
|
+
Object.assign(paths, parseDocsYamlMapping(fs.readFileSync(docsPath, "utf8")));
|
|
187
|
+
}
|
|
188
|
+
const resolved = {};
|
|
189
|
+
for (const [artifact, rel] of Object.entries(paths)) {
|
|
190
|
+
resolved[artifact] = path.join(projectRoot, rel);
|
|
191
|
+
}
|
|
192
|
+
return resolved;
|
|
193
|
+
}
|
|
194
|
+
function missingStatus(artifact, p, classification) {
|
|
195
|
+
return {
|
|
196
|
+
artifact,
|
|
197
|
+
path: p,
|
|
198
|
+
classification,
|
|
199
|
+
active_count: 0,
|
|
200
|
+
archive_count: 0,
|
|
201
|
+
total_count: 0,
|
|
202
|
+
over_limit_count: 0,
|
|
203
|
+
reason: "artifact path is not present",
|
|
204
|
+
protected_overflow_count: 0,
|
|
205
|
+
exists: false,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function yamlErrorStatus(artifact, p, message) {
|
|
209
|
+
return {
|
|
210
|
+
artifact,
|
|
211
|
+
path: p,
|
|
212
|
+
classification: "error",
|
|
213
|
+
active_count: null,
|
|
214
|
+
archive_count: null,
|
|
215
|
+
total_count: null,
|
|
216
|
+
over_limit_count: null,
|
|
217
|
+
reason: message.trim() || "invalid YAML mapping root",
|
|
218
|
+
protected_overflow_count: 0,
|
|
219
|
+
exists: true,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function yamlCounts(p, activeKey, archiveKey) {
|
|
223
|
+
const data = loadYamlMapping(fs.readFileSync(p, "utf8"));
|
|
224
|
+
if (!data || typeof data !== "object" || Array.isArray(data))
|
|
225
|
+
return [0, 0];
|
|
226
|
+
const active = data[activeKey] || [];
|
|
227
|
+
const archive = data[archiveKey] || [];
|
|
228
|
+
return [Array.isArray(active) ? active.length : 0, Array.isArray(archive) ? archive.length : 0];
|
|
229
|
+
}
|
|
230
|
+
function yamlLists(p, activeKey, archiveKey) {
|
|
231
|
+
const data = loadYamlMapping(fs.readFileSync(p, "utf8"));
|
|
232
|
+
if (!data || typeof data !== "object" || Array.isArray(data))
|
|
233
|
+
return [[], []];
|
|
234
|
+
const active = data[activeKey] || [];
|
|
235
|
+
const archive = data[archiveKey] || [];
|
|
236
|
+
return [Array.isArray(active) ? active : [], Array.isArray(archive) ? archive : []];
|
|
237
|
+
}
|
|
238
|
+
function yamlEntryNumber(entry) {
|
|
239
|
+
let summary;
|
|
240
|
+
if (entry && typeof entry === "object" && !Array.isArray(entry)) {
|
|
241
|
+
const number = entry.number;
|
|
242
|
+
if (typeof number === "number" && Number.isInteger(number))
|
|
243
|
+
return number;
|
|
244
|
+
if (typeof number === "string" && /^\d+$/.test(number))
|
|
245
|
+
return parseInt(number, 10);
|
|
246
|
+
summary = String(entry.summary ?? "");
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
summary = String(entry);
|
|
250
|
+
}
|
|
251
|
+
const match = NUMBER_RE.exec(summary);
|
|
252
|
+
return match ? parseInt(match[1], 10) : 0;
|
|
253
|
+
}
|
|
254
|
+
function yamlEntryTimestamp(entry) {
|
|
255
|
+
if (entry && typeof entry === "object" && !Array.isArray(entry)) {
|
|
256
|
+
return String(entry.timestamp || entry.date || "");
|
|
257
|
+
}
|
|
258
|
+
return "";
|
|
259
|
+
}
|
|
260
|
+
function yamlSummaryText(entry, ...fields) {
|
|
261
|
+
for (const field of fields) {
|
|
262
|
+
const value = entry[field];
|
|
263
|
+
if (typeof value === "string" && value.trim()) {
|
|
264
|
+
return truncateWords(value.trim(), 15);
|
|
265
|
+
}
|
|
266
|
+
if (value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length > 0) {
|
|
267
|
+
return truncateWords(Object.entries(value).map(([k, v]) => `${k}: ${v}`).join(", "), 15);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return "no summary";
|
|
271
|
+
}
|
|
272
|
+
function isEmptyish(v) {
|
|
273
|
+
return (v === null ||
|
|
274
|
+
v === undefined ||
|
|
275
|
+
v === "" ||
|
|
276
|
+
(Array.isArray(v) && v.length === 0) ||
|
|
277
|
+
(typeof v === "object" && v !== null && !Array.isArray(v) && Object.keys(v).length === 0));
|
|
278
|
+
}
|
|
279
|
+
function yamlArchiveEntry(specName, entry) {
|
|
280
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
281
|
+
return { summary: String(entry) };
|
|
282
|
+
}
|
|
283
|
+
const e = entry;
|
|
284
|
+
if (specName === "progress") {
|
|
285
|
+
const number = e.number ?? "?";
|
|
286
|
+
const date = String(e.timestamp ?? "").split(/\s+/)[0] || "";
|
|
287
|
+
const datePart = date ? ` (${date})` : "";
|
|
288
|
+
const summary = yamlSummaryText(e, "what", "type");
|
|
289
|
+
return { summary: `Cycle ${number}${datePart}: ${summary}` };
|
|
290
|
+
}
|
|
291
|
+
if (specName === "decisions") {
|
|
292
|
+
const number = e.number ?? "?";
|
|
293
|
+
const date = String(e.date ?? "");
|
|
294
|
+
const datePart = date ? ` (${date})` : "";
|
|
295
|
+
const choice = yamlSummaryText(e, "choice", "question");
|
|
296
|
+
const archiveEntry = { summary: `Decision ${number}${datePart}: ${choice}` };
|
|
297
|
+
for (const field of ["number", "date", "choice", "outcome", "feeds_into", "satisfaction"]) {
|
|
298
|
+
const value = e[field];
|
|
299
|
+
if (!isEmptyish(value))
|
|
300
|
+
archiveEntry[field] = value;
|
|
301
|
+
}
|
|
302
|
+
if (!("outcome" in archiveEntry) && !isEmptyish(archiveEntry.choice)) {
|
|
303
|
+
archiveEntry.outcome = archiveEntry.choice;
|
|
304
|
+
}
|
|
305
|
+
return archiveEntry;
|
|
306
|
+
}
|
|
307
|
+
if (specName === "health") {
|
|
308
|
+
const number = e.number ?? "?";
|
|
309
|
+
const date = String(e.date ?? "");
|
|
310
|
+
const datePart = date ? ` (${date})` : "";
|
|
311
|
+
const summary = yamlSummaryText(e, "trajectory", "grades");
|
|
312
|
+
return { summary: `Audit ${number}${datePart}: ${summary}` };
|
|
313
|
+
}
|
|
314
|
+
if (specName === "session") {
|
|
315
|
+
return { timestamp: String(e.timestamp ?? ""), summary: yamlSummaryText(e, "summary") };
|
|
316
|
+
}
|
|
317
|
+
return { summary: yamlSummaryText(e, "summary") };
|
|
318
|
+
}
|
|
319
|
+
function stableSortBy(arr, key, reverse = false) {
|
|
320
|
+
return arr
|
|
321
|
+
.map((x, i) => [x, i])
|
|
322
|
+
.sort((a, b) => {
|
|
323
|
+
const ka = key(a[0]);
|
|
324
|
+
const kb = key(b[0]);
|
|
325
|
+
let cmp = ka < kb ? -1 : ka > kb ? 1 : 0;
|
|
326
|
+
if (reverse)
|
|
327
|
+
cmp = -cmp;
|
|
328
|
+
return cmp !== 0 ? cmp : a[1] - b[1];
|
|
329
|
+
})
|
|
330
|
+
.map(([x]) => x);
|
|
331
|
+
}
|
|
332
|
+
function yamlSortEntries(entries, specName) {
|
|
333
|
+
if (specName === "decisions" || specName === "health") {
|
|
334
|
+
return stableSortBy(entries, yamlEntryNumber);
|
|
335
|
+
}
|
|
336
|
+
if (specName === "session") {
|
|
337
|
+
return stableSortBy(entries, yamlEntryTimestamp, true);
|
|
338
|
+
}
|
|
339
|
+
return stableSortBy(entries, yamlEntryNumber, true);
|
|
340
|
+
}
|
|
341
|
+
function yamlRecentFullAndOlder(entries, specName) {
|
|
342
|
+
const newestFirst = specName === "session"
|
|
343
|
+
? stableSortBy(entries, yamlEntryTimestamp, true)
|
|
344
|
+
: stableSortBy(entries, yamlEntryNumber, true);
|
|
345
|
+
const recent = newestFirst.slice(0, MAX_FULL_ENTRIES);
|
|
346
|
+
const older = newestFirst.slice(MAX_FULL_ENTRIES);
|
|
347
|
+
return [yamlSortEntries(recent, specName), older];
|
|
348
|
+
}
|
|
349
|
+
function yamlArchiveSortKey(entry) {
|
|
350
|
+
const timestamp = yamlEntryTimestamp(entry);
|
|
351
|
+
if (timestamp)
|
|
352
|
+
return ["timestamp", timestamp];
|
|
353
|
+
return ["number", yamlEntryNumber(entry)];
|
|
354
|
+
}
|
|
355
|
+
function yamlArchiveEntries(entries) {
|
|
356
|
+
// Sort by (tag, value) descending, mirroring Python tuple comparison.
|
|
357
|
+
return entries
|
|
358
|
+
.map((x, i) => [x, i])
|
|
359
|
+
.sort((a, b) => {
|
|
360
|
+
const ka = yamlArchiveSortKey(a[0]);
|
|
361
|
+
const kb = yamlArchiveSortKey(b[0]);
|
|
362
|
+
let cmp = 0;
|
|
363
|
+
if (ka[0] !== kb[0])
|
|
364
|
+
cmp = ka[0] < kb[0] ? -1 : 1;
|
|
365
|
+
else
|
|
366
|
+
cmp = ka[1] < kb[1] ? -1 : ka[1] > kb[1] ? 1 : 0;
|
|
367
|
+
cmp = -cmp;
|
|
368
|
+
return cmp !== 0 ? cmp : a[1] - b[1];
|
|
369
|
+
})
|
|
370
|
+
.map(([x]) => x);
|
|
371
|
+
}
|
|
372
|
+
function decisionSatisfactionState(entry) {
|
|
373
|
+
if (!entry || typeof entry !== "object")
|
|
374
|
+
return null;
|
|
375
|
+
const satisfaction = entry.satisfaction;
|
|
376
|
+
if (!satisfaction || typeof satisfaction !== "object")
|
|
377
|
+
return null;
|
|
378
|
+
const state = satisfaction.state;
|
|
379
|
+
return typeof state === "string" ? state : null;
|
|
380
|
+
}
|
|
381
|
+
function decisionRequiresUserReview(entry) {
|
|
382
|
+
if (!entry || typeof entry !== "object")
|
|
383
|
+
return true;
|
|
384
|
+
const satisfaction = entry.satisfaction;
|
|
385
|
+
if (!satisfaction || typeof satisfaction !== "object")
|
|
386
|
+
return true;
|
|
387
|
+
const confirmation = satisfaction.user_confirmation;
|
|
388
|
+
return (decisionSatisfactionState(entry) !== "user_confirmed_satisfied" ||
|
|
389
|
+
!confirmation ||
|
|
390
|
+
typeof confirmation !== "object" ||
|
|
391
|
+
Object.keys(confirmation).length === 0);
|
|
392
|
+
}
|
|
393
|
+
function decisionProtectedOverflowCount(active, archive) {
|
|
394
|
+
const protectedActive = active.filter((e) => decisionRequiresUserReview(e)).length;
|
|
395
|
+
const protectedArchive = archive.filter((e) => decisionRequiresUserReview(e)).length;
|
|
396
|
+
return Math.max(protectedActive - MAX_FULL_ENTRIES, protectedArchive - MAX_ONELINE_ENTRIES, protectedActive + protectedArchive - MAX_TOTAL_ENTRIES, 0);
|
|
397
|
+
}
|
|
398
|
+
function selectDecisionActiveEntries(active) {
|
|
399
|
+
const protectedEntries = active.filter((e) => decisionRequiresUserReview(e));
|
|
400
|
+
if (protectedEntries.length > MAX_FULL_ENTRIES) {
|
|
401
|
+
throw new Error("DECISIONS.md: protected-overflow review pressure; " +
|
|
402
|
+
`${protectedEntries.length} protected active decision(s) exceed ${MAX_FULL_ENTRIES} full-detail slots`);
|
|
403
|
+
}
|
|
404
|
+
const satisfied = active.filter((e) => !decisionRequiresUserReview(e));
|
|
405
|
+
const newestSatisfied = stableSortBy(satisfied, yamlEntryNumber, true);
|
|
406
|
+
const keepSatisfied = newestSatisfied.slice(0, MAX_FULL_ENTRIES - protectedEntries.length);
|
|
407
|
+
const compactSatisfied = newestSatisfied.slice(MAX_FULL_ENTRIES - protectedEntries.length);
|
|
408
|
+
return [yamlSortEntries([...protectedEntries, ...keepSatisfied], "decisions"), compactSatisfied];
|
|
409
|
+
}
|
|
410
|
+
function selectDecisionArchiveEntries(archiveCandidates) {
|
|
411
|
+
const protectedEntries = archiveCandidates.filter((e) => decisionRequiresUserReview(e));
|
|
412
|
+
if (protectedEntries.length > MAX_ONELINE_ENTRIES) {
|
|
413
|
+
throw new Error("DECISIONS.md: protected-overflow review pressure; " +
|
|
414
|
+
`${protectedEntries.length} protected archived decision(s) exceed ${MAX_ONELINE_ENTRIES} archive slots`);
|
|
415
|
+
}
|
|
416
|
+
const satisfied = archiveCandidates.filter((e) => !decisionRequiresUserReview(e));
|
|
417
|
+
const keepSatisfied = yamlArchiveEntries(satisfied).slice(0, MAX_ONELINE_ENTRIES - protectedEntries.length);
|
|
418
|
+
return yamlArchiveEntries([...protectedEntries, ...keepSatisfied]);
|
|
419
|
+
}
|
|
420
|
+
export function compactYamlFile(p, artifact) {
|
|
421
|
+
if (!(artifact in COMPACTABLE_YAML_ARTIFACTS)) {
|
|
422
|
+
throw new Error(`unsupported YAML artifact: ${artifact}`);
|
|
423
|
+
}
|
|
424
|
+
if (!fs.existsSync(p)) {
|
|
425
|
+
throw new Error(p);
|
|
426
|
+
}
|
|
427
|
+
const [activeKey, archiveKey] = COMPACTABLE_YAML_ARTIFACTS[artifact];
|
|
428
|
+
const specName = YAML_SPEC_BY_ARTIFACT[artifact];
|
|
429
|
+
const data = loadYamlMapping(fs.readFileSync(p, "utf8"));
|
|
430
|
+
let active = data[activeKey] || [];
|
|
431
|
+
let archive = data[archiveKey] || [];
|
|
432
|
+
if (!Array.isArray(active))
|
|
433
|
+
active = [];
|
|
434
|
+
if (!Array.isArray(archive))
|
|
435
|
+
archive = [];
|
|
436
|
+
const fullBefore = active.length;
|
|
437
|
+
const onelineBefore = archive.length;
|
|
438
|
+
if (overLimitCount(fullBefore, onelineBefore) === 0) {
|
|
439
|
+
return { full_before: fullBefore, oneline_before: onelineBefore, full_after: fullBefore, oneline_after: onelineBefore, dropped: 0, changed: false };
|
|
440
|
+
}
|
|
441
|
+
let recentFull;
|
|
442
|
+
let olderActive;
|
|
443
|
+
if (specName === "decisions") {
|
|
444
|
+
[recentFull, olderActive] = selectDecisionActiveEntries(active);
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
[recentFull, olderActive] = yamlRecentFullAndOlder(active, specName);
|
|
448
|
+
}
|
|
449
|
+
const compactedFromActive = olderActive.map((entry) => yamlArchiveEntry(specName, entry));
|
|
450
|
+
const archiveCandidates = yamlArchiveEntries([...compactedFromActive, ...archive]);
|
|
451
|
+
let archiveAfter;
|
|
452
|
+
if (specName === "decisions") {
|
|
453
|
+
archiveAfter = selectDecisionArchiveEntries(archiveCandidates);
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
const merged = applyRetentionCaps(recentFull, archiveCandidates);
|
|
457
|
+
archiveAfter = merged.slice(recentFull.length);
|
|
458
|
+
}
|
|
459
|
+
data[activeKey] = recentFull;
|
|
460
|
+
data[archiveKey] = archiveAfter;
|
|
461
|
+
fs.writeFileSync(p, YAML.stringify(data));
|
|
462
|
+
const fullAfter = recentFull.length;
|
|
463
|
+
const onelineAfter = archiveAfter.length;
|
|
464
|
+
const dropped = fullBefore + onelineBefore - fullAfter - onelineAfter;
|
|
465
|
+
return { full_before: fullBefore, oneline_before: onelineBefore, full_after: fullAfter, oneline_after: onelineAfter, dropped, changed: true };
|
|
466
|
+
}
|
|
467
|
+
function countStatus(artifact, p, activeCount, archiveCount, protectedOverflowCount = 0) {
|
|
468
|
+
const totalCount = activeCount + archiveCount;
|
|
469
|
+
return {
|
|
470
|
+
artifact,
|
|
471
|
+
path: p,
|
|
472
|
+
classification: "compactable",
|
|
473
|
+
active_count: activeCount,
|
|
474
|
+
archive_count: archiveCount,
|
|
475
|
+
total_count: totalCount,
|
|
476
|
+
over_limit_count: overLimitCount(activeCount, archiveCount),
|
|
477
|
+
reason: protectedOverflowCount ? "protected-overflow review pressure" : "uniform_10_40_50",
|
|
478
|
+
protected_overflow_count: protectedOverflowCount,
|
|
479
|
+
exists: true,
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
export function computeCompactionStatus(projectRoot) {
|
|
483
|
+
const paths = artifactPaths(projectRoot);
|
|
484
|
+
const statuses = [];
|
|
485
|
+
const todoPath = paths["TODO.md"];
|
|
486
|
+
if (fs.existsSync(todoPath)) {
|
|
487
|
+
const entries = parseEntries(fs.readFileSync(todoPath, "utf8"), "todo-resolved");
|
|
488
|
+
const activeCount = entries.filter((e) => e.kind === "full").length;
|
|
489
|
+
const archiveCount = entries.filter((e) => e.kind === "oneline").length;
|
|
490
|
+
statuses.push(countStatus("TODO.md#Resolved", todoPath, activeCount, archiveCount));
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
statuses.push(missingStatus("TODO.md#Resolved", todoPath, "compactable"));
|
|
494
|
+
}
|
|
495
|
+
for (const [artifact, [activeKey, archiveKey]] of Object.entries(COMPACTABLE_YAML_ARTIFACTS)) {
|
|
496
|
+
const p = paths[artifact];
|
|
497
|
+
if (fs.existsSync(p)) {
|
|
498
|
+
let active;
|
|
499
|
+
let archive;
|
|
500
|
+
try {
|
|
501
|
+
[active, archive] = yamlLists(p, activeKey, archiveKey);
|
|
502
|
+
}
|
|
503
|
+
catch (exc) {
|
|
504
|
+
statuses.push(yamlErrorStatus(artifact, p, exc.message));
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
const protectedOverflowCount = artifact === "DECISIONS.md" ? decisionProtectedOverflowCount(active, archive) : 0;
|
|
508
|
+
statuses.push(countStatus(artifact, p, active.length, archive.length, protectedOverflowCount));
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
statuses.push(missingStatus(artifact, p, "compactable"));
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
for (const [artifact, [classification, reason]] of Object.entries(NON_COMPACTABLE_ARTIFACTS)) {
|
|
515
|
+
const p = paths[artifact];
|
|
516
|
+
statuses.push({
|
|
517
|
+
artifact,
|
|
518
|
+
path: p,
|
|
519
|
+
classification,
|
|
520
|
+
active_count: null,
|
|
521
|
+
archive_count: null,
|
|
522
|
+
total_count: null,
|
|
523
|
+
over_limit_count: null,
|
|
524
|
+
reason,
|
|
525
|
+
protected_overflow_count: 0,
|
|
526
|
+
exists: fs.existsSync(p),
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
const optimeraDir = path.join(projectRoot, ".agentera", "optimera");
|
|
530
|
+
if (fs.existsSync(optimeraDir) && fs.statSync(optimeraDir).isDirectory()) {
|
|
531
|
+
const experimentPaths = [];
|
|
532
|
+
for (const sub of fs.readdirSync(optimeraDir).sort()) {
|
|
533
|
+
const expPath = path.join(optimeraDir, sub, "experiments.yaml");
|
|
534
|
+
if (fs.existsSync(expPath))
|
|
535
|
+
experimentPaths.push(expPath);
|
|
536
|
+
}
|
|
537
|
+
for (const expPath of experimentPaths) {
|
|
538
|
+
let activeCount;
|
|
539
|
+
let archiveCount;
|
|
540
|
+
try {
|
|
541
|
+
[activeCount, archiveCount] = yamlCounts(expPath, "experiments", "archive");
|
|
542
|
+
}
|
|
543
|
+
catch (exc) {
|
|
544
|
+
statuses.push(yamlErrorStatus("EXPERIMENTS.md", expPath, exc.message));
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
statuses.push({
|
|
548
|
+
artifact: "EXPERIMENTS.md",
|
|
549
|
+
path: expPath,
|
|
550
|
+
classification: "protected",
|
|
551
|
+
active_count: activeCount,
|
|
552
|
+
archive_count: archiveCount,
|
|
553
|
+
total_count: activeCount + archiveCount,
|
|
554
|
+
over_limit_count: overLimitCount(activeCount, archiveCount),
|
|
555
|
+
reason: "objective-state experiment files are classified but skipped by default",
|
|
556
|
+
protected_overflow_count: 0,
|
|
557
|
+
exists: true,
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
return statuses;
|
|
562
|
+
}
|
|
563
|
+
function operationForStatus(status, mode) {
|
|
564
|
+
const base = (action, message) => ({
|
|
565
|
+
status,
|
|
566
|
+
mode,
|
|
567
|
+
action,
|
|
568
|
+
changed: false,
|
|
569
|
+
result: null,
|
|
570
|
+
message,
|
|
571
|
+
});
|
|
572
|
+
if (!status.exists)
|
|
573
|
+
return base("missing", status.reason);
|
|
574
|
+
if (status.classification === "error")
|
|
575
|
+
return base("error", status.reason);
|
|
576
|
+
if (status.classification !== "compactable")
|
|
577
|
+
return base("skipped", status.reason);
|
|
578
|
+
if (status.protected_overflow_count) {
|
|
579
|
+
return base("protected_overflow", `protected-overflow review pressure by ${status.protected_overflow_count}`);
|
|
580
|
+
}
|
|
581
|
+
if (!status.over_limit_count)
|
|
582
|
+
return base("ok", "within uniform_10_40_50 limits");
|
|
583
|
+
return base(mode === "check" ? "over_limit" : "pending_fix", `over uniform_10_40_50 limit by ${status.over_limit_count}`);
|
|
584
|
+
}
|
|
585
|
+
export function checkCompaction(projectRoot) {
|
|
586
|
+
return computeCompactionStatus(projectRoot).map((status) => operationForStatus(status, "check"));
|
|
587
|
+
}
|
|
588
|
+
export function fixCompaction(projectRoot) {
|
|
589
|
+
const operations = [];
|
|
590
|
+
for (const status of computeCompactionStatus(projectRoot)) {
|
|
591
|
+
const baseline = operationForStatus(status, "fix");
|
|
592
|
+
if (baseline.action !== "pending_fix") {
|
|
593
|
+
operations.push(baseline);
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
const p = status.path;
|
|
597
|
+
let result;
|
|
598
|
+
try {
|
|
599
|
+
if (status.artifact === "TODO.md#Resolved") {
|
|
600
|
+
result = compactFile(p, "todo-resolved");
|
|
601
|
+
}
|
|
602
|
+
else if (status.artifact in YAML_SPEC_BY_ARTIFACT) {
|
|
603
|
+
result = compactYamlFile(p, status.artifact);
|
|
604
|
+
}
|
|
605
|
+
else {
|
|
606
|
+
operations.push({ status, mode: "fix", action: "skipped", changed: false, result: null, message: `no fixer registered for ${status.artifact}` });
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
catch (exc) {
|
|
611
|
+
operations.push({ status, mode: "fix", action: "error", changed: false, result: null, message: exc.message });
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
614
|
+
operations.push({
|
|
615
|
+
status,
|
|
616
|
+
mode: "fix",
|
|
617
|
+
action: result.changed ? "compacted" : "ok",
|
|
618
|
+
changed: result.changed,
|
|
619
|
+
result,
|
|
620
|
+
message: `full ${result.full_before}->${result.full_after}; ` +
|
|
621
|
+
`archive ${result.oneline_before}->${result.oneline_after}; ` +
|
|
622
|
+
`dropped ${result.dropped}`,
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
return operations;
|
|
626
|
+
}
|
|
627
|
+
export function runCompaction(projectRoot, mode = "check") {
|
|
628
|
+
if (mode === "check")
|
|
629
|
+
return checkCompaction(projectRoot);
|
|
630
|
+
if (mode === "fix")
|
|
631
|
+
return fixCompaction(projectRoot);
|
|
632
|
+
throw new Error(`unknown compaction mode: ${mode}`);
|
|
633
|
+
}
|
|
634
|
+
// --- markdown parse + compaction -------------------------------------------
|
|
635
|
+
function splitArchive(text, archiveHeading) {
|
|
636
|
+
if (!archiveHeading)
|
|
637
|
+
return [text, ""];
|
|
638
|
+
const pattern = new RegExp(`^${escapeRe(archiveHeading)}\\s*$`, "m");
|
|
639
|
+
const match = pattern.exec(text);
|
|
640
|
+
if (!match)
|
|
641
|
+
return [text, ""];
|
|
642
|
+
const pre = text.slice(0, match.index).replace(/\s+$/, "");
|
|
643
|
+
const after = text.slice(match.index + match[0].length);
|
|
644
|
+
const nextSection = /^##\s/m.exec(after);
|
|
645
|
+
if (nextSection) {
|
|
646
|
+
const archiveBody = after.slice(0, nextSection.index);
|
|
647
|
+
const trailing = after.slice(nextSection.index);
|
|
648
|
+
return [pre, archiveBody.trim() + (trailing ? "\n\n" + trailing : "")];
|
|
649
|
+
}
|
|
650
|
+
return [pre, after.trim()];
|
|
651
|
+
}
|
|
652
|
+
function parseFullEntries(text, spec) {
|
|
653
|
+
const entries = [];
|
|
654
|
+
const re = new RegExp(spec.entryHeadingRe.source, "gm");
|
|
655
|
+
const matches = [...text.matchAll(re)];
|
|
656
|
+
for (let i = 0; i < matches.length; i++) {
|
|
657
|
+
const m = matches[i];
|
|
658
|
+
const lineStart = text.lastIndexOf("\n", m.index - 1) + 1;
|
|
659
|
+
let lineEnd = text.indexOf("\n", m.index);
|
|
660
|
+
if (lineEnd === -1)
|
|
661
|
+
lineEnd = text.length;
|
|
662
|
+
const headerLine = text.slice(lineStart, lineEnd).trim();
|
|
663
|
+
const glyphMatch = /^(■)\s*/.exec(headerLine);
|
|
664
|
+
const glyph = glyphMatch ? glyphMatch[1] + " " : "";
|
|
665
|
+
const remainder = glyphMatch ? headerLine.slice(glyphMatch[0].length) : headerLine;
|
|
666
|
+
const header = glyph + remainder.replace(/^#+/, "").trim();
|
|
667
|
+
const bodyStart = lineEnd + 1;
|
|
668
|
+
let bodyEnd;
|
|
669
|
+
if (i + 1 < matches.length) {
|
|
670
|
+
bodyEnd = text.lastIndexOf("\n", matches[i + 1].index - 1) + 1;
|
|
671
|
+
}
|
|
672
|
+
else {
|
|
673
|
+
bodyEnd = text.length;
|
|
674
|
+
}
|
|
675
|
+
const body = text.slice(bodyStart, bodyEnd).trim();
|
|
676
|
+
entries.push({ header, body, kind: "full" });
|
|
677
|
+
}
|
|
678
|
+
return entries;
|
|
679
|
+
}
|
|
680
|
+
function parseOnelineEntries(text, spec) {
|
|
681
|
+
if (spec.onelineHeadingRe === null)
|
|
682
|
+
return [];
|
|
683
|
+
const entries = [];
|
|
684
|
+
const re = new RegExp(spec.onelineHeadingRe.source);
|
|
685
|
+
for (const line of text.split(/\r\n|\r|\n/)) {
|
|
686
|
+
if (re.test(line)) {
|
|
687
|
+
entries.push({ header: line.replace(/\s+$/, ""), body: "", kind: "oneline" });
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
return entries;
|
|
691
|
+
}
|
|
692
|
+
export function parseEntries(text, specName) {
|
|
693
|
+
const spec = SPECS[specName];
|
|
694
|
+
if (spec.name === "todo-resolved") {
|
|
695
|
+
return parseTodoResolved(text, spec);
|
|
696
|
+
}
|
|
697
|
+
const [pre, archiveBody] = splitArchive(text, spec.archiveHeading || "");
|
|
698
|
+
const fullEntries = parseFullEntries(pre, spec);
|
|
699
|
+
const onelineEntries = parseOnelineEntries(archiveBody, spec);
|
|
700
|
+
return [...fullEntries, ...onelineEntries];
|
|
701
|
+
}
|
|
702
|
+
function extractResolvedSection(text) {
|
|
703
|
+
const m = /^##\s+(?:✓\s+)?Resolved\s*$/m.exec(text);
|
|
704
|
+
if (!m)
|
|
705
|
+
return [-1, -1, ""];
|
|
706
|
+
const bodyStart = m.index + m[0].length + 1;
|
|
707
|
+
const nextSection = /^##\s/m.exec(text.slice(bodyStart));
|
|
708
|
+
const bodyEnd = nextSection ? bodyStart + nextSection.index : text.length;
|
|
709
|
+
return [m.index, bodyEnd, text.slice(bodyStart, bodyEnd)];
|
|
710
|
+
}
|
|
711
|
+
function parseTodoResolved(text, spec) {
|
|
712
|
+
const [, , body] = extractResolvedSection(text);
|
|
713
|
+
if (!body)
|
|
714
|
+
return [];
|
|
715
|
+
const entries = [];
|
|
716
|
+
const lines = body.split(/\r\n|\r|\n/);
|
|
717
|
+
const headRe = new RegExp(spec.entryHeadingRe.source);
|
|
718
|
+
let i = 0;
|
|
719
|
+
while (i < lines.length) {
|
|
720
|
+
const line = lines[i];
|
|
721
|
+
if (headRe.test(line)) {
|
|
722
|
+
const detailLines = [];
|
|
723
|
+
let j = i + 1;
|
|
724
|
+
while (j < lines.length) {
|
|
725
|
+
const nxt = lines[j];
|
|
726
|
+
if (nxt.startsWith(" ") || nxt.startsWith("\t")) {
|
|
727
|
+
detailLines.push(nxt);
|
|
728
|
+
j += 1;
|
|
729
|
+
}
|
|
730
|
+
else if (nxt.trim() === "") {
|
|
731
|
+
if (j + 1 < lines.length && (lines[j + 1].startsWith(" ") || lines[j + 1].startsWith("\t"))) {
|
|
732
|
+
detailLines.push(nxt);
|
|
733
|
+
j += 1;
|
|
734
|
+
}
|
|
735
|
+
else {
|
|
736
|
+
break;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
else {
|
|
740
|
+
break;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
const bodyText = detailLines.join("\n").trim();
|
|
744
|
+
entries.push({ header: line.replace(/\s+$/, ""), body: bodyText, kind: bodyText ? "full" : "oneline" });
|
|
745
|
+
i = j;
|
|
746
|
+
}
|
|
747
|
+
else {
|
|
748
|
+
i += 1;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
return entries;
|
|
752
|
+
}
|
|
753
|
+
function entryNumber(entry) {
|
|
754
|
+
const m = NUMBER_RE.exec(entry.header);
|
|
755
|
+
return m ? parseInt(m[1], 10) : 0;
|
|
756
|
+
}
|
|
757
|
+
function detectDirection(entries) {
|
|
758
|
+
let asc = 0;
|
|
759
|
+
let desc = 0;
|
|
760
|
+
for (let i = 0; i < entries.length - 1; i++) {
|
|
761
|
+
const a = entryNumber(entries[i]);
|
|
762
|
+
const b = entryNumber(entries[i + 1]);
|
|
763
|
+
if (a === 0 || b === 0 || a === b)
|
|
764
|
+
continue;
|
|
765
|
+
if (a < b)
|
|
766
|
+
asc += 1;
|
|
767
|
+
else
|
|
768
|
+
desc += 1;
|
|
769
|
+
}
|
|
770
|
+
if (asc === 0 && desc === 0)
|
|
771
|
+
return "descending";
|
|
772
|
+
return asc > desc ? "ascending" : "descending";
|
|
773
|
+
}
|
|
774
|
+
export function compactEntries(entries, maxFull = MAX_FULL_ENTRIES, maxOneline = MAX_ONELINE_ENTRIES, formatOneline = null) {
|
|
775
|
+
const maxTotal = maxFull + maxOneline;
|
|
776
|
+
if (entries.length === 0)
|
|
777
|
+
return [];
|
|
778
|
+
const ascending = detectDirection(entries) === "ascending";
|
|
779
|
+
const newestFirst = stableSortBy(entries, entryNumber, true);
|
|
780
|
+
const full = [];
|
|
781
|
+
const archive = [];
|
|
782
|
+
newestFirst.forEach((entry, i) => {
|
|
783
|
+
if (i < maxFull) {
|
|
784
|
+
full.push(entry);
|
|
785
|
+
}
|
|
786
|
+
else if (i < maxTotal) {
|
|
787
|
+
if (entry.kind === "full" && formatOneline !== null) {
|
|
788
|
+
archive.push({ header: formatOneline(entry), body: "", kind: "oneline" });
|
|
789
|
+
}
|
|
790
|
+
else {
|
|
791
|
+
archive.push(entry);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
const result = applyRetentionCaps(full, archive, { maxFull, maxOneline, maxTotal });
|
|
796
|
+
if (ascending)
|
|
797
|
+
result.reverse();
|
|
798
|
+
return result;
|
|
799
|
+
}
|
|
800
|
+
function compactTodoEntries(entries) {
|
|
801
|
+
const result = [];
|
|
802
|
+
let fullCount = 0;
|
|
803
|
+
let onelineCount = 0;
|
|
804
|
+
for (const entry of entries) {
|
|
805
|
+
if (entry.kind === "full" && fullCount < MAX_FULL_ENTRIES) {
|
|
806
|
+
result.push(entry);
|
|
807
|
+
fullCount += 1;
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
if (onelineCount < MAX_ONELINE_ENTRIES) {
|
|
811
|
+
if (entry.kind === "full") {
|
|
812
|
+
result.push({ header: formatTodoOneline(entry), body: "", kind: "oneline" });
|
|
813
|
+
}
|
|
814
|
+
else {
|
|
815
|
+
result.push(entry);
|
|
816
|
+
}
|
|
817
|
+
onelineCount += 1;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
return result;
|
|
821
|
+
}
|
|
822
|
+
function formatProgressLike(headerPrefix, entries, spec) {
|
|
823
|
+
const lines = [];
|
|
824
|
+
if (headerPrefix.trim()) {
|
|
825
|
+
lines.push(headerPrefix.replace(/\s+$/, ""));
|
|
826
|
+
lines.push("");
|
|
827
|
+
}
|
|
828
|
+
const fullEntries = entries.filter((e) => e.kind === "full");
|
|
829
|
+
const onelineEntries = entries.filter((e) => e.kind === "oneline");
|
|
830
|
+
for (const entry of fullEntries) {
|
|
831
|
+
const header = entry.header;
|
|
832
|
+
const glyphMatch = /^(■)\s+(.*)$/.exec(header);
|
|
833
|
+
if (glyphMatch) {
|
|
834
|
+
lines.push(`${glyphMatch[1]} ## ${glyphMatch[2]}`);
|
|
835
|
+
}
|
|
836
|
+
else {
|
|
837
|
+
lines.push(`## ${header}`);
|
|
838
|
+
}
|
|
839
|
+
if (entry.body) {
|
|
840
|
+
lines.push("");
|
|
841
|
+
lines.push(entry.body);
|
|
842
|
+
}
|
|
843
|
+
lines.push("");
|
|
844
|
+
}
|
|
845
|
+
if (onelineEntries.length > 0 && spec.archiveHeading) {
|
|
846
|
+
lines.push(spec.archiveHeading);
|
|
847
|
+
lines.push("");
|
|
848
|
+
for (const entry of onelineEntries) {
|
|
849
|
+
lines.push(entry.header);
|
|
850
|
+
}
|
|
851
|
+
lines.push("");
|
|
852
|
+
}
|
|
853
|
+
return lines.join("\n").replace(/\s+$/, "") + "\n";
|
|
854
|
+
}
|
|
855
|
+
function extractHeaderPrefix(text, spec) {
|
|
856
|
+
const firstEntry = new RegExp(spec.entryHeadingRe.source, "m").exec(text);
|
|
857
|
+
let firstArchiveIdx = -1;
|
|
858
|
+
if (spec.archiveHeading) {
|
|
859
|
+
const archiveMatch = new RegExp(`^${escapeRe(spec.archiveHeading)}\\s*$`, "m").exec(text);
|
|
860
|
+
if (archiveMatch)
|
|
861
|
+
firstArchiveIdx = archiveMatch.index;
|
|
862
|
+
}
|
|
863
|
+
const candidates = [firstEntry ? firstEntry.index : -1, firstArchiveIdx].filter((c) => c >= 0);
|
|
864
|
+
if (candidates.length === 0)
|
|
865
|
+
return text.replace(/\s+$/, "");
|
|
866
|
+
return text.slice(0, Math.min(...candidates)).replace(/\s+$/, "");
|
|
867
|
+
}
|
|
868
|
+
function compactTodoResolved(p) {
|
|
869
|
+
const spec = SPECS["todo-resolved"];
|
|
870
|
+
const text = fs.readFileSync(p, "utf8");
|
|
871
|
+
const [start, end, body] = extractResolvedSection(text);
|
|
872
|
+
if (start < 0)
|
|
873
|
+
return { full_before: 0, oneline_before: 0, full_after: 0, oneline_after: 0, dropped: 0, changed: false };
|
|
874
|
+
const entries = parseTodoResolved(text, spec);
|
|
875
|
+
const fullBefore = entries.filter((e) => e.kind === "full").length;
|
|
876
|
+
const onelineBefore = entries.filter((e) => e.kind === "oneline").length;
|
|
877
|
+
const totalBefore = entries.length;
|
|
878
|
+
if (totalBefore <= MAX_TOTAL_ENTRIES && fullBefore <= MAX_FULL_ENTRIES && onelineBefore <= MAX_ONELINE_ENTRIES) {
|
|
879
|
+
return { full_before: fullBefore, oneline_before: onelineBefore, full_after: fullBefore, oneline_after: onelineBefore, dropped: 0, changed: false };
|
|
880
|
+
}
|
|
881
|
+
const compacted = compactTodoEntries(entries);
|
|
882
|
+
const fullAfter = compacted.filter((e) => e.kind === "full").length;
|
|
883
|
+
const onelineAfter = compacted.filter((e) => e.kind === "oneline").length;
|
|
884
|
+
const dropped = totalBefore - compacted.length;
|
|
885
|
+
const newLines = [];
|
|
886
|
+
for (const entry of compacted) {
|
|
887
|
+
if (entry.kind === "full") {
|
|
888
|
+
newLines.push(entry.header);
|
|
889
|
+
if (entry.body)
|
|
890
|
+
newLines.push(entry.body);
|
|
891
|
+
}
|
|
892
|
+
else {
|
|
893
|
+
newLines.push(spec.formatOneline(entry));
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
const newBody = newLines.join("\n") + "\n";
|
|
897
|
+
const headingEnd = text.indexOf("\n", start) + 1;
|
|
898
|
+
const newText = text.slice(0, headingEnd) + "\n" + newBody + text.slice(end);
|
|
899
|
+
fs.writeFileSync(p, newText);
|
|
900
|
+
return { full_before: fullBefore, oneline_before: onelineBefore, full_after: fullAfter, oneline_after: onelineAfter, dropped, changed: true };
|
|
901
|
+
}
|
|
902
|
+
export function compactFile(p, specName) {
|
|
903
|
+
if (!(specName in SPECS))
|
|
904
|
+
throw new Error(`unknown spec: ${specName}`);
|
|
905
|
+
if (!fs.existsSync(p))
|
|
906
|
+
throw new Error(p);
|
|
907
|
+
const spec = SPECS[specName];
|
|
908
|
+
if (spec.name === "todo-resolved") {
|
|
909
|
+
return compactTodoResolved(p);
|
|
910
|
+
}
|
|
911
|
+
const text = fs.readFileSync(p, "utf8");
|
|
912
|
+
const entries = parseEntries(text, specName);
|
|
913
|
+
const fullBefore = entries.filter((e) => e.kind === "full").length;
|
|
914
|
+
const onelineBefore = entries.filter((e) => e.kind === "oneline").length;
|
|
915
|
+
const totalBefore = fullBefore + onelineBefore;
|
|
916
|
+
const needsCompact = fullBefore > MAX_FULL_ENTRIES || onelineBefore > MAX_ONELINE_ENTRIES || totalBefore > MAX_FULL_ENTRIES + MAX_ONELINE_ENTRIES;
|
|
917
|
+
if (!needsCompact) {
|
|
918
|
+
return { full_before: fullBefore, oneline_before: onelineBefore, full_after: fullBefore, oneline_after: onelineBefore, dropped: 0, changed: false };
|
|
919
|
+
}
|
|
920
|
+
const compacted = compactEntries(entries, MAX_FULL_ENTRIES, MAX_ONELINE_ENTRIES, spec.formatOneline);
|
|
921
|
+
const fullAfter = compacted.filter((e) => e.kind === "full").length;
|
|
922
|
+
const onelineAfter = compacted.filter((e) => e.kind === "oneline").length;
|
|
923
|
+
const dropped = totalBefore - compacted.length;
|
|
924
|
+
const headerPrefix = extractHeaderPrefix(text, spec);
|
|
925
|
+
const newText = formatProgressLike(headerPrefix, compacted, spec);
|
|
926
|
+
fs.writeFileSync(p, newText);
|
|
927
|
+
return { full_before: fullBefore, oneline_before: onelineBefore, full_after: fullAfter, oneline_after: onelineAfter, dropped, changed: true };
|
|
928
|
+
}
|
|
929
|
+
export function detectOverflow(text, specName) {
|
|
930
|
+
const entries = parseEntries(text, specName);
|
|
931
|
+
const fullCount = entries.filter((e) => e.kind === "full").length;
|
|
932
|
+
const onelineCount = entries.filter((e) => e.kind === "oneline").length;
|
|
933
|
+
return [fullCount, onelineCount];
|
|
934
|
+
}
|
|
935
|
+
//# sourceMappingURL=compaction.js.map
|