@warpgogol/forge 2.8.0 → 2.8.2
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/AGENTS.md +1 -1
- package/README.md +23 -13
- package/README.uk.md +19 -12
- package/dist/os/session/handlers/validate.d.ts.map +1 -1
- package/dist/os/session/handlers/validate.js +23 -1
- package/dist/os/session/handlers/validate.js.map +1 -1
- package/dist/os/session/types.d.ts +31 -1
- package/dist/os/session/types.d.ts.map +1 -1
- package/dist/os/session/types.js +7 -0
- package/dist/os/session/types.js.map +1 -1
- package/os/adr/adr.module.ts +158 -0
- package/os/adr/frontmatter-io.ts +80 -0
- package/os/adr/handlers/archive.ts +244 -0
- package/os/adr/handlers/implement-stamp.ts +331 -0
- package/os/adr/handlers/list-create.ts +207 -0
- package/os/adr/handlers/validate.test.ts +232 -0
- package/os/adr/handlers/validate.ts +429 -0
- package/os/adr/index.ts +33 -0
- package/os/adr/types.ts +141 -0
- package/os/audit/audit.module.ts +48 -0
- package/os/audit/frontmatter-io.ts +84 -0
- package/os/audit/handlers/archive.ts +239 -0
- package/os/audit/index.ts +23 -0
- package/os/audit/types.ts +29 -0
- package/os/compass/compass.module.ts +186 -0
- package/os/compass/handlers/compass-audit-handler.ts +382 -0
- package/os/compass/handlers/compass-change-summary-handler.ts +272 -0
- package/os/compass/handlers/compass-inventory-handler.ts +294 -0
- package/os/compass/handlers/compass-inventory.ts +521 -0
- package/os/compass/handlers/git-revision.ts +137 -0
- package/os/compass/handlers/resolve-scan-root-workpiece.test.ts +65 -0
- package/os/compass/handlers/resolve-scan-root.ts +79 -0
- package/os/compass/index.ts +33 -0
- package/os/core/core.module.ts +948 -0
- package/os/core/handlers/assets-check.ts +122 -0
- package/os/core/handlers/assets-helpers.ts +174 -0
- package/os/core/handlers/assets-list.ts +99 -0
- package/os/core/handlers/build.ts +155 -0
- package/os/core/handlers/determinism-check.ts +354 -0
- package/os/core/handlers/dev.ts +127 -0
- package/os/core/handlers/invariant-engine.test.ts +681 -0
- package/os/core/handlers/knowledge-compact.ts +248 -0
- package/os/core/handlers/lifecycle-handlers.test.ts +524 -0
- package/os/core/handlers/note-frontmatter-validate.test.ts +93 -0
- package/os/core/handlers/note-link-validate.test.ts +117 -0
- package/os/core/handlers/note-orphan-detect.test.ts +94 -0
- package/os/core/handlers/package-health.test.ts +226 -0
- package/os/core/handlers/package-health.ts +231 -0
- package/os/core/handlers/pinned-check.ts +200 -0
- package/os/core/handlers/pinned-init.ts +333 -0
- package/os/core/handlers/pinned-types.ts +50 -0
- package/os/core/handlers/pinned-validate.ts +301 -0
- package/os/core/handlers/profile-resolve.ts +92 -0
- package/os/core/handlers/release-prepare.ts +294 -0
- package/os/core/handlers/release-publish.ts +211 -0
- package/os/core/handlers/validate.ts +262 -0
- package/os/core/handlers/workspace-deps.ts +55 -0
- package/os/core/index.ts +13 -0
- package/os/exploration/exploration.module.ts +90 -0
- package/os/exploration/frontmatter-io.ts +77 -0
- package/os/exploration/handlers/archive.ts +159 -0
- package/os/exploration/handlers/list.ts +77 -0
- package/os/exploration/handlers/show.ts +107 -0
- package/os/exploration/index.ts +25 -0
- package/os/exploration/types.ts +65 -0
- package/os/mission/handlers/archive.test.ts +391 -0
- package/os/mission/handlers/archive.ts +453 -0
- package/os/mission/index.ts +1 -0
- package/os/mission/mission.module.ts +48 -0
- package/os/mission/types.ts +41 -0
- package/os/naming/index.ts +14 -0
- package/os/naming/naming-convention.test.ts +163 -0
- package/os/naming/naming-convention.ts +378 -0
- package/os/naming/naming.module.ts +36 -0
- package/os/notes/index.ts +10 -0
- package/os/notes/notes.module.ts +109 -0
- package/os/plan/frontmatter-io.ts +82 -0
- package/os/plan/handlers/archive.ts +238 -0
- package/os/plan/index.ts +23 -0
- package/os/plan/plan.module.ts +48 -0
- package/os/plan/types.ts +29 -0
- package/os/program/discovery.ts +294 -0
- package/os/program/handlers/complete.ts +419 -0
- package/os/program/handlers/lease.ts +433 -0
- package/os/program/handlers/seal.ts +332 -0
- package/os/program/handlers/validate.ts +213 -0
- package/os/program/lease.ts +156 -0
- package/os/program/program.module.ts +212 -0
- package/os/program/schemas.ts +218 -0
- package/os/program/state.ts +264 -0
- package/os/rfc/acceptance.ts +344 -0
- package/os/rfc/decision-log.ts +310 -0
- package/os/rfc/dna-trace.ts +280 -0
- package/os/rfc/frontmatter-io.test.ts +132 -0
- package/os/rfc/frontmatter-io.ts +130 -0
- package/os/rfc/handlers/archive.ts +300 -0
- package/os/rfc/handlers/check.ts +238 -0
- package/os/rfc/handlers/implement-stamp.ts +515 -0
- package/os/rfc/handlers/index-graph.ts +237 -0
- package/os/rfc/handlers/lifecycle.test.ts +127 -0
- package/os/rfc/handlers/lifecycle.ts +249 -0
- package/os/rfc/handlers/list-create.ts +296 -0
- package/os/rfc/handlers/pipeline-status.ts +173 -0
- package/os/rfc/handlers/shared.ts +121 -0
- package/os/rfc/handlers/supersede-propose.ts +241 -0
- package/os/rfc/handlers/validate-rules.test.ts +891 -0
- package/os/rfc/handlers/validate-rules.ts +984 -0
- package/os/rfc/handlers/validate.ts +166 -0
- package/os/rfc/handlers.ts +19 -0
- package/os/rfc/index.ts +97 -0
- package/os/rfc/rfc.module.ts +416 -0
- package/os/rfc/types.ts +596 -0
- package/os/rfc/verification-evidence.ts +244 -0
- package/os/session/atif-parser.ts +166 -0
- package/os/session/frontmatter-io.ts +163 -0
- package/os/session/handlers/archive.ts +288 -0
- package/os/session/handlers/list.ts +174 -0
- package/os/session/handlers/save.ts +348 -0
- package/os/session/handlers/validate.ts +250 -0
- package/os/session/index.ts +50 -0
- package/os/session/session.module.ts +126 -0
- package/os/session/types.ts +203 -0
- package/os/spec/live-spec-list-show-validate.test.ts +257 -0
- package/os/spec/live-spec-list.ts +78 -0
- package/os/spec/live-spec-merge.test.ts +261 -0
- package/os/spec/live-spec-merge.ts +413 -0
- package/os/spec/live-spec-show.ts +117 -0
- package/os/spec/live-spec-types.ts +93 -0
- package/os/spec/live-spec-validate.ts +170 -0
- package/os/spec/spec-materialize.ts +390 -0
- package/os/spec/spec-schema.ts +174 -0
- package/os/spec/spec-status.ts +284 -0
- package/os/spec/spec-validate.ts +513 -0
- package/os/spec/spec.module.ts +135 -0
- package/os/werkstatt/handlers/lock.ts +168 -0
- package/os/werkstatt/handlers/schema.ts +47 -0
- package/os/werkstatt/handlers/werkstatt-lock-recover.ts +175 -0
- package/os/werkstatt/handlers/werkstatt-lock-status.ts +73 -0
- package/os/werkstatt/handlers/werkstatt-operation-validate.ts +104 -0
- package/os/werkstatt/index.ts +39 -0
- package/os/werkstatt/werkstatt.module.ts +62 -0
- package/os/workflow/handlers.ts +304 -0
- package/os/workflow/index.ts +28 -0
- package/os/workflow/types.ts +99 -0
- package/os/workflow/workflow.module.ts +54 -0
- package/package.json +3 -3
- package/profiles/godot-csharp.yaml +4 -4
- package/profiles/knowledge-typescript-turborepo.yaml +110 -0
- package/profiles/phaser-turborepo.yaml +5 -5
- package/skills/_shared/fo-session-summary.md +132 -16
- package/skills/fo/fo-add-tests/pbt-guide.md +13 -13
- package/skills/fo/fo-doc-audit/SKILL.md +6 -6
- package/skills/fo/fo-handoff/SKILL.md +5 -0
- package/skills/fo/fo-idea/SKILL.md +1 -1
- package/skills/fo/fo-review/SKILL.md +1 -1
- package/skills/fo/fo-session-retro/SKILL.md +8 -0
- package/skills/fo/fo-session-save/SKILL.md +8 -0
- package/src/cli-output.ts +59 -0
- package/src/config/__tests__/resolve-terminology.test.ts +45 -0
- package/src/config/forge-config.ts +524 -0
- package/src/forge-module.ts +39 -0
- package/src/index.ts +172 -0
- package/src/knowledge/__tests__/promote.test.ts +219 -0
- package/src/knowledge/__tests__/serialize.test.ts +192 -0
- package/src/knowledge/budgets.ts +202 -0
- package/src/knowledge/compact.ts +405 -0
- package/src/knowledge/index.ts +53 -0
- package/src/knowledge/parse.ts +227 -0
- package/src/knowledge/promote.ts +156 -0
- package/src/knowledge/schema.ts +112 -0
- package/src/knowledge/serialize.ts +72 -0
- package/src/migration-adapters/__tests__/ignored-files.test.ts +51 -0
- package/src/migration-adapters/__tests__/registry.test.ts +131 -0
- package/src/migration-adapters/__tests__/types.test.ts +86 -0
- package/src/migration-adapters/git-utils.ts +71 -0
- package/src/migration-adapters/ignored-files.ts +221 -0
- package/src/migration-adapters/index.ts +21 -0
- package/src/migration-adapters/node-typescript-pnpm/index.ts +189 -0
- package/src/migration-adapters/phaser-pnpm/index.ts +189 -0
- package/src/migration-adapters/registry.ts +61 -0
- package/src/migration-adapters/types.ts +68 -0
- package/src/onboarding/__tests__/workspace-discovery.test.ts +134 -0
- package/src/onboarding/agents-generate.ts +443 -0
- package/src/onboarding/create.ts +427 -0
- package/src/onboarding/doctor.ts +1297 -0
- package/src/onboarding/init.ts +337 -0
- package/src/onboarding/invariant-engine.ts +448 -0
- package/src/onboarding/memory-scaffold.ts +189 -0
- package/src/onboarding/nested-agents-generate.ts +93 -0
- package/src/onboarding/nested-agents-templates.ts +233 -0
- package/src/onboarding/profile-validate.ts +139 -0
- package/src/onboarding/scaffold-project.ts +299 -0
- package/src/onboarding/scaffold.ts +143 -0
- package/src/onboarding/upgrade.ts +558 -0
- package/src/onboarding/workspace-discovery.ts +167 -0
- package/src/profiles/__tests__/profile-schema.test.ts +228 -0
- package/src/profiles/__tests__/stack-profile.test.ts +190 -0
- package/src/profiles/__tests__/terminology-utils.test.ts +71 -0
- package/src/profiles/profile-schema.ts +350 -0
- package/src/profiles/stack-profile.ts +188 -0
- package/src/profiles/terminology-utils.ts +36 -0
- package/src/registry.ts +185 -0
- package/src/skill-schema.ts +37 -0
- package/src/tests/acceptance-criteria.test.ts +99 -0
- package/src/tests/adr-implement-stamp.test.ts +368 -0
- package/src/tests/agents-generate-domain.test.ts +317 -0
- package/src/tests/agents-generate.test.ts +458 -0
- package/src/tests/bindings-schema.test.ts +314 -0
- package/src/tests/budgets.test.ts +213 -0
- package/src/tests/cli-output.test.ts +196 -0
- package/src/tests/compact.test.ts +796 -0
- package/src/tests/create.test.ts +289 -0
- package/src/tests/doctor-autonomy.test.ts +98 -0
- package/src/tests/doctor-bindings.test.ts +286 -0
- package/src/tests/doctor-domain.test.ts +113 -0
- package/src/tests/exploration-handlers.test.ts +256 -0
- package/src/tests/fixtures/agents-generate-business-before.txt +288 -0
- package/src/tests/forge-config.test.ts +420 -0
- package/src/tests/fs-atomic.test.ts +96 -0
- package/src/tests/fs.test.ts +103 -0
- package/src/tests/generated-marker.test.ts +161 -0
- package/src/tests/hash.test.ts +37 -0
- package/src/tests/implement-stamp.test.ts +505 -0
- package/src/tests/init-bindings.test.ts +183 -0
- package/src/tests/knowledge-parse.test.ts +307 -0
- package/src/tests/knowledge-pbt.test.ts +164 -0
- package/src/tests/memory-scaffold.test.ts +192 -0
- package/src/tests/migration-adapters.test.ts +480 -0
- package/src/tests/package-files.test.ts +54 -0
- package/src/tests/pinned-check.test.ts +194 -0
- package/src/tests/pinned-init.test.ts +126 -0
- package/src/tests/profile-schema.test.ts +194 -0
- package/src/tests/profile-validate.test.ts +120 -0
- package/src/tests/program-lease.test.ts +245 -0
- package/src/tests/program-paths-extended.test.ts +117 -0
- package/src/tests/program-paths.test.ts +98 -0
- package/src/tests/program-property.test.ts +139 -0
- package/src/tests/program-schemas.test.ts +445 -0
- package/src/tests/program-spec-node.test.ts +250 -0
- package/src/tests/promote.test.ts +481 -0
- package/src/tests/registry.test.ts +73 -0
- package/src/tests/scaffold-project.test.ts +150 -0
- package/src/tests/session-handlers.test.ts +503 -0
- package/src/tests/session-pbt.test.ts +190 -0
- package/src/tests/skill-schema.test.ts +146 -0
- package/src/tests/skill-validate-knowledge.test.ts +26 -0
- package/src/tests/skill-validate.test.ts +254 -0
- package/src/tests/stack-profile.test.ts +221 -0
- package/src/tests/string-utils.test.ts +42 -0
- package/src/tests/upgrade.test.ts +428 -0
- package/src/tests/werkstatt-lock.test.ts +343 -0
- package/src/tests/workspace-discovery-domain.test.ts +112 -0
- package/src/tests/workspace-discovery.test.ts +135 -0
- package/src/types.ts +221 -0
- package/src/utils/__tests__/fs-idempotent.test.ts +73 -0
- package/src/utils/__tests__/fs-trash.test.ts +45 -0
- package/src/utils/fs-atomic.ts +93 -0
- package/src/utils/fs-idempotent.ts +41 -0
- package/src/utils/fs-trash-sync.ts +37 -0
- package/src/utils/fs-trash.ts +24 -0
- package/src/utils/fs.ts +74 -0
- package/src/utils/generated-marker.ts +166 -0
- package/src/utils/hash.ts +19 -0
- package/src/utils/index.ts +29 -0
- package/src/utils/string-utils.ts +19 -0
- package/src/validators/__tests__/note-orphan-detect.test.ts +94 -0
- package/src/validators/note-frontmatter-validate.ts +142 -0
- package/src/validators/note-link-validate.ts +145 -0
- package/src/validators/note-orphan-detect.ts +146 -0
- package/src/validators/port-validate.ts +97 -0
- package/src/validators/skill-validate.ts +831 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Knowledge layer character budget computation and resolution — pure functions for hot/warm budget enforcement (RFC-0661).</purpose>
|
|
4
|
+
<non-goals>
|
|
5
|
+
<item>Do not emit warnings or violations — that is handled by SKILL-21 in skill-validate.ts.</item>
|
|
6
|
+
<item>Do not parse knowledge files — that is handled by parse.ts (RFC-0660).</item>
|
|
7
|
+
<item>Do not validate budget override shape — that is handled by forge.doctor.</item>
|
|
8
|
+
</non-goals>
|
|
9
|
+
</MODULE_CONTRACT>
|
|
10
|
+
<CHANGE_SUMMARY>
|
|
11
|
+
<item>RFC-0661: initial budget module with KnowledgeBudgets, LayerBudgetReport, computeLayerBudgets, resolveKnowledgeBudgets.</item>
|
|
12
|
+
</CHANGE_SUMMARY>
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
import { readFileSync } from "node:fs";
|
|
17
|
+
import { parse as parseYaml } from "yaml";
|
|
18
|
+
import type { ParsedKnowledgeFile, KnowledgeLayer } from "./schema.ts";
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Types (RFC-0661)
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
export interface KnowledgeBudgets {
|
|
25
|
+
hot: number; // default 4096 — L2 (learned-principles.md)
|
|
26
|
+
warm: number; // default 8192 — L1 (fix-patterns.md)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface LayerBudgetReport {
|
|
30
|
+
skill: string;
|
|
31
|
+
file: string;
|
|
32
|
+
layer: KnowledgeLayer;
|
|
33
|
+
activeChars: number;
|
|
34
|
+
budget: number;
|
|
35
|
+
exceededBy: number; // 0 when within budget
|
|
36
|
+
pack?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Defaults
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
export const DEFAULT_KNOWLEDGE_BUDGETS: KnowledgeBudgets = {
|
|
44
|
+
hot: 4096,
|
|
45
|
+
warm: 8192,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// computeLayerBudgets — pure function
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Compute per-file budget reports for parsed knowledge files.
|
|
54
|
+
*
|
|
55
|
+
* - Counts only `status: active` entries (heading + metadata block + body).
|
|
56
|
+
* - Uses `ParsedKnowledgeFile.layer` to determine which budget applies.
|
|
57
|
+
* - Skips files with undeterminable layer (null) or L0 (cold, no budget).
|
|
58
|
+
* - Skips files with parse issues (schema errors are already reported by SKILL-19).
|
|
59
|
+
*
|
|
60
|
+
* Pure: no filesystem side effects.
|
|
61
|
+
*/
|
|
62
|
+
export function computeLayerBudgets(
|
|
63
|
+
files: ParsedKnowledgeFile[],
|
|
64
|
+
budgets: KnowledgeBudgets,
|
|
65
|
+
skillNames: Map<string, string>,
|
|
66
|
+
): LayerBudgetReport[] {
|
|
67
|
+
const reports: LayerBudgetReport[] = [];
|
|
68
|
+
|
|
69
|
+
for (const file of files) {
|
|
70
|
+
// Skip files with undeterminable layer
|
|
71
|
+
if (file.layer === null) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Skip cold layer (L0) — no budget by design
|
|
76
|
+
if (file.layer === "L0") {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Skip files with parse issues — SKILL-19 already reports those
|
|
81
|
+
if (file.parseIssues.length > 0) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Skip knowledge-adjacent files (no structured entries)
|
|
86
|
+
if (file.isKnowledgeAdjacent) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const budget = file.layer === "L2" ? budgets.hot : budgets.warm;
|
|
91
|
+
const fileName = path.basename(file.path);
|
|
92
|
+
const skillName = skillNames.get(file.path) ?? path.basename(path.dirname(file.path));
|
|
93
|
+
|
|
94
|
+
// Count only active entries: heading + metadata block + body
|
|
95
|
+
let activeChars = 0;
|
|
96
|
+
for (const entry of file.entries) {
|
|
97
|
+
if (entry.meta.status !== "active") {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
// Heading: "### K-XXXX: <title>\n"
|
|
101
|
+
activeChars += `### ${entry.meta.id}: ${entry.title}\n`.length;
|
|
102
|
+
// Metadata block: ```knowledge-entry\n<yaml>\n```\n
|
|
103
|
+
const metaYaml = approximateMetaCharCount(entry.meta);
|
|
104
|
+
activeChars += "```knowledge-entry\n".length;
|
|
105
|
+
activeChars += metaYaml.length;
|
|
106
|
+
activeChars += "```\n".length;
|
|
107
|
+
// Body
|
|
108
|
+
activeChars += entry.body.length;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const exceededBy = Math.max(0, activeChars - budget);
|
|
112
|
+
|
|
113
|
+
reports.push({
|
|
114
|
+
skill: skillName,
|
|
115
|
+
file: fileName,
|
|
116
|
+
layer: file.layer,
|
|
117
|
+
activeChars,
|
|
118
|
+
budget,
|
|
119
|
+
exceededBy,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return reports;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
// resolveKnowledgeBudgets — reads forge.yaml
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Read effective knowledge budgets from forge.yaml bindings.
|
|
132
|
+
*
|
|
133
|
+
* Falls back to defaults (hot: 4096, warm: 8192) when:
|
|
134
|
+
* - forge.yaml is not found
|
|
135
|
+
* - bindings.knowledge.budgets is absent
|
|
136
|
+
* - override values are invalid (non-positive, non-integer)
|
|
137
|
+
*
|
|
138
|
+
* Does NOT validate the override shape — forge.doctor handles that separately.
|
|
139
|
+
*/
|
|
140
|
+
export function resolveKnowledgeBudgets(workspaceRoot: string): KnowledgeBudgets {
|
|
141
|
+
try {
|
|
142
|
+
const forgeYamlPath = path.join(workspaceRoot, "forge.yaml");
|
|
143
|
+
const content = readFileSync(forgeYamlPath, "utf8");
|
|
144
|
+
const config = parseYaml(content) as Record<string, unknown>;
|
|
145
|
+
const bindings = config?.bindings as Record<string, unknown> | undefined;
|
|
146
|
+
const knowledge = bindings?.knowledge as Record<string, unknown> | undefined;
|
|
147
|
+
const budgets = knowledge?.budgets as { hot?: unknown; warm?: unknown } | undefined;
|
|
148
|
+
|
|
149
|
+
if (!budgets) {
|
|
150
|
+
return { ...DEFAULT_KNOWLEDGE_BUDGETS };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const hot =
|
|
154
|
+
typeof budgets.hot === "number" && budgets.hot > 0 && Number.isInteger(budgets.hot)
|
|
155
|
+
? budgets.hot
|
|
156
|
+
: DEFAULT_KNOWLEDGE_BUDGETS.hot;
|
|
157
|
+
const warm =
|
|
158
|
+
typeof budgets.warm === "number" && budgets.warm > 0 && Number.isInteger(budgets.warm)
|
|
159
|
+
? budgets.warm
|
|
160
|
+
: DEFAULT_KNOWLEDGE_BUDGETS.warm;
|
|
161
|
+
|
|
162
|
+
return { hot, warm };
|
|
163
|
+
} catch {
|
|
164
|
+
return { ...DEFAULT_KNOWLEDGE_BUDGETS };
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
// Helper: approximate metadata character count for budget measurement
|
|
170
|
+
// ---------------------------------------------------------------------------
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Approximate metadata character count for budget purposes.
|
|
174
|
+
*
|
|
175
|
+
* This is NOT exact YAML serialization — it produces a line-per-field
|
|
176
|
+
* approximation that is deterministic and sufficient for character budget
|
|
177
|
+
* measurement. The RFC explicitly rejects tokenizer-accurate measurement;
|
|
178
|
+
* this approximation is the documented proxy.
|
|
179
|
+
*/
|
|
180
|
+
function approximateMetaCharCount(meta: ParsedKnowledgeFile["entries"][number]["meta"]): string {
|
|
181
|
+
const lines: string[] = [];
|
|
182
|
+
lines.push(`id: ${meta.id}`);
|
|
183
|
+
lines.push(`layer: ${meta.layer}`);
|
|
184
|
+
lines.push(`created: ${meta.created}`);
|
|
185
|
+
if (meta.lastConfirmedAt !== undefined) {
|
|
186
|
+
lines.push(`lastConfirmedAt: ${meta.lastConfirmedAt ?? "null"}`);
|
|
187
|
+
}
|
|
188
|
+
if (meta.confirmations !== undefined) {
|
|
189
|
+
lines.push(`confirmations: ${meta.confirmations}`);
|
|
190
|
+
}
|
|
191
|
+
if (meta.expiresAt !== undefined) {
|
|
192
|
+
lines.push(`expiresAt: ${meta.expiresAt ?? "null"}`);
|
|
193
|
+
}
|
|
194
|
+
if (meta.supersedes !== undefined) {
|
|
195
|
+
lines.push(`supersedes: [${meta.supersedes.join(", ")}]`);
|
|
196
|
+
}
|
|
197
|
+
if (meta.promotedTo !== undefined) {
|
|
198
|
+
lines.push(`promotedTo: ${meta.promotedTo ?? "null"}`);
|
|
199
|
+
}
|
|
200
|
+
lines.push(`status: ${meta.status}`);
|
|
201
|
+
return lines.join("\n") + "\n";
|
|
202
|
+
}
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Knowledge compaction planning and execution — archive aged/superseded entries, mark stale L2 principles (RFC-0662).</purpose>
|
|
4
|
+
<non-goals>
|
|
5
|
+
<item>Do not distill or group entries semantically — that is the fo-knowledge-distill skill's job.</item>
|
|
6
|
+
<item>Do not parse knowledge files — use parse.ts (RFC-0660).</item>
|
|
7
|
+
<item>Do not serialize — use serialize.ts (RFC-0660).</item>
|
|
8
|
+
<item>Do not wire into build pipelines or CI — invocation is operator-explicit.</item>
|
|
9
|
+
</non-goals>
|
|
10
|
+
</MODULE_CONTRACT>
|
|
11
|
+
<CHANGE_SUMMARY>
|
|
12
|
+
<item>RFC-0662: initial compaction module with planCompaction, executeCompaction, resolveRetentionDays, resolveStaleDays.</item>
|
|
13
|
+
</CHANGE_SUMMARY>
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import fs from "node:fs";
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
import { parse as parseYaml } from "yaml";
|
|
19
|
+
import type { ParsedKnowledgeFile, KnowledgeEntry, KnowledgeEntryMeta } from "./schema.ts";
|
|
20
|
+
import { parseKnowledgeFile } from "./parse.ts";
|
|
21
|
+
import { serializeKnowledgeFile } from "./serialize.ts";
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Types (RFC-0662)
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
export interface CompactOptions {
|
|
28
|
+
retentionDays: number;
|
|
29
|
+
staleDays: number;
|
|
30
|
+
today: string; // YYYY-MM-DD, injectable for tests
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type CompactActionKind =
|
|
34
|
+
"archive-expired" | "archive-superseded" | "archive-l0-retention" | "mark-stale";
|
|
35
|
+
|
|
36
|
+
export interface CompactAction {
|
|
37
|
+
kind: CompactActionKind;
|
|
38
|
+
file: string;
|
|
39
|
+
entryId: string;
|
|
40
|
+
reason: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface CompactFilePlan {
|
|
44
|
+
file: string;
|
|
45
|
+
archiveFile: string;
|
|
46
|
+
actions: CompactAction[];
|
|
47
|
+
legacySectionCount: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface CompactFileResult {
|
|
51
|
+
file: string;
|
|
52
|
+
archiveFile: string;
|
|
53
|
+
actions: CompactAction[];
|
|
54
|
+
legacySectionCount: number;
|
|
55
|
+
archived: number;
|
|
56
|
+
markedStale: number;
|
|
57
|
+
written: boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface CompactReport {
|
|
61
|
+
command: string;
|
|
62
|
+
status: "pass" | "fail";
|
|
63
|
+
dryRun: boolean;
|
|
64
|
+
files: CompactFileResult[];
|
|
65
|
+
totals: { archived: number; markedStale: number; legacyFiles: number };
|
|
66
|
+
errors?: string[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Defaults
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
export const DEFAULT_RETENTION_DAYS = 90;
|
|
74
|
+
export const DEFAULT_STALE_DAYS = 90;
|
|
75
|
+
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// Date helpers
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
function parseDate(dateStr: string): number {
|
|
81
|
+
const [y, m, d] = dateStr.split("-").map(Number);
|
|
82
|
+
return new Date(y, m - 1, d).getTime();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function daysBetween(from: string, to: string): number {
|
|
86
|
+
const ms = parseDate(to) - parseDate(from);
|
|
87
|
+
return Math.floor(ms / (1000 * 60 * 60 * 24));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
// Archive companion path
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
function archiveCompanionPath(filePath: string): string {
|
|
95
|
+
const dir = path.dirname(filePath);
|
|
96
|
+
const ext = path.extname(filePath);
|
|
97
|
+
const base = path.basename(filePath, ext);
|
|
98
|
+
return path.join(dir, `${base}.archive${ext}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
// planCompaction — pure function
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
export function planCompaction(
|
|
106
|
+
files: ParsedKnowledgeFile[],
|
|
107
|
+
options: CompactOptions,
|
|
108
|
+
): CompactFilePlan[] {
|
|
109
|
+
const plans: CompactFilePlan[] = [];
|
|
110
|
+
|
|
111
|
+
for (const file of files) {
|
|
112
|
+
// Skip knowledge-adjacent files (no structured entries)
|
|
113
|
+
if (file.isKnowledgeAdjacent) continue;
|
|
114
|
+
|
|
115
|
+
const actions: CompactAction[] = [];
|
|
116
|
+
|
|
117
|
+
for (const entry of file.entries) {
|
|
118
|
+
const meta = entry.meta;
|
|
119
|
+
|
|
120
|
+
// 1. Expiry archive (all layers)
|
|
121
|
+
if (meta.expiresAt !== undefined && meta.expiresAt !== null) {
|
|
122
|
+
const diff = daysBetween(meta.expiresAt, options.today);
|
|
123
|
+
if (diff > 0) {
|
|
124
|
+
actions.push({
|
|
125
|
+
kind: "archive-expired",
|
|
126
|
+
file: file.path,
|
|
127
|
+
entryId: meta.id,
|
|
128
|
+
reason: `expiresAt ${meta.expiresAt} is past today ${options.today}`,
|
|
129
|
+
});
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 2. Supersession archive (L1, L2)
|
|
135
|
+
if (meta.status === "superseded") {
|
|
136
|
+
actions.push({
|
|
137
|
+
kind: "archive-superseded",
|
|
138
|
+
file: file.path,
|
|
139
|
+
entryId: meta.id,
|
|
140
|
+
reason: `status is superseded`,
|
|
141
|
+
});
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 3. L0 retention archive
|
|
146
|
+
if (file.layer === "L0" && meta.status === "active") {
|
|
147
|
+
const age = daysBetween(meta.created, options.today);
|
|
148
|
+
if (age > options.retentionDays) {
|
|
149
|
+
actions.push({
|
|
150
|
+
kind: "archive-l0-retention",
|
|
151
|
+
file: file.path,
|
|
152
|
+
entryId: meta.id,
|
|
153
|
+
reason: `created ${meta.created} older than ${options.retentionDays} days`,
|
|
154
|
+
});
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// 4. L2 staleness marking
|
|
160
|
+
if (
|
|
161
|
+
file.layer === "L2" &&
|
|
162
|
+
meta.status === "active" &&
|
|
163
|
+
meta.lastConfirmedAt !== undefined &&
|
|
164
|
+
meta.lastConfirmedAt !== null
|
|
165
|
+
) {
|
|
166
|
+
const age = daysBetween(meta.lastConfirmedAt, options.today);
|
|
167
|
+
if (age > options.staleDays) {
|
|
168
|
+
actions.push({
|
|
169
|
+
kind: "mark-stale",
|
|
170
|
+
file: file.path,
|
|
171
|
+
entryId: meta.id,
|
|
172
|
+
reason: `lastConfirmedAt ${meta.lastConfirmedAt} older than ${options.staleDays} days`,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
plans.push({
|
|
179
|
+
file: file.path,
|
|
180
|
+
archiveFile: archiveCompanionPath(file.path),
|
|
181
|
+
actions,
|
|
182
|
+
legacySectionCount: file.legacySections.length,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return plans;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ---------------------------------------------------------------------------
|
|
190
|
+
// executeCompaction — I/O execution
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
|
|
193
|
+
export function executeCompaction(plans: CompactFilePlan[], dryRun: boolean): CompactReport {
|
|
194
|
+
const fileResults: CompactFileResult[] = [];
|
|
195
|
+
const errors: string[] = [];
|
|
196
|
+
let totalArchived = 0;
|
|
197
|
+
let totalMarkedStale = 0;
|
|
198
|
+
let totalLegacyFiles = 0;
|
|
199
|
+
|
|
200
|
+
for (const plan of plans) {
|
|
201
|
+
// Parse the live file
|
|
202
|
+
const parsed = parseKnowledgeFile(plan.file);
|
|
203
|
+
|
|
204
|
+
// Refuse if parse issues
|
|
205
|
+
if (parsed.parseIssues.length > 0) {
|
|
206
|
+
errors.push(
|
|
207
|
+
`${plan.file}: ${parsed.parseIssues.length} parse issue(s) — refusing to compact`,
|
|
208
|
+
);
|
|
209
|
+
fileResults.push({
|
|
210
|
+
file: plan.file,
|
|
211
|
+
archiveFile: plan.archiveFile,
|
|
212
|
+
actions: plan.actions,
|
|
213
|
+
legacySectionCount: plan.legacySectionCount,
|
|
214
|
+
archived: 0,
|
|
215
|
+
markedStale: 0,
|
|
216
|
+
written: false,
|
|
217
|
+
});
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Build action lookup
|
|
222
|
+
const actionByEntryId = new Map<string, CompactAction>();
|
|
223
|
+
for (const action of plan.actions) {
|
|
224
|
+
actionByEntryId.set(action.entryId, action);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Partition entries
|
|
228
|
+
const keepEntries: KnowledgeEntry[] = [];
|
|
229
|
+
const archiveEntries: KnowledgeEntry[] = [];
|
|
230
|
+
|
|
231
|
+
for (const entry of parsed.entries) {
|
|
232
|
+
const action = actionByEntryId.get(entry.meta.id);
|
|
233
|
+
if (action && action.kind.startsWith("archive-")) {
|
|
234
|
+
archiveEntries.push(entry);
|
|
235
|
+
} else if (action && action.kind === "mark-stale") {
|
|
236
|
+
// Keep in place, update status
|
|
237
|
+
keepEntries.push({
|
|
238
|
+
...entry,
|
|
239
|
+
meta: { ...entry.meta, status: "stale" },
|
|
240
|
+
});
|
|
241
|
+
} else {
|
|
242
|
+
keepEntries.push(entry);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Check archive companion for parse issues if it exists
|
|
247
|
+
if (archiveEntries.length > 0 && fs.existsSync(plan.archiveFile)) {
|
|
248
|
+
const archiveParsed = parseKnowledgeFile(plan.archiveFile);
|
|
249
|
+
if (archiveParsed.parseIssues.length > 0) {
|
|
250
|
+
errors.push(
|
|
251
|
+
`${plan.archiveFile}: ${archiveParsed.parseIssues.length} parse issue(s) — refusing to compact`,
|
|
252
|
+
);
|
|
253
|
+
fileResults.push({
|
|
254
|
+
file: plan.file,
|
|
255
|
+
archiveFile: plan.archiveFile,
|
|
256
|
+
actions: plan.actions,
|
|
257
|
+
legacySectionCount: plan.legacySectionCount,
|
|
258
|
+
archived: 0,
|
|
259
|
+
markedStale: 0,
|
|
260
|
+
written: false,
|
|
261
|
+
});
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const archived = archiveEntries.length;
|
|
267
|
+
const markedStale = plan.actions.filter((a) => a.kind === "mark-stale").length;
|
|
268
|
+
totalArchived += archived;
|
|
269
|
+
totalMarkedStale += markedStale;
|
|
270
|
+
if (plan.legacySectionCount > 0) totalLegacyFiles++;
|
|
271
|
+
|
|
272
|
+
if (dryRun) {
|
|
273
|
+
fileResults.push({
|
|
274
|
+
file: plan.file,
|
|
275
|
+
archiveFile: plan.archiveFile,
|
|
276
|
+
actions: plan.actions,
|
|
277
|
+
legacySectionCount: plan.legacySectionCount,
|
|
278
|
+
archived,
|
|
279
|
+
markedStale,
|
|
280
|
+
written: false,
|
|
281
|
+
});
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Write live file (only if there are changes)
|
|
286
|
+
if (plan.actions.length > 0) {
|
|
287
|
+
const liveContent = serializeKnowledgeFile({
|
|
288
|
+
...parsed,
|
|
289
|
+
entries: keepEntries,
|
|
290
|
+
});
|
|
291
|
+
try {
|
|
292
|
+
fs.writeFileSync(plan.file, liveContent, "utf8");
|
|
293
|
+
} catch (err) {
|
|
294
|
+
errors.push(`${plan.file}: write failed — ${(err as Error).message}`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Write archive companion (only if there are entries to archive)
|
|
299
|
+
if (archiveEntries.length > 0) {
|
|
300
|
+
let archiveParsed: ParsedKnowledgeFile;
|
|
301
|
+
if (fs.existsSync(plan.archiveFile)) {
|
|
302
|
+
archiveParsed = parseKnowledgeFile(plan.archiveFile);
|
|
303
|
+
} else {
|
|
304
|
+
// Create new archive file with a preamble
|
|
305
|
+
const fileName = path.basename(plan.file);
|
|
306
|
+
archiveParsed = {
|
|
307
|
+
path: plan.archiveFile,
|
|
308
|
+
layer: parsed.layer,
|
|
309
|
+
preamble: `<!-- knowledge-layer: ${parsed.layer ?? "L0"} -->\n# ${fileName} (archive)\n\nArchived entries from ${fileName}. Generated by forge.skill.knowledge.compact (RFC-0662).`,
|
|
310
|
+
entries: [],
|
|
311
|
+
legacySections: [],
|
|
312
|
+
parseIssues: [],
|
|
313
|
+
isKnowledgeAdjacent: false,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Rewrite status for archived entries
|
|
318
|
+
const archivedEntries: KnowledgeEntry[] = archiveEntries.map((entry) => {
|
|
319
|
+
const action = actionByEntryId.get(entry.meta.id)!;
|
|
320
|
+
const newMeta: KnowledgeEntryMeta = { ...entry.meta };
|
|
321
|
+
if (action.kind === "archive-expired" || action.kind === "archive-l0-retention") {
|
|
322
|
+
newMeta.status = "archived";
|
|
323
|
+
}
|
|
324
|
+
// For archive-superseded: keep status as "superseded" (preserves supersedes chain)
|
|
325
|
+
return { ...entry, meta: newMeta };
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
const mergedContent = serializeKnowledgeFile({
|
|
329
|
+
...archiveParsed,
|
|
330
|
+
entries: [...archiveParsed.entries, ...archivedEntries],
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
try {
|
|
334
|
+
// Use atomic write for archive companion
|
|
335
|
+
// writeFileAtomic is async, but we're in a sync context — use writeFileSync for simplicity
|
|
336
|
+
// The staging + rename pattern is handled by writeFileAtomic for production use
|
|
337
|
+
// For now, use direct write (the live file is already written non-atomically above)
|
|
338
|
+
fs.writeFileSync(plan.archiveFile, mergedContent, "utf8");
|
|
339
|
+
} catch (err) {
|
|
340
|
+
errors.push(`${plan.archiveFile}: write failed — ${(err as Error).message}`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
fileResults.push({
|
|
345
|
+
file: plan.file,
|
|
346
|
+
archiveFile: plan.archiveFile,
|
|
347
|
+
actions: plan.actions,
|
|
348
|
+
legacySectionCount: plan.legacySectionCount,
|
|
349
|
+
archived,
|
|
350
|
+
markedStale,
|
|
351
|
+
written: true,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
command: "forge.skill.knowledge.compact",
|
|
357
|
+
status: errors.length > 0 ? "fail" : "pass",
|
|
358
|
+
dryRun,
|
|
359
|
+
files: fileResults,
|
|
360
|
+
totals: {
|
|
361
|
+
archived: totalArchived,
|
|
362
|
+
markedStale: totalMarkedStale,
|
|
363
|
+
legacyFiles: totalLegacyFiles,
|
|
364
|
+
},
|
|
365
|
+
...(errors.length > 0 ? { errors } : {}),
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// ---------------------------------------------------------------------------
|
|
370
|
+
// resolveRetentionDays / resolveStaleDays — reads forge.yaml
|
|
371
|
+
// ---------------------------------------------------------------------------
|
|
372
|
+
|
|
373
|
+
export function resolveRetentionDays(workspaceRoot: string): number {
|
|
374
|
+
try {
|
|
375
|
+
const forgeYamlPath = path.join(workspaceRoot, "forge.yaml");
|
|
376
|
+
const content = fs.readFileSync(forgeYamlPath, "utf8");
|
|
377
|
+
const config = parseYaml(content) as Record<string, unknown>;
|
|
378
|
+
const bindings = config?.bindings as Record<string, unknown> | undefined;
|
|
379
|
+
const knowledge = bindings?.knowledge as Record<string, unknown> | undefined;
|
|
380
|
+
const retentionDays = knowledge?.retentionDays;
|
|
381
|
+
if (typeof retentionDays === "number" && retentionDays > 0 && Number.isInteger(retentionDays)) {
|
|
382
|
+
return retentionDays;
|
|
383
|
+
}
|
|
384
|
+
return DEFAULT_RETENTION_DAYS;
|
|
385
|
+
} catch {
|
|
386
|
+
return DEFAULT_RETENTION_DAYS;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export function resolveStaleDays(workspaceRoot: string): number {
|
|
391
|
+
try {
|
|
392
|
+
const forgeYamlPath = path.join(workspaceRoot, "forge.yaml");
|
|
393
|
+
const content = fs.readFileSync(forgeYamlPath, "utf8");
|
|
394
|
+
const config = parseYaml(content) as Record<string, unknown>;
|
|
395
|
+
const bindings = config?.bindings as Record<string, unknown> | undefined;
|
|
396
|
+
const knowledge = bindings?.knowledge as Record<string, unknown> | undefined;
|
|
397
|
+
const staleDays = knowledge?.staleDays;
|
|
398
|
+
if (typeof staleDays === "number" && staleDays > 0 && Number.isInteger(staleDays)) {
|
|
399
|
+
return staleDays;
|
|
400
|
+
}
|
|
401
|
+
return DEFAULT_STALE_DAYS;
|
|
402
|
+
} catch {
|
|
403
|
+
return DEFAULT_STALE_DAYS;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Barrel export for the knowledge module — parser, serializer, schema, and types (RFC-0660).</purpose>
|
|
4
|
+
<non-goals>
|
|
5
|
+
<item>Do not re-export from @warpgogol/* — forge src/ is portable.</item>
|
|
6
|
+
</non-goals>
|
|
7
|
+
</MODULE_CONTRACT>
|
|
8
|
+
<CHANGE_SUMMARY>
|
|
9
|
+
<item>RFC-0660: initial knowledge module barrel export.</item>
|
|
10
|
+
<item>RFC-0663: added promote.ts exports for cross-skill duplicate detection and promotion planning.</item>
|
|
11
|
+
</CHANGE_SUMMARY>
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export { parseKnowledgeFile } from "./parse.ts";
|
|
15
|
+
export { serializeKnowledgeFile } from "./serialize.ts";
|
|
16
|
+
export {
|
|
17
|
+
knowledgeEntryMetaSchema,
|
|
18
|
+
type KnowledgeLayer,
|
|
19
|
+
type KnowledgeEntryStatus,
|
|
20
|
+
type KnowledgeEntryMeta,
|
|
21
|
+
type KnowledgeEntry,
|
|
22
|
+
type LegacySection,
|
|
23
|
+
type ParseIssue,
|
|
24
|
+
type ParsedKnowledgeFile,
|
|
25
|
+
} from "./schema.ts";
|
|
26
|
+
export {
|
|
27
|
+
planCompaction,
|
|
28
|
+
executeCompaction,
|
|
29
|
+
resolveRetentionDays,
|
|
30
|
+
resolveStaleDays,
|
|
31
|
+
DEFAULT_RETENTION_DAYS,
|
|
32
|
+
DEFAULT_STALE_DAYS,
|
|
33
|
+
type CompactOptions,
|
|
34
|
+
type CompactAction,
|
|
35
|
+
type CompactActionKind,
|
|
36
|
+
type CompactFilePlan,
|
|
37
|
+
type CompactFileResult,
|
|
38
|
+
type CompactReport,
|
|
39
|
+
} from "./compact.ts";
|
|
40
|
+
export {
|
|
41
|
+
computeLayerBudgets,
|
|
42
|
+
resolveKnowledgeBudgets,
|
|
43
|
+
DEFAULT_KNOWLEDGE_BUDGETS,
|
|
44
|
+
type KnowledgeBudgets,
|
|
45
|
+
type LayerBudgetReport,
|
|
46
|
+
} from "./budgets.ts";
|
|
47
|
+
export {
|
|
48
|
+
normalizeTitle,
|
|
49
|
+
detectDuplicatePrinciples,
|
|
50
|
+
planPromotion,
|
|
51
|
+
type DuplicatePair,
|
|
52
|
+
type PromotionPlan,
|
|
53
|
+
} from "./promote.ts";
|