omegon 0.6.0
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/.gitattributes +3 -0
- package/AGENTS.md +16 -0
- package/LICENSE +15 -0
- package/README.md +289 -0
- package/bin/pi.mjs +30 -0
- package/extensions/00-secrets/index.ts +1126 -0
- package/extensions/01-auth/auth.ts +401 -0
- package/extensions/01-auth/index.ts +289 -0
- package/extensions/auto-compact.ts +42 -0
- package/extensions/bootstrap/deps.ts +291 -0
- package/extensions/bootstrap/index.ts +811 -0
- package/extensions/chronos/chronos.sh +487 -0
- package/extensions/chronos/index.ts +148 -0
- package/extensions/cleave/assessment.ts +754 -0
- package/extensions/cleave/bridge.ts +31 -0
- package/extensions/cleave/conflicts.ts +250 -0
- package/extensions/cleave/dispatcher.ts +808 -0
- package/extensions/cleave/guardrails.ts +426 -0
- package/extensions/cleave/index.ts +3121 -0
- package/extensions/cleave/lifecycle-emitter.ts +20 -0
- package/extensions/cleave/openspec.ts +811 -0
- package/extensions/cleave/planner.ts +260 -0
- package/extensions/cleave/review.ts +579 -0
- package/extensions/cleave/skills.ts +355 -0
- package/extensions/cleave/types.ts +261 -0
- package/extensions/cleave/workspace.ts +861 -0
- package/extensions/cleave/worktree.ts +243 -0
- package/extensions/core-renderers.ts +253 -0
- package/extensions/dashboard/context-gauge.ts +58 -0
- package/extensions/dashboard/file-watch.ts +14 -0
- package/extensions/dashboard/footer.ts +1145 -0
- package/extensions/dashboard/git.ts +185 -0
- package/extensions/dashboard/index.ts +478 -0
- package/extensions/dashboard/memory-audit.ts +34 -0
- package/extensions/dashboard/overlay-data.ts +705 -0
- package/extensions/dashboard/overlay.ts +365 -0
- package/extensions/dashboard/render-utils.ts +54 -0
- package/extensions/dashboard/types.ts +191 -0
- package/extensions/dashboard/uri-helper.ts +45 -0
- package/extensions/debug.ts +69 -0
- package/extensions/defaults.ts +282 -0
- package/extensions/design-tree/dashboard-state.ts +161 -0
- package/extensions/design-tree/design-card.ts +362 -0
- package/extensions/design-tree/index.ts +2130 -0
- package/extensions/design-tree/lifecycle-emitter.ts +41 -0
- package/extensions/design-tree/tree.ts +1607 -0
- package/extensions/design-tree/types.ts +163 -0
- package/extensions/distill.ts +127 -0
- package/extensions/effort/index.ts +395 -0
- package/extensions/effort/tiers.ts +146 -0
- package/extensions/effort/types.ts +105 -0
- package/extensions/lib/git-state.ts +227 -0
- package/extensions/lib/local-models.ts +157 -0
- package/extensions/lib/model-preferences.ts +51 -0
- package/extensions/lib/model-routing.ts +720 -0
- package/extensions/lib/operator-fallback.ts +205 -0
- package/extensions/lib/operator-profile.ts +360 -0
- package/extensions/lib/slash-command-bridge.ts +253 -0
- package/extensions/lib/typebox-helpers.ts +16 -0
- package/extensions/local-inference/index.ts +727 -0
- package/extensions/mcp-bridge/README.md +220 -0
- package/extensions/mcp-bridge/index.ts +951 -0
- package/extensions/mcp-bridge/lib.ts +365 -0
- package/extensions/mcp-bridge/mcp.json +3 -0
- package/extensions/mcp-bridge/package.json +11 -0
- package/extensions/model-budget.ts +752 -0
- package/extensions/offline-driver.ts +403 -0
- package/extensions/openspec/archive-gate.ts +164 -0
- package/extensions/openspec/branch-cleanup.ts +64 -0
- package/extensions/openspec/dashboard-state.ts +50 -0
- package/extensions/openspec/index.ts +1917 -0
- package/extensions/openspec/lifecycle-emitter.ts +65 -0
- package/extensions/openspec/lifecycle-files.ts +70 -0
- package/extensions/openspec/lifecycle.ts +50 -0
- package/extensions/openspec/reconcile.ts +187 -0
- package/extensions/openspec/spec.ts +1385 -0
- package/extensions/openspec/types.ts +98 -0
- package/extensions/project-memory/DESIGN-global-mind.md +198 -0
- package/extensions/project-memory/README.md +202 -0
- package/extensions/project-memory/api-types.ts +382 -0
- package/extensions/project-memory/compaction-policy.ts +29 -0
- package/extensions/project-memory/core.ts +164 -0
- package/extensions/project-memory/embeddings.ts +230 -0
- package/extensions/project-memory/extraction-v2.ts +861 -0
- package/extensions/project-memory/factstore.ts +2177 -0
- package/extensions/project-memory/index.ts +3459 -0
- package/extensions/project-memory/injection-metrics.ts +91 -0
- package/extensions/project-memory/jsonl-io.ts +12 -0
- package/extensions/project-memory/lifecycle.ts +331 -0
- package/extensions/project-memory/migration.ts +293 -0
- package/extensions/project-memory/package.json +9 -0
- package/extensions/project-memory/sci-renderers.ts +7 -0
- package/extensions/project-memory/template.ts +103 -0
- package/extensions/project-memory/triggers.ts +52 -0
- package/extensions/project-memory/types.ts +102 -0
- package/extensions/render/composition/fonts/Inter-Bold.ttf +0 -0
- package/extensions/render/composition/fonts/Inter-Regular.ttf +0 -0
- package/extensions/render/composition/fonts/Tomorrow-Bold.ttf +0 -0
- package/extensions/render/composition/fonts/Tomorrow-Regular.ttf +0 -0
- package/extensions/render/composition/package-lock.json +534 -0
- package/extensions/render/composition/package.json +22 -0
- package/extensions/render/composition/render.mjs +246 -0
- package/extensions/render/composition/test-comp.tsx +87 -0
- package/extensions/render/composition/types.ts +24 -0
- package/extensions/render/excalidraw/UPSTREAM.md +81 -0
- package/extensions/render/excalidraw/elements.ts +764 -0
- package/extensions/render/excalidraw/index.ts +66 -0
- package/extensions/render/excalidraw/types.ts +223 -0
- package/extensions/render/excalidraw-renderer/pyproject.toml +8 -0
- package/extensions/render/excalidraw-renderer/render_excalidraw.py +182 -0
- package/extensions/render/excalidraw-renderer/render_template.html +59 -0
- package/extensions/render/index.ts +830 -0
- package/extensions/render/native-diagrams/index.ts +57 -0
- package/extensions/render/native-diagrams/motifs.ts +542 -0
- package/extensions/render/native-diagrams/raster.ts +8 -0
- package/extensions/render/native-diagrams/scene.ts +75 -0
- package/extensions/render/native-diagrams/spec.ts +204 -0
- package/extensions/render/native-diagrams/svg.ts +116 -0
- package/extensions/sci-ui.ts +304 -0
- package/extensions/session-log.ts +174 -0
- package/extensions/shared-state.ts +146 -0
- package/extensions/spinner-verbs.ts +91 -0
- package/extensions/style.ts +281 -0
- package/extensions/terminal-title.ts +191 -0
- package/extensions/tool-profile/index.ts +291 -0
- package/extensions/tool-profile/profiles.ts +290 -0
- package/extensions/types.d.ts +9 -0
- package/extensions/vault/index.ts +185 -0
- package/extensions/version-check.ts +90 -0
- package/extensions/view/index.ts +859 -0
- package/extensions/view/uri-resolver.ts +148 -0
- package/extensions/web-search/index.ts +182 -0
- package/extensions/web-search/providers.ts +121 -0
- package/extensions/web-ui/index.ts +110 -0
- package/extensions/web-ui/server.ts +265 -0
- package/extensions/web-ui/state.ts +462 -0
- package/extensions/web-ui/static/index.html +145 -0
- package/extensions/web-ui/types.ts +284 -0
- package/package.json +76 -0
- package/prompts/init.md +75 -0
- package/prompts/new-repo.md +54 -0
- package/prompts/oci-login.md +56 -0
- package/prompts/status.md +50 -0
- package/settings.json +4 -0
- package/skills/cleave/SKILL.md +218 -0
- package/skills/git/SKILL.md +209 -0
- package/skills/git/_reference/ci-validation.md +204 -0
- package/skills/oci/SKILL.md +338 -0
- package/skills/openspec/SKILL.md +346 -0
- package/skills/pi-extensions/SKILL.md +191 -0
- package/skills/pi-tui/SKILL.md +517 -0
- package/skills/python/SKILL.md +189 -0
- package/skills/rust/SKILL.md +268 -0
- package/skills/security/SKILL.md +206 -0
- package/skills/style/SKILL.md +264 -0
- package/skills/typescript/SKILL.md +225 -0
- package/skills/vault/SKILL.md +102 -0
- package/themes/alpharius-legacy.json +85 -0
- package/themes/alpharius.conf +59 -0
- package/themes/alpharius.json +88 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
export type MemoryInjectionMode = "tiny" | "bulk" | "semantic";
|
|
2
|
+
|
|
3
|
+
export interface MemoryInjectionMetrics {
|
|
4
|
+
mode: MemoryInjectionMode;
|
|
5
|
+
projectFactCount: number;
|
|
6
|
+
edgeCount: number;
|
|
7
|
+
workingMemoryFactCount: number;
|
|
8
|
+
semanticHitCount: number;
|
|
9
|
+
episodeCount: number;
|
|
10
|
+
globalFactCount: number;
|
|
11
|
+
payloadChars: number;
|
|
12
|
+
estimatedTokens: number;
|
|
13
|
+
baselineContextTokens?: number | null;
|
|
14
|
+
userPromptTokensEstimate?: number;
|
|
15
|
+
observedInputTokens?: number;
|
|
16
|
+
inferredAdditionalPromptTokens?: number | null;
|
|
17
|
+
estimatedVsObservedDelta?: number | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function estimateTokensFromChars(content: string): number {
|
|
21
|
+
return Math.round(content.length / 4);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function createMemoryInjectionMetrics(input: {
|
|
25
|
+
mode: MemoryInjectionMode;
|
|
26
|
+
projectFactCount?: number;
|
|
27
|
+
edgeCount?: number;
|
|
28
|
+
workingMemoryFactCount?: number;
|
|
29
|
+
semanticHitCount?: number;
|
|
30
|
+
episodeCount?: number;
|
|
31
|
+
globalFactCount?: number;
|
|
32
|
+
payloadChars: number;
|
|
33
|
+
baselineContextTokens?: number | null;
|
|
34
|
+
userPromptTokensEstimate?: number;
|
|
35
|
+
observedInputTokens?: number;
|
|
36
|
+
inferredAdditionalPromptTokens?: number | null;
|
|
37
|
+
estimatedVsObservedDelta?: number | null;
|
|
38
|
+
}): MemoryInjectionMetrics {
|
|
39
|
+
return {
|
|
40
|
+
mode: input.mode,
|
|
41
|
+
projectFactCount: input.projectFactCount ?? 0,
|
|
42
|
+
edgeCount: input.edgeCount ?? 0,
|
|
43
|
+
workingMemoryFactCount: input.workingMemoryFactCount ?? 0,
|
|
44
|
+
semanticHitCount: input.semanticHitCount ?? 0,
|
|
45
|
+
episodeCount: input.episodeCount ?? 0,
|
|
46
|
+
globalFactCount: input.globalFactCount ?? 0,
|
|
47
|
+
payloadChars: input.payloadChars,
|
|
48
|
+
estimatedTokens: Math.round(input.payloadChars / 4),
|
|
49
|
+
baselineContextTokens: input.baselineContextTokens,
|
|
50
|
+
userPromptTokensEstimate: input.userPromptTokensEstimate,
|
|
51
|
+
observedInputTokens: input.observedInputTokens,
|
|
52
|
+
inferredAdditionalPromptTokens: input.inferredAdditionalPromptTokens,
|
|
53
|
+
estimatedVsObservedDelta: input.estimatedVsObservedDelta,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function formatMemoryInjectionMetrics(metrics: MemoryInjectionMetrics | null | undefined): string[] {
|
|
58
|
+
if (!metrics) {
|
|
59
|
+
return ["Last injection: none recorded this session"];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const lines = [
|
|
63
|
+
`Last injection mode: ${metrics.mode}`,
|
|
64
|
+
`Last injection facts: ${metrics.projectFactCount}`,
|
|
65
|
+
`Last injection edges: ${metrics.edgeCount}`,
|
|
66
|
+
`Last injection working-memory facts: ${metrics.workingMemoryFactCount}`,
|
|
67
|
+
`Last injection semantic hits: ${metrics.semanticHitCount}`,
|
|
68
|
+
`Last injection episodes: ${metrics.episodeCount}`,
|
|
69
|
+
`Last injection global facts: ${metrics.globalFactCount}`,
|
|
70
|
+
`Last injection payload: ${metrics.payloadChars} chars`,
|
|
71
|
+
`Last injection estimate: ${metrics.estimatedTokens} tokens`,
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
if (metrics.baselineContextTokens !== undefined) {
|
|
75
|
+
lines.push(`Baseline context before injection: ${metrics.baselineContextTokens ?? "unknown"}`);
|
|
76
|
+
}
|
|
77
|
+
if (metrics.userPromptTokensEstimate !== undefined) {
|
|
78
|
+
lines.push(`User prompt estimate: ${metrics.userPromptTokensEstimate} tokens`);
|
|
79
|
+
}
|
|
80
|
+
if (metrics.observedInputTokens !== undefined) {
|
|
81
|
+
lines.push(`Observed next input usage: ${metrics.observedInputTokens} tokens`);
|
|
82
|
+
}
|
|
83
|
+
if (metrics.inferredAdditionalPromptTokens !== undefined) {
|
|
84
|
+
lines.push(`Inferred added prompt tokens: ${metrics.inferredAdditionalPromptTokens ?? "unknown"}`);
|
|
85
|
+
}
|
|
86
|
+
if (metrics.estimatedVsObservedDelta !== undefined) {
|
|
87
|
+
lines.push(`Estimate delta vs inferred: ${metrics.estimatedVsObservedDelta ?? "unknown"}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return lines;
|
|
91
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function writeJsonlIfChanged(
|
|
2
|
+
fsSync: Pick<typeof import("node:fs"), "existsSync" | "readFileSync" | "writeFileSync">,
|
|
3
|
+
jsonlPath: string,
|
|
4
|
+
nextJsonl: string,
|
|
5
|
+
): boolean {
|
|
6
|
+
const existing = fsSync.existsSync(jsonlPath)
|
|
7
|
+
? fsSync.readFileSync(jsonlPath, "utf8")
|
|
8
|
+
: null;
|
|
9
|
+
if (existing === nextJsonl) return false;
|
|
10
|
+
fsSync.writeFileSync(jsonlPath, nextJsonl, "utf8");
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Memory — Lifecycle Integration
|
|
3
|
+
*
|
|
4
|
+
* Structured lifecycle candidate ingestion from design-tree, openspec, and cleave.
|
|
5
|
+
* Handles explicit vs inferred classification, pointer-fact formatting, and deduplication.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { SectionName } from "./template.ts";
|
|
9
|
+
import type { FactStore, Fact, StoreFactOptions } from "./factstore.ts";
|
|
10
|
+
|
|
11
|
+
// --- Lifecycle Candidate Types ---
|
|
12
|
+
|
|
13
|
+
/** Source kind for lifecycle candidates */
|
|
14
|
+
export type LifecycleSourceKind =
|
|
15
|
+
| "design-decision" // Design tree decided decision
|
|
16
|
+
| "design-constraint" // Design tree implementation constraint
|
|
17
|
+
| "openspec-archive" // OpenSpec archived spec
|
|
18
|
+
| "openspec-assess" // Post-assess reconciliation finding
|
|
19
|
+
| "cleave-outcome" // Cleave final durable outcome
|
|
20
|
+
| "cleave-bug-fix"; // Resolved bug fix conclusion
|
|
21
|
+
|
|
22
|
+
/** Authority level for lifecycle candidates */
|
|
23
|
+
export type LifecycleAuthority =
|
|
24
|
+
| "explicit" // Structured conclusion from authoritative source
|
|
25
|
+
| "inferred"; // Summary/interpretation requiring confirmation
|
|
26
|
+
|
|
27
|
+
/** Normalized lifecycle candidate shape */
|
|
28
|
+
export interface LifecycleCandidate {
|
|
29
|
+
/** Source kind that generated this candidate */
|
|
30
|
+
sourceKind: LifecycleSourceKind;
|
|
31
|
+
/** Authority level - explicit auto-stores, inferred needs confirmation */
|
|
32
|
+
authority: LifecycleAuthority;
|
|
33
|
+
/** Target memory section */
|
|
34
|
+
section: SectionName;
|
|
35
|
+
/** Fact content */
|
|
36
|
+
content: string;
|
|
37
|
+
/** Reference to authoritative artifact */
|
|
38
|
+
artifactRef?: ArtifactReference;
|
|
39
|
+
/** Optional supersede target if replacing existing fact */
|
|
40
|
+
supersedes?: string;
|
|
41
|
+
/** Optional session identifier */
|
|
42
|
+
session?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Reference to authoritative source artifact */
|
|
46
|
+
export interface ArtifactReference {
|
|
47
|
+
/** Type of artifact */
|
|
48
|
+
type: "design-node" | "openspec-spec" | "openspec-baseline" | "cleave-review";
|
|
49
|
+
/** Path or identifier */
|
|
50
|
+
path: string;
|
|
51
|
+
/** Optional sub-reference (e.g. decision title, spec section) */
|
|
52
|
+
subRef?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Result of lifecycle candidate processing */
|
|
56
|
+
export interface LifecycleCandidateResult {
|
|
57
|
+
/** Whether candidate was auto-stored */
|
|
58
|
+
autoStored: boolean;
|
|
59
|
+
/** Fact ID if stored */
|
|
60
|
+
factId?: string;
|
|
61
|
+
/** Whether it was a duplicate */
|
|
62
|
+
duplicate: boolean;
|
|
63
|
+
/** Reason for rejection or deferral */
|
|
64
|
+
reason?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// --- Candidate Classification Rules ---
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Determine if a lifecycle candidate should auto-store or require confirmation.
|
|
71
|
+
* Explicit structured conclusions from authoritative sources auto-store.
|
|
72
|
+
* Inferred summaries require operator confirmation.
|
|
73
|
+
*/
|
|
74
|
+
export function shouldAutoStore(candidate: LifecycleCandidate): boolean {
|
|
75
|
+
return candidate.authority === "explicit";
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Check if candidate content represents low-signal workflow chatter
|
|
80
|
+
* that should be rejected by default.
|
|
81
|
+
*/
|
|
82
|
+
export function isLowSignalContent(candidate: LifecycleCandidate): boolean {
|
|
83
|
+
const content = candidate.content.toLowerCase();
|
|
84
|
+
|
|
85
|
+
// Reject proposal-stage intent
|
|
86
|
+
if (candidate.sourceKind === "openspec-archive" &&
|
|
87
|
+
(content.includes("proposal") || content.includes("planning"))) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Reject child execution chatter
|
|
92
|
+
if (candidate.sourceKind === "cleave-outcome" &&
|
|
93
|
+
(content.includes("intermediate") || content.includes("reasoning"))) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Reject open questions from design constraints
|
|
98
|
+
if (candidate.sourceKind === "design-constraint" &&
|
|
99
|
+
content.includes("?")) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// --- Pointer-Fact Formatting ---
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Format lifecycle candidate content as a pointer-fact that references
|
|
110
|
+
* authoritative docs/specs rather than copying long-form artifact text.
|
|
111
|
+
*/
|
|
112
|
+
export function formatAsPointerFact(candidate: LifecycleCandidate): string {
|
|
113
|
+
if (!candidate.artifactRef) {
|
|
114
|
+
return candidate.content;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const { type, path, subRef } = candidate.artifactRef;
|
|
118
|
+
let pointerSuffix = "";
|
|
119
|
+
|
|
120
|
+
switch (type) {
|
|
121
|
+
case "design-node":
|
|
122
|
+
pointerSuffix = subRef
|
|
123
|
+
? ` See ${path}, decision: ${subRef}`
|
|
124
|
+
: ` See ${path}`;
|
|
125
|
+
break;
|
|
126
|
+
case "openspec-spec":
|
|
127
|
+
case "openspec-baseline":
|
|
128
|
+
pointerSuffix = subRef
|
|
129
|
+
? ` See ${path}#${subRef}`
|
|
130
|
+
: ` See ${path}`;
|
|
131
|
+
break;
|
|
132
|
+
case "cleave-review":
|
|
133
|
+
pointerSuffix = ` See ${path}`;
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Append pointer suffix if content doesn't already reference the artifact
|
|
138
|
+
if (!candidate.content.includes(path)) {
|
|
139
|
+
return `${candidate.content}.${pointerSuffix}`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return candidate.content;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// --- Deduplication & Supersede Handling ---
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Check if an equivalent lifecycle fact already exists in project memory.
|
|
149
|
+
* Returns the existing fact ID if found, null otherwise.
|
|
150
|
+
*/
|
|
151
|
+
export function findEquivalentFact(
|
|
152
|
+
store: FactStore,
|
|
153
|
+
mind: string,
|
|
154
|
+
candidate: LifecycleCandidate
|
|
155
|
+
): string | null {
|
|
156
|
+
const activeFacts = store.getActiveFacts(mind);
|
|
157
|
+
|
|
158
|
+
// Check for exact content match in same section
|
|
159
|
+
for (const fact of activeFacts) {
|
|
160
|
+
if (fact.section === candidate.section) {
|
|
161
|
+
const normalizedExisting = fact.content.toLowerCase().trim();
|
|
162
|
+
const normalizedCandidate = candidate.content.toLowerCase().trim();
|
|
163
|
+
|
|
164
|
+
// Exact match
|
|
165
|
+
if (normalizedExisting === normalizedCandidate) {
|
|
166
|
+
return fact.id;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Semantic equivalence (simplified - could be enhanced with embeddings)
|
|
170
|
+
if (areSemanticallyEquivalent(normalizedExisting, normalizedCandidate)) {
|
|
171
|
+
return fact.id;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Simple semantic equivalence check for lifecycle facts.
|
|
181
|
+
* Could be enhanced with embedding-based similarity in the future.
|
|
182
|
+
*/
|
|
183
|
+
function areSemanticallyEquivalent(content1: string, content2: string): boolean {
|
|
184
|
+
// Remove common variations
|
|
185
|
+
const normalize = (text: string) => text
|
|
186
|
+
.replace(/\s+/g, " ")
|
|
187
|
+
.replace(/[.,;:]/g, "")
|
|
188
|
+
.replace(/\b(the|a|an|is|are|was|were)\b/g, "")
|
|
189
|
+
.trim();
|
|
190
|
+
|
|
191
|
+
const norm1 = normalize(content1);
|
|
192
|
+
const norm2 = normalize(content2);
|
|
193
|
+
|
|
194
|
+
// Check if one is a subset of the other (90% overlap)
|
|
195
|
+
const words1 = norm1.split(" ");
|
|
196
|
+
const words2 = norm2.split(" ");
|
|
197
|
+
const set1 = new Set(words1);
|
|
198
|
+
const set2 = new Set(words2);
|
|
199
|
+
|
|
200
|
+
const intersection = new Set([...set1].filter(w => set2.has(w)));
|
|
201
|
+
const union = new Set([...set1, ...set2]);
|
|
202
|
+
|
|
203
|
+
const overlap = intersection.size / Math.min(set1.size, set2.size);
|
|
204
|
+
return overlap > 0.9;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// --- Lifecycle Candidate Ingestion API ---
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Process a lifecycle candidate through the structured ingestion pipeline.
|
|
211
|
+
* Handles classification, deduplication, formatting, and storage.
|
|
212
|
+
*/
|
|
213
|
+
export function ingestLifecycleCandidate(
|
|
214
|
+
store: FactStore,
|
|
215
|
+
mind: string,
|
|
216
|
+
candidate: LifecycleCandidate
|
|
217
|
+
): LifecycleCandidateResult {
|
|
218
|
+
// Reject low-signal workflow chatter
|
|
219
|
+
if (isLowSignalContent(candidate)) {
|
|
220
|
+
return {
|
|
221
|
+
autoStored: false,
|
|
222
|
+
duplicate: false,
|
|
223
|
+
reason: "Rejected as low-signal workflow chatter",
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Check for existing equivalent fact
|
|
228
|
+
const existingFactId = findEquivalentFact(store, mind, candidate);
|
|
229
|
+
if (existingFactId) {
|
|
230
|
+
if (candidate.authority === "explicit") {
|
|
231
|
+
// Reinforce existing fact with newer authoritative version
|
|
232
|
+
store.reinforceFact(existingFactId);
|
|
233
|
+
return {
|
|
234
|
+
autoStored: true,
|
|
235
|
+
factId: existingFactId,
|
|
236
|
+
duplicate: true,
|
|
237
|
+
reason: "Reinforced existing equivalent fact",
|
|
238
|
+
};
|
|
239
|
+
} else {
|
|
240
|
+
// Don't store inferred duplicate
|
|
241
|
+
return {
|
|
242
|
+
autoStored: false,
|
|
243
|
+
duplicate: true,
|
|
244
|
+
reason: "Equivalent fact already exists",
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Auto-store explicit candidates, defer inferred candidates
|
|
250
|
+
if (!shouldAutoStore(candidate)) {
|
|
251
|
+
return {
|
|
252
|
+
autoStored: false,
|
|
253
|
+
duplicate: false,
|
|
254
|
+
reason: "Inferred candidate requires operator confirmation",
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Format as pointer-fact and store
|
|
259
|
+
const formattedContent = formatAsPointerFact(candidate);
|
|
260
|
+
|
|
261
|
+
const storeOptions: StoreFactOptions = {
|
|
262
|
+
mind,
|
|
263
|
+
section: candidate.section,
|
|
264
|
+
content: formattedContent,
|
|
265
|
+
source: "lifecycle", // New source type for lifecycle ingestion
|
|
266
|
+
session: candidate.session,
|
|
267
|
+
supersedes: candidate.supersedes,
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const result = store.storeFact(storeOptions);
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
autoStored: true,
|
|
274
|
+
factId: result.id,
|
|
275
|
+
duplicate: result.duplicate,
|
|
276
|
+
reason: result.duplicate ? "Duplicate content hash" : undefined,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// --- Batch Processing ---
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Process multiple lifecycle candidates in a transaction.
|
|
284
|
+
* Returns aggregated results.
|
|
285
|
+
*/
|
|
286
|
+
export interface BatchIngestResult {
|
|
287
|
+
autoStored: number;
|
|
288
|
+
reinforced: number;
|
|
289
|
+
rejected: number;
|
|
290
|
+
deferred: number;
|
|
291
|
+
factIds: string[];
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function ingestLifecycleCandidatesBatch(
|
|
295
|
+
store: FactStore,
|
|
296
|
+
mind: string,
|
|
297
|
+
candidates: LifecycleCandidate[]
|
|
298
|
+
): BatchIngestResult {
|
|
299
|
+
const result: BatchIngestResult = {
|
|
300
|
+
autoStored: 0,
|
|
301
|
+
reinforced: 0,
|
|
302
|
+
rejected: 0,
|
|
303
|
+
deferred: 0,
|
|
304
|
+
factIds: [],
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
// Use transaction for batch processing
|
|
308
|
+
const tx = (store as any).db.transaction(() => {
|
|
309
|
+
for (const candidate of candidates) {
|
|
310
|
+
const candidateResult = ingestLifecycleCandidate(store, mind, candidate);
|
|
311
|
+
|
|
312
|
+
if (candidateResult.autoStored) {
|
|
313
|
+
if (candidateResult.duplicate) {
|
|
314
|
+
result.reinforced++;
|
|
315
|
+
} else {
|
|
316
|
+
result.autoStored++;
|
|
317
|
+
}
|
|
318
|
+
if (candidateResult.factId) {
|
|
319
|
+
result.factIds.push(candidateResult.factId);
|
|
320
|
+
}
|
|
321
|
+
} else if (candidateResult.reason?.includes("requires operator confirmation")) {
|
|
322
|
+
result.deferred++;
|
|
323
|
+
} else {
|
|
324
|
+
result.rejected++;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
tx();
|
|
330
|
+
return result;
|
|
331
|
+
}
|