memorix 1.1.13 → 1.2.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/CHANGELOG.md +19 -0
- package/README.md +3 -2
- package/README.zh-CN.md +3 -2
- package/dist/cli/index.js +36313 -31189
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/static/app.js +50 -30
- package/dist/index.js +5348 -674
- package/dist/index.js.map +1 -1
- package/dist/maintenance-runner.d.ts +1 -1
- package/dist/maintenance-runner.js +3661 -293
- package/dist/maintenance-runner.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +19 -0
- package/dist/sdk.d.ts +1 -1
- package/dist/sdk.js +5346 -672
- package/dist/sdk.js.map +1 -1
- package/docs/1.2.0-CLAIM-LEDGER.md +72 -0
- package/docs/1.2.0-CODE-STATE.md +61 -0
- package/docs/1.2.0-DEVELOPMENT-CHARTER.md +255 -0
- package/docs/1.2.0-DYNAMIC-LIFECYCLE.md +71 -0
- package/docs/1.2.0-EVALUATION-HARNESS.md +51 -0
- package/docs/1.2.0-IMPLEMENTATION-PLAN.md +554 -0
- package/docs/1.2.0-KNOWLEDGE-WORKFLOW-RESEARCH.md +205 -0
- package/docs/1.2.0-KNOWLEDGE-WORKSPACE.md +68 -0
- package/docs/1.2.0-PRODUCT-STORY.md +234 -0
- package/docs/1.2.0-PROVIDER-QUALITY.md +189 -0
- package/docs/1.2.0-WORKFLOW-INHERITANCE.md +101 -0
- package/docs/1.2.0-WORKSET-RETRIEVAL.md +80 -0
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +6 -3
- package/docs/API_REFERENCE.md +25 -6
- package/docs/CONFIGURATION.md +21 -3
- package/docs/README.md +17 -2
- package/docs/dev-log/progress.txt +120 -40
- package/llms-full.txt +16 -2
- package/llms.txt +9 -4
- package/package.json +3 -2
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/src/cli/capability-map.ts +1 -0
- package/src/cli/commands/codegraph.ts +112 -9
- package/src/cli/commands/context.ts +2 -0
- package/src/cli/commands/doctor.ts +73 -4
- package/src/cli/commands/knowledge.ts +282 -0
- package/src/cli/commands/serve-http.ts +12 -1
- package/src/cli/index.ts +3 -1
- package/src/cli/tui/App.tsx +1 -1
- package/src/cli/tui/Panels.tsx +8 -8
- package/src/cli/tui/theme.ts +1 -1
- package/src/cli/tui/views/GraphView.tsx +8 -7
- package/src/cli/tui/views/KnowledgeView.tsx +9 -9
- package/src/codegraph/auto-context.ts +171 -9
- package/src/codegraph/code-state.ts +95 -0
- package/src/codegraph/context-pack.ts +82 -1
- package/src/codegraph/external-provider.ts +581 -0
- package/src/codegraph/lite-provider.ts +64 -19
- package/src/codegraph/project-context.ts +9 -1
- package/src/codegraph/store.ts +154 -6
- package/src/codegraph/types.ts +117 -0
- package/src/config/resolved-config.ts +28 -0
- package/src/config/toml-loader.ts +3 -0
- package/src/config/yaml-loader.ts +6 -0
- package/src/dashboard/server.ts +15 -1
- package/src/evaluation/workset-evaluation.ts +120 -0
- package/src/hooks/handler.ts +48 -6
- package/src/knowledge/claim-store.ts +267 -0
- package/src/knowledge/claims.ts +537 -0
- package/src/knowledge/markdown.ts +129 -0
- package/src/knowledge/types.ts +157 -0
- package/src/knowledge/wiki.ts +524 -0
- package/src/knowledge/workflow-store.ts +168 -0
- package/src/knowledge/workflow-types.ts +95 -0
- package/src/knowledge/workflows.ts +743 -0
- package/src/knowledge/workset.ts +515 -0
- package/src/knowledge/workspace-store.ts +220 -0
- package/src/knowledge/workspace-types.ts +106 -0
- package/src/knowledge/workspace.ts +220 -0
- package/src/memory/observations.ts +19 -0
- package/src/runtime/control-plane-maintenance.ts +5 -0
- package/src/runtime/isolated-maintenance.ts +5 -0
- package/src/runtime/lifecycle-status.ts +102 -0
- package/src/runtime/lifecycle.ts +107 -0
- package/src/runtime/maintenance-jobs.ts +5 -0
- package/src/runtime/project-maintenance.ts +190 -0
- package/src/server/tool-profile.ts +3 -2
- package/src/server.ts +354 -14
- package/src/store/file-lock.ts +24 -4
- package/src/store/sqlite-db.ts +307 -0
- package/src/wiki/generator.ts +4 -2
- package/src/wiki/knowledge-graph.ts +7 -4
- package/src/wiki/types.ts +16 -4
|
@@ -45,9 +45,9 @@ function loadSqlite() {
|
|
|
45
45
|
throw new Error("[memorix] Neither better-sqlite3 nor bun:sqlite is available");
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
|
-
function createDatabase(
|
|
48
|
+
function createDatabase(path13, options) {
|
|
49
49
|
const Sqlite = loadSqlite();
|
|
50
|
-
const db2 = new Sqlite(
|
|
50
|
+
const db2 = new Sqlite(path13, options);
|
|
51
51
|
if (!db2.pragma) {
|
|
52
52
|
db2.pragma = function(pragma, options2) {
|
|
53
53
|
if (options2 && options2.simple) {
|
|
@@ -81,6 +81,28 @@ function loadBetterSqlite3() {
|
|
|
81
81
|
throw new Error("[memorix] SQLite is not available (neither better-sqlite3 nor bun:sqlite)");
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
|
+
function hasColumn(db2, table, column) {
|
|
85
|
+
return db2.prepare("PRAGMA table_info(" + table + ")").all().some((row) => row.name === column);
|
|
86
|
+
}
|
|
87
|
+
function addColumnIfMissing(db2, table, column, definition) {
|
|
88
|
+
if (hasColumn(db2, table, column)) return;
|
|
89
|
+
db2.exec("ALTER TABLE " + table + " ADD COLUMN " + definition);
|
|
90
|
+
}
|
|
91
|
+
function applySchemaMigrations(db2) {
|
|
92
|
+
db2.exec(CREATE_SCHEMA_MIGRATIONS_TABLE);
|
|
93
|
+
for (const migration of SCHEMA_MIGRATIONS) {
|
|
94
|
+
const applied = db2.prepare("SELECT 1 FROM schema_migrations WHERE id = ?").get(migration.id);
|
|
95
|
+
if (applied) continue;
|
|
96
|
+
const apply = db2.transaction(() => {
|
|
97
|
+
migration.apply(db2);
|
|
98
|
+
db2.prepare("INSERT INTO schema_migrations (id, applied_at) VALUES (?, ?)").run(
|
|
99
|
+
migration.id,
|
|
100
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
apply();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
84
106
|
function getDatabase(dataDir) {
|
|
85
107
|
const normalized = path2.resolve(dataDir);
|
|
86
108
|
const existing = _dbCache.get(normalized);
|
|
@@ -143,6 +165,7 @@ function getDatabase(dataDir) {
|
|
|
143
165
|
db2.exec(`ALTER TABLE team_messages ADD COLUMN handoff_status TEXT`);
|
|
144
166
|
} catch {
|
|
145
167
|
}
|
|
168
|
+
applySchemaMigrations(db2);
|
|
146
169
|
db2.exec(CREATE_INDEXES);
|
|
147
170
|
db2.prepare(`INSERT OR IGNORE INTO meta (key, value) VALUES ('storage_generation', '0')`).run();
|
|
148
171
|
db2.prepare(`INSERT OR IGNORE INTO meta (key, value) VALUES ('next_id', '1')`).run();
|
|
@@ -159,7 +182,7 @@ function closeAllDatabases() {
|
|
|
159
182
|
_dbCache.delete(key);
|
|
160
183
|
}
|
|
161
184
|
}
|
|
162
|
-
var BetterSqlite3, CREATE_OBSERVATIONS_TABLE, CREATE_MINI_SKILLS_TABLE, CREATE_SESSIONS_TABLE, CREATE_META_TABLE, CREATE_TEAM_AGENTS_TABLE, CREATE_TEAM_MESSAGES_TABLE, CREATE_TEAM_TASKS_TABLE, CREATE_TEAM_TASK_DEPS_TABLE, CREATE_TEAM_LOCKS_TABLE, CREATE_TEAM_ROLES_TABLE, CREATE_CHAT_TRANSCRIPT_TABLE, CREATE_GRAPH_ENTITIES_TABLE, CREATE_GRAPH_RELATIONS_TABLE, CREATE_CODE_FILES_TABLE, CREATE_CODE_SYMBOLS_TABLE, CREATE_CODE_EDGES_TABLE, CREATE_OBSERVATION_CODE_REFS_TABLE, CREATE_MAINTENANCE_JOBS_TABLE, CREATE_MAINTENANCE_TARGETS_TABLE, CREATE_INDEXES, _dbCache;
|
|
185
|
+
var BetterSqlite3, CREATE_OBSERVATIONS_TABLE, CREATE_MINI_SKILLS_TABLE, CREATE_SESSIONS_TABLE, CREATE_META_TABLE, CREATE_TEAM_AGENTS_TABLE, CREATE_TEAM_MESSAGES_TABLE, CREATE_TEAM_TASKS_TABLE, CREATE_TEAM_TASK_DEPS_TABLE, CREATE_TEAM_LOCKS_TABLE, CREATE_TEAM_ROLES_TABLE, CREATE_CHAT_TRANSCRIPT_TABLE, CREATE_GRAPH_ENTITIES_TABLE, CREATE_GRAPH_RELATIONS_TABLE, CREATE_CODE_FILES_TABLE, CREATE_CODE_SYMBOLS_TABLE, CREATE_CODE_EDGES_TABLE, CREATE_OBSERVATION_CODE_REFS_TABLE, CREATE_SCHEMA_MIGRATIONS_TABLE, CREATE_CODE_STATE_SNAPSHOTS_TABLE, CREATE_KNOWLEDGE_CLAIMS_TABLE, CREATE_KNOWLEDGE_CLAIM_EVIDENCE_TABLE, CREATE_KNOWLEDGE_CLAIM_EVENTS_TABLE, CREATE_KNOWLEDGE_WORKSPACES_TABLE, CREATE_KNOWLEDGE_PAGES_TABLE, CREATE_KNOWLEDGE_PAGE_CLAIMS_TABLE, CREATE_KNOWLEDGE_PAGE_LINKS_TABLE, CREATE_KNOWLEDGE_PROPOSALS_TABLE, CREATE_KNOWLEDGE_WORKFLOWS_TABLE, CREATE_KNOWLEDGE_WORKFLOW_RUNS_TABLE, CREATE_MAINTENANCE_JOBS_TABLE, CREATE_MAINTENANCE_TARGETS_TABLE, CREATE_INDEXES, SCHEMA_MIGRATIONS, _dbCache;
|
|
163
186
|
var init_sqlite_db = __esm({
|
|
164
187
|
"src/store/sqlite-db.ts"() {
|
|
165
188
|
"use strict";
|
|
@@ -399,6 +422,188 @@ CREATE TABLE IF NOT EXISTS observation_code_refs (
|
|
|
399
422
|
updatedAt TEXT
|
|
400
423
|
);
|
|
401
424
|
`;
|
|
425
|
+
CREATE_SCHEMA_MIGRATIONS_TABLE = [
|
|
426
|
+
"CREATE TABLE IF NOT EXISTS schema_migrations (",
|
|
427
|
+
" id TEXT PRIMARY KEY,",
|
|
428
|
+
" applied_at TEXT NOT NULL",
|
|
429
|
+
");"
|
|
430
|
+
].join("\n");
|
|
431
|
+
CREATE_CODE_STATE_SNAPSHOTS_TABLE = [
|
|
432
|
+
"CREATE TABLE IF NOT EXISTS code_state_snapshots (",
|
|
433
|
+
" id TEXT PRIMARY KEY,",
|
|
434
|
+
" projectId TEXT NOT NULL,",
|
|
435
|
+
" provider TEXT NOT NULL,",
|
|
436
|
+
" baseRevision TEXT,",
|
|
437
|
+
" worktreeFingerprint TEXT NOT NULL,",
|
|
438
|
+
" worktreeState TEXT NOT NULL,",
|
|
439
|
+
" changedPathCount INTEGER NOT NULL DEFAULT 0,",
|
|
440
|
+
" indexedAt TEXT NOT NULL,",
|
|
441
|
+
" sourceEpoch INTEGER NOT NULL,",
|
|
442
|
+
" completenessJson TEXT NOT NULL DEFAULT '{}',",
|
|
443
|
+
" previousSnapshotId TEXT,",
|
|
444
|
+
" UNIQUE(projectId, sourceEpoch)",
|
|
445
|
+
");"
|
|
446
|
+
].join("\n");
|
|
447
|
+
CREATE_KNOWLEDGE_CLAIMS_TABLE = `
|
|
448
|
+
CREATE TABLE IF NOT EXISTS knowledge_claims (
|
|
449
|
+
id TEXT PRIMARY KEY,
|
|
450
|
+
projectId TEXT NOT NULL,
|
|
451
|
+
subject TEXT NOT NULL,
|
|
452
|
+
predicate TEXT NOT NULL,
|
|
453
|
+
objectValue TEXT NOT NULL,
|
|
454
|
+
scope TEXT NOT NULL,
|
|
455
|
+
claimKey TEXT NOT NULL,
|
|
456
|
+
conflictKey TEXT NOT NULL,
|
|
457
|
+
status TEXT NOT NULL,
|
|
458
|
+
confidence REAL NOT NULL,
|
|
459
|
+
observedAt TEXT NOT NULL,
|
|
460
|
+
validFrom TEXT,
|
|
461
|
+
validTo TEXT,
|
|
462
|
+
supersededBy TEXT,
|
|
463
|
+
reviewState TEXT NOT NULL,
|
|
464
|
+
origin TEXT NOT NULL,
|
|
465
|
+
createdAt TEXT NOT NULL,
|
|
466
|
+
updatedAt TEXT NOT NULL
|
|
467
|
+
);
|
|
468
|
+
`;
|
|
469
|
+
CREATE_KNOWLEDGE_CLAIM_EVIDENCE_TABLE = `
|
|
470
|
+
CREATE TABLE IF NOT EXISTS knowledge_claim_evidence (
|
|
471
|
+
id TEXT PRIMARY KEY,
|
|
472
|
+
claimId TEXT NOT NULL,
|
|
473
|
+
evidenceKind TEXT NOT NULL,
|
|
474
|
+
evidenceId TEXT NOT NULL,
|
|
475
|
+
relation TEXT NOT NULL,
|
|
476
|
+
snapshotId TEXT,
|
|
477
|
+
locator TEXT,
|
|
478
|
+
capturedHash TEXT,
|
|
479
|
+
evidenceKey TEXT NOT NULL,
|
|
480
|
+
createdAt TEXT NOT NULL,
|
|
481
|
+
UNIQUE(claimId, evidenceKey),
|
|
482
|
+
FOREIGN KEY (claimId) REFERENCES knowledge_claims(id) ON DELETE CASCADE
|
|
483
|
+
);
|
|
484
|
+
`;
|
|
485
|
+
CREATE_KNOWLEDGE_CLAIM_EVENTS_TABLE = `
|
|
486
|
+
CREATE TABLE IF NOT EXISTS knowledge_claim_events (
|
|
487
|
+
id TEXT PRIMARY KEY,
|
|
488
|
+
projectId TEXT NOT NULL,
|
|
489
|
+
claimId TEXT NOT NULL,
|
|
490
|
+
kind TEXT NOT NULL,
|
|
491
|
+
fromStatus TEXT,
|
|
492
|
+
toStatus TEXT,
|
|
493
|
+
relatedClaimId TEXT,
|
|
494
|
+
detail TEXT,
|
|
495
|
+
createdAt TEXT NOT NULL,
|
|
496
|
+
FOREIGN KEY (claimId) REFERENCES knowledge_claims(id) ON DELETE CASCADE
|
|
497
|
+
);
|
|
498
|
+
`;
|
|
499
|
+
CREATE_KNOWLEDGE_WORKSPACES_TABLE = [
|
|
500
|
+
"CREATE TABLE IF NOT EXISTS knowledge_workspaces (",
|
|
501
|
+
" id TEXT PRIMARY KEY,",
|
|
502
|
+
" projectId TEXT NOT NULL,",
|
|
503
|
+
" mode TEXT NOT NULL,",
|
|
504
|
+
" rootPath TEXT NOT NULL,",
|
|
505
|
+
" projectRoot TEXT,",
|
|
506
|
+
" status TEXT NOT NULL,",
|
|
507
|
+
" createdAt TEXT NOT NULL,",
|
|
508
|
+
" updatedAt TEXT NOT NULL,",
|
|
509
|
+
" lastCompiledAt TEXT,",
|
|
510
|
+
" lastLintedAt TEXT,",
|
|
511
|
+
" UNIQUE(projectId, mode)",
|
|
512
|
+
");"
|
|
513
|
+
].join("\n");
|
|
514
|
+
CREATE_KNOWLEDGE_PAGES_TABLE = [
|
|
515
|
+
"CREATE TABLE IF NOT EXISTS knowledge_pages (",
|
|
516
|
+
" id TEXT PRIMARY KEY,",
|
|
517
|
+
" workspaceId TEXT NOT NULL,",
|
|
518
|
+
" relativePath TEXT NOT NULL,",
|
|
519
|
+
" title TEXT NOT NULL,",
|
|
520
|
+
" kind TEXT NOT NULL,",
|
|
521
|
+
" status TEXT NOT NULL,",
|
|
522
|
+
" reviewState TEXT NOT NULL,",
|
|
523
|
+
" contentHash TEXT NOT NULL,",
|
|
524
|
+
" sourceHash TEXT NOT NULL,",
|
|
525
|
+
" claimIdsJson TEXT NOT NULL DEFAULT '[]',",
|
|
526
|
+
" snapshotId TEXT,",
|
|
527
|
+
" tagsJson TEXT NOT NULL DEFAULT '[]',",
|
|
528
|
+
" generatedAt TEXT NOT NULL,",
|
|
529
|
+
" updatedAt TEXT NOT NULL,",
|
|
530
|
+
" lastLintedAt TEXT,",
|
|
531
|
+
" manualContentHash TEXT,",
|
|
532
|
+
" UNIQUE(workspaceId, relativePath),",
|
|
533
|
+
" FOREIGN KEY (workspaceId) REFERENCES knowledge_workspaces(id) ON DELETE CASCADE",
|
|
534
|
+
");"
|
|
535
|
+
].join("\n");
|
|
536
|
+
CREATE_KNOWLEDGE_PAGE_CLAIMS_TABLE = [
|
|
537
|
+
"CREATE TABLE IF NOT EXISTS knowledge_page_claims (",
|
|
538
|
+
" pageId TEXT NOT NULL,",
|
|
539
|
+
" claimId TEXT NOT NULL,",
|
|
540
|
+
" role TEXT NOT NULL,",
|
|
541
|
+
" PRIMARY KEY (pageId, claimId),",
|
|
542
|
+
" FOREIGN KEY (pageId) REFERENCES knowledge_pages(id) ON DELETE CASCADE,",
|
|
543
|
+
" FOREIGN KEY (claimId) REFERENCES knowledge_claims(id) ON DELETE RESTRICT",
|
|
544
|
+
");"
|
|
545
|
+
].join("\n");
|
|
546
|
+
CREATE_KNOWLEDGE_PAGE_LINKS_TABLE = [
|
|
547
|
+
"CREATE TABLE IF NOT EXISTS knowledge_page_links (",
|
|
548
|
+
" sourcePageId TEXT NOT NULL,",
|
|
549
|
+
" targetPath TEXT NOT NULL,",
|
|
550
|
+
" PRIMARY KEY (sourcePageId, targetPath),",
|
|
551
|
+
" FOREIGN KEY (sourcePageId) REFERENCES knowledge_pages(id) ON DELETE CASCADE",
|
|
552
|
+
");"
|
|
553
|
+
].join("\n");
|
|
554
|
+
CREATE_KNOWLEDGE_PROPOSALS_TABLE = [
|
|
555
|
+
"CREATE TABLE IF NOT EXISTS knowledge_proposals (",
|
|
556
|
+
" id TEXT PRIMARY KEY,",
|
|
557
|
+
" workspaceId TEXT NOT NULL,",
|
|
558
|
+
" pageId TEXT NOT NULL,",
|
|
559
|
+
" targetPath TEXT NOT NULL,",
|
|
560
|
+
" proposalPath TEXT NOT NULL,",
|
|
561
|
+
" baseContentHash TEXT,",
|
|
562
|
+
" sourceHash TEXT NOT NULL,",
|
|
563
|
+
" reason TEXT NOT NULL,",
|
|
564
|
+
" status TEXT NOT NULL,",
|
|
565
|
+
" createdAt TEXT NOT NULL,",
|
|
566
|
+
" appliedAt TEXT,",
|
|
567
|
+
" UNIQUE(workspaceId, proposalPath),",
|
|
568
|
+
" FOREIGN KEY (workspaceId) REFERENCES knowledge_workspaces(id) ON DELETE CASCADE,",
|
|
569
|
+
" FOREIGN KEY (pageId) REFERENCES knowledge_pages(id) ON DELETE CASCADE",
|
|
570
|
+
");"
|
|
571
|
+
].join("\n");
|
|
572
|
+
CREATE_KNOWLEDGE_WORKFLOWS_TABLE = [
|
|
573
|
+
"CREATE TABLE IF NOT EXISTS knowledge_workflows (",
|
|
574
|
+
" id TEXT PRIMARY KEY,",
|
|
575
|
+
" workspaceId TEXT NOT NULL,",
|
|
576
|
+
" sourcePath TEXT NOT NULL,",
|
|
577
|
+
" title TEXT NOT NULL,",
|
|
578
|
+
" status TEXT NOT NULL,",
|
|
579
|
+
" version INTEGER NOT NULL,",
|
|
580
|
+
" sourceHash TEXT NOT NULL,",
|
|
581
|
+
" contentHash TEXT NOT NULL,",
|
|
582
|
+
" specJson TEXT NOT NULL,",
|
|
583
|
+
" importedFrom TEXT,",
|
|
584
|
+
" createdAt TEXT NOT NULL,",
|
|
585
|
+
" updatedAt TEXT NOT NULL,",
|
|
586
|
+
" UNIQUE(workspaceId, sourcePath),",
|
|
587
|
+
" FOREIGN KEY (workspaceId) REFERENCES knowledge_workspaces(id) ON DELETE CASCADE",
|
|
588
|
+
");"
|
|
589
|
+
].join("\n");
|
|
590
|
+
CREATE_KNOWLEDGE_WORKFLOW_RUNS_TABLE = [
|
|
591
|
+
"CREATE TABLE IF NOT EXISTS knowledge_workflow_runs (",
|
|
592
|
+
" id TEXT PRIMARY KEY,",
|
|
593
|
+
" workflowId TEXT NOT NULL,",
|
|
594
|
+
" projectId TEXT NOT NULL,",
|
|
595
|
+
" task TEXT NOT NULL,",
|
|
596
|
+
" startingSnapshotId TEXT,",
|
|
597
|
+
" selectedEvidenceJson TEXT NOT NULL DEFAULT '[]',",
|
|
598
|
+
" phaseStateJson TEXT NOT NULL DEFAULT '{}',",
|
|
599
|
+
" outcome TEXT NOT NULL,",
|
|
600
|
+
" verificationVerdict TEXT NOT NULL,",
|
|
601
|
+
" failureReason TEXT,",
|
|
602
|
+
" startedAt TEXT NOT NULL,",
|
|
603
|
+
" completedAt TEXT,",
|
|
604
|
+
" FOREIGN KEY (workflowId) REFERENCES knowledge_workflows(id) ON DELETE CASCADE",
|
|
605
|
+
");"
|
|
606
|
+
].join("\n");
|
|
402
607
|
CREATE_MAINTENANCE_JOBS_TABLE = `
|
|
403
608
|
CREATE TABLE IF NOT EXISTS maintenance_jobs (
|
|
404
609
|
id TEXT PRIMARY KEY,
|
|
@@ -450,8 +655,23 @@ CREATE INDEX IF NOT EXISTS idx_code_files_project ON code_files(projectId);
|
|
|
450
655
|
CREATE INDEX IF NOT EXISTS idx_code_symbols_project_name ON code_symbols(projectId, name);
|
|
451
656
|
CREATE INDEX IF NOT EXISTS idx_code_symbols_file ON code_symbols(fileId);
|
|
452
657
|
CREATE INDEX IF NOT EXISTS idx_code_edges_project ON code_edges(projectId, type);
|
|
658
|
+
CREATE INDEX IF NOT EXISTS idx_code_snapshots_project_epoch ON code_state_snapshots(projectId, sourceEpoch DESC);
|
|
659
|
+
CREATE INDEX IF NOT EXISTS idx_code_files_snapshot ON code_files(projectId, snapshotId);
|
|
660
|
+
CREATE INDEX IF NOT EXISTS idx_code_symbols_snapshot ON code_symbols(projectId, snapshotId);
|
|
661
|
+
CREATE INDEX IF NOT EXISTS idx_code_edges_snapshot ON code_edges(projectId, snapshotId);
|
|
453
662
|
CREATE INDEX IF NOT EXISTS idx_observation_code_refs_obs ON observation_code_refs(projectId, observationId);
|
|
454
663
|
CREATE INDEX IF NOT EXISTS idx_observation_code_refs_status ON observation_code_refs(projectId, status);
|
|
664
|
+
CREATE INDEX IF NOT EXISTS idx_knowledge_claims_project_status ON knowledge_claims(projectId, status, updatedAt DESC);
|
|
665
|
+
CREATE INDEX IF NOT EXISTS idx_knowledge_claims_project_conflict ON knowledge_claims(projectId, conflictKey, status);
|
|
666
|
+
CREATE INDEX IF NOT EXISTS idx_knowledge_claims_project_key ON knowledge_claims(projectId, claimKey, status);
|
|
667
|
+
CREATE INDEX IF NOT EXISTS idx_knowledge_claim_evidence_claim ON knowledge_claim_evidence(claimId, createdAt);
|
|
668
|
+
CREATE INDEX IF NOT EXISTS idx_knowledge_claim_events_claim ON knowledge_claim_events(claimId, createdAt);
|
|
669
|
+
CREATE INDEX IF NOT EXISTS idx_knowledge_workspaces_project ON knowledge_workspaces(projectId, mode);
|
|
670
|
+
CREATE INDEX IF NOT EXISTS idx_knowledge_pages_workspace_status ON knowledge_pages(workspaceId, status, updatedAt DESC);
|
|
671
|
+
CREATE INDEX IF NOT EXISTS idx_knowledge_page_claims_claim ON knowledge_page_claims(claimId);
|
|
672
|
+
CREATE INDEX IF NOT EXISTS idx_knowledge_proposals_workspace_status ON knowledge_proposals(workspaceId, status, createdAt DESC);
|
|
673
|
+
CREATE INDEX IF NOT EXISTS idx_knowledge_workflows_workspace_status ON knowledge_workflows(workspaceId, status, updatedAt DESC);
|
|
674
|
+
CREATE INDEX IF NOT EXISTS idx_knowledge_workflow_runs_project_workflow ON knowledge_workflow_runs(projectId, workflowId, startedAt DESC);
|
|
455
675
|
CREATE INDEX IF NOT EXISTS idx_maintenance_jobs_ready ON maintenance_jobs(status, run_after);
|
|
456
676
|
CREATE INDEX IF NOT EXISTS idx_maintenance_jobs_project ON maintenance_jobs(project_id, status, run_after);
|
|
457
677
|
CREATE INDEX IF NOT EXISTS idx_maintenance_targets_updated ON maintenance_targets(updated_at);
|
|
@@ -459,6 +679,61 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_maintenance_jobs_active_dedupe
|
|
|
459
679
|
ON maintenance_jobs(project_id, kind, dedupe_key)
|
|
460
680
|
WHERE status IN ('pending', 'running', 'retry');
|
|
461
681
|
`;
|
|
682
|
+
SCHEMA_MIGRATIONS = [
|
|
683
|
+
{
|
|
684
|
+
id: "1.2-code-state-snapshots",
|
|
685
|
+
apply: (db2) => {
|
|
686
|
+
db2.exec(CREATE_CODE_STATE_SNAPSHOTS_TABLE);
|
|
687
|
+
addColumnIfMissing(db2, "code_files", "snapshotId", "snapshotId TEXT");
|
|
688
|
+
addColumnIfMissing(db2, "code_files", "sourceEpoch", "sourceEpoch INTEGER");
|
|
689
|
+
addColumnIfMissing(db2, "code_symbols", "snapshotId", "snapshotId TEXT");
|
|
690
|
+
addColumnIfMissing(db2, "code_symbols", "sourceEpoch", "sourceEpoch INTEGER");
|
|
691
|
+
addColumnIfMissing(db2, "code_edges", "snapshotId", "snapshotId TEXT");
|
|
692
|
+
addColumnIfMissing(db2, "code_edges", "sourceEpoch", "sourceEpoch INTEGER");
|
|
693
|
+
addColumnIfMissing(db2, "observation_code_refs", "snapshotId", "snapshotId TEXT");
|
|
694
|
+
db2.exec("CREATE INDEX IF NOT EXISTS idx_code_snapshots_project_epoch ON code_state_snapshots(projectId, sourceEpoch DESC)");
|
|
695
|
+
db2.exec("CREATE INDEX IF NOT EXISTS idx_code_files_snapshot ON code_files(projectId, snapshotId)");
|
|
696
|
+
db2.exec("CREATE INDEX IF NOT EXISTS idx_code_symbols_snapshot ON code_symbols(projectId, snapshotId)");
|
|
697
|
+
db2.exec("CREATE INDEX IF NOT EXISTS idx_code_edges_snapshot ON code_edges(projectId, snapshotId)");
|
|
698
|
+
}
|
|
699
|
+
},
|
|
700
|
+
{
|
|
701
|
+
id: "1.2-knowledge-claim-ledger",
|
|
702
|
+
apply: (db2) => {
|
|
703
|
+
db2.exec(CREATE_KNOWLEDGE_CLAIMS_TABLE);
|
|
704
|
+
db2.exec(CREATE_KNOWLEDGE_CLAIM_EVIDENCE_TABLE);
|
|
705
|
+
db2.exec(CREATE_KNOWLEDGE_CLAIM_EVENTS_TABLE);
|
|
706
|
+
db2.exec("CREATE INDEX IF NOT EXISTS idx_knowledge_claims_project_status ON knowledge_claims(projectId, status, updatedAt DESC)");
|
|
707
|
+
db2.exec("CREATE INDEX IF NOT EXISTS idx_knowledge_claims_project_conflict ON knowledge_claims(projectId, conflictKey, status)");
|
|
708
|
+
db2.exec("CREATE INDEX IF NOT EXISTS idx_knowledge_claims_project_key ON knowledge_claims(projectId, claimKey, status)");
|
|
709
|
+
db2.exec("CREATE INDEX IF NOT EXISTS idx_knowledge_claim_evidence_claim ON knowledge_claim_evidence(claimId, createdAt)");
|
|
710
|
+
db2.exec("CREATE INDEX IF NOT EXISTS idx_knowledge_claim_events_claim ON knowledge_claim_events(claimId, createdAt)");
|
|
711
|
+
}
|
|
712
|
+
},
|
|
713
|
+
{
|
|
714
|
+
id: "1.2-knowledge-workspace",
|
|
715
|
+
apply: (db2) => {
|
|
716
|
+
db2.exec(CREATE_KNOWLEDGE_WORKSPACES_TABLE);
|
|
717
|
+
db2.exec(CREATE_KNOWLEDGE_PAGES_TABLE);
|
|
718
|
+
db2.exec(CREATE_KNOWLEDGE_PAGE_CLAIMS_TABLE);
|
|
719
|
+
db2.exec(CREATE_KNOWLEDGE_PAGE_LINKS_TABLE);
|
|
720
|
+
db2.exec(CREATE_KNOWLEDGE_PROPOSALS_TABLE);
|
|
721
|
+
db2.exec("CREATE INDEX IF NOT EXISTS idx_knowledge_workspaces_project ON knowledge_workspaces(projectId, mode)");
|
|
722
|
+
db2.exec("CREATE INDEX IF NOT EXISTS idx_knowledge_pages_workspace_status ON knowledge_pages(workspaceId, status, updatedAt DESC)");
|
|
723
|
+
db2.exec("CREATE INDEX IF NOT EXISTS idx_knowledge_page_claims_claim ON knowledge_page_claims(claimId)");
|
|
724
|
+
db2.exec("CREATE INDEX IF NOT EXISTS idx_knowledge_proposals_workspace_status ON knowledge_proposals(workspaceId, status, createdAt DESC)");
|
|
725
|
+
}
|
|
726
|
+
},
|
|
727
|
+
{
|
|
728
|
+
id: "1.2-workflow-inheritance",
|
|
729
|
+
apply: (db2) => {
|
|
730
|
+
db2.exec(CREATE_KNOWLEDGE_WORKFLOWS_TABLE);
|
|
731
|
+
db2.exec(CREATE_KNOWLEDGE_WORKFLOW_RUNS_TABLE);
|
|
732
|
+
db2.exec("CREATE INDEX IF NOT EXISTS idx_knowledge_workflows_workspace_status ON knowledge_workflows(workspaceId, status, updatedAt DESC)");
|
|
733
|
+
db2.exec("CREATE INDEX IF NOT EXISTS idx_knowledge_workflow_runs_project_workflow ON knowledge_workflow_runs(projectId, workflowId, startedAt DESC)");
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
];
|
|
462
737
|
_dbCache = /* @__PURE__ */ new Map();
|
|
463
738
|
}
|
|
464
739
|
});
|
|
@@ -598,8 +873,8 @@ function parseYaml(content) {
|
|
|
598
873
|
return yaml.load(content) ?? {};
|
|
599
874
|
} catch {
|
|
600
875
|
try {
|
|
601
|
-
const
|
|
602
|
-
const parsed =
|
|
876
|
+
const matter4 = require2("gray-matter");
|
|
877
|
+
const parsed = matter4(`---
|
|
603
878
|
${content}
|
|
604
879
|
---`);
|
|
605
880
|
return parsed.data ?? {};
|
|
@@ -851,9 +1126,9 @@ function parseTomlConfig(content, filePath) {
|
|
|
851
1126
|
}
|
|
852
1127
|
return root;
|
|
853
1128
|
}
|
|
854
|
-
function ensureTable(root,
|
|
1129
|
+
function ensureTable(root, path13) {
|
|
855
1130
|
let cursor = root;
|
|
856
|
-
for (const part of
|
|
1131
|
+
for (const part of path13) {
|
|
857
1132
|
const value = cursor[part];
|
|
858
1133
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
859
1134
|
cursor[part] = {};
|
|
@@ -1000,7 +1275,23 @@ function getResolvedConfig(options = {}) {
|
|
|
1000
1275
|
},
|
|
1001
1276
|
codegraph: {
|
|
1002
1277
|
excludePatterns: firstArray(toml.codegraph?.exclude_patterns, yaml.codegraph?.excludePatterns),
|
|
1003
|
-
maxFileBytes: firstNumber(toml.codegraph?.max_file_bytes, yaml.codegraph?.maxFileBytes)
|
|
1278
|
+
maxFileBytes: firstNumber(toml.codegraph?.max_file_bytes, yaml.codegraph?.maxFileBytes),
|
|
1279
|
+
externalContext: first(
|
|
1280
|
+
normalizeExternalContext(process.env.MEMORIX_CODEGRAPH_EXTERNAL_CONTEXT),
|
|
1281
|
+
toml.codegraph?.external_context,
|
|
1282
|
+
yaml.codegraph?.externalContext,
|
|
1283
|
+
"auto"
|
|
1284
|
+
),
|
|
1285
|
+
externalCommand: first(
|
|
1286
|
+
process.env.MEMORIX_CODEGRAPH_EXTERNAL_COMMAND,
|
|
1287
|
+
toml.codegraph?.external_command,
|
|
1288
|
+
yaml.codegraph?.externalCommand
|
|
1289
|
+
),
|
|
1290
|
+
externalTimeoutMs: firstNumber(
|
|
1291
|
+
parseNumber(process.env.MEMORIX_CODEGRAPH_EXTERNAL_TIMEOUT_MS),
|
|
1292
|
+
toml.codegraph?.external_timeout_ms,
|
|
1293
|
+
yaml.codegraph?.externalTimeoutMs
|
|
1294
|
+
)
|
|
1004
1295
|
},
|
|
1005
1296
|
server: {
|
|
1006
1297
|
transport: first(toml.server?.transport, yaml.server?.transport),
|
|
@@ -1085,6 +1376,9 @@ function getEnvSourceNames() {
|
|
|
1085
1376
|
"MEMORIX_EMBEDDING_BASE_URL",
|
|
1086
1377
|
"MEMORIX_EMBEDDING_MODEL",
|
|
1087
1378
|
"MEMORIX_EMBEDDING_DIMENSIONS",
|
|
1379
|
+
"MEMORIX_CODEGRAPH_EXTERNAL_CONTEXT",
|
|
1380
|
+
"MEMORIX_CODEGRAPH_EXTERNAL_COMMAND",
|
|
1381
|
+
"MEMORIX_CODEGRAPH_EXTERNAL_TIMEOUT_MS",
|
|
1088
1382
|
"OPENROUTER_API_KEY"
|
|
1089
1383
|
].filter((name) => process.env[name]);
|
|
1090
1384
|
}
|
|
@@ -1097,6 +1391,11 @@ function isOpenRouterUrl(value) {
|
|
|
1097
1391
|
return /(^|\.)openrouter\.ai(?::|\/|$)/i.test(value);
|
|
1098
1392
|
}
|
|
1099
1393
|
}
|
|
1394
|
+
function normalizeExternalContext(value) {
|
|
1395
|
+
const normalized = value?.trim().toLowerCase();
|
|
1396
|
+
if (normalized === "auto" || normalized === "off") return normalized;
|
|
1397
|
+
return void 0;
|
|
1398
|
+
}
|
|
1100
1399
|
var init_resolved_config = __esm({
|
|
1101
1400
|
"src/config/resolved-config.ts"() {
|
|
1102
1401
|
"use strict";
|
|
@@ -2394,7 +2693,12 @@ var init_maintenance_jobs = __esm({
|
|
|
2394
2693
|
"vector-backfill",
|
|
2395
2694
|
"retention-archive",
|
|
2396
2695
|
"consolidation",
|
|
2397
|
-
"codegraph-refresh"
|
|
2696
|
+
"codegraph-refresh",
|
|
2697
|
+
"claim-derive",
|
|
2698
|
+
"claim-requalification",
|
|
2699
|
+
"knowledge-compile",
|
|
2700
|
+
"knowledge-lint",
|
|
2701
|
+
"workflow-index"
|
|
2398
2702
|
];
|
|
2399
2703
|
DEFAULT_MAX_ATTEMPTS = 8;
|
|
2400
2704
|
DEFAULT_LEASE_MS = 3e4;
|
|
@@ -2407,14 +2711,14 @@ var init_maintenance_jobs = __esm({
|
|
|
2407
2711
|
this.db = getDatabase(dataDir);
|
|
2408
2712
|
}
|
|
2409
2713
|
enqueue(input) {
|
|
2410
|
-
const
|
|
2714
|
+
const now3 = input.now ?? Date.now();
|
|
2411
2715
|
const dedupeKey = input.dedupeKey ?? input.kind;
|
|
2412
|
-
const runAfter = input.runAfter ??
|
|
2716
|
+
const runAfter = input.runAfter ?? now3;
|
|
2413
2717
|
const maxAttempts = clampPositiveInteger(input.maxAttempts, DEFAULT_MAX_ATTEMPTS);
|
|
2414
2718
|
const payloadJson = JSON.stringify(input.payload ?? {});
|
|
2415
2719
|
this.begin();
|
|
2416
2720
|
try {
|
|
2417
|
-
this.pruneCompletedHistory({ now });
|
|
2721
|
+
this.pruneCompletedHistory({ now: now3 });
|
|
2418
2722
|
const existing = this.db.prepare(`
|
|
2419
2723
|
SELECT * FROM maintenance_jobs
|
|
2420
2724
|
WHERE project_id = ? AND kind = ? AND dedupe_key = ?
|
|
@@ -2427,7 +2731,7 @@ var init_maintenance_jobs = __esm({
|
|
|
2427
2731
|
UPDATE maintenance_jobs
|
|
2428
2732
|
SET payload_json = ?, run_after = ?, updated_at = ?
|
|
2429
2733
|
WHERE id = ?
|
|
2430
|
-
`).run(payloadJson, nextRunAfter,
|
|
2734
|
+
`).run(payloadJson, nextRunAfter, now3, existing.id);
|
|
2431
2735
|
const updated = this.getRow(existing.id);
|
|
2432
2736
|
this.commit();
|
|
2433
2737
|
return rowToJob(updated);
|
|
@@ -2438,7 +2742,7 @@ var init_maintenance_jobs = __esm({
|
|
|
2438
2742
|
id, project_id, kind, dedupe_key, payload_json, status, attempts,
|
|
2439
2743
|
max_attempts, run_after, created_at, updated_at
|
|
2440
2744
|
) VALUES (?, ?, ?, ?, ?, 'pending', 0, ?, ?, ?, ?)
|
|
2441
|
-
`).run(id, input.projectId, input.kind, dedupeKey, payloadJson, maxAttempts, runAfter,
|
|
2745
|
+
`).run(id, input.projectId, input.kind, dedupeKey, payloadJson, maxAttempts, runAfter, now3, now3);
|
|
2442
2746
|
const created = this.getRow(id);
|
|
2443
2747
|
this.commit();
|
|
2444
2748
|
return rowToJob(created);
|
|
@@ -2507,18 +2811,18 @@ var init_maintenance_jobs = __esm({
|
|
|
2507
2811
|
* resolves them or clears the database.
|
|
2508
2812
|
*/
|
|
2509
2813
|
pruneCompletedHistory(options = {}) {
|
|
2510
|
-
const
|
|
2814
|
+
const now3 = options.now ?? Date.now();
|
|
2511
2815
|
const maxAgeMs = Number.isFinite(options.maxAgeMs) ? Math.max(0, Math.floor(options.maxAgeMs)) : DEFAULT_COMPLETED_HISTORY_RETENTION_MS;
|
|
2512
2816
|
const result = this.db.prepare(`
|
|
2513
2817
|
DELETE FROM maintenance_jobs
|
|
2514
2818
|
WHERE status = 'completed'
|
|
2515
2819
|
AND completed_at IS NOT NULL
|
|
2516
2820
|
AND completed_at < ?
|
|
2517
|
-
`).run(
|
|
2821
|
+
`).run(now3 - maxAgeMs);
|
|
2518
2822
|
return Number(result.changes ?? 0);
|
|
2519
2823
|
}
|
|
2520
2824
|
claimNext(options) {
|
|
2521
|
-
const
|
|
2825
|
+
const now3 = options.now ?? Date.now();
|
|
2522
2826
|
const leaseMs = clampPositiveInteger(options.leaseMs, DEFAULT_LEASE_MS);
|
|
2523
2827
|
const kinds = normalizedKinds(options.kinds);
|
|
2524
2828
|
this.begin();
|
|
@@ -2533,12 +2837,12 @@ var init_maintenance_jobs = __esm({
|
|
|
2533
2837
|
last_error = COALESCE(last_error, 'worker lease expired'),
|
|
2534
2838
|
updated_at = ?
|
|
2535
2839
|
WHERE status = 'running' AND lease_expires_at IS NOT NULL AND lease_expires_at <= ?
|
|
2536
|
-
`).run(
|
|
2840
|
+
`).run(now3, now3, now3);
|
|
2537
2841
|
this.db.prepare(`
|
|
2538
2842
|
UPDATE maintenance_jobs
|
|
2539
2843
|
SET status = 'failed', updated_at = ?
|
|
2540
2844
|
WHERE status IN ('pending', 'retry') AND attempts >= max_attempts
|
|
2541
|
-
`).run(
|
|
2845
|
+
`).run(now3);
|
|
2542
2846
|
if (options.kinds && kinds?.length === 0) {
|
|
2543
2847
|
this.commit();
|
|
2544
2848
|
return void 0;
|
|
@@ -2546,7 +2850,7 @@ var init_maintenance_jobs = __esm({
|
|
|
2546
2850
|
const projectClause = options.projectId ? "AND project_id = ?" : "";
|
|
2547
2851
|
const kindClause = kinds?.length ? `AND kind IN (${kinds.map(() => "?").join(", ")})` : "";
|
|
2548
2852
|
const values = [
|
|
2549
|
-
|
|
2853
|
+
now3,
|
|
2550
2854
|
...kinds ?? [],
|
|
2551
2855
|
...options.projectId ? [options.projectId] : []
|
|
2552
2856
|
];
|
|
@@ -2562,13 +2866,13 @@ var init_maintenance_jobs = __esm({
|
|
|
2562
2866
|
this.commit();
|
|
2563
2867
|
return void 0;
|
|
2564
2868
|
}
|
|
2565
|
-
const leaseExpiresAt =
|
|
2869
|
+
const leaseExpiresAt = now3 + leaseMs;
|
|
2566
2870
|
this.db.prepare(`
|
|
2567
2871
|
UPDATE maintenance_jobs
|
|
2568
2872
|
SET status = 'running', attempts = attempts + 1, lease_owner = ?,
|
|
2569
2873
|
lease_expires_at = ?, updated_at = ?
|
|
2570
2874
|
WHERE id = ?
|
|
2571
|
-
`).run(options.workerId, leaseExpiresAt,
|
|
2875
|
+
`).run(options.workerId, leaseExpiresAt, now3, candidate.id);
|
|
2572
2876
|
const claimed = this.getRow(candidate.id);
|
|
2573
2877
|
this.commit();
|
|
2574
2878
|
return rowToJob(claimed);
|
|
@@ -2581,26 +2885,26 @@ var init_maintenance_jobs = __esm({
|
|
|
2581
2885
|
* Extend a running job's lease. A worker may only renew its own lease, so a
|
|
2582
2886
|
* stale worker cannot reclaim work that was already recovered elsewhere.
|
|
2583
2887
|
*/
|
|
2584
|
-
renewLease(id, workerId, leaseMs = DEFAULT_LEASE_MS,
|
|
2585
|
-
const expiresAt =
|
|
2888
|
+
renewLease(id, workerId, leaseMs = DEFAULT_LEASE_MS, now3 = Date.now()) {
|
|
2889
|
+
const expiresAt = now3 + clampPositiveInteger(leaseMs, DEFAULT_LEASE_MS);
|
|
2586
2890
|
const result = this.db.prepare(`
|
|
2587
2891
|
UPDATE maintenance_jobs
|
|
2588
2892
|
SET lease_expires_at = ?, updated_at = ?
|
|
2589
2893
|
WHERE id = ? AND status = 'running' AND lease_owner = ?
|
|
2590
|
-
`).run(expiresAt,
|
|
2894
|
+
`).run(expiresAt, now3, id, workerId);
|
|
2591
2895
|
return Number(result.changes) > 0;
|
|
2592
2896
|
}
|
|
2593
|
-
complete(id, workerId,
|
|
2897
|
+
complete(id, workerId, now3 = Date.now()) {
|
|
2594
2898
|
this.db.prepare(`
|
|
2595
2899
|
UPDATE maintenance_jobs
|
|
2596
2900
|
SET status = 'completed', lease_owner = NULL, lease_expires_at = NULL,
|
|
2597
2901
|
completed_at = ?, updated_at = ?
|
|
2598
2902
|
WHERE id = ? AND status = 'running' AND lease_owner = ?
|
|
2599
|
-
`).run(
|
|
2903
|
+
`).run(now3, now3, id, workerId);
|
|
2600
2904
|
return this.get(id);
|
|
2601
2905
|
}
|
|
2602
2906
|
reschedule(id, workerId, options) {
|
|
2603
|
-
const
|
|
2907
|
+
const now3 = options.now ?? Date.now();
|
|
2604
2908
|
const delayMs = Number.isFinite(options.delayMs) ? Math.max(0, Math.floor(options.delayMs)) : 0;
|
|
2605
2909
|
const payloadJson = options.payload === void 0 ? null : JSON.stringify(options.payload);
|
|
2606
2910
|
this.db.prepare(`
|
|
@@ -2609,10 +2913,10 @@ var init_maintenance_jobs = __esm({
|
|
|
2609
2913
|
payload_json = COALESCE(?, payload_json),
|
|
2610
2914
|
lease_owner = NULL, lease_expires_at = NULL, updated_at = ?
|
|
2611
2915
|
WHERE id = ? AND status = 'running' AND lease_owner = ?
|
|
2612
|
-
`).run(
|
|
2916
|
+
`).run(now3 + delayMs, options.resetAttempts ? 1 : 0, payloadJson, now3, id, workerId);
|
|
2613
2917
|
return this.get(id);
|
|
2614
2918
|
}
|
|
2615
|
-
fail(id, workerId, error,
|
|
2919
|
+
fail(id, workerId, error, now3 = Date.now()) {
|
|
2616
2920
|
this.begin();
|
|
2617
2921
|
try {
|
|
2618
2922
|
const current = this.db.prepare(`
|
|
@@ -2626,13 +2930,13 @@ var init_maintenance_jobs = __esm({
|
|
|
2626
2930
|
const attempts = Number(current.attempts);
|
|
2627
2931
|
const exhausted = attempts >= Number(current.max_attempts);
|
|
2628
2932
|
const status = exhausted ? "failed" : "retry";
|
|
2629
|
-
const runAfter = exhausted ? Number(current.run_after) :
|
|
2933
|
+
const runAfter = exhausted ? Number(current.run_after) : now3 + getMaintenanceRetryDelayMs(attempts);
|
|
2630
2934
|
this.db.prepare(`
|
|
2631
2935
|
UPDATE maintenance_jobs
|
|
2632
2936
|
SET status = ?, run_after = ?, lease_owner = NULL, lease_expires_at = NULL,
|
|
2633
2937
|
last_error = ?, updated_at = ?
|
|
2634
2938
|
WHERE id = ?
|
|
2635
|
-
`).run(status, runAfter, errorText(error),
|
|
2939
|
+
`).run(status, runAfter, errorText(error), now3, id);
|
|
2636
2940
|
const updated = this.getRow(id);
|
|
2637
2941
|
this.commit();
|
|
2638
2942
|
return rowToJob(updated);
|
|
@@ -2676,7 +2980,7 @@ var init_maintenance_jobs = __esm({
|
|
|
2676
2980
|
kinds;
|
|
2677
2981
|
timer = null;
|
|
2678
2982
|
running = false;
|
|
2679
|
-
async runOnce(
|
|
2983
|
+
async runOnce(now3 = Date.now()) {
|
|
2680
2984
|
if (this.running) return { state: "busy" };
|
|
2681
2985
|
this.running = true;
|
|
2682
2986
|
try {
|
|
@@ -2684,7 +2988,7 @@ var init_maintenance_jobs = __esm({
|
|
|
2684
2988
|
workerId: this.workerId,
|
|
2685
2989
|
projectId: this.projectId,
|
|
2686
2990
|
kinds: this.kinds,
|
|
2687
|
-
now,
|
|
2991
|
+
now: now3,
|
|
2688
2992
|
leaseMs: this.leaseMs
|
|
2689
2993
|
});
|
|
2690
2994
|
if (!job) return { state: "idle" };
|
|
@@ -2707,14 +3011,14 @@ var init_maintenance_jobs = __esm({
|
|
|
2707
3011
|
delayMs: result.delayMs,
|
|
2708
3012
|
resetAttempts: result.resetAttempts,
|
|
2709
3013
|
payload: result.payload,
|
|
2710
|
-
now
|
|
3014
|
+
now: now3
|
|
2711
3015
|
});
|
|
2712
3016
|
return { state: "rescheduled", job: updated2 };
|
|
2713
3017
|
}
|
|
2714
|
-
const updated = this.store.complete(job.id, this.workerId,
|
|
3018
|
+
const updated = this.store.complete(job.id, this.workerId, now3);
|
|
2715
3019
|
return { state: "completed", job: updated };
|
|
2716
3020
|
} catch (error) {
|
|
2717
|
-
const updated = this.store.fail(job.id, this.workerId, error,
|
|
3021
|
+
const updated = this.store.fail(job.id, this.workerId, error, now3);
|
|
2718
3022
|
return { state: "failed", job: updated };
|
|
2719
3023
|
} finally {
|
|
2720
3024
|
clearInterval(heartbeat);
|
|
@@ -2742,6 +3046,527 @@ var init_maintenance_jobs = __esm({
|
|
|
2742
3046
|
}
|
|
2743
3047
|
});
|
|
2744
3048
|
|
|
3049
|
+
// src/runtime/lifecycle.ts
|
|
3050
|
+
function queueFor(input) {
|
|
3051
|
+
return input.queue ?? new MaintenanceJobStore(input.dataDir);
|
|
3052
|
+
}
|
|
3053
|
+
function enqueueClaimRequalification(input) {
|
|
3054
|
+
queueFor(input).enqueue({
|
|
3055
|
+
projectId: input.projectId,
|
|
3056
|
+
kind: "claim-requalification",
|
|
3057
|
+
dedupeKey: "claim-requalification",
|
|
3058
|
+
payload: {
|
|
3059
|
+
source: input.source,
|
|
3060
|
+
...input.snapshotId ? { snapshotId: input.snapshotId } : {}
|
|
3061
|
+
}
|
|
3062
|
+
});
|
|
3063
|
+
}
|
|
3064
|
+
function enqueueClaimDerivation(input) {
|
|
3065
|
+
queueFor(input).enqueue({
|
|
3066
|
+
projectId: input.projectId,
|
|
3067
|
+
kind: "claim-derive",
|
|
3068
|
+
dedupeKey: "claim-derive:" + input.observationId,
|
|
3069
|
+
payload: { observationId: input.observationId }
|
|
3070
|
+
});
|
|
3071
|
+
}
|
|
3072
|
+
function enqueueKnowledgeFollowups(input) {
|
|
3073
|
+
const queue = queueFor(input);
|
|
3074
|
+
const payload = {
|
|
3075
|
+
source: input.source,
|
|
3076
|
+
...input.workspaceMode ? { workspaceMode: input.workspaceMode } : {},
|
|
3077
|
+
...input.allowVersionedWrite ? { allowVersionedWrite: true } : {}
|
|
3078
|
+
};
|
|
3079
|
+
if (input.includeCompile) {
|
|
3080
|
+
queue.enqueue({
|
|
3081
|
+
projectId: input.projectId,
|
|
3082
|
+
kind: "knowledge-compile",
|
|
3083
|
+
dedupeKey: "knowledge-compile",
|
|
3084
|
+
payload
|
|
3085
|
+
});
|
|
3086
|
+
}
|
|
3087
|
+
queue.enqueue({
|
|
3088
|
+
projectId: input.projectId,
|
|
3089
|
+
kind: "knowledge-lint",
|
|
3090
|
+
dedupeKey: "knowledge-lint",
|
|
3091
|
+
payload
|
|
3092
|
+
});
|
|
3093
|
+
queue.enqueue({
|
|
3094
|
+
projectId: input.projectId,
|
|
3095
|
+
kind: "workflow-index",
|
|
3096
|
+
dedupeKey: "workflow-index",
|
|
3097
|
+
payload
|
|
3098
|
+
});
|
|
3099
|
+
}
|
|
3100
|
+
var init_lifecycle = __esm({
|
|
3101
|
+
"src/runtime/lifecycle.ts"() {
|
|
3102
|
+
"use strict";
|
|
3103
|
+
init_esm_shims();
|
|
3104
|
+
init_maintenance_jobs();
|
|
3105
|
+
}
|
|
3106
|
+
});
|
|
3107
|
+
|
|
3108
|
+
// src/store/file-lock.ts
|
|
3109
|
+
import { promises as fs3 } from "fs";
|
|
3110
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
3111
|
+
import path6 from "path";
|
|
3112
|
+
async function acquireLock(lockPath) {
|
|
3113
|
+
for (let i = 0; i < MAX_RETRIES; i++) {
|
|
3114
|
+
try {
|
|
3115
|
+
const fd = await fs3.open(lockPath, "wx");
|
|
3116
|
+
await fd.writeFile(JSON.stringify({ pid: process.pid, time: Date.now() }));
|
|
3117
|
+
await fd.close();
|
|
3118
|
+
return;
|
|
3119
|
+
} catch (err) {
|
|
3120
|
+
const code = err instanceof Error && "code" in err ? err.code : void 0;
|
|
3121
|
+
if (code === "EEXIST" || code === "EPERM") {
|
|
3122
|
+
try {
|
|
3123
|
+
const stat = await fs3.stat(lockPath);
|
|
3124
|
+
if (Date.now() - stat.mtimeMs > LOCK_STALE_MS) {
|
|
3125
|
+
await fs3.unlink(lockPath).catch(() => {
|
|
3126
|
+
});
|
|
3127
|
+
continue;
|
|
3128
|
+
}
|
|
3129
|
+
} catch {
|
|
3130
|
+
continue;
|
|
3131
|
+
}
|
|
3132
|
+
await new Promise((r) => setTimeout(r, RETRY_INTERVAL_MS));
|
|
3133
|
+
} else {
|
|
3134
|
+
throw err;
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
3138
|
+
await fs3.unlink(lockPath).catch(() => {
|
|
3139
|
+
});
|
|
3140
|
+
try {
|
|
3141
|
+
const fd = await fs3.open(lockPath, "wx");
|
|
3142
|
+
await fd.writeFile(JSON.stringify({ pid: process.pid, time: Date.now() }));
|
|
3143
|
+
await fd.close();
|
|
3144
|
+
return;
|
|
3145
|
+
} catch {
|
|
3146
|
+
throw new Error(`Failed to acquire lock: ${lockPath} (timeout after ${MAX_RETRIES * RETRY_INTERVAL_MS}ms)`);
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
3149
|
+
async function releaseLock(lockPath) {
|
|
3150
|
+
await fs3.unlink(lockPath).catch(() => {
|
|
3151
|
+
});
|
|
3152
|
+
}
|
|
3153
|
+
async function withFileLock(projectDir2, fn) {
|
|
3154
|
+
const lockPath = path6.join(projectDir2, ".memorix.lock");
|
|
3155
|
+
await acquireLock(lockPath);
|
|
3156
|
+
try {
|
|
3157
|
+
return await fn();
|
|
3158
|
+
} finally {
|
|
3159
|
+
await releaseLock(lockPath);
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3162
|
+
async function atomicWriteFileOnce(filePath, data) {
|
|
3163
|
+
const tmpPath = filePath + `.tmp.${process.pid}.${randomUUID2()}`;
|
|
3164
|
+
try {
|
|
3165
|
+
await fs3.writeFile(tmpPath, data, "utf-8");
|
|
3166
|
+
await fs3.rename(tmpPath, filePath);
|
|
3167
|
+
} catch (error) {
|
|
3168
|
+
await fs3.unlink(tmpPath).catch(() => {
|
|
3169
|
+
});
|
|
3170
|
+
throw error;
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
3173
|
+
function atomicWriteFile(filePath, data) {
|
|
3174
|
+
const previous = atomicWriteTails.get(filePath) ?? Promise.resolve();
|
|
3175
|
+
const next = previous.catch(() => void 0).then(() => atomicWriteFileOnce(filePath, data));
|
|
3176
|
+
atomicWriteTails.set(filePath, next);
|
|
3177
|
+
return next.finally(() => {
|
|
3178
|
+
if (atomicWriteTails.get(filePath) === next) atomicWriteTails.delete(filePath);
|
|
3179
|
+
});
|
|
3180
|
+
}
|
|
3181
|
+
var LOCK_STALE_MS, RETRY_INTERVAL_MS, MAX_RETRIES, atomicWriteTails;
|
|
3182
|
+
var init_file_lock = __esm({
|
|
3183
|
+
"src/store/file-lock.ts"() {
|
|
3184
|
+
"use strict";
|
|
3185
|
+
init_esm_shims();
|
|
3186
|
+
LOCK_STALE_MS = 1e4;
|
|
3187
|
+
RETRY_INTERVAL_MS = 50;
|
|
3188
|
+
MAX_RETRIES = 60;
|
|
3189
|
+
atomicWriteTails = /* @__PURE__ */ new Map();
|
|
3190
|
+
}
|
|
3191
|
+
});
|
|
3192
|
+
|
|
3193
|
+
// src/knowledge/workspace-store.ts
|
|
3194
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
3195
|
+
function arrayValue(raw) {
|
|
3196
|
+
if (typeof raw !== "string" || !raw) return [];
|
|
3197
|
+
try {
|
|
3198
|
+
const parsed = JSON.parse(raw);
|
|
3199
|
+
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
|
|
3200
|
+
} catch {
|
|
3201
|
+
return [];
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
function optionalText(value) {
|
|
3205
|
+
return typeof value === "string" && value ? value : void 0;
|
|
3206
|
+
}
|
|
3207
|
+
function rowToWorkspace(row) {
|
|
3208
|
+
return {
|
|
3209
|
+
id: row.id,
|
|
3210
|
+
projectId: row.projectId,
|
|
3211
|
+
mode: row.mode,
|
|
3212
|
+
rootPath: row.rootPath,
|
|
3213
|
+
...optionalText(row.projectRoot) ? { projectRoot: row.projectRoot } : {},
|
|
3214
|
+
status: row.status,
|
|
3215
|
+
createdAt: row.createdAt,
|
|
3216
|
+
updatedAt: row.updatedAt,
|
|
3217
|
+
...optionalText(row.lastCompiledAt) ? { lastCompiledAt: row.lastCompiledAt } : {},
|
|
3218
|
+
...optionalText(row.lastLintedAt) ? { lastLintedAt: row.lastLintedAt } : {}
|
|
3219
|
+
};
|
|
3220
|
+
}
|
|
3221
|
+
function rowToPage(row) {
|
|
3222
|
+
return {
|
|
3223
|
+
id: row.id,
|
|
3224
|
+
workspaceId: row.workspaceId,
|
|
3225
|
+
relativePath: row.relativePath,
|
|
3226
|
+
title: row.title,
|
|
3227
|
+
kind: row.kind,
|
|
3228
|
+
status: row.status,
|
|
3229
|
+
reviewState: row.reviewState,
|
|
3230
|
+
contentHash: row.contentHash,
|
|
3231
|
+
sourceHash: row.sourceHash,
|
|
3232
|
+
claimIds: arrayValue(row.claimIdsJson),
|
|
3233
|
+
...optionalText(row.snapshotId) ? { snapshotId: row.snapshotId } : {},
|
|
3234
|
+
tags: arrayValue(row.tagsJson),
|
|
3235
|
+
generatedAt: row.generatedAt,
|
|
3236
|
+
updatedAt: row.updatedAt,
|
|
3237
|
+
...optionalText(row.lastLintedAt) ? { lastLintedAt: row.lastLintedAt } : {},
|
|
3238
|
+
...optionalText(row.manualContentHash) ? { manualContentHash: row.manualContentHash } : {}
|
|
3239
|
+
};
|
|
3240
|
+
}
|
|
3241
|
+
function rowToProposal(row) {
|
|
3242
|
+
return {
|
|
3243
|
+
id: row.id,
|
|
3244
|
+
workspaceId: row.workspaceId,
|
|
3245
|
+
pageId: row.pageId,
|
|
3246
|
+
targetPath: row.targetPath,
|
|
3247
|
+
proposalPath: row.proposalPath,
|
|
3248
|
+
...optionalText(row.baseContentHash) ? { baseContentHash: row.baseContentHash } : {},
|
|
3249
|
+
sourceHash: row.sourceHash,
|
|
3250
|
+
reason: row.reason,
|
|
3251
|
+
status: row.status,
|
|
3252
|
+
createdAt: row.createdAt,
|
|
3253
|
+
...optionalText(row.appliedAt) ? { appliedAt: row.appliedAt } : {}
|
|
3254
|
+
};
|
|
3255
|
+
}
|
|
3256
|
+
var KnowledgeWorkspaceStore;
|
|
3257
|
+
var init_workspace_store = __esm({
|
|
3258
|
+
"src/knowledge/workspace-store.ts"() {
|
|
3259
|
+
"use strict";
|
|
3260
|
+
init_esm_shims();
|
|
3261
|
+
init_sqlite_db();
|
|
3262
|
+
KnowledgeWorkspaceStore = class {
|
|
3263
|
+
db = null;
|
|
3264
|
+
async init(dataDir) {
|
|
3265
|
+
this.db = getDatabase(dataDir);
|
|
3266
|
+
}
|
|
3267
|
+
upsertWorkspace(input) {
|
|
3268
|
+
const now3 = input.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
3269
|
+
const createdAt = input.createdAt ?? now3;
|
|
3270
|
+
this.db.prepare(
|
|
3271
|
+
"INSERT INTO knowledge_workspaces (id, projectId, mode, rootPath, projectRoot, status, createdAt, updatedAt, lastCompiledAt, lastLintedAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET mode = excluded.mode, rootPath = excluded.rootPath, projectRoot = excluded.projectRoot, status = excluded.status, updatedAt = excluded.updatedAt, lastCompiledAt = excluded.lastCompiledAt, lastLintedAt = excluded.lastLintedAt"
|
|
3272
|
+
).run(
|
|
3273
|
+
input.id,
|
|
3274
|
+
input.projectId,
|
|
3275
|
+
input.mode,
|
|
3276
|
+
input.rootPath,
|
|
3277
|
+
input.projectRoot ?? null,
|
|
3278
|
+
input.status,
|
|
3279
|
+
createdAt,
|
|
3280
|
+
now3,
|
|
3281
|
+
input.lastCompiledAt ?? null,
|
|
3282
|
+
input.lastLintedAt ?? null
|
|
3283
|
+
);
|
|
3284
|
+
return this.getWorkspace(input.id);
|
|
3285
|
+
}
|
|
3286
|
+
getWorkspace(id) {
|
|
3287
|
+
const row = this.db.prepare("SELECT * FROM knowledge_workspaces WHERE id = ?").get(id);
|
|
3288
|
+
return row ? rowToWorkspace(row) : void 0;
|
|
3289
|
+
}
|
|
3290
|
+
findWorkspace(projectId, mode) {
|
|
3291
|
+
const row = this.db.prepare("SELECT * FROM knowledge_workspaces WHERE projectId = ? AND mode = ? ORDER BY updatedAt DESC LIMIT 1").get(projectId, mode);
|
|
3292
|
+
return row ? rowToWorkspace(row) : void 0;
|
|
3293
|
+
}
|
|
3294
|
+
upsertPage(page) {
|
|
3295
|
+
this.db.prepare(
|
|
3296
|
+
"INSERT INTO knowledge_pages (id, workspaceId, relativePath, title, kind, status, reviewState, contentHash, sourceHash, claimIdsJson, snapshotId, tagsJson, generatedAt, updatedAt, lastLintedAt, manualContentHash) VALUES (@id, @workspaceId, @relativePath, @title, @kind, @status, @reviewState, @contentHash, @sourceHash, @claimIdsJson, @snapshotId, @tagsJson, @generatedAt, @updatedAt, @lastLintedAt, @manualContentHash) ON CONFLICT(workspaceId, relativePath) DO UPDATE SET id = excluded.id, title = excluded.title, kind = excluded.kind, status = excluded.status, reviewState = excluded.reviewState, contentHash = excluded.contentHash, sourceHash = excluded.sourceHash, claimIdsJson = excluded.claimIdsJson, snapshotId = excluded.snapshotId, tagsJson = excluded.tagsJson, generatedAt = excluded.generatedAt, updatedAt = excluded.updatedAt, lastLintedAt = excluded.lastLintedAt, manualContentHash = excluded.manualContentHash"
|
|
3297
|
+
).run({
|
|
3298
|
+
...page,
|
|
3299
|
+
claimIdsJson: JSON.stringify(page.claimIds),
|
|
3300
|
+
snapshotId: page.snapshotId ?? null,
|
|
3301
|
+
tagsJson: JSON.stringify(page.tags),
|
|
3302
|
+
lastLintedAt: page.lastLintedAt ?? null,
|
|
3303
|
+
manualContentHash: page.manualContentHash ?? null
|
|
3304
|
+
});
|
|
3305
|
+
}
|
|
3306
|
+
getPage(workspaceId2, relativePath) {
|
|
3307
|
+
const row = this.db.prepare("SELECT * FROM knowledge_pages WHERE workspaceId = ? AND relativePath = ?").get(workspaceId2, relativePath);
|
|
3308
|
+
return row ? rowToPage(row) : void 0;
|
|
3309
|
+
}
|
|
3310
|
+
getPageById(id) {
|
|
3311
|
+
const row = this.db.prepare("SELECT * FROM knowledge_pages WHERE id = ?").get(id);
|
|
3312
|
+
return row ? rowToPage(row) : void 0;
|
|
3313
|
+
}
|
|
3314
|
+
listPages(workspaceId2) {
|
|
3315
|
+
return this.db.prepare("SELECT * FROM knowledge_pages WHERE workspaceId = ? ORDER BY relativePath").all(workspaceId2).map(rowToPage);
|
|
3316
|
+
}
|
|
3317
|
+
replacePageClaims(pageId, claimIds) {
|
|
3318
|
+
const remove3 = this.db.prepare("DELETE FROM knowledge_page_claims WHERE pageId = ?");
|
|
3319
|
+
const insert3 = this.db.prepare("INSERT OR IGNORE INTO knowledge_page_claims (pageId, claimId, role) VALUES (?, ?, 'primary')");
|
|
3320
|
+
this.db.transaction(() => {
|
|
3321
|
+
remove3.run(pageId);
|
|
3322
|
+
for (const claimId of [...new Set(claimIds)]) insert3.run(pageId, claimId);
|
|
3323
|
+
})();
|
|
3324
|
+
}
|
|
3325
|
+
replacePageLinks(pageId, targets) {
|
|
3326
|
+
const remove3 = this.db.prepare("DELETE FROM knowledge_page_links WHERE sourcePageId = ?");
|
|
3327
|
+
const insert3 = this.db.prepare("INSERT OR IGNORE INTO knowledge_page_links (sourcePageId, targetPath) VALUES (?, ?)");
|
|
3328
|
+
this.db.transaction(() => {
|
|
3329
|
+
remove3.run(pageId);
|
|
3330
|
+
for (const target of [...new Set(targets)]) insert3.run(pageId, target);
|
|
3331
|
+
})();
|
|
3332
|
+
}
|
|
3333
|
+
createProposal(input) {
|
|
3334
|
+
const proposal = {
|
|
3335
|
+
id: input.id ?? randomUUID3(),
|
|
3336
|
+
...input,
|
|
3337
|
+
status: input.status ?? "pending",
|
|
3338
|
+
createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
3339
|
+
};
|
|
3340
|
+
this.db.prepare(
|
|
3341
|
+
"INSERT INTO knowledge_proposals (id, workspaceId, pageId, targetPath, proposalPath, baseContentHash, sourceHash, reason, status, createdAt, appliedAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(workspaceId, proposalPath) DO UPDATE SET pageId = excluded.pageId, targetPath = excluded.targetPath, baseContentHash = excluded.baseContentHash, sourceHash = excluded.sourceHash, reason = excluded.reason, status = excluded.status, createdAt = excluded.createdAt, appliedAt = excluded.appliedAt"
|
|
3342
|
+
).run(
|
|
3343
|
+
proposal.id,
|
|
3344
|
+
proposal.workspaceId,
|
|
3345
|
+
proposal.pageId,
|
|
3346
|
+
proposal.targetPath,
|
|
3347
|
+
proposal.proposalPath,
|
|
3348
|
+
proposal.baseContentHash ?? null,
|
|
3349
|
+
proposal.sourceHash,
|
|
3350
|
+
proposal.reason,
|
|
3351
|
+
proposal.status,
|
|
3352
|
+
proposal.createdAt,
|
|
3353
|
+
proposal.appliedAt ?? null
|
|
3354
|
+
);
|
|
3355
|
+
const row = this.db.prepare("SELECT * FROM knowledge_proposals WHERE workspaceId = ? AND proposalPath = ?").get(proposal.workspaceId, proposal.proposalPath);
|
|
3356
|
+
return rowToProposal(row);
|
|
3357
|
+
}
|
|
3358
|
+
getProposal(id) {
|
|
3359
|
+
const row = this.db.prepare("SELECT * FROM knowledge_proposals WHERE id = ?").get(id);
|
|
3360
|
+
return row ? rowToProposal(row) : void 0;
|
|
3361
|
+
}
|
|
3362
|
+
listProposals(workspaceId2, status) {
|
|
3363
|
+
const rows = status ? this.db.prepare("SELECT * FROM knowledge_proposals WHERE workspaceId = ? AND status = ? ORDER BY createdAt DESC, id").all(workspaceId2, status) : this.db.prepare("SELECT * FROM knowledge_proposals WHERE workspaceId = ? ORDER BY createdAt DESC, id").all(workspaceId2);
|
|
3364
|
+
return rows.map(rowToProposal);
|
|
3365
|
+
}
|
|
3366
|
+
markProposalApplied(id, appliedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
3367
|
+
this.db.prepare("UPDATE knowledge_proposals SET status = 'applied', appliedAt = ? WHERE id = ?").run(appliedAt, id);
|
|
3368
|
+
return this.getProposal(id);
|
|
3369
|
+
}
|
|
3370
|
+
markWorkspaceCompiled(id, at = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
3371
|
+
this.db.prepare("UPDATE knowledge_workspaces SET lastCompiledAt = ?, updatedAt = ? WHERE id = ?").run(at, at, id);
|
|
3372
|
+
}
|
|
3373
|
+
markWorkspaceLinted(id, status, at = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
3374
|
+
this.db.prepare("UPDATE knowledge_workspaces SET status = ?, lastLintedAt = ?, updatedAt = ? WHERE id = ?").run(status, at, at, id);
|
|
3375
|
+
}
|
|
3376
|
+
};
|
|
3377
|
+
}
|
|
3378
|
+
});
|
|
3379
|
+
|
|
3380
|
+
// src/knowledge/workspace.ts
|
|
3381
|
+
var workspace_exports = {};
|
|
3382
|
+
__export(workspace_exports, {
|
|
3383
|
+
getKnowledgeWorkspacePaths: () => getKnowledgeWorkspacePaths,
|
|
3384
|
+
initializeKnowledgeWorkspace: () => initializeKnowledgeWorkspace,
|
|
3385
|
+
loadKnowledgeWorkspace: () => loadKnowledgeWorkspace,
|
|
3386
|
+
resolveKnowledgeWorkspaceFile: () => resolveKnowledgeWorkspaceFile
|
|
3387
|
+
});
|
|
3388
|
+
import { createHash } from "crypto";
|
|
3389
|
+
import { execFile } from "child_process";
|
|
3390
|
+
import { promises as fs4 } from "fs";
|
|
3391
|
+
import path7 from "path";
|
|
3392
|
+
import { promisify } from "util";
|
|
3393
|
+
function hash(value) {
|
|
3394
|
+
return createHash("sha256").update(value).digest("hex");
|
|
3395
|
+
}
|
|
3396
|
+
function workspaceId(projectId, mode) {
|
|
3397
|
+
return "workspace:" + hash(projectId + ":" + mode).slice(0, 24);
|
|
3398
|
+
}
|
|
3399
|
+
function localWorkspacePath(dataDir, projectId) {
|
|
3400
|
+
return path7.join(path7.resolve(dataDir), "knowledge-workspaces", hash(projectId).slice(0, 24));
|
|
3401
|
+
}
|
|
3402
|
+
function isWithin(root, candidate) {
|
|
3403
|
+
const relative2 = path7.relative(root, candidate);
|
|
3404
|
+
return relative2 !== "" && !relative2.startsWith(".." + path7.sep) && relative2 !== ".." && !path7.isAbsolute(relative2);
|
|
3405
|
+
}
|
|
3406
|
+
function ensureVersionedPath(projectRoot, rootPath) {
|
|
3407
|
+
if (!path7.isAbsolute(rootPath)) {
|
|
3408
|
+
throw new Error("A versioned knowledge workspace requires an explicit absolute path");
|
|
3409
|
+
}
|
|
3410
|
+
const resolvedProject = path7.resolve(projectRoot);
|
|
3411
|
+
const resolvedWorkspace = path7.resolve(rootPath);
|
|
3412
|
+
if (!isWithin(resolvedProject, resolvedWorkspace)) {
|
|
3413
|
+
throw new Error("A versioned knowledge workspace must live inside the Git project");
|
|
3414
|
+
}
|
|
3415
|
+
const segments = path7.relative(resolvedProject, resolvedWorkspace).split(path7.sep);
|
|
3416
|
+
if (segments.includes(".git") || segments.includes("node_modules") || segments.includes("dist")) {
|
|
3417
|
+
throw new Error("The selected knowledge workspace path is not safe for project artifacts");
|
|
3418
|
+
}
|
|
3419
|
+
return resolvedWorkspace;
|
|
3420
|
+
}
|
|
3421
|
+
async function isIgnoredByGit(projectRoot, workspaceRoot) {
|
|
3422
|
+
const relative2 = path7.relative(projectRoot, workspaceRoot).split(path7.sep).join("/");
|
|
3423
|
+
const probes = [relative2, relative2.replace(/\/+$/, "") + "/.memorix-workspace-probe"];
|
|
3424
|
+
for (const probe of probes) {
|
|
3425
|
+
try {
|
|
3426
|
+
await execFileAsync("git", ["-C", projectRoot, "check-ignore", "--quiet", "--no-index", "--", probe], {
|
|
3427
|
+
timeout: 3e3,
|
|
3428
|
+
windowsHide: true
|
|
3429
|
+
});
|
|
3430
|
+
return true;
|
|
3431
|
+
} catch (error) {
|
|
3432
|
+
const code = error && typeof error === "object" && "code" in error ? error.code : void 0;
|
|
3433
|
+
if (code === 1 || code === "1") continue;
|
|
3434
|
+
return false;
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
return false;
|
|
3438
|
+
}
|
|
3439
|
+
async function assertNoSymlinkEscape(projectRoot, workspaceRoot) {
|
|
3440
|
+
const [realProject, realWorkspace] = await Promise.all([
|
|
3441
|
+
fs4.realpath(projectRoot),
|
|
3442
|
+
fs4.realpath(workspaceRoot)
|
|
3443
|
+
]);
|
|
3444
|
+
if (!isWithin(realProject, realWorkspace)) {
|
|
3445
|
+
throw new Error("The selected knowledge workspace resolves outside the Git project");
|
|
3446
|
+
}
|
|
3447
|
+
}
|
|
3448
|
+
function getKnowledgeWorkspacePaths(workspace) {
|
|
3449
|
+
const root = path7.resolve(workspace.rootPath);
|
|
3450
|
+
return {
|
|
3451
|
+
root,
|
|
3452
|
+
pages: path7.join(root, "pages"),
|
|
3453
|
+
proposals: path7.join(root, "proposals"),
|
|
3454
|
+
workflows: path7.join(root, "workflows"),
|
|
3455
|
+
index: path7.join(root, "index.md"),
|
|
3456
|
+
log: path7.join(root, "log.md"),
|
|
3457
|
+
schema: path7.join(root, "schema.md")
|
|
3458
|
+
};
|
|
3459
|
+
}
|
|
3460
|
+
function resolveKnowledgeWorkspaceFile(workspace, relativePath) {
|
|
3461
|
+
if (!relativePath || path7.isAbsolute(relativePath)) {
|
|
3462
|
+
throw new Error("Knowledge workspace paths must be relative");
|
|
3463
|
+
}
|
|
3464
|
+
const root = path7.resolve(workspace.rootPath);
|
|
3465
|
+
const candidate = path7.resolve(root, relativePath);
|
|
3466
|
+
if (candidate === root || !isWithin(root, candidate)) {
|
|
3467
|
+
throw new Error("Knowledge workspace path escapes its root");
|
|
3468
|
+
}
|
|
3469
|
+
return candidate;
|
|
3470
|
+
}
|
|
3471
|
+
async function writeIfMissing(filePath, content) {
|
|
3472
|
+
try {
|
|
3473
|
+
await fs4.access(filePath);
|
|
3474
|
+
} catch {
|
|
3475
|
+
await atomicWriteFile(filePath, content);
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
function schemaContent() {
|
|
3479
|
+
return [
|
|
3480
|
+
"# Memorix Knowledge Workspace",
|
|
3481
|
+
"",
|
|
3482
|
+
"This workspace is compiled from source-qualified Memorix claims.",
|
|
3483
|
+
"Topic pages are proposal-first: review and apply a proposal before relying",
|
|
3484
|
+
"on it as a published project knowledge page.",
|
|
3485
|
+
"",
|
|
3486
|
+
"Do not remove claim ids, evidence references, source hashes, or snapshot",
|
|
3487
|
+
"references from page frontmatter. Manual body edits are preserved and cause",
|
|
3488
|
+
"later compiler output to remain a proposal.",
|
|
3489
|
+
""
|
|
3490
|
+
].join("\n");
|
|
3491
|
+
}
|
|
3492
|
+
function indexContent() {
|
|
3493
|
+
return [
|
|
3494
|
+
"# Knowledge Workspace",
|
|
3495
|
+
"",
|
|
3496
|
+
"## Published pages",
|
|
3497
|
+
"",
|
|
3498
|
+
"No published pages yet.",
|
|
3499
|
+
"",
|
|
3500
|
+
"## Review queue",
|
|
3501
|
+
"",
|
|
3502
|
+
"No pending proposals.",
|
|
3503
|
+
""
|
|
3504
|
+
].join("\n");
|
|
3505
|
+
}
|
|
3506
|
+
function logContent() {
|
|
3507
|
+
return "# Knowledge log\n\n";
|
|
3508
|
+
}
|
|
3509
|
+
async function initializeKnowledgeWorkspace(input) {
|
|
3510
|
+
if (!input.projectId.trim()) throw new Error("Knowledge workspace project id is required");
|
|
3511
|
+
if (!input.dataDir.trim()) throw new Error("Knowledge workspace data directory is required");
|
|
3512
|
+
let rootPath;
|
|
3513
|
+
let projectRoot;
|
|
3514
|
+
if (input.mode === "local") {
|
|
3515
|
+
rootPath = localWorkspacePath(input.dataDir, input.projectId);
|
|
3516
|
+
} else {
|
|
3517
|
+
if (!input.projectRoot || !input.rootPath) {
|
|
3518
|
+
throw new Error("A versioned knowledge workspace requires projectRoot and rootPath");
|
|
3519
|
+
}
|
|
3520
|
+
projectRoot = path7.resolve(input.projectRoot);
|
|
3521
|
+
rootPath = ensureVersionedPath(projectRoot, input.rootPath);
|
|
3522
|
+
if (await isIgnoredByGit(projectRoot, rootPath)) {
|
|
3523
|
+
throw new Error("The selected versioned knowledge workspace is ignored by Git");
|
|
3524
|
+
}
|
|
3525
|
+
}
|
|
3526
|
+
const paths = getKnowledgeWorkspacePaths({ rootPath });
|
|
3527
|
+
await fs4.mkdir(paths.pages, { recursive: true });
|
|
3528
|
+
await Promise.all([
|
|
3529
|
+
fs4.mkdir(paths.proposals, { recursive: true }),
|
|
3530
|
+
fs4.mkdir(paths.workflows, { recursive: true })
|
|
3531
|
+
]);
|
|
3532
|
+
if (projectRoot) await assertNoSymlinkEscape(projectRoot, paths.root);
|
|
3533
|
+
const workspace = {
|
|
3534
|
+
id: workspaceId(input.projectId, input.mode),
|
|
3535
|
+
projectId: input.projectId,
|
|
3536
|
+
dataDir: path7.resolve(input.dataDir),
|
|
3537
|
+
mode: input.mode,
|
|
3538
|
+
rootPath: paths.root,
|
|
3539
|
+
...projectRoot ? { projectRoot } : {},
|
|
3540
|
+
status: "ready",
|
|
3541
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3542
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3543
|
+
};
|
|
3544
|
+
await withFileLock(paths.root, async () => {
|
|
3545
|
+
await writeIfMissing(paths.schema, schemaContent());
|
|
3546
|
+
await writeIfMissing(paths.index, indexContent());
|
|
3547
|
+
await writeIfMissing(paths.log, logContent());
|
|
3548
|
+
});
|
|
3549
|
+
const store = new KnowledgeWorkspaceStore();
|
|
3550
|
+
await store.init(input.dataDir);
|
|
3551
|
+
return { ...store.upsertWorkspace(workspace), dataDir: workspace.dataDir };
|
|
3552
|
+
}
|
|
3553
|
+
async function loadKnowledgeWorkspace(input) {
|
|
3554
|
+
const store = new KnowledgeWorkspaceStore();
|
|
3555
|
+
await store.init(input.dataDir);
|
|
3556
|
+
const workspace = store.findWorkspace(input.projectId, input.mode ?? "local");
|
|
3557
|
+
return workspace ? { ...workspace, dataDir: path7.resolve(input.dataDir) } : void 0;
|
|
3558
|
+
}
|
|
3559
|
+
var execFileAsync;
|
|
3560
|
+
var init_workspace = __esm({
|
|
3561
|
+
"src/knowledge/workspace.ts"() {
|
|
3562
|
+
"use strict";
|
|
3563
|
+
init_esm_shims();
|
|
3564
|
+
init_file_lock();
|
|
3565
|
+
init_workspace_store();
|
|
3566
|
+
execFileAsync = promisify(execFile);
|
|
3567
|
+
}
|
|
3568
|
+
});
|
|
3569
|
+
|
|
2745
3570
|
// src/memory/consolidation.ts
|
|
2746
3571
|
var consolidation_exports = {};
|
|
2747
3572
|
__export(consolidation_exports, {
|
|
@@ -3174,12 +3999,12 @@ function getImportanceLevel(doc) {
|
|
|
3174
3999
|
return TYPE_IMPORTANCE[doc.type] ?? "medium";
|
|
3175
4000
|
}
|
|
3176
4001
|
function calculateRelevance(doc, referenceTime) {
|
|
3177
|
-
const
|
|
4002
|
+
const now3 = referenceTime ?? /* @__PURE__ */ new Date();
|
|
3178
4003
|
const importance = getImportanceLevel(doc);
|
|
3179
4004
|
const base = BASE_IMPORTANCE[importance];
|
|
3180
4005
|
const retention = getEffectiveRetentionDays(doc);
|
|
3181
4006
|
const createdAt = new Date(doc.createdAt);
|
|
3182
|
-
const ageDays = Math.max(0, (
|
|
4007
|
+
const ageDays = Math.max(0, (now3.getTime() - createdAt.getTime()) / (1e3 * 60 * 60 * 24));
|
|
3183
4008
|
const decayFactor = Math.exp(-ageDays / retention);
|
|
3184
4009
|
const accessCount = doc.accessCount ?? 0;
|
|
3185
4010
|
const accessBoost = Math.min(2, 1 + 0.1 * accessCount);
|
|
@@ -3202,14 +4027,14 @@ function rankByRelevance(docs, referenceTime) {
|
|
|
3202
4027
|
return docs.map((doc) => calculateRelevance(doc, referenceTime)).sort((a, b) => b.totalScore - a.totalScore);
|
|
3203
4028
|
}
|
|
3204
4029
|
function getRetentionZone(doc, referenceTime) {
|
|
3205
|
-
const
|
|
4030
|
+
const now3 = referenceTime ?? /* @__PURE__ */ new Date();
|
|
3206
4031
|
const importance = getImportanceLevel(doc);
|
|
3207
4032
|
const retention = getEffectiveRetentionDays(doc);
|
|
3208
4033
|
const createdAt = new Date(doc.createdAt);
|
|
3209
|
-
const ageDays = (
|
|
4034
|
+
const ageDays = (now3.getTime() - createdAt.getTime()) / (1e3 * 60 * 60 * 24);
|
|
3210
4035
|
if (doc.lastAccessedAt) {
|
|
3211
4036
|
const lastAccess = new Date(doc.lastAccessedAt);
|
|
3212
|
-
const daysSinceAccess = (
|
|
4037
|
+
const daysSinceAccess = (now3.getTime() - lastAccess.getTime()) / (1e3 * 60 * 60 * 24);
|
|
3213
4038
|
if (daysSinceAccess < 7) return "active";
|
|
3214
4039
|
}
|
|
3215
4040
|
if (isImmune(doc)) return "active";
|
|
@@ -3243,10 +4068,10 @@ function explainRetention(doc, referenceTime) {
|
|
|
3243
4068
|
const zone = getRetentionZone(doc, referenceTime);
|
|
3244
4069
|
const immuneFlag = isImmune(doc);
|
|
3245
4070
|
const immunityReason = getImmunityReason(doc);
|
|
3246
|
-
const
|
|
4071
|
+
const now3 = referenceTime ?? /* @__PURE__ */ new Date();
|
|
3247
4072
|
const ageDays = Math.max(
|
|
3248
4073
|
0,
|
|
3249
|
-
(
|
|
4074
|
+
(now3.getTime() - new Date(doc.createdAt).getTime()) / (1e3 * 60 * 60 * 24)
|
|
3250
4075
|
);
|
|
3251
4076
|
const parts = [];
|
|
3252
4077
|
parts.push(`${importance} importance (${baseRetention}d base)`);
|
|
@@ -3402,16 +4227,16 @@ var init_retention = __esm({
|
|
|
3402
4227
|
});
|
|
3403
4228
|
|
|
3404
4229
|
// src/codegraph/ids.ts
|
|
3405
|
-
import { createHash } from "crypto";
|
|
4230
|
+
import { createHash as createHash2 } from "crypto";
|
|
3406
4231
|
function digest(input) {
|
|
3407
|
-
return
|
|
4232
|
+
return createHash2("sha256").update(input).digest("hex").slice(0, 16);
|
|
3408
4233
|
}
|
|
3409
|
-
function normalizeCodePath(
|
|
3410
|
-
return
|
|
4234
|
+
function normalizeCodePath(path13) {
|
|
4235
|
+
return path13.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+/g, "/");
|
|
3411
4236
|
}
|
|
3412
|
-
function makeCodeFileId(projectId,
|
|
4237
|
+
function makeCodeFileId(projectId, path13) {
|
|
3413
4238
|
return `file:${digest(`${projectId}
|
|
3414
|
-
${normalizeCodePath(
|
|
4239
|
+
${normalizeCodePath(path13)}`)}`;
|
|
3415
4240
|
}
|
|
3416
4241
|
function makeCodeSymbolId(input) {
|
|
3417
4242
|
return `symbol:${digest([
|
|
@@ -3424,8 +4249,8 @@ function makeCodeSymbolId(input) {
|
|
|
3424
4249
|
function makeCodeEdgeId(projectId, from, type, to) {
|
|
3425
4250
|
return `edge:${digest([projectId, from, type, to].join("\n"))}`;
|
|
3426
4251
|
}
|
|
3427
|
-
function makeObservationCodeRefId(projectId,
|
|
3428
|
-
return `coderef:${digest([projectId, String(
|
|
4252
|
+
function makeObservationCodeRefId(projectId, observationId2, fileId, symbolId) {
|
|
4253
|
+
return `coderef:${digest([projectId, String(observationId2), fileId ?? "", symbolId ?? ""].join("\n"))}`;
|
|
3429
4254
|
}
|
|
3430
4255
|
var init_ids = __esm({
|
|
3431
4256
|
"src/codegraph/ids.ts"() {
|
|
@@ -3439,6 +4264,7 @@ var store_exports = {};
|
|
|
3439
4264
|
__export(store_exports, {
|
|
3440
4265
|
CodeGraphStore: () => CodeGraphStore
|
|
3441
4266
|
});
|
|
4267
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
3442
4268
|
function rowToFile(row) {
|
|
3443
4269
|
return {
|
|
3444
4270
|
id: row.id,
|
|
@@ -3449,7 +4275,9 @@ function rowToFile(row) {
|
|
|
3449
4275
|
...row.mtimeMs != null ? { mtimeMs: row.mtimeMs } : {},
|
|
3450
4276
|
...row.sizeBytes != null ? { sizeBytes: row.sizeBytes } : {},
|
|
3451
4277
|
indexedAt: row.indexedAt,
|
|
3452
|
-
...row.gitCommit ? { gitCommit: row.gitCommit } : {}
|
|
4278
|
+
...row.gitCommit ? { gitCommit: row.gitCommit } : {},
|
|
4279
|
+
...row.snapshotId ? { snapshotId: row.snapshotId } : {},
|
|
4280
|
+
...row.sourceEpoch != null ? { sourceEpoch: Number(row.sourceEpoch) } : {}
|
|
3453
4281
|
};
|
|
3454
4282
|
}
|
|
3455
4283
|
function rowToSymbol(row) {
|
|
@@ -3466,7 +4294,9 @@ function rowToSymbol(row) {
|
|
|
3466
4294
|
...row.signature ? { signature: row.signature } : {},
|
|
3467
4295
|
...row.contentHash ? { contentHash: row.contentHash } : {},
|
|
3468
4296
|
indexedAt: row.indexedAt,
|
|
3469
|
-
stale: !!row.stale
|
|
4297
|
+
stale: !!row.stale,
|
|
4298
|
+
...row.snapshotId ? { snapshotId: row.snapshotId } : {},
|
|
4299
|
+
...row.sourceEpoch != null ? { sourceEpoch: Number(row.sourceEpoch) } : {}
|
|
3470
4300
|
};
|
|
3471
4301
|
}
|
|
3472
4302
|
function rowToEdge(row) {
|
|
@@ -3480,7 +4310,9 @@ function rowToEdge(row) {
|
|
|
3480
4310
|
type: row.type,
|
|
3481
4311
|
confidence: row.confidence,
|
|
3482
4312
|
...row.evidence ? { evidence: row.evidence } : {},
|
|
3483
|
-
indexedAt: row.indexedAt
|
|
4313
|
+
indexedAt: row.indexedAt,
|
|
4314
|
+
...row.snapshotId ? { snapshotId: row.snapshotId } : {},
|
|
4315
|
+
...row.sourceEpoch != null ? { sourceEpoch: Number(row.sourceEpoch) } : {}
|
|
3484
4316
|
};
|
|
3485
4317
|
}
|
|
3486
4318
|
function rowToRef(row) {
|
|
@@ -3495,7 +4327,52 @@ function rowToRef(row) {
|
|
|
3495
4327
|
status: row.status,
|
|
3496
4328
|
...row.reason ? { reason: row.reason } : {},
|
|
3497
4329
|
createdAt: row.createdAt,
|
|
3498
|
-
...row.updatedAt ? { updatedAt: row.updatedAt } : {}
|
|
4330
|
+
...row.updatedAt ? { updatedAt: row.updatedAt } : {},
|
|
4331
|
+
...row.snapshotId ? { snapshotId: row.snapshotId } : {}
|
|
4332
|
+
};
|
|
4333
|
+
}
|
|
4334
|
+
function parseCompleteness(raw) {
|
|
4335
|
+
try {
|
|
4336
|
+
const parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
4337
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("invalid completeness");
|
|
4338
|
+
return {
|
|
4339
|
+
scannedFiles: Number(parsed.scannedFiles) || 0,
|
|
4340
|
+
maxFiles: Number(parsed.maxFiles) || 0,
|
|
4341
|
+
changedFiles: Number(parsed.changedFiles) || 0,
|
|
4342
|
+
unchangedFiles: Number(parsed.unchangedFiles) || 0,
|
|
4343
|
+
metadataOnlyFiles: Number(parsed.metadataOnlyFiles) || 0,
|
|
4344
|
+
removedFiles: Number(parsed.removedFiles) || 0,
|
|
4345
|
+
skippedOversizedFiles: Number(parsed.skippedOversizedFiles) || 0,
|
|
4346
|
+
unreadableFiles: Number(parsed.unreadableFiles) || 0,
|
|
4347
|
+
removalScanDeferred: Boolean(parsed.removalScanDeferred)
|
|
4348
|
+
};
|
|
4349
|
+
} catch {
|
|
4350
|
+
return {
|
|
4351
|
+
scannedFiles: 0,
|
|
4352
|
+
maxFiles: 0,
|
|
4353
|
+
changedFiles: 0,
|
|
4354
|
+
unchangedFiles: 0,
|
|
4355
|
+
metadataOnlyFiles: 0,
|
|
4356
|
+
removedFiles: 0,
|
|
4357
|
+
skippedOversizedFiles: 0,
|
|
4358
|
+
unreadableFiles: 0,
|
|
4359
|
+
removalScanDeferred: false
|
|
4360
|
+
};
|
|
4361
|
+
}
|
|
4362
|
+
}
|
|
4363
|
+
function rowToSnapshot(row) {
|
|
4364
|
+
return {
|
|
4365
|
+
id: row.id,
|
|
4366
|
+
projectId: row.projectId,
|
|
4367
|
+
provider: row.provider,
|
|
4368
|
+
...row.baseRevision ? { baseRevision: row.baseRevision } : {},
|
|
4369
|
+
worktreeFingerprint: row.worktreeFingerprint,
|
|
4370
|
+
worktreeState: row.worktreeState,
|
|
4371
|
+
changedPathCount: Number(row.changedPathCount),
|
|
4372
|
+
indexedAt: row.indexedAt,
|
|
4373
|
+
sourceEpoch: Number(row.sourceEpoch),
|
|
4374
|
+
completeness: parseCompleteness(row.completenessJson),
|
|
4375
|
+
...row.previousSnapshotId ? { previousSnapshotId: row.previousSnapshotId } : {}
|
|
3499
4376
|
};
|
|
3500
4377
|
}
|
|
3501
4378
|
var CodeGraphStore;
|
|
@@ -3507,9 +4384,16 @@ var init_store = __esm({
|
|
|
3507
4384
|
init_ids();
|
|
3508
4385
|
CodeGraphStore = class {
|
|
3509
4386
|
db = null;
|
|
4387
|
+
dataDir = null;
|
|
3510
4388
|
async init(dataDir) {
|
|
4389
|
+
this.dataDir = dataDir;
|
|
3511
4390
|
this.db = getDatabase(dataDir);
|
|
3512
4391
|
}
|
|
4392
|
+
/** Data directory shared with the rest of the local project evidence stores. */
|
|
4393
|
+
getDataDir() {
|
|
4394
|
+
if (!this.dataDir) throw new Error("CodeGraphStore is not initialized");
|
|
4395
|
+
return this.dataDir;
|
|
4396
|
+
}
|
|
3513
4397
|
upsertFiles(files) {
|
|
3514
4398
|
if (files.length === 0) return;
|
|
3515
4399
|
const stmt = this.db.prepare(`
|
|
@@ -3770,9 +4654,9 @@ var init_store = __esm({
|
|
|
3770
4654
|
if (refs.length === 0) return;
|
|
3771
4655
|
const stmt = this.db.prepare(`
|
|
3772
4656
|
INSERT OR REPLACE INTO observation_code_refs
|
|
3773
|
-
(id, projectId, observationId, fileId, symbolId, capturedFileHash, capturedSymbolHash, status, reason, createdAt, updatedAt)
|
|
4657
|
+
(id, projectId, observationId, fileId, symbolId, capturedFileHash, capturedSymbolHash, status, reason, createdAt, updatedAt, snapshotId)
|
|
3774
4658
|
VALUES
|
|
3775
|
-
(@id, @projectId, @observationId, @fileId, @symbolId, @capturedFileHash, @capturedSymbolHash, @status, @reason, @createdAt, @updatedAt)
|
|
4659
|
+
(@id, @projectId, @observationId, @fileId, @symbolId, @capturedFileHash, @capturedSymbolHash, @status, @reason, @createdAt, @updatedAt, @snapshotId)
|
|
3776
4660
|
`);
|
|
3777
4661
|
const tx = this.db.transaction((items) => {
|
|
3778
4662
|
for (const ref of items) {
|
|
@@ -3787,25 +4671,26 @@ var init_store = __esm({
|
|
|
3787
4671
|
status: ref.status,
|
|
3788
4672
|
reason: ref.reason ?? null,
|
|
3789
4673
|
createdAt: ref.createdAt,
|
|
3790
|
-
updatedAt: ref.updatedAt ?? null
|
|
4674
|
+
updatedAt: ref.updatedAt ?? null,
|
|
4675
|
+
snapshotId: ref.snapshotId ?? this.latestSnapshot(ref.projectId)?.id ?? null
|
|
3791
4676
|
});
|
|
3792
4677
|
}
|
|
3793
4678
|
});
|
|
3794
4679
|
tx(refs);
|
|
3795
4680
|
}
|
|
3796
|
-
replaceObservationRefs(projectId,
|
|
4681
|
+
replaceObservationRefs(projectId, observationId2, refs) {
|
|
3797
4682
|
const deleteRefs = this.db.prepare(`
|
|
3798
4683
|
DELETE FROM observation_code_refs
|
|
3799
4684
|
WHERE projectId = ? AND observationId = ?
|
|
3800
4685
|
`);
|
|
3801
4686
|
const insertRef = this.db.prepare(`
|
|
3802
4687
|
INSERT OR REPLACE INTO observation_code_refs
|
|
3803
|
-
(id, projectId, observationId, fileId, symbolId, capturedFileHash, capturedSymbolHash, status, reason, createdAt, updatedAt)
|
|
4688
|
+
(id, projectId, observationId, fileId, symbolId, capturedFileHash, capturedSymbolHash, status, reason, createdAt, updatedAt, snapshotId)
|
|
3804
4689
|
VALUES
|
|
3805
|
-
(@id, @projectId, @observationId, @fileId, @symbolId, @capturedFileHash, @capturedSymbolHash, @status, @reason, @createdAt, @updatedAt)
|
|
4690
|
+
(@id, @projectId, @observationId, @fileId, @symbolId, @capturedFileHash, @capturedSymbolHash, @status, @reason, @createdAt, @updatedAt, @snapshotId)
|
|
3806
4691
|
`);
|
|
3807
4692
|
const tx = this.db.transaction(() => {
|
|
3808
|
-
deleteRefs.run(projectId,
|
|
4693
|
+
deleteRefs.run(projectId, observationId2);
|
|
3809
4694
|
for (const ref of refs) {
|
|
3810
4695
|
insertRef.run({
|
|
3811
4696
|
id: ref.id,
|
|
@@ -3818,14 +4703,15 @@ var init_store = __esm({
|
|
|
3818
4703
|
status: ref.status,
|
|
3819
4704
|
reason: ref.reason ?? null,
|
|
3820
4705
|
createdAt: ref.createdAt,
|
|
3821
|
-
updatedAt: ref.updatedAt ?? null
|
|
4706
|
+
updatedAt: ref.updatedAt ?? null,
|
|
4707
|
+
snapshotId: ref.snapshotId ?? this.latestSnapshot(ref.projectId)?.id ?? null
|
|
3822
4708
|
});
|
|
3823
4709
|
}
|
|
3824
4710
|
});
|
|
3825
4711
|
tx();
|
|
3826
4712
|
}
|
|
3827
|
-
getFile(projectId,
|
|
3828
|
-
const row = this.db.prepare(`SELECT * FROM code_files WHERE projectId = ? AND path = ?`).get(projectId, normalizeCodePath(
|
|
4713
|
+
getFile(projectId, path13) {
|
|
4714
|
+
const row = this.db.prepare(`SELECT * FROM code_files WHERE projectId = ? AND path = ?`).get(projectId, normalizeCodePath(path13));
|
|
3829
4715
|
return row ? rowToFile(row) : null;
|
|
3830
4716
|
}
|
|
3831
4717
|
listFiles(projectId) {
|
|
@@ -3884,12 +4770,12 @@ var init_store = __esm({
|
|
|
3884
4770
|
listEdges(projectId) {
|
|
3885
4771
|
return this.db.prepare(`SELECT * FROM code_edges WHERE projectId = ? ORDER BY type, id`).all(projectId).map(rowToEdge);
|
|
3886
4772
|
}
|
|
3887
|
-
listObservationRefs(projectId,
|
|
4773
|
+
listObservationRefs(projectId, observationId2) {
|
|
3888
4774
|
return this.db.prepare(`
|
|
3889
4775
|
SELECT * FROM observation_code_refs
|
|
3890
4776
|
WHERE projectId = ? AND observationId = ?
|
|
3891
4777
|
ORDER BY status, id
|
|
3892
|
-
`).all(projectId,
|
|
4778
|
+
`).all(projectId, observationId2).map(rowToRef);
|
|
3893
4779
|
}
|
|
3894
4780
|
listProjectObservationRefs(projectId) {
|
|
3895
4781
|
return this.db.prepare(`
|
|
@@ -3907,18 +4793,152 @@ var init_store = __esm({
|
|
|
3907
4793
|
ORDER BY symbols.path, symbols.startLine
|
|
3908
4794
|
`).all(projectId, projectId).map(rowToSymbol);
|
|
3909
4795
|
}
|
|
4796
|
+
latestSnapshot(projectId) {
|
|
4797
|
+
const row = this.db.prepare(
|
|
4798
|
+
"SELECT * FROM code_state_snapshots WHERE projectId = ? ORDER BY sourceEpoch DESC LIMIT 1"
|
|
4799
|
+
).get(projectId);
|
|
4800
|
+
return row ? rowToSnapshot(row) : void 0;
|
|
4801
|
+
}
|
|
4802
|
+
listSnapshots(projectId, limit = 20) {
|
|
4803
|
+
const safeLimit = Number.isFinite(limit) ? Math.max(1, Math.min(200, Math.floor(limit))) : 20;
|
|
4804
|
+
return this.db.prepare(
|
|
4805
|
+
"SELECT * FROM code_state_snapshots WHERE projectId = ? ORDER BY sourceEpoch DESC LIMIT ?"
|
|
4806
|
+
).all(projectId, safeLimit).map(rowToSnapshot);
|
|
4807
|
+
}
|
|
4808
|
+
/**
|
|
4809
|
+
* Record a completed scan and mark all current structural facts with its
|
|
4810
|
+
* epoch. The snapshot is written only after refresh reconciliation succeeds,
|
|
4811
|
+
* so an interrupted scan cannot be advertised as complete.
|
|
4812
|
+
*/
|
|
4813
|
+
recordCodeStateSnapshot(input) {
|
|
4814
|
+
const id = randomUUID4();
|
|
4815
|
+
let snapshot;
|
|
4816
|
+
const tx = this.db.transaction(() => {
|
|
4817
|
+
const previous = this.db.prepare(
|
|
4818
|
+
"SELECT id, sourceEpoch FROM code_state_snapshots WHERE projectId = ? ORDER BY sourceEpoch DESC LIMIT 1"
|
|
4819
|
+
).get(input.projectId);
|
|
4820
|
+
const sourceEpoch = Number(previous?.sourceEpoch ?? 0) + 1;
|
|
4821
|
+
const previousSnapshotId = previous?.id;
|
|
4822
|
+
this.db.prepare(
|
|
4823
|
+
"INSERT INTO code_state_snapshots (id, projectId, provider, baseRevision, worktreeFingerprint, worktreeState, changedPathCount, indexedAt, sourceEpoch, completenessJson, previousSnapshotId) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
4824
|
+
).run(
|
|
4825
|
+
id,
|
|
4826
|
+
input.projectId,
|
|
4827
|
+
input.provider,
|
|
4828
|
+
input.baseRevision ?? null,
|
|
4829
|
+
input.worktreeFingerprint,
|
|
4830
|
+
input.worktreeState,
|
|
4831
|
+
input.changedPathCount,
|
|
4832
|
+
input.indexedAt,
|
|
4833
|
+
sourceEpoch,
|
|
4834
|
+
JSON.stringify(input.completeness),
|
|
4835
|
+
previousSnapshotId ?? null
|
|
4836
|
+
);
|
|
4837
|
+
this.db.prepare(
|
|
4838
|
+
"UPDATE code_files SET snapshotId = ?, sourceEpoch = ?, gitCommit = COALESCE(?, gitCommit) WHERE projectId = ?"
|
|
4839
|
+
).run(id, sourceEpoch, input.baseRevision ?? null, input.projectId);
|
|
4840
|
+
this.db.prepare(
|
|
4841
|
+
"UPDATE code_symbols SET snapshotId = ?, sourceEpoch = ? WHERE projectId = ? AND stale = 0"
|
|
4842
|
+
).run(id, sourceEpoch, input.projectId);
|
|
4843
|
+
this.db.prepare(
|
|
4844
|
+
"UPDATE code_edges SET snapshotId = ?, sourceEpoch = ? WHERE projectId = ?"
|
|
4845
|
+
).run(id, sourceEpoch, input.projectId);
|
|
4846
|
+
this.db.prepare(
|
|
4847
|
+
"UPDATE observation_code_refs SET snapshotId = ? WHERE projectId = ? AND status = 'current'"
|
|
4848
|
+
).run(id, input.projectId);
|
|
4849
|
+
snapshot = {
|
|
4850
|
+
...input,
|
|
4851
|
+
id,
|
|
4852
|
+
sourceEpoch,
|
|
4853
|
+
...previousSnapshotId ? { previousSnapshotId } : {}
|
|
4854
|
+
};
|
|
4855
|
+
});
|
|
4856
|
+
tx();
|
|
4857
|
+
return snapshot;
|
|
4858
|
+
}
|
|
3910
4859
|
status(projectId) {
|
|
3911
4860
|
const files = this.db.prepare(`SELECT COUNT(*) AS count FROM code_files WHERE projectId = ?`).get(projectId).count;
|
|
3912
4861
|
const symbols = this.db.prepare(`SELECT COUNT(*) AS count FROM code_symbols WHERE projectId = ? AND stale = 0`).get(projectId).count;
|
|
3913
4862
|
const edges = this.db.prepare(`SELECT COUNT(*) AS count FROM code_edges WHERE projectId = ?`).get(projectId).count;
|
|
3914
4863
|
const refs = this.db.prepare(`SELECT COUNT(*) AS count FROM observation_code_refs WHERE projectId = ?`).get(projectId).count;
|
|
3915
4864
|
const latest = this.db.prepare(`SELECT MAX(indexedAt) AS indexedAt FROM code_files WHERE projectId = ?`).get(projectId);
|
|
3916
|
-
|
|
4865
|
+
const latestSnapshot = this.latestSnapshot(projectId);
|
|
4866
|
+
return {
|
|
4867
|
+
provider: "lite",
|
|
4868
|
+
files,
|
|
4869
|
+
symbols,
|
|
4870
|
+
edges,
|
|
4871
|
+
refs,
|
|
4872
|
+
...latest?.indexedAt ? { indexedAt: latest.indexedAt } : {},
|
|
4873
|
+
...latestSnapshot ? { latestSnapshot } : {}
|
|
4874
|
+
};
|
|
3917
4875
|
}
|
|
3918
4876
|
};
|
|
3919
4877
|
}
|
|
3920
4878
|
});
|
|
3921
4879
|
|
|
4880
|
+
// src/codegraph/code-state.ts
|
|
4881
|
+
import { execFile as execFile2 } from "child_process";
|
|
4882
|
+
import { createHash as createHash3 } from "crypto";
|
|
4883
|
+
import { promisify as promisify2 } from "util";
|
|
4884
|
+
async function runGit(rootPath, args) {
|
|
4885
|
+
try {
|
|
4886
|
+
const result = await execFileAsync2("git", ["-C", rootPath, ...args], {
|
|
4887
|
+
encoding: "buffer",
|
|
4888
|
+
windowsHide: true,
|
|
4889
|
+
timeout: GIT_TIMEOUT_MS,
|
|
4890
|
+
maxBuffer: GIT_MAX_BUFFER_BYTES
|
|
4891
|
+
});
|
|
4892
|
+
const output = Buffer.isBuffer(result.stdout) ? result.stdout : Buffer.from(result.stdout ?? "");
|
|
4893
|
+
return { ok: true, output };
|
|
4894
|
+
} catch {
|
|
4895
|
+
return { ok: false, output: Buffer.alloc(0) };
|
|
4896
|
+
}
|
|
4897
|
+
}
|
|
4898
|
+
function changedPathCount(status) {
|
|
4899
|
+
const entries = status.toString("utf8").split("\0");
|
|
4900
|
+
let count2 = 0;
|
|
4901
|
+
for (let index = 0; index < entries.length; index++) {
|
|
4902
|
+
const entry = entries[index];
|
|
4903
|
+
if (!entry) continue;
|
|
4904
|
+
count2++;
|
|
4905
|
+
const code = entry.slice(0, 2);
|
|
4906
|
+
if (code.includes("R") || code.includes("C")) index++;
|
|
4907
|
+
}
|
|
4908
|
+
return count2;
|
|
4909
|
+
}
|
|
4910
|
+
function fingerprint(baseRevision, status) {
|
|
4911
|
+
return createHash3("sha256").update(baseRevision ?? "").update("\0").update(status).digest("hex");
|
|
4912
|
+
}
|
|
4913
|
+
async function collectCodeStateSnapshot(input) {
|
|
4914
|
+
const [revision, status] = await Promise.all([
|
|
4915
|
+
runGit(input.projectRoot, ["rev-parse", "HEAD"]),
|
|
4916
|
+
runGit(input.projectRoot, ["status", "--porcelain=v1", "-z", "--untracked-files=normal"])
|
|
4917
|
+
]);
|
|
4918
|
+
const baseRevision = revision.ok ? revision.output.toString("utf8").trim() || void 0 : void 0;
|
|
4919
|
+
const worktreeState = status.ok ? status.output.length > 0 ? "dirty" : "clean" : "unavailable";
|
|
4920
|
+
return {
|
|
4921
|
+
projectId: input.projectId,
|
|
4922
|
+
provider: input.provider,
|
|
4923
|
+
...baseRevision ? { baseRevision } : {},
|
|
4924
|
+
worktreeFingerprint: fingerprint(baseRevision, status.ok ? status.output : Buffer.alloc(0)),
|
|
4925
|
+
worktreeState,
|
|
4926
|
+
changedPathCount: status.ok ? changedPathCount(status.output) : 0,
|
|
4927
|
+
indexedAt: input.indexedAt,
|
|
4928
|
+
completeness: input.completeness
|
|
4929
|
+
};
|
|
4930
|
+
}
|
|
4931
|
+
var execFileAsync2, GIT_TIMEOUT_MS, GIT_MAX_BUFFER_BYTES;
|
|
4932
|
+
var init_code_state = __esm({
|
|
4933
|
+
"src/codegraph/code-state.ts"() {
|
|
4934
|
+
"use strict";
|
|
4935
|
+
init_esm_shims();
|
|
4936
|
+
execFileAsync2 = promisify2(execFile2);
|
|
4937
|
+
GIT_TIMEOUT_MS = 3e3;
|
|
4938
|
+
GIT_MAX_BUFFER_BYTES = 256 * 1024;
|
|
4939
|
+
}
|
|
4940
|
+
});
|
|
4941
|
+
|
|
3922
4942
|
// src/codegraph/exclude.ts
|
|
3923
4943
|
function normalizeCodeGraphExcludePatterns(exclude) {
|
|
3924
4944
|
return [.../* @__PURE__ */ new Set([
|
|
@@ -3926,30 +4946,30 @@ function normalizeCodeGraphExcludePatterns(exclude) {
|
|
|
3926
4946
|
...(exclude ?? []).map((pattern) => pattern.trim()).filter(Boolean)
|
|
3927
4947
|
])];
|
|
3928
4948
|
}
|
|
3929
|
-
function isCodeGraphExcludedPath(
|
|
3930
|
-
const normalized = normalizeCodePath2(
|
|
4949
|
+
function isCodeGraphExcludedPath(path13, exclude) {
|
|
4950
|
+
const normalized = normalizeCodePath2(path13);
|
|
3931
4951
|
return normalizeCodeGraphExcludePatterns(exclude).some((pattern) => matchesPattern(normalized, normalizeCodePath2(pattern)));
|
|
3932
4952
|
}
|
|
3933
|
-
function normalizeCodePath2(
|
|
3934
|
-
return
|
|
4953
|
+
function normalizeCodePath2(path13) {
|
|
4954
|
+
return path13.replace(/\\/g, "/").replace(/^\.\/+/, "");
|
|
3935
4955
|
}
|
|
3936
|
-
function matchesPattern(
|
|
4956
|
+
function matchesPattern(path13, pattern) {
|
|
3937
4957
|
if (pattern.endsWith("/**")) {
|
|
3938
4958
|
const base = pattern.slice(0, -3);
|
|
3939
4959
|
if (base.startsWith("**/")) {
|
|
3940
4960
|
const suffix = base.slice(3);
|
|
3941
|
-
return
|
|
4961
|
+
return path13 === suffix || path13.endsWith(`/${suffix}`) || path13.includes(`/${suffix}/`);
|
|
3942
4962
|
}
|
|
3943
4963
|
if (!base.includes("/")) {
|
|
3944
|
-
return
|
|
4964
|
+
return path13 === base || path13.startsWith(`${base}/`) || path13.includes(`/${base}/`);
|
|
3945
4965
|
}
|
|
3946
|
-
return
|
|
4966
|
+
return path13 === base || path13.startsWith(`${base}/`);
|
|
3947
4967
|
}
|
|
3948
4968
|
if (pattern.startsWith("**/")) {
|
|
3949
4969
|
const suffix = pattern.slice(3);
|
|
3950
|
-
return
|
|
4970
|
+
return path13 === suffix || path13.endsWith(`/${suffix}`);
|
|
3951
4971
|
}
|
|
3952
|
-
return
|
|
4972
|
+
return path13 === pattern || path13.startsWith(`${pattern}/`);
|
|
3953
4973
|
}
|
|
3954
4974
|
var DEFAULT_CODEGRAPH_EXCLUDES;
|
|
3955
4975
|
var init_exclude = __esm({
|
|
@@ -3988,18 +5008,18 @@ __export(lite_provider_exports, {
|
|
|
3988
5008
|
indexProjectLite: () => indexProjectLite,
|
|
3989
5009
|
refreshProjectLite: () => refreshProjectLite
|
|
3990
5010
|
});
|
|
3991
|
-
import { createHash as
|
|
5011
|
+
import { createHash as createHash4 } from "crypto";
|
|
3992
5012
|
import { readdirSync as readdirSync2, readFileSync as readFileSync6, statSync as statSync2 } from "fs";
|
|
3993
5013
|
import { join as join2, relative } from "path";
|
|
3994
5014
|
function hashText(text) {
|
|
3995
|
-
return
|
|
5015
|
+
return createHash4("sha256").update(text).digest("hex");
|
|
3996
5016
|
}
|
|
3997
|
-
function extension(
|
|
3998
|
-
const index =
|
|
3999
|
-
return index === -1 ? "" :
|
|
5017
|
+
function extension(path13) {
|
|
5018
|
+
const index = path13.lastIndexOf(".");
|
|
5019
|
+
return index === -1 ? "" : path13.slice(index);
|
|
4000
5020
|
}
|
|
4001
|
-
function languageForPath(
|
|
4002
|
-
const ext = extension(
|
|
5021
|
+
function languageForPath(path13) {
|
|
5022
|
+
const ext = extension(path13);
|
|
4003
5023
|
return LANGUAGE_BY_EXTENSION.get(ext) ?? "unknown";
|
|
4004
5024
|
}
|
|
4005
5025
|
function resolveMaxFileBytes(value) {
|
|
@@ -4117,17 +5137,22 @@ function indexFileLite(projectId, projectRoot, abs, indexedAt, fileStat) {
|
|
|
4117
5137
|
path: rel,
|
|
4118
5138
|
language: languageForPath(rel),
|
|
4119
5139
|
contentHash: hashText(text),
|
|
4120
|
-
|
|
5140
|
+
// Preserve filesystem precision. Rounding here made same-size edits in
|
|
5141
|
+
// a short Windows timestamp window indistinguishable to refresh.
|
|
5142
|
+
mtimeMs: Number(stat.mtimeMs),
|
|
4121
5143
|
sizeBytes: Number(stat.size),
|
|
4122
5144
|
indexedAt
|
|
4123
5145
|
};
|
|
4124
5146
|
return {
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
5147
|
+
delta: {
|
|
5148
|
+
file,
|
|
5149
|
+
symbols: extractSymbols(projectId, file, text, indexedAt),
|
|
5150
|
+
edges: extractImportEdges(projectId, file, text, indexedAt)
|
|
5151
|
+
},
|
|
5152
|
+
unreadable: false
|
|
4128
5153
|
};
|
|
4129
5154
|
} catch {
|
|
4130
|
-
return
|
|
5155
|
+
return { unreadable: true };
|
|
4131
5156
|
}
|
|
4132
5157
|
}
|
|
4133
5158
|
async function indexProjectLite(options) {
|
|
@@ -4140,24 +5165,29 @@ async function indexProjectLite(options) {
|
|
|
4140
5165
|
const symbols = [];
|
|
4141
5166
|
const edges = [];
|
|
4142
5167
|
let skippedOversizedFiles = 0;
|
|
5168
|
+
let unreadableFiles = 0;
|
|
4143
5169
|
for (const abs of paths) {
|
|
4144
5170
|
let stat;
|
|
4145
5171
|
try {
|
|
4146
5172
|
stat = statSync2(abs);
|
|
4147
5173
|
} catch {
|
|
5174
|
+
unreadableFiles++;
|
|
4148
5175
|
continue;
|
|
4149
5176
|
}
|
|
4150
5177
|
if (Number(stat.size) > maxFileBytes) {
|
|
4151
5178
|
skippedOversizedFiles++;
|
|
4152
5179
|
continue;
|
|
4153
5180
|
}
|
|
4154
|
-
const
|
|
4155
|
-
if (!delta)
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
5181
|
+
const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt, stat);
|
|
5182
|
+
if (!attempt.delta) {
|
|
5183
|
+
if (attempt.unreadable) unreadableFiles++;
|
|
5184
|
+
continue;
|
|
5185
|
+
}
|
|
5186
|
+
files.push(attempt.delta.file);
|
|
5187
|
+
symbols.push(...attempt.delta.symbols);
|
|
5188
|
+
edges.push(...attempt.delta.edges);
|
|
4159
5189
|
}
|
|
4160
|
-
return { files, symbols, edges, skippedOversizedFiles };
|
|
5190
|
+
return { files, symbols, edges, skippedOversizedFiles, unreadableFiles };
|
|
4161
5191
|
}
|
|
4162
5192
|
async function refreshProjectLite(store, options) {
|
|
4163
5193
|
const exclude = normalizeCodeGraphExcludePatterns(options.exclude);
|
|
@@ -4172,29 +5202,35 @@ async function refreshProjectLite(store, options) {
|
|
|
4172
5202
|
const oversizedExistingFileIds = /* @__PURE__ */ new Set();
|
|
4173
5203
|
let unchangedFiles = 0;
|
|
4174
5204
|
let skippedOversizedFiles = 0;
|
|
5205
|
+
let unreadableFiles = 0;
|
|
4175
5206
|
for (const abs of paths) {
|
|
4176
5207
|
const rel = normalizeCodePath(relative(options.projectRoot, abs));
|
|
5208
|
+
seenPaths.add(rel);
|
|
4177
5209
|
let stat;
|
|
4178
5210
|
try {
|
|
4179
5211
|
stat = statSync2(abs);
|
|
4180
5212
|
} catch {
|
|
5213
|
+
unreadableFiles++;
|
|
4181
5214
|
continue;
|
|
4182
5215
|
}
|
|
4183
|
-
seenPaths.add(rel);
|
|
4184
5216
|
const existing = existingByPath.get(rel);
|
|
4185
|
-
const
|
|
5217
|
+
const mtimeMs = Number(stat.mtimeMs);
|
|
4186
5218
|
const sizeBytes = Number(stat.size);
|
|
4187
5219
|
if (sizeBytes > maxFileBytes) {
|
|
4188
5220
|
skippedOversizedFiles++;
|
|
4189
5221
|
if (existing) oversizedExistingFileIds.add(existing.id);
|
|
4190
5222
|
continue;
|
|
4191
5223
|
}
|
|
4192
|
-
if (existing && existing.mtimeMs ===
|
|
5224
|
+
if (existing && existing.mtimeMs === mtimeMs && existing.sizeBytes === sizeBytes) {
|
|
4193
5225
|
unchangedFiles++;
|
|
4194
5226
|
continue;
|
|
4195
5227
|
}
|
|
4196
|
-
const
|
|
4197
|
-
if (!delta)
|
|
5228
|
+
const attempt = indexFileLite(options.projectId, options.projectRoot, abs, indexedAt, stat);
|
|
5229
|
+
if (!attempt.delta) {
|
|
5230
|
+
if (attempt.unreadable) unreadableFiles++;
|
|
5231
|
+
continue;
|
|
5232
|
+
}
|
|
5233
|
+
const delta = attempt.delta;
|
|
4198
5234
|
if (existing?.contentHash === delta.file.contentHash) {
|
|
4199
5235
|
metadataOnly.push(delta.file);
|
|
4200
5236
|
unchangedFiles++;
|
|
@@ -4212,23 +5248,44 @@ async function refreshProjectLite(store, options) {
|
|
|
4212
5248
|
metadataOnly,
|
|
4213
5249
|
removedFileIds
|
|
4214
5250
|
});
|
|
4215
|
-
|
|
5251
|
+
const completeness = {
|
|
4216
5252
|
scannedFiles: paths.length,
|
|
5253
|
+
maxFiles,
|
|
4217
5254
|
changedFiles: changed.length,
|
|
4218
5255
|
unchangedFiles,
|
|
4219
5256
|
metadataOnlyFiles: metadataOnly.length,
|
|
4220
5257
|
removedFiles: removedFileIds.length,
|
|
4221
|
-
indexedSymbols: changed.reduce((total, delta) => total + delta.symbols.length, 0),
|
|
4222
|
-
indexedEdges: changed.reduce((total, delta) => total + delta.edges.length, 0),
|
|
4223
5258
|
skippedOversizedFiles,
|
|
5259
|
+
unreadableFiles,
|
|
4224
5260
|
removalScanDeferred
|
|
4225
5261
|
};
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
5262
|
+
const snapshot = store.recordCodeStateSnapshot(await collectCodeStateSnapshot({
|
|
5263
|
+
projectId: options.projectId,
|
|
5264
|
+
projectRoot: options.projectRoot,
|
|
5265
|
+
provider: "lite",
|
|
5266
|
+
indexedAt,
|
|
5267
|
+
completeness
|
|
5268
|
+
}));
|
|
5269
|
+
return {
|
|
5270
|
+
scannedFiles: paths.length,
|
|
5271
|
+
changedFiles: changed.length,
|
|
5272
|
+
unchangedFiles,
|
|
5273
|
+
metadataOnlyFiles: metadataOnly.length,
|
|
5274
|
+
removedFiles: removedFileIds.length,
|
|
5275
|
+
indexedSymbols: changed.reduce((total, delta) => total + delta.symbols.length, 0),
|
|
5276
|
+
indexedEdges: changed.reduce((total, delta) => total + delta.edges.length, 0),
|
|
5277
|
+
skippedOversizedFiles,
|
|
5278
|
+
unreadableFiles,
|
|
5279
|
+
removalScanDeferred,
|
|
5280
|
+
snapshot
|
|
5281
|
+
};
|
|
5282
|
+
}
|
|
5283
|
+
var DEFAULT_CODEGRAPH_MAX_FILE_BYTES, LANGUAGE_BY_EXTENSION, SUPPORTED_EXTENSIONS, identifier, languageProfiles;
|
|
5284
|
+
var init_lite_provider = __esm({
|
|
4229
5285
|
"src/codegraph/lite-provider.ts"() {
|
|
4230
5286
|
"use strict";
|
|
4231
5287
|
init_esm_shims();
|
|
5288
|
+
init_code_state();
|
|
4232
5289
|
init_ids();
|
|
4233
5290
|
init_exclude();
|
|
4234
5291
|
DEFAULT_CODEGRAPH_MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
@@ -4382,138 +5439,2274 @@ function codeIdentifierCandidates(text) {
|
|
|
4382
5439
|
for (const match of text.matchAll(/\b([A-Za-z_$][\w$]*(?:::[A-Za-z_$][\w$]*)*[!?=]?)\s*\(/g)) explicit.add(match[1]);
|
|
4383
5440
|
return identifierCandidates(text).filter((candidate) => explicit.has(candidate) || candidate.includes("::") || /[!?=]$/.test(candidate) || /^[A-Z]/.test(candidate) || /[_$]/.test(candidate) || /[a-z0-9][A-Z]/.test(candidate) || /[A-Z]{2}/.test(candidate));
|
|
4384
5441
|
}
|
|
4385
|
-
function fileRef(projectId, obs, file) {
|
|
5442
|
+
function fileRef(projectId, obs, file) {
|
|
5443
|
+
return {
|
|
5444
|
+
id: makeObservationCodeRefId(projectId, obs.id, file.id),
|
|
5445
|
+
projectId,
|
|
5446
|
+
observationId: obs.id,
|
|
5447
|
+
fileId: file.id,
|
|
5448
|
+
capturedFileHash: file.contentHash,
|
|
5449
|
+
status: "current",
|
|
5450
|
+
reason: "bound by file path",
|
|
5451
|
+
createdAt: obs.createdAt
|
|
5452
|
+
};
|
|
5453
|
+
}
|
|
5454
|
+
function symbolRef(projectId, obs, file, symbol) {
|
|
5455
|
+
return {
|
|
5456
|
+
id: makeObservationCodeRefId(projectId, obs.id, file.id, symbol.id),
|
|
5457
|
+
projectId,
|
|
5458
|
+
observationId: obs.id,
|
|
5459
|
+
fileId: file.id,
|
|
5460
|
+
symbolId: symbol.id,
|
|
5461
|
+
capturedFileHash: file.contentHash,
|
|
5462
|
+
...symbol.contentHash ? { capturedSymbolHash: symbol.contentHash } : {},
|
|
5463
|
+
status: "current",
|
|
5464
|
+
reason: "bound by symbol mention",
|
|
5465
|
+
createdAt: obs.createdAt
|
|
5466
|
+
};
|
|
5467
|
+
}
|
|
5468
|
+
function resolveObservationCodeRefs(obs, lookup) {
|
|
5469
|
+
const refs = /* @__PURE__ */ new Map();
|
|
5470
|
+
const text = observationText(obs);
|
|
5471
|
+
const candidateFiles = /* @__PURE__ */ new Map();
|
|
5472
|
+
for (const rawPath of obs.filesModified ?? []) {
|
|
5473
|
+
const file = lookup.findFile(normalizeCodePath(rawPath));
|
|
5474
|
+
if (!file) continue;
|
|
5475
|
+
candidateFiles.set(file.id, file);
|
|
5476
|
+
const ref = fileRef(obs.projectId, obs, file);
|
|
5477
|
+
refs.set(ref.id, ref);
|
|
5478
|
+
}
|
|
5479
|
+
const hintedFileIds = new Set(candidateFiles.keys());
|
|
5480
|
+
const symbolNames = hintedFileIds.size > 0 ? identifierCandidates(text) : codeIdentifierCandidates(text);
|
|
5481
|
+
const symbols = lookup.findSymbols(symbolNames, [...hintedFileIds]);
|
|
5482
|
+
const symbolsByName = /* @__PURE__ */ new Map();
|
|
5483
|
+
for (const symbol of symbols) {
|
|
5484
|
+
const group = symbolsByName.get(symbol.name) ?? [];
|
|
5485
|
+
group.push(symbol);
|
|
5486
|
+
symbolsByName.set(symbol.name, group);
|
|
5487
|
+
}
|
|
5488
|
+
for (const group of symbolsByName.values()) {
|
|
5489
|
+
const candidates = group.length === 1 ? group : [];
|
|
5490
|
+
for (const symbol of candidates) {
|
|
5491
|
+
if (!mentionsSymbol(text, symbol)) continue;
|
|
5492
|
+
const file = candidateFiles.get(symbol.fileId) ?? lookup.findFile(symbol.path);
|
|
5493
|
+
if (!file) continue;
|
|
5494
|
+
candidateFiles.set(file.id, file);
|
|
5495
|
+
const ref = symbolRef(obs.projectId, obs, file, symbol);
|
|
5496
|
+
refs.set(ref.id, ref);
|
|
5497
|
+
}
|
|
5498
|
+
}
|
|
5499
|
+
return [...refs.values()];
|
|
5500
|
+
}
|
|
5501
|
+
function createStoreLookup(store, projectId) {
|
|
5502
|
+
return {
|
|
5503
|
+
findFile: (path13) => store.getFile(projectId, path13) ?? void 0,
|
|
5504
|
+
findSymbols: (names, hintedFileIds) => store.findSymbolsByNames(projectId, names, hintedFileIds)
|
|
5505
|
+
};
|
|
5506
|
+
}
|
|
5507
|
+
function createSnapshotLookup(files, symbols) {
|
|
5508
|
+
const filesByPath = new Map(files.map((file) => [normalizeCodePath(file.path), file]));
|
|
5509
|
+
const symbolsByName = /* @__PURE__ */ new Map();
|
|
5510
|
+
for (const symbol of symbols) {
|
|
5511
|
+
const group = symbolsByName.get(symbol.name) ?? [];
|
|
5512
|
+
group.push(symbol);
|
|
5513
|
+
symbolsByName.set(symbol.name, group);
|
|
5514
|
+
}
|
|
5515
|
+
return {
|
|
5516
|
+
findFile: (path13) => filesByPath.get(normalizeCodePath(path13)),
|
|
5517
|
+
findSymbols: (names, hintedFileIds) => {
|
|
5518
|
+
const hinted = new Set(hintedFileIds);
|
|
5519
|
+
const found = [];
|
|
5520
|
+
for (const name of names) {
|
|
5521
|
+
const candidates = symbolsByName.get(name) ?? [];
|
|
5522
|
+
found.push(...hinted.size > 0 ? candidates.filter((symbol) => hinted.has(symbol.fileId)) : candidates);
|
|
5523
|
+
}
|
|
5524
|
+
return found;
|
|
5525
|
+
}
|
|
5526
|
+
};
|
|
5527
|
+
}
|
|
5528
|
+
async function bindObservationToCode(store, obs) {
|
|
5529
|
+
const refs = resolveObservationCodeRefs(obs, createStoreLookup(store, obs.projectId));
|
|
5530
|
+
store.replaceObservationRefs(obs.projectId, obs.id, refs);
|
|
5531
|
+
return refs;
|
|
5532
|
+
}
|
|
5533
|
+
async function backfillMissingObservationCodeRefs(store, observations2) {
|
|
5534
|
+
let observationsBackfilled = 0;
|
|
5535
|
+
let refsBackfilled = 0;
|
|
5536
|
+
const boundObservationIdsByProject = /* @__PURE__ */ new Map();
|
|
5537
|
+
const lookupByProject = /* @__PURE__ */ new Map();
|
|
5538
|
+
for (const projectId of new Set(observations2.map((observation) => observation.projectId))) {
|
|
5539
|
+
boundObservationIdsByProject.set(
|
|
5540
|
+
projectId,
|
|
5541
|
+
new Set(store.listProjectObservationRefs(projectId).map((ref) => ref.observationId))
|
|
5542
|
+
);
|
|
5543
|
+
lookupByProject.set(
|
|
5544
|
+
projectId,
|
|
5545
|
+
createSnapshotLookup(store.listFiles(projectId), store.listSymbols(projectId))
|
|
5546
|
+
);
|
|
5547
|
+
}
|
|
5548
|
+
const refsToInsert = [];
|
|
5549
|
+
for (const observation of observations2) {
|
|
5550
|
+
if (boundObservationIdsByProject.get(observation.projectId)?.has(observation.id)) continue;
|
|
5551
|
+
if ((observation.filesModified?.length ?? 0) === 0 && codeIdentifierCandidates(observationText(observation)).length === 0) {
|
|
5552
|
+
continue;
|
|
5553
|
+
}
|
|
5554
|
+
const lookup = lookupByProject.get(observation.projectId);
|
|
5555
|
+
if (!lookup) continue;
|
|
5556
|
+
const refs = resolveObservationCodeRefs(observation, lookup);
|
|
5557
|
+
if (refs.length === 0) continue;
|
|
5558
|
+
observationsBackfilled += 1;
|
|
5559
|
+
refsBackfilled += refs.length;
|
|
5560
|
+
refsToInsert.push(...refs);
|
|
5561
|
+
}
|
|
5562
|
+
store.upsertObservationRefs(refsToInsert);
|
|
5563
|
+
return {
|
|
5564
|
+
observationsScanned: observations2.length,
|
|
5565
|
+
observationsBackfilled,
|
|
5566
|
+
refsBackfilled
|
|
5567
|
+
};
|
|
5568
|
+
}
|
|
5569
|
+
var init_binder = __esm({
|
|
5570
|
+
"src/codegraph/binder.ts"() {
|
|
5571
|
+
"use strict";
|
|
5572
|
+
init_esm_shims();
|
|
5573
|
+
init_ids();
|
|
5574
|
+
}
|
|
5575
|
+
});
|
|
5576
|
+
|
|
5577
|
+
// src/knowledge/claim-store.ts
|
|
5578
|
+
var claim_store_exports = {};
|
|
5579
|
+
__export(claim_store_exports, {
|
|
5580
|
+
ClaimStore: () => ClaimStore
|
|
5581
|
+
});
|
|
5582
|
+
import { createHash as createHash5, randomUUID as randomUUID5 } from "crypto";
|
|
5583
|
+
function optionalText2(value) {
|
|
5584
|
+
return typeof value === "string" && value ? value : void 0;
|
|
5585
|
+
}
|
|
5586
|
+
function rowToClaim(row) {
|
|
5587
|
+
return {
|
|
5588
|
+
id: row.id,
|
|
5589
|
+
projectId: row.projectId,
|
|
5590
|
+
subject: row.subject,
|
|
5591
|
+
predicate: row.predicate,
|
|
5592
|
+
objectValue: row.objectValue,
|
|
5593
|
+
scope: row.scope,
|
|
5594
|
+
claimKey: row.claimKey,
|
|
5595
|
+
conflictKey: row.conflictKey,
|
|
5596
|
+
status: row.status,
|
|
5597
|
+
confidence: Number(row.confidence),
|
|
5598
|
+
observedAt: row.observedAt,
|
|
5599
|
+
...optionalText2(row.validFrom) ? { validFrom: row.validFrom } : {},
|
|
5600
|
+
...optionalText2(row.validTo) ? { validTo: row.validTo } : {},
|
|
5601
|
+
...optionalText2(row.supersededBy) ? { supersededBy: row.supersededBy } : {},
|
|
5602
|
+
reviewState: row.reviewState,
|
|
5603
|
+
origin: row.origin,
|
|
5604
|
+
createdAt: row.createdAt,
|
|
5605
|
+
updatedAt: row.updatedAt
|
|
5606
|
+
};
|
|
5607
|
+
}
|
|
5608
|
+
function rowToEvidence(row) {
|
|
5609
|
+
return {
|
|
5610
|
+
id: row.id,
|
|
5611
|
+
claimId: row.claimId,
|
|
5612
|
+
evidenceKind: row.evidenceKind,
|
|
5613
|
+
evidenceId: row.evidenceId,
|
|
5614
|
+
relation: row.relation,
|
|
5615
|
+
...optionalText2(row.snapshotId) ? { snapshotId: row.snapshotId } : {},
|
|
5616
|
+
...optionalText2(row.locator) ? { locator: row.locator } : {},
|
|
5617
|
+
...optionalText2(row.capturedHash) ? { capturedHash: row.capturedHash } : {},
|
|
5618
|
+
createdAt: row.createdAt
|
|
5619
|
+
};
|
|
5620
|
+
}
|
|
5621
|
+
function rowToEvent(row) {
|
|
5622
|
+
return {
|
|
5623
|
+
id: row.id,
|
|
5624
|
+
projectId: row.projectId,
|
|
5625
|
+
claimId: row.claimId,
|
|
5626
|
+
kind: row.kind,
|
|
5627
|
+
...optionalText2(row.fromStatus) ? { fromStatus: row.fromStatus } : {},
|
|
5628
|
+
...optionalText2(row.toStatus) ? { toStatus: row.toStatus } : {},
|
|
5629
|
+
...optionalText2(row.relatedClaimId) ? { relatedClaimId: row.relatedClaimId } : {},
|
|
5630
|
+
...optionalText2(row.detail) ? { detail: row.detail } : {},
|
|
5631
|
+
createdAt: row.createdAt
|
|
5632
|
+
};
|
|
5633
|
+
}
|
|
5634
|
+
function evidenceKey(input) {
|
|
5635
|
+
return createHash5("sha256").update(JSON.stringify([
|
|
5636
|
+
input.evidenceKind,
|
|
5637
|
+
input.evidenceId,
|
|
5638
|
+
input.relation,
|
|
5639
|
+
input.snapshotId ?? "",
|
|
5640
|
+
input.locator ?? "",
|
|
5641
|
+
input.capturedHash ?? ""
|
|
5642
|
+
])).digest("hex");
|
|
5643
|
+
}
|
|
5644
|
+
var ClaimStore;
|
|
5645
|
+
var init_claim_store = __esm({
|
|
5646
|
+
"src/knowledge/claim-store.ts"() {
|
|
5647
|
+
"use strict";
|
|
5648
|
+
init_esm_shims();
|
|
5649
|
+
init_secret_filter();
|
|
5650
|
+
init_sqlite_db();
|
|
5651
|
+
ClaimStore = class {
|
|
5652
|
+
db = null;
|
|
5653
|
+
async init(dataDir) {
|
|
5654
|
+
this.db = getDatabase(dataDir);
|
|
5655
|
+
}
|
|
5656
|
+
transaction(fn) {
|
|
5657
|
+
return this.db.transaction(fn)();
|
|
5658
|
+
}
|
|
5659
|
+
getClaim(id) {
|
|
5660
|
+
const row = this.db.prepare("SELECT * FROM knowledge_claims WHERE id = ?").get(id);
|
|
5661
|
+
return row ? rowToClaim(row) : void 0;
|
|
5662
|
+
}
|
|
5663
|
+
listClaims(projectId, options = {}) {
|
|
5664
|
+
const limit = Math.max(1, Math.min(1e3, Math.floor(options.limit ?? 500)));
|
|
5665
|
+
if (options.statuses && options.statuses.length > 0) {
|
|
5666
|
+
return this.db.prepare(`
|
|
5667
|
+
SELECT * FROM knowledge_claims
|
|
5668
|
+
WHERE projectId = ? AND status IN (SELECT value FROM json_each(?))
|
|
5669
|
+
ORDER BY updatedAt DESC, id
|
|
5670
|
+
LIMIT ?
|
|
5671
|
+
`).all(projectId, JSON.stringify([...new Set(options.statuses)]), limit).map(rowToClaim);
|
|
5672
|
+
}
|
|
5673
|
+
return this.db.prepare(`
|
|
5674
|
+
SELECT * FROM knowledge_claims
|
|
5675
|
+
WHERE projectId = ?
|
|
5676
|
+
ORDER BY updatedAt DESC, id
|
|
5677
|
+
LIMIT ?
|
|
5678
|
+
`).all(projectId, limit).map(rowToClaim);
|
|
5679
|
+
}
|
|
5680
|
+
findReusableClaim(projectId, claimKey) {
|
|
5681
|
+
const row = this.db.prepare(`
|
|
5682
|
+
SELECT * FROM knowledge_claims
|
|
5683
|
+
WHERE projectId = ? AND claimKey = ? AND status IN ('active', 'disputed')
|
|
5684
|
+
ORDER BY updatedAt DESC
|
|
5685
|
+
LIMIT 1
|
|
5686
|
+
`).get(projectId, claimKey);
|
|
5687
|
+
return row ? rowToClaim(row) : void 0;
|
|
5688
|
+
}
|
|
5689
|
+
listClaimsByConflictKey(projectId, conflictKey) {
|
|
5690
|
+
return this.db.prepare(`
|
|
5691
|
+
SELECT * FROM knowledge_claims
|
|
5692
|
+
WHERE projectId = ? AND conflictKey = ?
|
|
5693
|
+
ORDER BY createdAt ASC, id
|
|
5694
|
+
`).all(projectId, conflictKey).map(rowToClaim);
|
|
5695
|
+
}
|
|
5696
|
+
listConflicts(projectId) {
|
|
5697
|
+
const rows = this.db.prepare(`
|
|
5698
|
+
SELECT conflictKey
|
|
5699
|
+
FROM knowledge_claims
|
|
5700
|
+
WHERE projectId = ? AND status IN ('active', 'disputed') AND reviewState = 'approved'
|
|
5701
|
+
GROUP BY conflictKey
|
|
5702
|
+
HAVING COUNT(DISTINCT claimKey) > 1
|
|
5703
|
+
ORDER BY conflictKey
|
|
5704
|
+
`).all(projectId);
|
|
5705
|
+
return rows.map(({ conflictKey }) => ({
|
|
5706
|
+
conflictKey,
|
|
5707
|
+
claims: this.listClaimsByConflictKey(projectId, conflictKey).filter((claim) => claim.status === "active" || claim.status === "disputed")
|
|
5708
|
+
}));
|
|
5709
|
+
}
|
|
5710
|
+
insertClaim(claim) {
|
|
5711
|
+
this.db.prepare(`
|
|
5712
|
+
INSERT INTO knowledge_claims (
|
|
5713
|
+
id, projectId, subject, predicate, objectValue, scope, claimKey, conflictKey,
|
|
5714
|
+
status, confidence, observedAt, validFrom, validTo, supersededBy,
|
|
5715
|
+
reviewState, origin, createdAt, updatedAt
|
|
5716
|
+
) VALUES (
|
|
5717
|
+
@id, @projectId, @subject, @predicate, @objectValue, @scope, @claimKey, @conflictKey,
|
|
5718
|
+
@status, @confidence, @observedAt, @validFrom, @validTo, @supersededBy,
|
|
5719
|
+
@reviewState, @origin, @createdAt, @updatedAt
|
|
5720
|
+
)
|
|
5721
|
+
`).run({
|
|
5722
|
+
...claim,
|
|
5723
|
+
validFrom: claim.validFrom ?? null,
|
|
5724
|
+
validTo: claim.validTo ?? null,
|
|
5725
|
+
supersededBy: claim.supersededBy ?? null
|
|
5726
|
+
});
|
|
5727
|
+
}
|
|
5728
|
+
updateClaim(claim) {
|
|
5729
|
+
this.db.prepare(`
|
|
5730
|
+
UPDATE knowledge_claims SET
|
|
5731
|
+
subject = @subject,
|
|
5732
|
+
predicate = @predicate,
|
|
5733
|
+
objectValue = @objectValue,
|
|
5734
|
+
scope = @scope,
|
|
5735
|
+
claimKey = @claimKey,
|
|
5736
|
+
conflictKey = @conflictKey,
|
|
5737
|
+
status = @status,
|
|
5738
|
+
confidence = @confidence,
|
|
5739
|
+
observedAt = @observedAt,
|
|
5740
|
+
validFrom = @validFrom,
|
|
5741
|
+
validTo = @validTo,
|
|
5742
|
+
supersededBy = @supersededBy,
|
|
5743
|
+
reviewState = @reviewState,
|
|
5744
|
+
origin = @origin,
|
|
5745
|
+
updatedAt = @updatedAt
|
|
5746
|
+
WHERE id = @id
|
|
5747
|
+
`).run({
|
|
5748
|
+
...claim,
|
|
5749
|
+
validFrom: claim.validFrom ?? null,
|
|
5750
|
+
validTo: claim.validTo ?? null,
|
|
5751
|
+
supersededBy: claim.supersededBy ?? null
|
|
5752
|
+
});
|
|
5753
|
+
}
|
|
5754
|
+
touchClaim(id, updatedAt) {
|
|
5755
|
+
this.db.prepare("UPDATE knowledge_claims SET updatedAt = ? WHERE id = ?").run(updatedAt, id);
|
|
5756
|
+
}
|
|
5757
|
+
insertEvidence(input) {
|
|
5758
|
+
const createdAt = input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
5759
|
+
const key = evidenceKey(input);
|
|
5760
|
+
const result = this.db.prepare(`
|
|
5761
|
+
INSERT OR IGNORE INTO knowledge_claim_evidence (
|
|
5762
|
+
id, claimId, evidenceKind, evidenceId, relation, snapshotId,
|
|
5763
|
+
locator, capturedHash, evidenceKey, createdAt
|
|
5764
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
5765
|
+
`).run(
|
|
5766
|
+
randomUUID5(),
|
|
5767
|
+
input.claimId,
|
|
5768
|
+
input.evidenceKind,
|
|
5769
|
+
input.evidenceId,
|
|
5770
|
+
input.relation,
|
|
5771
|
+
input.snapshotId ?? null,
|
|
5772
|
+
input.locator ? sanitizeCredentials(input.locator) : null,
|
|
5773
|
+
input.capturedHash ?? null,
|
|
5774
|
+
key,
|
|
5775
|
+
createdAt
|
|
5776
|
+
);
|
|
5777
|
+
return Number(result.changes ?? 0) > 0;
|
|
5778
|
+
}
|
|
5779
|
+
listEvidence(claimId) {
|
|
5780
|
+
return this.db.prepare(`
|
|
5781
|
+
SELECT * FROM knowledge_claim_evidence
|
|
5782
|
+
WHERE claimId = ?
|
|
5783
|
+
ORDER BY createdAt ASC, id
|
|
5784
|
+
`).all(claimId).map(rowToEvidence);
|
|
5785
|
+
}
|
|
5786
|
+
recordEvent(input) {
|
|
5787
|
+
const event = {
|
|
5788
|
+
id: randomUUID5(),
|
|
5789
|
+
...input,
|
|
5790
|
+
...input.detail ? { detail: sanitizeCredentials(input.detail) } : {},
|
|
5791
|
+
createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
5792
|
+
};
|
|
5793
|
+
this.db.prepare(`
|
|
5794
|
+
INSERT INTO knowledge_claim_events (
|
|
5795
|
+
id, projectId, claimId, kind, fromStatus, toStatus,
|
|
5796
|
+
relatedClaimId, detail, createdAt
|
|
5797
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
5798
|
+
`).run(
|
|
5799
|
+
event.id,
|
|
5800
|
+
event.projectId,
|
|
5801
|
+
event.claimId,
|
|
5802
|
+
event.kind,
|
|
5803
|
+
event.fromStatus ?? null,
|
|
5804
|
+
event.toStatus ?? null,
|
|
5805
|
+
event.relatedClaimId ?? null,
|
|
5806
|
+
event.detail ?? null,
|
|
5807
|
+
event.createdAt
|
|
5808
|
+
);
|
|
5809
|
+
return event;
|
|
5810
|
+
}
|
|
5811
|
+
listEvents(claimId) {
|
|
5812
|
+
return this.db.prepare(`
|
|
5813
|
+
SELECT * FROM knowledge_claim_events
|
|
5814
|
+
WHERE claimId = ?
|
|
5815
|
+
ORDER BY createdAt ASC, id
|
|
5816
|
+
`).all(claimId).map(rowToEvent);
|
|
5817
|
+
}
|
|
5818
|
+
};
|
|
5819
|
+
}
|
|
5820
|
+
});
|
|
5821
|
+
|
|
5822
|
+
// src/compact/token-budget.ts
|
|
5823
|
+
import { countTokens, isWithinTokenLimit } from "gpt-tokenizer";
|
|
5824
|
+
function countTextTokens(text) {
|
|
5825
|
+
return countTokens(text);
|
|
5826
|
+
}
|
|
5827
|
+
var init_token_budget = __esm({
|
|
5828
|
+
"src/compact/token-budget.ts"() {
|
|
5829
|
+
"use strict";
|
|
5830
|
+
init_esm_shims();
|
|
5831
|
+
}
|
|
5832
|
+
});
|
|
5833
|
+
|
|
5834
|
+
// src/knowledge/claims.ts
|
|
5835
|
+
var claims_exports = {};
|
|
5836
|
+
__export(claims_exports, {
|
|
5837
|
+
buildClaimIdentity: () => buildClaimIdentity,
|
|
5838
|
+
deriveLowRiskClaimsFromObservation: () => deriveLowRiskClaimsFromObservation,
|
|
5839
|
+
requalifyClaimsForCodeState: () => requalifyClaimsForCodeState,
|
|
5840
|
+
selectClaimsForTask: () => selectClaimsForTask,
|
|
5841
|
+
supersedeClaim: () => supersedeClaim,
|
|
5842
|
+
writeClaim: () => writeClaim
|
|
5843
|
+
});
|
|
5844
|
+
import { createHash as createHash6, randomUUID as randomUUID6 } from "crypto";
|
|
5845
|
+
function compactText(value, field) {
|
|
5846
|
+
if (typeof value !== "string") throw new Error("Claim " + field + " must be text");
|
|
5847
|
+
const sanitized = sanitizeCredentials(value).trim().replace(/\s+/g, " ");
|
|
5848
|
+
if (!sanitized) throw new Error("Claim " + field + " is required");
|
|
5849
|
+
if (sanitized.length > MAX_TERM_LENGTH) throw new Error("Claim " + field + " is too long");
|
|
5850
|
+
return sanitized;
|
|
5851
|
+
}
|
|
5852
|
+
function normalizedTerm(value) {
|
|
5853
|
+
return compactText(value, "identity").toLocaleLowerCase("en-US");
|
|
5854
|
+
}
|
|
5855
|
+
function hashIdentity(parts) {
|
|
5856
|
+
return createHash6("sha256").update(parts.join(String.fromCharCode(31))).digest("hex");
|
|
5857
|
+
}
|
|
5858
|
+
function buildClaimIdentity(input) {
|
|
5859
|
+
const subject = normalizedTerm(input.subject);
|
|
5860
|
+
const predicate = normalizedTerm(input.predicate);
|
|
5861
|
+
const objectValue = normalizedTerm(input.objectValue);
|
|
5862
|
+
return {
|
|
5863
|
+
claimKey: hashIdentity([subject, predicate, objectValue, input.scope]),
|
|
5864
|
+
conflictKey: hashIdentity([subject, predicate, input.scope])
|
|
5865
|
+
};
|
|
5866
|
+
}
|
|
5867
|
+
function clampConfidence(value) {
|
|
5868
|
+
if (value === void 0) return 0.7;
|
|
5869
|
+
if (!Number.isFinite(value) || value < 0 || value > 1) {
|
|
5870
|
+
throw new Error("Claim confidence must be a number between 0 and 1");
|
|
5871
|
+
}
|
|
5872
|
+
return value;
|
|
5873
|
+
}
|
|
5874
|
+
function validateEvidence(input) {
|
|
5875
|
+
if (!Array.isArray(input) || input.length === 0) {
|
|
5876
|
+
throw new Error("A knowledge claim requires at least one evidence reference");
|
|
5877
|
+
}
|
|
5878
|
+
return input.map((evidence) => {
|
|
5879
|
+
if (!evidence || typeof evidence !== "object") {
|
|
5880
|
+
throw new Error("Claim evidence must be an object");
|
|
5881
|
+
}
|
|
5882
|
+
const evidenceId = compactText(evidence.evidenceId, "evidence id");
|
|
5883
|
+
if (evidenceId.length > MAX_EVIDENCE_ID_LENGTH) {
|
|
5884
|
+
throw new Error("Claim evidence id is too long");
|
|
5885
|
+
}
|
|
5886
|
+
if (!["observation", "git", "code", "test", "document", "workflow", "run"].includes(evidence.evidenceKind)) {
|
|
5887
|
+
throw new Error("Claim evidence kind is invalid");
|
|
5888
|
+
}
|
|
5889
|
+
if (!["supports", "contradicts", "derives", "verifies"].includes(evidence.relation)) {
|
|
5890
|
+
throw new Error("Claim evidence relation is invalid");
|
|
5891
|
+
}
|
|
5892
|
+
return {
|
|
5893
|
+
evidenceKind: evidence.evidenceKind,
|
|
5894
|
+
evidenceId,
|
|
5895
|
+
relation: evidence.relation,
|
|
5896
|
+
...evidence.snapshotId ? { snapshotId: compactText(evidence.snapshotId, "evidence snapshot id") } : {},
|
|
5897
|
+
...evidence.locator ? { locator: compactText(evidence.locator, "evidence locator") } : {},
|
|
5898
|
+
...evidence.capturedHash ? { capturedHash: compactText(evidence.capturedHash, "evidence hash") } : {}
|
|
5899
|
+
};
|
|
5900
|
+
});
|
|
5901
|
+
}
|
|
5902
|
+
function normalizeClaimInput(input) {
|
|
5903
|
+
if (!["project", "workspace", "team", "workflow", "task"].includes(input.scope)) {
|
|
5904
|
+
throw new Error("Claim scope is invalid");
|
|
5905
|
+
}
|
|
5906
|
+
const projectId = compactText(input.projectId, "project id");
|
|
5907
|
+
const subject = compactText(input.subject, "subject");
|
|
5908
|
+
const predicate = compactText(input.predicate, "predicate");
|
|
5909
|
+
const objectValue = compactText(input.objectValue, "object value");
|
|
5910
|
+
const identity = buildClaimIdentity({ subject, predicate, objectValue, scope: input.scope });
|
|
5911
|
+
const origin = input.origin ?? "explicit";
|
|
5912
|
+
let reviewState = input.reviewState ?? "approved";
|
|
5913
|
+
let status = input.status ?? "active";
|
|
5914
|
+
if (origin === "model") {
|
|
5915
|
+
if (input.reviewState === "approved" || input.status === "active") {
|
|
5916
|
+
throw new Error("A model-origin claim cannot be approved or active without explicit review");
|
|
5917
|
+
}
|
|
5918
|
+
reviewState = input.reviewState ?? "draft";
|
|
5919
|
+
status = input.status ?? "unknown";
|
|
5920
|
+
}
|
|
5921
|
+
if (!["approved", "needs-review", "draft", "rejected"].includes(reviewState)) {
|
|
5922
|
+
throw new Error("Claim review state is invalid");
|
|
5923
|
+
}
|
|
5924
|
+
if (!["active", "superseded", "disputed", "unknown"].includes(status)) {
|
|
5925
|
+
throw new Error("Claim status is invalid");
|
|
5926
|
+
}
|
|
5927
|
+
const now3 = (/* @__PURE__ */ new Date()).toISOString();
|
|
5928
|
+
return {
|
|
5929
|
+
claim: {
|
|
5930
|
+
id: randomUUID6(),
|
|
5931
|
+
projectId,
|
|
5932
|
+
subject,
|
|
5933
|
+
predicate,
|
|
5934
|
+
objectValue,
|
|
5935
|
+
scope: input.scope,
|
|
5936
|
+
claimKey: identity.claimKey,
|
|
5937
|
+
conflictKey: identity.conflictKey,
|
|
5938
|
+
status,
|
|
5939
|
+
confidence: clampConfidence(input.confidence),
|
|
5940
|
+
observedAt: input.observedAt ?? now3,
|
|
5941
|
+
...input.validFrom ? { validFrom: input.validFrom } : {},
|
|
5942
|
+
...input.validTo ? { validTo: input.validTo } : {},
|
|
5943
|
+
reviewState,
|
|
5944
|
+
origin,
|
|
5945
|
+
createdAt: now3,
|
|
5946
|
+
updatedAt: now3
|
|
5947
|
+
},
|
|
5948
|
+
evidence: validateEvidence(input.evidence)
|
|
5949
|
+
};
|
|
5950
|
+
}
|
|
5951
|
+
function activeConflictClaims(store, projectId, conflictKey) {
|
|
5952
|
+
return store.listClaimsByConflictKey(projectId, conflictKey).filter((claim) => (claim.status === "active" || claim.status === "disputed") && claim.reviewState === "approved");
|
|
5953
|
+
}
|
|
5954
|
+
function reconcileConflicts(store, projectId, conflictKey, now3) {
|
|
5955
|
+
const claims = activeConflictClaims(store, projectId, conflictKey);
|
|
5956
|
+
const hasConflict = new Set(claims.map((claim) => claim.claimKey)).size > 1;
|
|
5957
|
+
for (const claim of claims) {
|
|
5958
|
+
const nextStatus = hasConflict ? "disputed" : "active";
|
|
5959
|
+
if (claim.status === nextStatus) continue;
|
|
5960
|
+
store.updateClaim({ ...claim, status: nextStatus, updatedAt: now3 });
|
|
5961
|
+
store.recordEvent({
|
|
5962
|
+
projectId,
|
|
5963
|
+
claimId: claim.id,
|
|
5964
|
+
kind: hasConflict ? "conflicted" : "requalified",
|
|
5965
|
+
fromStatus: claim.status,
|
|
5966
|
+
toStatus: nextStatus,
|
|
5967
|
+
detail: hasConflict ? "A different approved assertion has the same subject, predicate, and scope." : "No remaining approved competing assertion exists.",
|
|
5968
|
+
createdAt: now3
|
|
5969
|
+
});
|
|
5970
|
+
}
|
|
5971
|
+
return store.listConflicts(projectId).filter((conflict) => conflict.conflictKey === conflictKey);
|
|
5972
|
+
}
|
|
5973
|
+
function persistEvidence(store, claim, evidence, now3) {
|
|
5974
|
+
let added = false;
|
|
5975
|
+
for (const item of evidence) {
|
|
5976
|
+
added = store.insertEvidence({ claimId: claim.id, ...item, createdAt: now3 }) || added;
|
|
5977
|
+
}
|
|
5978
|
+
if (added) {
|
|
5979
|
+
store.touchClaim(claim.id, now3);
|
|
5980
|
+
store.recordEvent({
|
|
5981
|
+
projectId: claim.projectId,
|
|
5982
|
+
claimId: claim.id,
|
|
5983
|
+
kind: "evidence-added",
|
|
5984
|
+
detail: "Additional source evidence was attached.",
|
|
5985
|
+
createdAt: now3
|
|
5986
|
+
});
|
|
5987
|
+
}
|
|
5988
|
+
}
|
|
5989
|
+
function writeClaim(store, input) {
|
|
5990
|
+
const normalized = normalizeClaimInput(input);
|
|
5991
|
+
return store.transaction(() => {
|
|
5992
|
+
const existing = store.findReusableClaim(normalized.claim.projectId, normalized.claim.claimKey);
|
|
5993
|
+
if (existing) {
|
|
5994
|
+
persistEvidence(store, existing, normalized.evidence, normalized.claim.updatedAt);
|
|
5995
|
+
return {
|
|
5996
|
+
claim: store.getClaim(existing.id) ?? existing,
|
|
5997
|
+
created: false,
|
|
5998
|
+
conflicts: store.listConflicts(existing.projectId).filter((conflict) => conflict.conflictKey === existing.conflictKey)
|
|
5999
|
+
};
|
|
6000
|
+
}
|
|
6001
|
+
store.insertClaim(normalized.claim);
|
|
6002
|
+
for (const evidence of normalized.evidence) {
|
|
6003
|
+
store.insertEvidence({ claimId: normalized.claim.id, ...evidence, createdAt: normalized.claim.createdAt });
|
|
6004
|
+
}
|
|
6005
|
+
store.recordEvent({
|
|
6006
|
+
projectId: normalized.claim.projectId,
|
|
6007
|
+
claimId: normalized.claim.id,
|
|
6008
|
+
kind: "created",
|
|
6009
|
+
toStatus: normalized.claim.status,
|
|
6010
|
+
detail: "Claim created with source evidence.",
|
|
6011
|
+
createdAt: normalized.claim.createdAt
|
|
6012
|
+
});
|
|
6013
|
+
const conflicts = reconcileConflicts(
|
|
6014
|
+
store,
|
|
6015
|
+
normalized.claim.projectId,
|
|
6016
|
+
normalized.claim.conflictKey,
|
|
6017
|
+
normalized.claim.updatedAt
|
|
6018
|
+
);
|
|
6019
|
+
return {
|
|
6020
|
+
claim: store.getClaim(normalized.claim.id) ?? normalized.claim,
|
|
6021
|
+
created: true,
|
|
6022
|
+
conflicts
|
|
6023
|
+
};
|
|
6024
|
+
});
|
|
6025
|
+
}
|
|
6026
|
+
function supersedeClaim(store, input) {
|
|
6027
|
+
const evidence = validateEvidence(input.evidence);
|
|
6028
|
+
return store.transaction(() => {
|
|
6029
|
+
const claim = store.getClaim(input.claimId);
|
|
6030
|
+
const replacement = store.getClaim(input.replacementClaimId);
|
|
6031
|
+
if (!claim || !replacement) throw new Error("Both claims must exist before supersession");
|
|
6032
|
+
if (claim.projectId !== replacement.projectId || claim.conflictKey !== replacement.conflictKey) {
|
|
6033
|
+
throw new Error("A replacement claim must address the same project assertion");
|
|
6034
|
+
}
|
|
6035
|
+
if (claim.id === replacement.id) throw new Error("A claim cannot supersede itself");
|
|
6036
|
+
const now3 = (/* @__PURE__ */ new Date()).toISOString();
|
|
6037
|
+
persistEvidence(store, claim, evidence, now3);
|
|
6038
|
+
const updated = {
|
|
6039
|
+
...claim,
|
|
6040
|
+
status: "superseded",
|
|
6041
|
+
validTo: now3,
|
|
6042
|
+
supersededBy: replacement.id,
|
|
6043
|
+
updatedAt: now3
|
|
6044
|
+
};
|
|
6045
|
+
store.updateClaim(updated);
|
|
6046
|
+
store.recordEvent({
|
|
6047
|
+
projectId: claim.projectId,
|
|
6048
|
+
claimId: claim.id,
|
|
6049
|
+
kind: "superseded",
|
|
6050
|
+
fromStatus: claim.status,
|
|
6051
|
+
toStatus: "superseded",
|
|
6052
|
+
relatedClaimId: replacement.id,
|
|
6053
|
+
detail: "Superseded by an evidence-backed replacement claim.",
|
|
6054
|
+
createdAt: now3
|
|
6055
|
+
});
|
|
6056
|
+
reconcileConflicts(store, claim.projectId, claim.conflictKey, now3);
|
|
6057
|
+
return {
|
|
6058
|
+
superseded: store.getClaim(claim.id) ?? updated,
|
|
6059
|
+
replacement: store.getClaim(replacement.id) ?? replacement
|
|
6060
|
+
};
|
|
6061
|
+
});
|
|
6062
|
+
}
|
|
6063
|
+
function hashObservation(observation) {
|
|
6064
|
+
return createHash6("sha256").update(JSON.stringify([
|
|
6065
|
+
observation.title,
|
|
6066
|
+
observation.narrative,
|
|
6067
|
+
observation.facts,
|
|
6068
|
+
observation.filesModified,
|
|
6069
|
+
observation.updatedAt ?? observation.createdAt
|
|
6070
|
+
])).digest("hex");
|
|
6071
|
+
}
|
|
6072
|
+
function evidenceFromCodeRef(ref) {
|
|
6073
|
+
return {
|
|
6074
|
+
evidenceKind: "code",
|
|
6075
|
+
evidenceId: "code-ref:" + ref.id,
|
|
6076
|
+
relation: "supports",
|
|
6077
|
+
...ref.snapshotId ? { snapshotId: ref.snapshotId } : {},
|
|
6078
|
+
locator: "code-ref/" + ref.id,
|
|
6079
|
+
...ref.capturedSymbolHash || ref.capturedFileHash ? { capturedHash: ref.capturedSymbolHash ?? ref.capturedFileHash } : {}
|
|
6080
|
+
};
|
|
6081
|
+
}
|
|
6082
|
+
function deriveLowRiskClaimsFromObservation(store, observation, codeStore) {
|
|
6083
|
+
const predicate = PREDICATE_BY_OBSERVATION_TYPE[observation.type];
|
|
6084
|
+
const isExplicit = observation.sourceDetail === "explicit";
|
|
6085
|
+
const isGit = observation.source === "git" || observation.sourceDetail === "git-ingest";
|
|
6086
|
+
if (!predicate || observation.status !== "active" || !isExplicit && !isGit) return [];
|
|
6087
|
+
const evidence = [{
|
|
6088
|
+
evidenceKind: "observation",
|
|
6089
|
+
evidenceId: "observation:" + observation.id,
|
|
6090
|
+
relation: "supports",
|
|
6091
|
+
locator: "observation/" + observation.id,
|
|
6092
|
+
capturedHash: hashObservation(observation)
|
|
6093
|
+
}];
|
|
6094
|
+
if (observation.commitHash) {
|
|
6095
|
+
evidence.push({
|
|
6096
|
+
evidenceKind: "git",
|
|
6097
|
+
evidenceId: "git:" + observation.commitHash,
|
|
6098
|
+
relation: "verifies",
|
|
6099
|
+
locator: "git:" + observation.commitHash,
|
|
6100
|
+
capturedHash: observation.commitHash
|
|
6101
|
+
});
|
|
6102
|
+
}
|
|
6103
|
+
const refs = codeStore?.listObservationRefs(observation.projectId, observation.id) ?? [];
|
|
6104
|
+
for (const ref of refs) evidence.push(evidenceFromCodeRef(ref));
|
|
6105
|
+
const confidence = Math.min(0.9, (isGit ? 0.75 : 0.7) + refs.filter((ref) => ref.status === "current").length * 0.05);
|
|
6106
|
+
const result = writeClaim(store, {
|
|
6107
|
+
projectId: observation.projectId,
|
|
6108
|
+
subject: observation.entityName,
|
|
6109
|
+
predicate,
|
|
6110
|
+
objectValue: observation.title,
|
|
6111
|
+
scope: "project",
|
|
6112
|
+
confidence,
|
|
6113
|
+
observedAt: observation.updatedAt ?? observation.createdAt,
|
|
6114
|
+
reviewState: "approved",
|
|
6115
|
+
origin: isGit ? "git" : "derived",
|
|
6116
|
+
evidence
|
|
6117
|
+
});
|
|
6118
|
+
return [result.claim];
|
|
6119
|
+
}
|
|
6120
|
+
function incompleteSnapshot(snapshot) {
|
|
6121
|
+
if (!snapshot) return false;
|
|
6122
|
+
return snapshot.completeness.skippedOversizedFiles > 0 || (snapshot.completeness.unreadableFiles ?? 0) > 0 || snapshot.completeness.removalScanDeferred;
|
|
6123
|
+
}
|
|
6124
|
+
function requalification(claim, refs, evidence, snapshot) {
|
|
6125
|
+
if (claim.status === "superseded" || claim.reviewState === "draft" || claim.reviewState === "rejected") return void 0;
|
|
6126
|
+
const codeEvidence = evidence.filter((item) => item.evidenceKind === "code");
|
|
6127
|
+
if (codeEvidence.length === 0) return void 0;
|
|
6128
|
+
const linkedRefs = codeEvidence.map((item) => refs.get(item.evidenceId.replace(/^code-ref:/, ""))).filter((ref) => !!ref);
|
|
6129
|
+
const missingOrStale = linkedRefs.length !== codeEvidence.length || linkedRefs.some((ref) => ref.status === "stale");
|
|
6130
|
+
if (missingOrStale) {
|
|
6131
|
+
return {
|
|
6132
|
+
status: "unknown",
|
|
6133
|
+
confidence: Math.min(claim.confidence, 0.2),
|
|
6134
|
+
reviewState: "needs-review",
|
|
6135
|
+
reason: "Bound code evidence is no longer current."
|
|
6136
|
+
};
|
|
6137
|
+
}
|
|
6138
|
+
if (linkedRefs.some((ref) => ref.status === "suspect")) {
|
|
6139
|
+
return {
|
|
6140
|
+
status: claim.status === "disputed" ? "disputed" : "active",
|
|
6141
|
+
confidence: Math.min(claim.confidence, 0.5),
|
|
6142
|
+
reviewState: "needs-review",
|
|
6143
|
+
reason: "Bound file evidence changed and needs review."
|
|
6144
|
+
};
|
|
6145
|
+
}
|
|
6146
|
+
if (incompleteSnapshot(snapshot)) {
|
|
6147
|
+
return {
|
|
6148
|
+
status: claim.status === "disputed" ? "disputed" : "active",
|
|
6149
|
+
confidence: Math.min(claim.confidence, 0.55),
|
|
6150
|
+
reviewState: "needs-review",
|
|
6151
|
+
reason: "The latest code scan is incomplete."
|
|
6152
|
+
};
|
|
6153
|
+
}
|
|
6154
|
+
return void 0;
|
|
6155
|
+
}
|
|
6156
|
+
function requalifyClaimsForCodeState(store, codeStore, projectId) {
|
|
6157
|
+
const snapshot = codeStore.latestSnapshot(projectId);
|
|
6158
|
+
const refs = new Map(codeStore.listProjectObservationRefs(projectId).map((ref) => [ref.id, ref]));
|
|
6159
|
+
let requalified = 0;
|
|
6160
|
+
store.transaction(() => {
|
|
6161
|
+
for (const claim of store.listClaims(projectId)) {
|
|
6162
|
+
const next = requalification(claim, refs, store.listEvidence(claim.id), snapshot);
|
|
6163
|
+
if (!next) continue;
|
|
6164
|
+
if (next.status === claim.status && next.confidence === claim.confidence && next.reviewState === claim.reviewState) {
|
|
6165
|
+
continue;
|
|
6166
|
+
}
|
|
6167
|
+
const updated = {
|
|
6168
|
+
...claim,
|
|
6169
|
+
status: next.status,
|
|
6170
|
+
confidence: next.confidence,
|
|
6171
|
+
reviewState: next.reviewState,
|
|
6172
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6173
|
+
};
|
|
6174
|
+
store.updateClaim(updated);
|
|
6175
|
+
store.recordEvent({
|
|
6176
|
+
projectId,
|
|
6177
|
+
claimId: claim.id,
|
|
6178
|
+
kind: "requalified",
|
|
6179
|
+
fromStatus: claim.status,
|
|
6180
|
+
toStatus: next.status,
|
|
6181
|
+
detail: next.reason,
|
|
6182
|
+
createdAt: updated.updatedAt
|
|
6183
|
+
});
|
|
6184
|
+
requalified += 1;
|
|
6185
|
+
}
|
|
6186
|
+
});
|
|
6187
|
+
return { requalified, incompleteSnapshot: incompleteSnapshot(snapshot) };
|
|
6188
|
+
}
|
|
6189
|
+
function normalizedTokens(value) {
|
|
6190
|
+
const raw = sanitizeCredentials(value).toLocaleLowerCase("en-US").match(TOKEN_PATTERN) ?? [];
|
|
6191
|
+
return new Set(raw.map((token) => token.replace(/^[,;:!?]+|[,;:!?.]+$/g, "")).filter((token) => token.length > 1));
|
|
6192
|
+
}
|
|
6193
|
+
function taskTokens(task) {
|
|
6194
|
+
return normalizedTokens(task);
|
|
6195
|
+
}
|
|
6196
|
+
function scoreClaim(claim, tokens) {
|
|
6197
|
+
const text = [claim.subject, claim.predicate, claim.objectValue].join(" ").toLocaleLowerCase("en-US");
|
|
6198
|
+
const words = normalizedTokens(text);
|
|
6199
|
+
let matches = 0;
|
|
6200
|
+
for (const token of tokens) {
|
|
6201
|
+
if (words.has(token)) matches += 1;
|
|
6202
|
+
}
|
|
6203
|
+
if (matches === 0) return 0;
|
|
6204
|
+
const statusWeight = claim.status === "active" ? 0.25 : claim.status === "disputed" ? 0.2 : 0.1;
|
|
6205
|
+
return matches * 10 + claim.confidence + statusWeight;
|
|
6206
|
+
}
|
|
6207
|
+
function formatClaim(claim) {
|
|
6208
|
+
return sanitizeCredentials([claim.subject, claim.predicate, claim.objectValue].join(" "));
|
|
6209
|
+
}
|
|
6210
|
+
function selectClaimsForTask(store, input) {
|
|
6211
|
+
const limit = Math.max(1, Math.min(12, Math.floor(input.limit ?? 4)));
|
|
6212
|
+
const maxTokens = Math.max(16, Math.min(1e3, Math.floor(input.maxTokens ?? 160)));
|
|
6213
|
+
const tokens = taskTokens(input.task);
|
|
6214
|
+
if (tokens.size === 0) return { claims: [], cautions: [], tokenCount: 0, reasons: {} };
|
|
6215
|
+
const candidates = store.listClaims(input.projectId, {
|
|
6216
|
+
statuses: ["active", "disputed", "unknown"],
|
|
6217
|
+
limit: 1e3
|
|
6218
|
+
}).filter((claim) => claim.reviewState !== "draft" && claim.reviewState !== "rejected");
|
|
6219
|
+
const scored = candidates.map((claim) => ({ claim, score: scoreClaim(claim, tokens) })).filter((item) => item.score > 0).sort((left, right) => right.score - left.score || left.claim.id.localeCompare(right.claim.id));
|
|
6220
|
+
if (scored.length === 0) return { claims: [], cautions: [], tokenCount: 0, reasons: {} };
|
|
6221
|
+
const selected = /* @__PURE__ */ new Map();
|
|
6222
|
+
for (const { claim } of scored.slice(0, limit)) selected.set(claim.id, claim);
|
|
6223
|
+
for (const claim of [...selected.values()]) {
|
|
6224
|
+
for (const sibling of store.listClaimsByConflictKey(input.projectId, claim.conflictKey)) {
|
|
6225
|
+
if (sibling.status === "active" || sibling.status === "disputed") selected.set(sibling.id, sibling);
|
|
6226
|
+
}
|
|
6227
|
+
}
|
|
6228
|
+
const ordered = [...selected.values()].sort((left, right) => scoreClaim(right, tokens) - scoreClaim(left, tokens) || left.id.localeCompare(right.id));
|
|
6229
|
+
const included = [];
|
|
6230
|
+
let tokenCount = 0;
|
|
6231
|
+
for (const claim of ordered) {
|
|
6232
|
+
const nextTokens = countTextTokens(formatClaim(claim));
|
|
6233
|
+
if (included.length > 0 && tokenCount + nextTokens > maxTokens) continue;
|
|
6234
|
+
included.push(claim);
|
|
6235
|
+
tokenCount += nextTokens;
|
|
6236
|
+
}
|
|
6237
|
+
const cautions = /* @__PURE__ */ new Set();
|
|
6238
|
+
const reasons = {};
|
|
6239
|
+
for (const claim of included) {
|
|
6240
|
+
reasons[claim.id] = "task terms matched source-qualified claim";
|
|
6241
|
+
if (claim.status === "disputed") cautions.add("claim-conflict");
|
|
6242
|
+
if (claim.status === "unknown" || claim.reviewState === "needs-review") {
|
|
6243
|
+
cautions.add("claim-needs-review");
|
|
6244
|
+
}
|
|
6245
|
+
}
|
|
6246
|
+
return { claims: included, cautions: [...cautions].sort(), tokenCount, reasons };
|
|
6247
|
+
}
|
|
6248
|
+
var MAX_TERM_LENGTH, MAX_EVIDENCE_ID_LENGTH, TOKEN_PATTERN, PREDICATE_BY_OBSERVATION_TYPE;
|
|
6249
|
+
var init_claims = __esm({
|
|
6250
|
+
"src/knowledge/claims.ts"() {
|
|
6251
|
+
"use strict";
|
|
6252
|
+
init_esm_shims();
|
|
6253
|
+
init_token_budget();
|
|
6254
|
+
init_secret_filter();
|
|
6255
|
+
MAX_TERM_LENGTH = 2e3;
|
|
6256
|
+
MAX_EVIDENCE_ID_LENGTH = 1e3;
|
|
6257
|
+
TOKEN_PATTERN = /[\p{L}\p{N}_./:-]+/gu;
|
|
6258
|
+
PREDICATE_BY_OBSERVATION_TYPE = {
|
|
6259
|
+
decision: "decision",
|
|
6260
|
+
"trade-off": "trade-off",
|
|
6261
|
+
"why-it-exists": "rationale",
|
|
6262
|
+
"what-changed": "changed",
|
|
6263
|
+
"problem-solution": "workaround",
|
|
6264
|
+
gotcha: "caution",
|
|
6265
|
+
"how-it-works": "behavior",
|
|
6266
|
+
discovery: "finding",
|
|
6267
|
+
reasoning: "rationale"
|
|
6268
|
+
};
|
|
6269
|
+
}
|
|
6270
|
+
});
|
|
6271
|
+
|
|
6272
|
+
// src/knowledge/markdown.ts
|
|
6273
|
+
import { createHash as createHash7 } from "crypto";
|
|
6274
|
+
import { promises as fs5 } from "fs";
|
|
6275
|
+
import path8 from "path";
|
|
6276
|
+
import matter from "gray-matter";
|
|
6277
|
+
function hashContent(content) {
|
|
6278
|
+
return createHash7("sha256").update(content).digest("hex");
|
|
6279
|
+
}
|
|
6280
|
+
function stringField(data, key) {
|
|
6281
|
+
const value = data[key];
|
|
6282
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
6283
|
+
throw new Error("missing required field " + key);
|
|
6284
|
+
}
|
|
6285
|
+
return value.trim();
|
|
6286
|
+
}
|
|
6287
|
+
function stringArray(data, key) {
|
|
6288
|
+
const value = data[key];
|
|
6289
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
6290
|
+
throw new Error("field " + key + " must be a string array");
|
|
6291
|
+
}
|
|
6292
|
+
return value.map((item) => item.trim()).filter(Boolean);
|
|
6293
|
+
}
|
|
6294
|
+
function optionalString(data, key) {
|
|
6295
|
+
const value = data[key];
|
|
6296
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
6297
|
+
if (typeof value !== "string") throw new Error("field " + key + " must be text");
|
|
6298
|
+
return value.trim() || void 0;
|
|
6299
|
+
}
|
|
6300
|
+
function validateKnowledgePageFrontmatter(data) {
|
|
6301
|
+
const kind = stringField(data, "kind");
|
|
6302
|
+
const status = stringField(data, "status");
|
|
6303
|
+
const reviewState = stringField(data, "reviewState");
|
|
6304
|
+
if (!PAGE_KINDS.includes(kind)) throw new Error("field kind is invalid");
|
|
6305
|
+
if (!PAGE_STATUSES.includes(status)) throw new Error("field status is invalid");
|
|
6306
|
+
if (!REVIEW_STATES.includes(reviewState)) throw new Error("field reviewState is invalid");
|
|
6307
|
+
return {
|
|
6308
|
+
id: stringField(data, "id"),
|
|
6309
|
+
title: stringField(data, "title"),
|
|
6310
|
+
kind,
|
|
6311
|
+
status,
|
|
6312
|
+
reviewState,
|
|
6313
|
+
claimIds: stringArray(data, "claimIds"),
|
|
6314
|
+
evidenceRefs: stringArray(data, "evidenceRefs"),
|
|
6315
|
+
...optionalString(data, "snapshotId") ? { snapshotId: optionalString(data, "snapshotId") } : {},
|
|
6316
|
+
tags: stringArray(data, "tags"),
|
|
6317
|
+
sourceHash: stringField(data, "sourceHash"),
|
|
6318
|
+
generatedAt: stringField(data, "generatedAt"),
|
|
6319
|
+
updatedAt: stringField(data, "updatedAt")
|
|
6320
|
+
};
|
|
6321
|
+
}
|
|
6322
|
+
function extractInternalMarkdownLinks(body) {
|
|
6323
|
+
const links = /* @__PURE__ */ new Set();
|
|
6324
|
+
for (const match of body.matchAll(LINK_PATTERN)) {
|
|
6325
|
+
const raw = match[1].trim();
|
|
6326
|
+
if (!raw || raw.startsWith("#") || /^[a-z][a-z0-9+.-]*:/i.test(raw)) continue;
|
|
6327
|
+
const pathPart = raw.split("#", 1)[0].replace(/\\/g, "/");
|
|
6328
|
+
if (!pathPart.endsWith(".md")) continue;
|
|
6329
|
+
links.add(pathPart);
|
|
6330
|
+
}
|
|
6331
|
+
return [...links].sort();
|
|
6332
|
+
}
|
|
6333
|
+
function resolvePageLink(sourceRelativePath, target) {
|
|
6334
|
+
const sourceDir = path8.posix.dirname(sourceRelativePath.replace(/\\/g, "/"));
|
|
6335
|
+
const normalized = path8.posix.normalize(path8.posix.join(sourceDir, target.replace(/\\/g, "/")));
|
|
6336
|
+
if (!normalized || normalized === "." || normalized === ".." || normalized.startsWith("../")) return void 0;
|
|
6337
|
+
return normalized;
|
|
6338
|
+
}
|
|
6339
|
+
function renderKnowledgePage(frontmatter, body) {
|
|
6340
|
+
const normalizedBody = body.trimEnd() + "\n";
|
|
6341
|
+
return matter.stringify(normalizedBody, {
|
|
6342
|
+
id: frontmatter.id,
|
|
6343
|
+
title: frontmatter.title,
|
|
6344
|
+
kind: frontmatter.kind,
|
|
6345
|
+
status: frontmatter.status,
|
|
6346
|
+
reviewState: frontmatter.reviewState,
|
|
6347
|
+
claimIds: frontmatter.claimIds,
|
|
6348
|
+
evidenceRefs: frontmatter.evidenceRefs,
|
|
6349
|
+
...frontmatter.snapshotId ? { snapshotId: frontmatter.snapshotId } : {},
|
|
6350
|
+
tags: frontmatter.tags,
|
|
6351
|
+
sourceHash: frontmatter.sourceHash,
|
|
6352
|
+
generatedAt: frontmatter.generatedAt,
|
|
6353
|
+
updatedAt: frontmatter.updatedAt
|
|
6354
|
+
});
|
|
6355
|
+
}
|
|
6356
|
+
async function readKnowledgePage(absolutePath, workspaceRoot) {
|
|
6357
|
+
const raw = await fs5.readFile(absolutePath, "utf8");
|
|
6358
|
+
try {
|
|
6359
|
+
const parsed = matter(raw);
|
|
6360
|
+
const frontmatter = validateKnowledgePageFrontmatter(parsed.data);
|
|
6361
|
+
return {
|
|
6362
|
+
absolutePath,
|
|
6363
|
+
relativePath: workspaceRoot ? path8.relative(workspaceRoot, absolutePath).split(path8.sep).join("/") : path8.basename(absolutePath),
|
|
6364
|
+
frontmatter,
|
|
6365
|
+
body: parsed.content.trim(),
|
|
6366
|
+
contentHash: hashContent(raw),
|
|
6367
|
+
links: extractInternalMarkdownLinks(parsed.content)
|
|
6368
|
+
};
|
|
6369
|
+
} catch (error) {
|
|
6370
|
+
const detail = error instanceof Error ? error.message : "invalid page data";
|
|
6371
|
+
throw new Error("Malformed knowledge page frontmatter in " + absolutePath + ": " + detail);
|
|
6372
|
+
}
|
|
6373
|
+
}
|
|
6374
|
+
async function writeKnowledgePage(absolutePath, content) {
|
|
6375
|
+
await fs5.mkdir(path8.dirname(absolutePath), { recursive: true });
|
|
6376
|
+
await atomicWriteFile(absolutePath, content);
|
|
6377
|
+
return hashContent(content);
|
|
6378
|
+
}
|
|
6379
|
+
function pageContentHash(content) {
|
|
6380
|
+
return hashContent(content);
|
|
6381
|
+
}
|
|
6382
|
+
var PAGE_KINDS, PAGE_STATUSES, REVIEW_STATES, LINK_PATTERN;
|
|
6383
|
+
var init_markdown = __esm({
|
|
6384
|
+
"src/knowledge/markdown.ts"() {
|
|
6385
|
+
"use strict";
|
|
6386
|
+
init_esm_shims();
|
|
6387
|
+
init_file_lock();
|
|
6388
|
+
PAGE_KINDS = ["topic", "decision", "risk", "index", "schema", "log"];
|
|
6389
|
+
PAGE_STATUSES = ["active", "proposed"];
|
|
6390
|
+
REVIEW_STATES = ["approved", "needs-review"];
|
|
6391
|
+
LINK_PATTERN = /\[[^\]]+\]\(([^)]+)\)/g;
|
|
6392
|
+
}
|
|
6393
|
+
});
|
|
6394
|
+
|
|
6395
|
+
// src/knowledge/wiki.ts
|
|
6396
|
+
var wiki_exports = {};
|
|
6397
|
+
__export(wiki_exports, {
|
|
6398
|
+
applyKnowledgeProposal: () => applyKnowledgeProposal,
|
|
6399
|
+
compileKnowledgeWorkspace: () => compileKnowledgeWorkspace,
|
|
6400
|
+
lintKnowledgeWorkspace: () => lintKnowledgeWorkspace,
|
|
6401
|
+
readKnowledgePage: () => readKnowledgePage
|
|
6402
|
+
});
|
|
6403
|
+
import { createHash as createHash8 } from "crypto";
|
|
6404
|
+
import { promises as fs6 } from "fs";
|
|
6405
|
+
import path9 from "path";
|
|
6406
|
+
function hash2(value) {
|
|
6407
|
+
return createHash8("sha256").update(value).digest("hex");
|
|
6408
|
+
}
|
|
6409
|
+
function now() {
|
|
6410
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
6411
|
+
}
|
|
6412
|
+
async function storeFor(workspace) {
|
|
6413
|
+
if (!workspace.dataDir) throw new Error("Knowledge workspace has no operational data directory");
|
|
6414
|
+
const store = new KnowledgeWorkspaceStore();
|
|
6415
|
+
await store.init(workspace.dataDir);
|
|
6416
|
+
return store;
|
|
6417
|
+
}
|
|
6418
|
+
function topicSlug(subject) {
|
|
6419
|
+
const safe = subject.toLocaleLowerCase("en-US").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
|
|
6420
|
+
return (safe || "topic") + "-" + hash2(subject).slice(0, 8);
|
|
6421
|
+
}
|
|
6422
|
+
function groupClaims(claims) {
|
|
6423
|
+
const groups = /* @__PURE__ */ new Map();
|
|
6424
|
+
for (const claim of claims) {
|
|
6425
|
+
const group = groups.get(claim.subject) ?? [];
|
|
6426
|
+
group.push(claim);
|
|
6427
|
+
groups.set(claim.subject, group);
|
|
6428
|
+
}
|
|
6429
|
+
return groups;
|
|
6430
|
+
}
|
|
6431
|
+
function selectPublishableClaims(claims, projectId) {
|
|
6432
|
+
return claims.listClaims(projectId, { statuses: ["active"], limit: 1e3 }).filter((claim) => claim.reviewState === "approved");
|
|
6433
|
+
}
|
|
6434
|
+
function evidenceRefs(claims, store) {
|
|
6435
|
+
const refs = /* @__PURE__ */ new Set();
|
|
6436
|
+
for (const claim of claims) {
|
|
6437
|
+
for (const evidence of store.listEvidence(claim.id)) {
|
|
6438
|
+
refs.add("claim:" + claim.id + ":evidence:" + evidence.id);
|
|
6439
|
+
}
|
|
6440
|
+
}
|
|
6441
|
+
return [...refs].sort();
|
|
6442
|
+
}
|
|
6443
|
+
function snapshotIdForClaims(claims, store) {
|
|
6444
|
+
const ids = /* @__PURE__ */ new Set();
|
|
6445
|
+
for (const claim of claims) {
|
|
6446
|
+
for (const evidence of store.listEvidence(claim.id)) {
|
|
6447
|
+
if (evidence.snapshotId) ids.add(evidence.snapshotId);
|
|
6448
|
+
}
|
|
6449
|
+
}
|
|
6450
|
+
return ids.size === 1 ? [...ids][0] : void 0;
|
|
6451
|
+
}
|
|
6452
|
+
function sourceHashForClaims(claims, store) {
|
|
6453
|
+
const source = claims.map((claim) => ({
|
|
6454
|
+
id: claim.id,
|
|
6455
|
+
status: claim.status,
|
|
6456
|
+
confidence: claim.confidence,
|
|
6457
|
+
evidence: store.listEvidence(claim.id).map((item) => ({
|
|
6458
|
+
id: item.id,
|
|
6459
|
+
kind: item.evidenceKind,
|
|
6460
|
+
evidenceId: item.evidenceId,
|
|
6461
|
+
relation: item.relation,
|
|
6462
|
+
snapshotId: item.snapshotId ?? "",
|
|
6463
|
+
capturedHash: item.capturedHash ?? ""
|
|
6464
|
+
}))
|
|
6465
|
+
}));
|
|
6466
|
+
return hash2(JSON.stringify(source));
|
|
6467
|
+
}
|
|
6468
|
+
function renderTopicBody(title, claims, store) {
|
|
6469
|
+
const lines = [
|
|
6470
|
+
"# " + title,
|
|
6471
|
+
"",
|
|
6472
|
+
"This page is compiled from approved source-qualified claims.",
|
|
6473
|
+
"",
|
|
6474
|
+
"## Current claims",
|
|
6475
|
+
""
|
|
6476
|
+
];
|
|
6477
|
+
for (const claim of claims) {
|
|
6478
|
+
lines.push("- " + claim.predicate + ": " + claim.objectValue + " (claim " + claim.id + ")");
|
|
6479
|
+
}
|
|
6480
|
+
lines.push("");
|
|
6481
|
+
lines.push("## Evidence");
|
|
6482
|
+
lines.push("");
|
|
6483
|
+
for (const claim of claims) {
|
|
6484
|
+
lines.push("### Claim " + claim.id);
|
|
6485
|
+
const evidence = store.listEvidence(claim.id);
|
|
6486
|
+
if (evidence.length === 0) {
|
|
6487
|
+
lines.push("- Missing evidence.");
|
|
6488
|
+
continue;
|
|
6489
|
+
}
|
|
6490
|
+
for (const item of evidence) {
|
|
6491
|
+
const location = item.locator ? " at " + item.locator : "";
|
|
6492
|
+
const snapshot = item.snapshotId ? " (snapshot " + item.snapshotId + ")" : "";
|
|
6493
|
+
lines.push("- " + item.evidenceKind + ": " + item.evidenceId + location + snapshot);
|
|
6494
|
+
}
|
|
6495
|
+
}
|
|
6496
|
+
lines.push("");
|
|
6497
|
+
return lines.join("\n");
|
|
6498
|
+
}
|
|
6499
|
+
function candidatePage(workspace, title, claims, store) {
|
|
6500
|
+
const sourceHash = sourceHashForClaims(claims, store);
|
|
6501
|
+
const generatedAt = now();
|
|
6502
|
+
const slug2 = topicSlug(title);
|
|
6503
|
+
const targetRelativePath = "pages/" + slug2 + ".md";
|
|
6504
|
+
const proposalRelativePath = "proposals/" + slug2 + ".md";
|
|
6505
|
+
const frontmatter = {
|
|
6506
|
+
id: "page:" + hash2(workspace.projectId + ":" + title).slice(0, 24),
|
|
6507
|
+
title,
|
|
6508
|
+
kind: "topic",
|
|
6509
|
+
status: "proposed",
|
|
6510
|
+
reviewState: "needs-review",
|
|
6511
|
+
claimIds: claims.map((claim) => claim.id).sort(),
|
|
6512
|
+
evidenceRefs: evidenceRefs(claims, store),
|
|
6513
|
+
...snapshotIdForClaims(claims, store) ? { snapshotId: snapshotIdForClaims(claims, store) } : {},
|
|
6514
|
+
tags: ["generated", "topic"],
|
|
6515
|
+
sourceHash,
|
|
6516
|
+
generatedAt,
|
|
6517
|
+
updatedAt: generatedAt
|
|
6518
|
+
};
|
|
6519
|
+
const content = renderKnowledgePage(frontmatter, renderTopicBody(title, claims, store));
|
|
6520
|
+
const proposalPath = resolveKnowledgeWorkspaceFile(workspace, proposalRelativePath);
|
|
6521
|
+
return {
|
|
6522
|
+
page: {
|
|
6523
|
+
absolutePath: proposalPath,
|
|
6524
|
+
relativePath: proposalRelativePath,
|
|
6525
|
+
frontmatter,
|
|
6526
|
+
body: renderTopicBody(title, claims, store),
|
|
6527
|
+
contentHash: pageContentHash(content),
|
|
6528
|
+
links: []
|
|
6529
|
+
},
|
|
6530
|
+
content,
|
|
6531
|
+
targetRelativePath,
|
|
6532
|
+
proposalRelativePath
|
|
6533
|
+
};
|
|
6534
|
+
}
|
|
6535
|
+
async function fileHashIfPresent(filePath) {
|
|
6536
|
+
try {
|
|
6537
|
+
return pageContentHash(await fs6.readFile(filePath, "utf8"));
|
|
6538
|
+
} catch (error) {
|
|
6539
|
+
const code = error && typeof error === "object" && "code" in error ? error.code : void 0;
|
|
6540
|
+
if (code === "ENOENT") return void 0;
|
|
6541
|
+
throw error;
|
|
6542
|
+
}
|
|
6543
|
+
}
|
|
6544
|
+
async function writeIndex(workspace, store) {
|
|
6545
|
+
const paths = getKnowledgeWorkspacePaths(workspace);
|
|
6546
|
+
const published = store.listPages(workspace.id).filter((page) => page.status === "active").sort((left, right) => left.title.localeCompare(right.title));
|
|
6547
|
+
const pending = store.listProposals(workspace.id, "pending").sort((left, right) => left.proposalPath.localeCompare(right.proposalPath));
|
|
6548
|
+
const lines = ["# Knowledge Workspace", "", "## Published pages", ""];
|
|
6549
|
+
if (published.length === 0) {
|
|
6550
|
+
lines.push("No published pages yet.");
|
|
6551
|
+
} else {
|
|
6552
|
+
for (const page of published) lines.push("- [" + page.title + "](" + page.relativePath + ")");
|
|
6553
|
+
}
|
|
6554
|
+
lines.push("");
|
|
6555
|
+
lines.push("## Review queue");
|
|
6556
|
+
lines.push("");
|
|
6557
|
+
if (pending.length === 0) {
|
|
6558
|
+
lines.push("No pending proposals.");
|
|
6559
|
+
} else {
|
|
6560
|
+
for (const proposal of pending) {
|
|
6561
|
+
const relative2 = path9.relative(paths.root, proposal.proposalPath).split(path9.sep).join("/");
|
|
6562
|
+
lines.push("- [" + path9.basename(proposal.proposalPath, ".md") + "](" + relative2 + ")");
|
|
6563
|
+
}
|
|
6564
|
+
}
|
|
6565
|
+
lines.push("");
|
|
6566
|
+
await writeKnowledgePage(paths.index, lines.join("\n"));
|
|
6567
|
+
}
|
|
6568
|
+
async function appendLog(workspace, line) {
|
|
6569
|
+
const logPath = getKnowledgeWorkspacePaths(workspace).log;
|
|
6570
|
+
await fs6.appendFile(logPath, "- " + now() + " " + line + "\n", "utf8");
|
|
6571
|
+
}
|
|
6572
|
+
function toPageRecord(workspace, page, status, contentHash, manualContentHash) {
|
|
6573
|
+
return {
|
|
6574
|
+
id: page.frontmatter.id,
|
|
6575
|
+
workspaceId: workspace.id,
|
|
6576
|
+
relativePath: page.relativePath.replace(/^proposals\//, "pages/"),
|
|
6577
|
+
title: page.frontmatter.title,
|
|
6578
|
+
kind: page.frontmatter.kind,
|
|
6579
|
+
status,
|
|
6580
|
+
reviewState: page.frontmatter.reviewState,
|
|
6581
|
+
contentHash,
|
|
6582
|
+
sourceHash: page.frontmatter.sourceHash,
|
|
6583
|
+
claimIds: page.frontmatter.claimIds,
|
|
6584
|
+
...page.frontmatter.snapshotId ? { snapshotId: page.frontmatter.snapshotId } : {},
|
|
6585
|
+
tags: page.frontmatter.tags,
|
|
6586
|
+
generatedAt: page.frontmatter.generatedAt,
|
|
6587
|
+
updatedAt: page.frontmatter.updatedAt,
|
|
6588
|
+
...manualContentHash ? { manualContentHash } : {}
|
|
6589
|
+
};
|
|
6590
|
+
}
|
|
6591
|
+
async function compileKnowledgeWorkspace(input) {
|
|
6592
|
+
const store = await storeFor(input.workspace);
|
|
6593
|
+
const published = [];
|
|
6594
|
+
const proposals = [];
|
|
6595
|
+
const claims = selectPublishableClaims(input.claims, input.workspace.projectId);
|
|
6596
|
+
await withFileLock(getKnowledgeWorkspacePaths(input.workspace).root, async () => {
|
|
6597
|
+
for (const [title, group] of groupClaims(claims)) {
|
|
6598
|
+
const candidate = candidatePage(input.workspace, title, group, input.claims);
|
|
6599
|
+
const targetPath = resolveKnowledgeWorkspaceFile(input.workspace, candidate.targetRelativePath);
|
|
6600
|
+
const proposalPath = resolveKnowledgeWorkspaceFile(input.workspace, candidate.proposalRelativePath);
|
|
6601
|
+
let existing = store.getPage(input.workspace.id, candidate.targetRelativePath);
|
|
6602
|
+
const actualTargetHash = await fileHashIfPresent(targetPath);
|
|
6603
|
+
if (actualTargetHash && !existing) {
|
|
6604
|
+
const manualRecord = {
|
|
6605
|
+
...toPageRecord(input.workspace, candidate.page, "active", actualTargetHash, actualTargetHash),
|
|
6606
|
+
reviewState: "needs-review",
|
|
6607
|
+
sourceHash: "manual:" + actualTargetHash
|
|
6608
|
+
};
|
|
6609
|
+
store.upsertPage(manualRecord);
|
|
6610
|
+
existing = manualRecord;
|
|
6611
|
+
}
|
|
6612
|
+
if (actualTargetHash && existing && existing.status === "active" && !existing.manualContentHash && actualTargetHash === existing.contentHash && existing.sourceHash === candidate.page.frontmatter.sourceHash) {
|
|
6613
|
+
published.push(existing);
|
|
6614
|
+
continue;
|
|
6615
|
+
}
|
|
6616
|
+
let reason = "new-page";
|
|
6617
|
+
let baseContentHash;
|
|
6618
|
+
if (actualTargetHash) {
|
|
6619
|
+
baseContentHash = existing?.contentHash;
|
|
6620
|
+
if (!existing || existing.status !== "active" || !!existing.manualContentHash || actualTargetHash !== existing.contentHash) {
|
|
6621
|
+
reason = "manual-edit-protected";
|
|
6622
|
+
} else {
|
|
6623
|
+
reason = "source-changed";
|
|
6624
|
+
}
|
|
6625
|
+
}
|
|
6626
|
+
if (!actualTargetHash) {
|
|
6627
|
+
store.upsertPage(toPageRecord(input.workspace, candidate.page, "proposed", candidate.page.contentHash));
|
|
6628
|
+
}
|
|
6629
|
+
await writeKnowledgePage(proposalPath, candidate.content);
|
|
6630
|
+
const proposal = store.createProposal({
|
|
6631
|
+
workspaceId: input.workspace.id,
|
|
6632
|
+
pageId: candidate.page.frontmatter.id,
|
|
6633
|
+
targetPath,
|
|
6634
|
+
proposalPath,
|
|
6635
|
+
...baseContentHash ? { baseContentHash } : {},
|
|
6636
|
+
sourceHash: candidate.page.frontmatter.sourceHash,
|
|
6637
|
+
reason
|
|
6638
|
+
});
|
|
6639
|
+
proposals.push(proposal);
|
|
6640
|
+
await appendLog(input.workspace, "proposed " + candidate.targetRelativePath + " (" + reason + ").");
|
|
6641
|
+
}
|
|
6642
|
+
store.markWorkspaceCompiled(input.workspace.id);
|
|
6643
|
+
await writeIndex(input.workspace, store);
|
|
6644
|
+
});
|
|
6645
|
+
return { proposals, published };
|
|
6646
|
+
}
|
|
6647
|
+
async function applyKnowledgeProposal(input) {
|
|
6648
|
+
const store = await storeFor(input.workspace);
|
|
6649
|
+
const proposal = store.getProposal(input.proposalId);
|
|
6650
|
+
if (!proposal || proposal.workspaceId !== input.workspace.id) {
|
|
6651
|
+
throw new Error("Knowledge proposal was not found for this workspace");
|
|
6652
|
+
}
|
|
6653
|
+
if (proposal.status !== "pending") throw new Error("Knowledge proposal is not pending review");
|
|
6654
|
+
const root = getKnowledgeWorkspacePaths(input.workspace).root;
|
|
6655
|
+
const targetPath = path9.resolve(proposal.targetPath);
|
|
6656
|
+
const proposalPath = path9.resolve(proposal.proposalPath);
|
|
6657
|
+
if (!targetPath.startsWith(root + path9.sep) || !proposalPath.startsWith(root + path9.sep)) {
|
|
6658
|
+
throw new Error("Knowledge proposal path escapes its workspace");
|
|
6659
|
+
}
|
|
6660
|
+
await withFileLock(root, async () => {
|
|
6661
|
+
const actualTargetHash = await fileHashIfPresent(targetPath);
|
|
6662
|
+
if (proposal.baseContentHash && actualTargetHash !== proposal.baseContentHash && !input.allowManualOverwrite) {
|
|
6663
|
+
throw new Error("The target page has manual changes; review the proposal before applying it");
|
|
6664
|
+
}
|
|
6665
|
+
if (!proposal.baseContentHash && actualTargetHash && !input.allowManualOverwrite) {
|
|
6666
|
+
throw new Error("The target page already exists and has no known safe base revision");
|
|
6667
|
+
}
|
|
6668
|
+
const proposalPage = await readKnowledgePage(proposalPath, root);
|
|
6669
|
+
const activeFrontmatter = {
|
|
6670
|
+
...proposalPage.frontmatter,
|
|
6671
|
+
status: "active",
|
|
6672
|
+
reviewState: "approved",
|
|
6673
|
+
updatedAt: now()
|
|
6674
|
+
};
|
|
6675
|
+
const content = renderKnowledgePage(activeFrontmatter, proposalPage.body);
|
|
6676
|
+
const contentHash = await writeKnowledgePage(targetPath, content);
|
|
6677
|
+
const activePage = await readKnowledgePage(targetPath, root);
|
|
6678
|
+
const record = toPageRecord(input.workspace, activePage, "active", contentHash);
|
|
6679
|
+
store.upsertPage(record);
|
|
6680
|
+
store.replacePageClaims(record.id, record.claimIds);
|
|
6681
|
+
store.replacePageLinks(record.id, activePage.links.map((link) => resolvePageLink(record.relativePath, link)).filter((link) => !!link));
|
|
6682
|
+
store.markProposalApplied(proposal.id);
|
|
6683
|
+
await appendLog(
|
|
6684
|
+
input.workspace,
|
|
6685
|
+
"applied " + record.relativePath + (input.allowManualOverwrite ? " after explicit manual overwrite review." : ".")
|
|
6686
|
+
);
|
|
6687
|
+
await writeIndex(input.workspace, store);
|
|
6688
|
+
});
|
|
6689
|
+
return { proposal: store.getProposal(proposal.id), targetPath };
|
|
6690
|
+
}
|
|
6691
|
+
async function markdownFiles(directory) {
|
|
6692
|
+
const files = [];
|
|
6693
|
+
let entries;
|
|
6694
|
+
try {
|
|
6695
|
+
entries = await fs6.readdir(directory, { withFileTypes: true });
|
|
6696
|
+
} catch {
|
|
6697
|
+
return files;
|
|
6698
|
+
}
|
|
6699
|
+
for (const entry of entries) {
|
|
6700
|
+
const filePath = path9.join(directory, entry.name);
|
|
6701
|
+
if (entry.isDirectory()) {
|
|
6702
|
+
files.push(...await markdownFiles(filePath));
|
|
6703
|
+
} else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
|
|
6704
|
+
files.push(filePath);
|
|
6705
|
+
}
|
|
6706
|
+
}
|
|
6707
|
+
return files;
|
|
6708
|
+
}
|
|
6709
|
+
async function lintKnowledgeWorkspace(input) {
|
|
6710
|
+
const store = await storeFor(input.workspace);
|
|
6711
|
+
const paths = getKnowledgeWorkspacePaths(input.workspace);
|
|
6712
|
+
const issues = [];
|
|
6713
|
+
const validPages = [];
|
|
6714
|
+
const files = await markdownFiles(paths.pages);
|
|
6715
|
+
for (const filePath of files) {
|
|
6716
|
+
try {
|
|
6717
|
+
validPages.push(await readKnowledgePage(filePath, paths.root));
|
|
6718
|
+
} catch (error) {
|
|
6719
|
+
issues.push({
|
|
6720
|
+
kind: "malformed-frontmatter",
|
|
6721
|
+
severity: "error",
|
|
6722
|
+
message: error instanceof Error ? error.message : "Malformed knowledge page.",
|
|
6723
|
+
relativePath: path9.relative(paths.root, filePath).split(path9.sep).join("/")
|
|
6724
|
+
});
|
|
6725
|
+
}
|
|
6726
|
+
}
|
|
6727
|
+
const indexed = /* @__PURE__ */ new Set();
|
|
6728
|
+
try {
|
|
6729
|
+
const index = await fs6.readFile(paths.index, "utf8");
|
|
6730
|
+
for (const link of extractInternalMarkdownLinks(index)) {
|
|
6731
|
+
const resolved = resolvePageLink("index.md", link);
|
|
6732
|
+
if (resolved) indexed.add(resolved);
|
|
6733
|
+
}
|
|
6734
|
+
} catch {
|
|
6735
|
+
issues.push({
|
|
6736
|
+
kind: "broken-link",
|
|
6737
|
+
severity: "error",
|
|
6738
|
+
message: "Knowledge index is missing.",
|
|
6739
|
+
relativePath: "index.md"
|
|
6740
|
+
});
|
|
6741
|
+
}
|
|
6742
|
+
const latestSnapshotId = input.codeStore?.latestSnapshot(input.workspace.projectId)?.id;
|
|
6743
|
+
for (const page of validPages) {
|
|
6744
|
+
if (!indexed.has(page.relativePath)) {
|
|
6745
|
+
issues.push({
|
|
6746
|
+
kind: "orphan-page",
|
|
6747
|
+
severity: "warning",
|
|
6748
|
+
message: "Page is not linked from index.md.",
|
|
6749
|
+
relativePath: page.relativePath
|
|
6750
|
+
});
|
|
6751
|
+
}
|
|
6752
|
+
for (const link of page.links) {
|
|
6753
|
+
const target = resolvePageLink(page.relativePath, link);
|
|
6754
|
+
if (!target || !await fileHashIfPresent(resolveKnowledgeWorkspaceFile(input.workspace, target))) {
|
|
6755
|
+
issues.push({
|
|
6756
|
+
kind: "broken-link",
|
|
6757
|
+
severity: "error",
|
|
6758
|
+
message: "Internal Markdown link does not resolve to a workspace page.",
|
|
6759
|
+
relativePath: page.relativePath
|
|
6760
|
+
});
|
|
6761
|
+
}
|
|
6762
|
+
}
|
|
6763
|
+
for (const claimId of page.frontmatter.claimIds) {
|
|
6764
|
+
const claim = input.claims.getClaim(claimId);
|
|
6765
|
+
if (!claim) {
|
|
6766
|
+
issues.push({
|
|
6767
|
+
kind: "missing-claim",
|
|
6768
|
+
severity: "error",
|
|
6769
|
+
message: "Page references a claim that is not present in the ledger.",
|
|
6770
|
+
relativePath: page.relativePath,
|
|
6771
|
+
claimId
|
|
6772
|
+
});
|
|
6773
|
+
continue;
|
|
6774
|
+
}
|
|
6775
|
+
if (input.claims.listEvidence(claim.id).length === 0) {
|
|
6776
|
+
issues.push({
|
|
6777
|
+
kind: "missing-evidence",
|
|
6778
|
+
severity: "error",
|
|
6779
|
+
message: "Page claim has no source evidence.",
|
|
6780
|
+
relativePath: page.relativePath,
|
|
6781
|
+
claimId
|
|
6782
|
+
});
|
|
6783
|
+
}
|
|
6784
|
+
if (claim.status === "superseded") {
|
|
6785
|
+
issues.push({
|
|
6786
|
+
kind: "superseded-claim",
|
|
6787
|
+
severity: "error",
|
|
6788
|
+
message: "Page still presents a superseded primary claim.",
|
|
6789
|
+
relativePath: page.relativePath,
|
|
6790
|
+
claimId
|
|
6791
|
+
});
|
|
6792
|
+
}
|
|
6793
|
+
if (claim.status === "disputed") {
|
|
6794
|
+
issues.push({
|
|
6795
|
+
kind: "unresolved-conflict",
|
|
6796
|
+
severity: "error",
|
|
6797
|
+
message: "Page claim has an unresolved competing assertion.",
|
|
6798
|
+
relativePath: page.relativePath,
|
|
6799
|
+
claimId
|
|
6800
|
+
});
|
|
6801
|
+
}
|
|
6802
|
+
}
|
|
6803
|
+
if (latestSnapshotId && page.frontmatter.snapshotId && page.frontmatter.snapshotId !== latestSnapshotId) {
|
|
6804
|
+
issues.push({
|
|
6805
|
+
kind: "stale-snapshot",
|
|
6806
|
+
severity: "warning",
|
|
6807
|
+
message: "Page was compiled against an older code snapshot.",
|
|
6808
|
+
relativePath: page.relativePath
|
|
6809
|
+
});
|
|
6810
|
+
}
|
|
6811
|
+
}
|
|
6812
|
+
for (const proposal of store.listProposals(input.workspace.id, "pending")) {
|
|
6813
|
+
if (proposal.reason === "manual-edit-protected") {
|
|
6814
|
+
issues.push({
|
|
6815
|
+
kind: "manual-edit-protected",
|
|
6816
|
+
severity: "warning",
|
|
6817
|
+
message: "A newer proposal was held because the target page has manual edits.",
|
|
6818
|
+
relativePath: path9.relative(paths.root, proposal.targetPath).split(path9.sep).join("/")
|
|
6819
|
+
});
|
|
6820
|
+
}
|
|
6821
|
+
}
|
|
6822
|
+
const valid = !issues.some((issue) => issue.severity === "error");
|
|
6823
|
+
store.markWorkspaceLinted(input.workspace.id, valid ? "ready" : "needs-review");
|
|
6824
|
+
return { valid, pagesScanned: validPages.length, issues };
|
|
6825
|
+
}
|
|
6826
|
+
var init_wiki = __esm({
|
|
6827
|
+
"src/knowledge/wiki.ts"() {
|
|
6828
|
+
"use strict";
|
|
6829
|
+
init_esm_shims();
|
|
6830
|
+
init_file_lock();
|
|
6831
|
+
init_markdown();
|
|
6832
|
+
init_workspace_store();
|
|
6833
|
+
init_workspace();
|
|
6834
|
+
init_markdown();
|
|
6835
|
+
}
|
|
6836
|
+
});
|
|
6837
|
+
|
|
6838
|
+
// src/workspace/workflow-sync.ts
|
|
6839
|
+
import matter2 from "gray-matter";
|
|
6840
|
+
var WorkflowSyncer;
|
|
6841
|
+
var init_workflow_sync = __esm({
|
|
6842
|
+
"src/workspace/workflow-sync.ts"() {
|
|
6843
|
+
"use strict";
|
|
6844
|
+
init_esm_shims();
|
|
6845
|
+
WorkflowSyncer = class {
|
|
6846
|
+
/**
|
|
6847
|
+
* Parse a Windsurf workflow markdown file into a WorkflowEntry.
|
|
6848
|
+
*/
|
|
6849
|
+
parseWindsurfWorkflow(fileName, raw) {
|
|
6850
|
+
const name = fileName.replace(/\.md$/i, "");
|
|
6851
|
+
let description = "";
|
|
6852
|
+
let content = raw;
|
|
6853
|
+
try {
|
|
6854
|
+
const parsed = matter2(raw);
|
|
6855
|
+
description = parsed.data?.description ?? "";
|
|
6856
|
+
content = parsed.content.trim();
|
|
6857
|
+
} catch {
|
|
6858
|
+
}
|
|
6859
|
+
return {
|
|
6860
|
+
name,
|
|
6861
|
+
description,
|
|
6862
|
+
content,
|
|
6863
|
+
source: "windsurf",
|
|
6864
|
+
filePath: `.windsurf/workflows/${fileName}`
|
|
6865
|
+
};
|
|
6866
|
+
}
|
|
6867
|
+
/**
|
|
6868
|
+
* Convert a workflow to Codex SKILL.md format.
|
|
6869
|
+
*/
|
|
6870
|
+
toCodexSkill(wf) {
|
|
6871
|
+
const safeName = this.sanitizeName(wf.name);
|
|
6872
|
+
const fm = { name: safeName };
|
|
6873
|
+
if (wf.description) {
|
|
6874
|
+
fm.description = wf.description;
|
|
6875
|
+
}
|
|
6876
|
+
const content = matter2.stringify(wf.content, fm);
|
|
6877
|
+
return {
|
|
6878
|
+
filePath: `.agents/skills/${safeName}/SKILL.md`,
|
|
6879
|
+
content
|
|
6880
|
+
};
|
|
6881
|
+
}
|
|
6882
|
+
/**
|
|
6883
|
+
* Convert a workflow to Cursor .mdc rule format.
|
|
6884
|
+
*/
|
|
6885
|
+
toCursorRule(wf) {
|
|
6886
|
+
const safeName = this.sanitizeName(wf.name);
|
|
6887
|
+
const fm = {};
|
|
6888
|
+
if (wf.description) {
|
|
6889
|
+
fm.description = wf.description;
|
|
6890
|
+
}
|
|
6891
|
+
fm.globs = "";
|
|
6892
|
+
fm.alwaysApply = "false";
|
|
6893
|
+
const content = matter2.stringify(wf.content, fm);
|
|
6894
|
+
return {
|
|
6895
|
+
filePath: `.cursor/rules/${safeName}.mdc`,
|
|
6896
|
+
content
|
|
6897
|
+
};
|
|
6898
|
+
}
|
|
6899
|
+
/**
|
|
6900
|
+
* Convert a workflow to a CLAUDE.md section string.
|
|
6901
|
+
*/
|
|
6902
|
+
toClaudeSection(wf) {
|
|
6903
|
+
const lines = [];
|
|
6904
|
+
lines.push(`## Workflow: ${wf.name}`);
|
|
6905
|
+
if (wf.description) {
|
|
6906
|
+
lines.push("");
|
|
6907
|
+
lines.push(`> ${wf.description}`);
|
|
6908
|
+
}
|
|
6909
|
+
lines.push("");
|
|
6910
|
+
lines.push(wf.content);
|
|
6911
|
+
return lines.join("\n");
|
|
6912
|
+
}
|
|
6913
|
+
/**
|
|
6914
|
+
* Convert all workflows to the target agent format.
|
|
6915
|
+
* Returns an array of { filePath, content } for each generated file.
|
|
6916
|
+
*/
|
|
6917
|
+
convertAll(workflows, target) {
|
|
6918
|
+
if (target === "windsurf") {
|
|
6919
|
+
return [];
|
|
6920
|
+
}
|
|
6921
|
+
if (target === "codex") {
|
|
6922
|
+
return workflows.map((wf) => this.toCodexSkill(wf));
|
|
6923
|
+
}
|
|
6924
|
+
if (target === "cursor") {
|
|
6925
|
+
return workflows.map((wf) => this.toCursorRule(wf));
|
|
6926
|
+
}
|
|
6927
|
+
if (target === "claude-code") {
|
|
6928
|
+
const sections = workflows.map((wf) => this.toClaudeSection(wf));
|
|
6929
|
+
return [
|
|
6930
|
+
{
|
|
6931
|
+
filePath: "CLAUDE.md",
|
|
6932
|
+
content: sections.join("\n\n")
|
|
6933
|
+
}
|
|
6934
|
+
];
|
|
6935
|
+
}
|
|
6936
|
+
return [];
|
|
6937
|
+
}
|
|
6938
|
+
// ---- Helpers ----
|
|
6939
|
+
sanitizeName(name) {
|
|
6940
|
+
return name.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "workflow";
|
|
6941
|
+
}
|
|
6942
|
+
};
|
|
6943
|
+
}
|
|
6944
|
+
});
|
|
6945
|
+
|
|
6946
|
+
// src/knowledge/workflow-store.ts
|
|
6947
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
6948
|
+
function parseJson(value, fallback) {
|
|
6949
|
+
if (typeof value !== "string" || !value) return fallback;
|
|
6950
|
+
try {
|
|
6951
|
+
return JSON.parse(value);
|
|
6952
|
+
} catch {
|
|
6953
|
+
return fallback;
|
|
6954
|
+
}
|
|
6955
|
+
}
|
|
6956
|
+
function optionalText3(value) {
|
|
6957
|
+
return typeof value === "string" && value.trim() ? value : void 0;
|
|
6958
|
+
}
|
|
6959
|
+
function rowToWorkflow(row) {
|
|
6960
|
+
return parseJson(row.specJson, {
|
|
6961
|
+
id: row.id,
|
|
6962
|
+
workspaceId: row.workspaceId,
|
|
6963
|
+
title: row.title,
|
|
6964
|
+
description: "",
|
|
6965
|
+
status: row.status,
|
|
6966
|
+
version: row.version,
|
|
6967
|
+
taskLenses: [],
|
|
6968
|
+
triggers: [],
|
|
6969
|
+
assumptions: [],
|
|
6970
|
+
requiredContext: [],
|
|
6971
|
+
guardrails: [],
|
|
6972
|
+
allowedTools: [],
|
|
6973
|
+
phases: [],
|
|
6974
|
+
verificationGates: [],
|
|
6975
|
+
claimIds: [],
|
|
6976
|
+
evidenceRefs: [],
|
|
6977
|
+
codeRefs: [],
|
|
6978
|
+
compatibleAgents: [],
|
|
6979
|
+
body: "",
|
|
6980
|
+
sourcePath: row.sourcePath,
|
|
6981
|
+
sourceHash: row.sourceHash,
|
|
6982
|
+
contentHash: row.contentHash,
|
|
6983
|
+
createdAt: row.createdAt,
|
|
6984
|
+
updatedAt: row.updatedAt
|
|
6985
|
+
});
|
|
6986
|
+
}
|
|
6987
|
+
function rowToRun(row) {
|
|
6988
|
+
return {
|
|
6989
|
+
id: row.id,
|
|
6990
|
+
workflowId: row.workflowId,
|
|
6991
|
+
projectId: row.projectId,
|
|
6992
|
+
task: row.task,
|
|
6993
|
+
...optionalText3(row.startingSnapshotId) ? { startingSnapshotId: row.startingSnapshotId } : {},
|
|
6994
|
+
selectedEvidence: parseJson(row.selectedEvidenceJson, []),
|
|
6995
|
+
phaseState: parseJson(row.phaseStateJson, {}),
|
|
6996
|
+
outcome: row.outcome,
|
|
6997
|
+
verificationVerdict: row.verificationVerdict,
|
|
6998
|
+
...optionalText3(row.failureReason) ? { failureReason: row.failureReason } : {},
|
|
6999
|
+
startedAt: row.startedAt,
|
|
7000
|
+
...optionalText3(row.completedAt) ? { completedAt: row.completedAt } : {}
|
|
7001
|
+
};
|
|
7002
|
+
}
|
|
7003
|
+
var WorkflowStore;
|
|
7004
|
+
var init_workflow_store = __esm({
|
|
7005
|
+
"src/knowledge/workflow-store.ts"() {
|
|
7006
|
+
"use strict";
|
|
7007
|
+
init_esm_shims();
|
|
7008
|
+
init_sqlite_db();
|
|
7009
|
+
WorkflowStore = class {
|
|
7010
|
+
db = null;
|
|
7011
|
+
async init(dataDir) {
|
|
7012
|
+
this.db = getDatabase(dataDir);
|
|
7013
|
+
}
|
|
7014
|
+
upsertWorkflow(workflow) {
|
|
7015
|
+
this.db.prepare(
|
|
7016
|
+
"INSERT INTO knowledge_workflows (id, workspaceId, sourcePath, title, status, version, sourceHash, contentHash, specJson, importedFrom, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(workspaceId, sourcePath) DO UPDATE SET id = excluded.id, title = excluded.title, status = excluded.status, version = excluded.version, sourceHash = excluded.sourceHash, contentHash = excluded.contentHash, specJson = excluded.specJson, importedFrom = excluded.importedFrom, updatedAt = excluded.updatedAt"
|
|
7017
|
+
).run(
|
|
7018
|
+
workflow.id,
|
|
7019
|
+
workflow.workspaceId,
|
|
7020
|
+
workflow.sourcePath,
|
|
7021
|
+
workflow.title,
|
|
7022
|
+
workflow.status,
|
|
7023
|
+
workflow.version,
|
|
7024
|
+
workflow.sourceHash,
|
|
7025
|
+
workflow.contentHash,
|
|
7026
|
+
JSON.stringify(workflow),
|
|
7027
|
+
workflow.importedFrom ?? null,
|
|
7028
|
+
workflow.createdAt,
|
|
7029
|
+
workflow.updatedAt
|
|
7030
|
+
);
|
|
7031
|
+
return this.getWorkflowByPath(workflow.workspaceId, workflow.sourcePath);
|
|
7032
|
+
}
|
|
7033
|
+
getWorkflow(id) {
|
|
7034
|
+
const row = this.db.prepare("SELECT * FROM knowledge_workflows WHERE id = ?").get(id);
|
|
7035
|
+
return row ? rowToWorkflow(row) : void 0;
|
|
7036
|
+
}
|
|
7037
|
+
getWorkflowByPath(workspaceId2, sourcePath) {
|
|
7038
|
+
const row = this.db.prepare("SELECT * FROM knowledge_workflows WHERE workspaceId = ? AND sourcePath = ?").get(workspaceId2, sourcePath);
|
|
7039
|
+
return row ? rowToWorkflow(row) : void 0;
|
|
7040
|
+
}
|
|
7041
|
+
listWorkflows(workspaceId2, status) {
|
|
7042
|
+
const rows = status ? this.db.prepare("SELECT * FROM knowledge_workflows WHERE workspaceId = ? AND status = ? ORDER BY title, id").all(workspaceId2, status) : this.db.prepare("SELECT * FROM knowledge_workflows WHERE workspaceId = ? ORDER BY title, id").all(workspaceId2);
|
|
7043
|
+
return rows.map(rowToWorkflow);
|
|
7044
|
+
}
|
|
7045
|
+
recordRun(input) {
|
|
7046
|
+
const run = {
|
|
7047
|
+
id: randomUUID7(),
|
|
7048
|
+
workflowId: input.workflowId,
|
|
7049
|
+
projectId: input.projectId,
|
|
7050
|
+
task: input.task.trim(),
|
|
7051
|
+
...input.startingSnapshotId ? { startingSnapshotId: input.startingSnapshotId } : {},
|
|
7052
|
+
selectedEvidence: [...new Set(input.selectedEvidence ?? [])],
|
|
7053
|
+
phaseState: input.phaseState ?? {},
|
|
7054
|
+
outcome: input.outcome,
|
|
7055
|
+
verificationVerdict: input.verificationVerdict ?? (input.outcome === "failed" ? "failed" : "not-run"),
|
|
7056
|
+
...input.failureReason?.trim() ? { failureReason: input.failureReason.trim() } : {},
|
|
7057
|
+
startedAt: input.startedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
7058
|
+
...input.completedAt ? { completedAt: input.completedAt } : input.outcome !== "in-progress" ? { completedAt: (/* @__PURE__ */ new Date()).toISOString() } : {}
|
|
7059
|
+
};
|
|
7060
|
+
this.db.prepare(
|
|
7061
|
+
"INSERT INTO knowledge_workflow_runs (id, workflowId, projectId, task, startingSnapshotId, selectedEvidenceJson, phaseStateJson, outcome, verificationVerdict, failureReason, startedAt, completedAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
7062
|
+
).run(
|
|
7063
|
+
run.id,
|
|
7064
|
+
run.workflowId,
|
|
7065
|
+
run.projectId,
|
|
7066
|
+
run.task,
|
|
7067
|
+
run.startingSnapshotId ?? null,
|
|
7068
|
+
JSON.stringify(run.selectedEvidence),
|
|
7069
|
+
JSON.stringify(run.phaseState),
|
|
7070
|
+
run.outcome,
|
|
7071
|
+
run.verificationVerdict,
|
|
7072
|
+
run.failureReason ?? null,
|
|
7073
|
+
run.startedAt,
|
|
7074
|
+
run.completedAt ?? null
|
|
7075
|
+
);
|
|
7076
|
+
return this.getRun(run.id);
|
|
7077
|
+
}
|
|
7078
|
+
getRun(id) {
|
|
7079
|
+
const row = this.db.prepare("SELECT * FROM knowledge_workflow_runs WHERE id = ?").get(id);
|
|
7080
|
+
return row ? rowToRun(row) : void 0;
|
|
7081
|
+
}
|
|
7082
|
+
listRuns(projectId, workflowId, limit = 20) {
|
|
7083
|
+
const boundedLimit = Math.max(1, Math.min(Math.floor(limit), 100));
|
|
7084
|
+
const rows = workflowId ? this.db.prepare("SELECT * FROM knowledge_workflow_runs WHERE projectId = ? AND workflowId = ? ORDER BY startedAt DESC LIMIT ?").all(projectId, workflowId, boundedLimit) : this.db.prepare("SELECT * FROM knowledge_workflow_runs WHERE projectId = ? ORDER BY startedAt DESC LIMIT ?").all(projectId, boundedLimit);
|
|
7085
|
+
return rows.map(rowToRun);
|
|
7086
|
+
}
|
|
7087
|
+
recentFailureCautions(projectId, workflowId, limit = 2) {
|
|
7088
|
+
return this.listRuns(projectId, workflowId, limit).filter((run) => run.outcome === "failed" || run.verificationVerdict === "failed").map((run) => run.failureReason ? "Previous run failed: " + run.failureReason : "Previous run has a failed verification gate.");
|
|
7089
|
+
}
|
|
7090
|
+
};
|
|
7091
|
+
}
|
|
7092
|
+
});
|
|
7093
|
+
|
|
7094
|
+
// src/knowledge/workflows.ts
|
|
7095
|
+
var workflows_exports = {};
|
|
7096
|
+
__export(workflows_exports, {
|
|
7097
|
+
WORKFLOW_ADAPTER_TARGETS: () => WORKFLOW_ADAPTER_TARGETS,
|
|
7098
|
+
applyWorkflowAdapter: () => applyWorkflowAdapter,
|
|
7099
|
+
importWindsurfWorkflows: () => importWindsurfWorkflows,
|
|
7100
|
+
parseWorkflowMarkdown: () => parseWorkflowMarkdown,
|
|
7101
|
+
previewWorkflowAdapter: () => previewWorkflowAdapter,
|
|
7102
|
+
recordWorkflowRun: () => recordWorkflowRun,
|
|
7103
|
+
renderWorkflowMarkdown: () => renderWorkflowMarkdown,
|
|
7104
|
+
selectWorkflows: () => selectWorkflows,
|
|
7105
|
+
selectWorkspaceWorkflows: () => selectWorkspaceWorkflows,
|
|
7106
|
+
syncCanonicalWorkflows: () => syncCanonicalWorkflows,
|
|
7107
|
+
writeCanonicalWorkflow: () => writeCanonicalWorkflow
|
|
7108
|
+
});
|
|
7109
|
+
import { createHash as createHash9 } from "crypto";
|
|
7110
|
+
import { promises as fs7 } from "fs";
|
|
7111
|
+
import path10 from "path";
|
|
7112
|
+
import matter3 from "gray-matter";
|
|
7113
|
+
function hash3(value) {
|
|
7114
|
+
return createHash9("sha256").update(value).digest("hex");
|
|
7115
|
+
}
|
|
7116
|
+
function now2() {
|
|
7117
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
7118
|
+
}
|
|
7119
|
+
function slug(value) {
|
|
7120
|
+
const normalized = value.trim().toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 72);
|
|
7121
|
+
return normalized || "workflow";
|
|
7122
|
+
}
|
|
7123
|
+
function titleFromName(name) {
|
|
7124
|
+
return name.replace(/[-_]+/g, " ").split(" ").filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" ") || "Imported Workflow";
|
|
7125
|
+
}
|
|
7126
|
+
function requiredText(data, key) {
|
|
7127
|
+
const value = data[key];
|
|
7128
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
7129
|
+
throw new Error("workflow frontmatter requires " + key);
|
|
7130
|
+
}
|
|
7131
|
+
return value.trim();
|
|
7132
|
+
}
|
|
7133
|
+
function optionalText4(data, key) {
|
|
7134
|
+
const value = data[key];
|
|
7135
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
7136
|
+
if (typeof value !== "string") throw new Error("workflow frontmatter field " + key + " must be text");
|
|
7137
|
+
return value.trim() || void 0;
|
|
7138
|
+
}
|
|
7139
|
+
function optionalStringArray(data, key) {
|
|
7140
|
+
const value = data[key];
|
|
7141
|
+
if (value === void 0 || value === null) return [];
|
|
7142
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
7143
|
+
throw new Error("workflow frontmatter field " + key + " must be a string array");
|
|
7144
|
+
}
|
|
7145
|
+
return [...new Set(value.map((item) => item.trim()).filter(Boolean))];
|
|
7146
|
+
}
|
|
7147
|
+
function optionalNumber(data, key, fallback) {
|
|
7148
|
+
const value = data[key];
|
|
7149
|
+
if (value === void 0 || value === null || value === "") return fallback;
|
|
7150
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
|
|
7151
|
+
throw new Error("workflow frontmatter field " + key + " must be a positive integer");
|
|
7152
|
+
}
|
|
7153
|
+
return value;
|
|
7154
|
+
}
|
|
7155
|
+
function optionalStatus(data) {
|
|
7156
|
+
const value = data.status;
|
|
7157
|
+
if (value === void 0 || value === null || value === "") return "draft";
|
|
7158
|
+
if (typeof value !== "string" || !STATUS_VALUES.includes(value)) {
|
|
7159
|
+
throw new Error("workflow frontmatter field status is invalid");
|
|
7160
|
+
}
|
|
7161
|
+
return value;
|
|
7162
|
+
}
|
|
7163
|
+
function normalizeAgents(values) {
|
|
7164
|
+
const unknown = values.filter((value) => !KNOWN_AGENTS.includes(value));
|
|
7165
|
+
if (unknown.length > 0) {
|
|
7166
|
+
throw new Error("workflow frontmatter has unsupported agent: " + unknown[0]);
|
|
7167
|
+
}
|
|
7168
|
+
return values;
|
|
7169
|
+
}
|
|
7170
|
+
function phaseFromValue(value, index) {
|
|
7171
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
7172
|
+
throw new Error("workflow frontmatter phases must contain objects");
|
|
7173
|
+
}
|
|
7174
|
+
const data = value;
|
|
7175
|
+
const title = requiredText(data, "title");
|
|
7176
|
+
const phaseId = optionalText4(data, "id") ?? slug(title) + "-" + (index + 1);
|
|
7177
|
+
return {
|
|
7178
|
+
id: phaseId,
|
|
7179
|
+
title,
|
|
7180
|
+
instructions: optionalText4(data, "instructions") ?? "",
|
|
7181
|
+
branches: optionalStringArray(data, "branches"),
|
|
7182
|
+
expectedOutputs: optionalStringArray(data, "expectedOutputs"),
|
|
7183
|
+
verificationGates: optionalStringArray(data, "verificationGates")
|
|
7184
|
+
};
|
|
7185
|
+
}
|
|
7186
|
+
function phasesFromBody(body) {
|
|
7187
|
+
const headers = [...body.matchAll(/^##\s+(.+?)\s*$/gm)];
|
|
7188
|
+
if (headers.length === 0) {
|
|
7189
|
+
const instructions = body.trim();
|
|
7190
|
+
return instructions ? [{
|
|
7191
|
+
id: "execute",
|
|
7192
|
+
title: "Execute",
|
|
7193
|
+
instructions,
|
|
7194
|
+
branches: [],
|
|
7195
|
+
expectedOutputs: [],
|
|
7196
|
+
verificationGates: []
|
|
7197
|
+
}] : [];
|
|
7198
|
+
}
|
|
7199
|
+
return headers.map((match, index) => {
|
|
7200
|
+
const title = match[1].trim();
|
|
7201
|
+
const start = (match.index ?? 0) + match[0].length;
|
|
7202
|
+
const end = index + 1 < headers.length ? headers[index + 1].index ?? body.length : body.length;
|
|
7203
|
+
return {
|
|
7204
|
+
id: slug(title) + "-" + (index + 1),
|
|
7205
|
+
title,
|
|
7206
|
+
instructions: body.slice(start, end).trim(),
|
|
7207
|
+
branches: [],
|
|
7208
|
+
expectedOutputs: [],
|
|
7209
|
+
verificationGates: []
|
|
7210
|
+
};
|
|
7211
|
+
});
|
|
7212
|
+
}
|
|
7213
|
+
function normalizeSourcePath(sourcePath) {
|
|
7214
|
+
const normalized = sourcePath.replace(/\\/g, "/");
|
|
7215
|
+
if (!normalized.startsWith("workflows/") || !normalized.toLowerCase().endsWith(".md") || normalized.includes("../") || path10.posix.normalize(normalized) !== normalized) {
|
|
7216
|
+
throw new Error("workflow source path must be a safe workflows/*.md path");
|
|
7217
|
+
}
|
|
7218
|
+
return normalized;
|
|
7219
|
+
}
|
|
7220
|
+
function sourceHashFor(workflow) {
|
|
7221
|
+
return hash3(JSON.stringify({
|
|
7222
|
+
id: workflow.id,
|
|
7223
|
+
title: workflow.title,
|
|
7224
|
+
description: workflow.description,
|
|
7225
|
+
status: workflow.status,
|
|
7226
|
+
version: workflow.version,
|
|
7227
|
+
taskLenses: workflow.taskLenses,
|
|
7228
|
+
triggers: workflow.triggers,
|
|
7229
|
+
assumptions: workflow.assumptions,
|
|
7230
|
+
requiredContext: workflow.requiredContext,
|
|
7231
|
+
guardrails: workflow.guardrails,
|
|
7232
|
+
allowedTools: workflow.allowedTools,
|
|
7233
|
+
phases: workflow.phases,
|
|
7234
|
+
verificationGates: workflow.verificationGates,
|
|
7235
|
+
claimIds: workflow.claimIds,
|
|
7236
|
+
evidenceRefs: workflow.evidenceRefs,
|
|
7237
|
+
codeRefs: workflow.codeRefs,
|
|
7238
|
+
compatibleAgents: workflow.compatibleAgents,
|
|
7239
|
+
body: workflow.body,
|
|
7240
|
+
sourcePath: workflow.sourcePath,
|
|
7241
|
+
importedFrom: workflow.importedFrom ?? null
|
|
7242
|
+
}));
|
|
7243
|
+
}
|
|
7244
|
+
function workflowFrontmatter(workflow, sourceHash) {
|
|
4386
7245
|
return {
|
|
4387
|
-
id:
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
7246
|
+
id: workflow.id,
|
|
7247
|
+
title: workflow.title,
|
|
7248
|
+
description: workflow.description,
|
|
7249
|
+
status: workflow.status,
|
|
7250
|
+
version: workflow.version,
|
|
7251
|
+
taskLenses: workflow.taskLenses,
|
|
7252
|
+
triggers: workflow.triggers,
|
|
7253
|
+
assumptions: workflow.assumptions,
|
|
7254
|
+
requiredContext: workflow.requiredContext,
|
|
7255
|
+
guardrails: workflow.guardrails,
|
|
7256
|
+
allowedTools: workflow.allowedTools,
|
|
7257
|
+
phases: workflow.phases.map((phase) => ({
|
|
7258
|
+
id: phase.id,
|
|
7259
|
+
title: phase.title,
|
|
7260
|
+
...phase.instructions ? { instructions: phase.instructions } : {},
|
|
7261
|
+
...phase.branches.length ? { branches: phase.branches } : {},
|
|
7262
|
+
...phase.expectedOutputs.length ? { expectedOutputs: phase.expectedOutputs } : {},
|
|
7263
|
+
...phase.verificationGates.length ? { verificationGates: phase.verificationGates } : {}
|
|
7264
|
+
})),
|
|
7265
|
+
verificationGates: workflow.verificationGates,
|
|
7266
|
+
claimIds: workflow.claimIds,
|
|
7267
|
+
evidenceRefs: workflow.evidenceRefs,
|
|
7268
|
+
codeRefs: workflow.codeRefs,
|
|
7269
|
+
compatibleAgents: workflow.compatibleAgents,
|
|
7270
|
+
sourceHash,
|
|
7271
|
+
createdAt: workflow.createdAt,
|
|
7272
|
+
updatedAt: workflow.updatedAt,
|
|
7273
|
+
...workflow.importedFrom ? { importedFrom: workflow.importedFrom } : {}
|
|
4395
7274
|
};
|
|
4396
7275
|
}
|
|
4397
|
-
function
|
|
7276
|
+
function renderWorkflowMarkdown(workflow) {
|
|
7277
|
+
const sourceHash = sourceHashFor(workflow);
|
|
7278
|
+
return matter3.stringify(workflow.body.trimEnd() + "\n", workflowFrontmatter(workflow, sourceHash));
|
|
7279
|
+
}
|
|
7280
|
+
function parsedWorkflow(data, body, input) {
|
|
7281
|
+
const phasesValue = data.phases;
|
|
7282
|
+
const parsedPhases = phasesValue === void 0 || phasesValue === null ? phasesFromBody(body) : Array.isArray(phasesValue) ? phasesValue.map(phaseFromValue) : (() => {
|
|
7283
|
+
throw new Error("workflow frontmatter field phases must be an array");
|
|
7284
|
+
})();
|
|
7285
|
+
const candidate = {
|
|
7286
|
+
id: requiredText(data, "id"),
|
|
7287
|
+
workspaceId: input.workspaceId,
|
|
7288
|
+
title: requiredText(data, "title"),
|
|
7289
|
+
description: optionalText4(data, "description") ?? "",
|
|
7290
|
+
status: optionalStatus(data),
|
|
7291
|
+
version: optionalNumber(data, "version", 1),
|
|
7292
|
+
taskLenses: optionalStringArray(data, "taskLenses"),
|
|
7293
|
+
triggers: optionalStringArray(data, "triggers"),
|
|
7294
|
+
assumptions: optionalStringArray(data, "assumptions"),
|
|
7295
|
+
requiredContext: optionalStringArray(data, "requiredContext"),
|
|
7296
|
+
guardrails: optionalStringArray(data, "guardrails"),
|
|
7297
|
+
allowedTools: optionalStringArray(data, "allowedTools"),
|
|
7298
|
+
phases: parsedPhases,
|
|
7299
|
+
verificationGates: optionalStringArray(data, "verificationGates"),
|
|
7300
|
+
claimIds: optionalStringArray(data, "claimIds"),
|
|
7301
|
+
evidenceRefs: optionalStringArray(data, "evidenceRefs"),
|
|
7302
|
+
codeRefs: optionalStringArray(data, "codeRefs"),
|
|
7303
|
+
compatibleAgents: normalizeAgents(optionalStringArray(data, "compatibleAgents").length ? optionalStringArray(data, "compatibleAgents") : optionalStringArray(data, "allowedAgents")),
|
|
7304
|
+
body: body.trim(),
|
|
7305
|
+
sourcePath: normalizeSourcePath(input.sourcePath),
|
|
7306
|
+
sourceHash: "",
|
|
7307
|
+
contentHash: input.contentHash,
|
|
7308
|
+
createdAt: optionalText4(data, "createdAt") ?? now2(),
|
|
7309
|
+
updatedAt: optionalText4(data, "updatedAt") ?? now2(),
|
|
7310
|
+
...optionalText4(data, "importedFrom") ? { importedFrom: optionalText4(data, "importedFrom") } : {}
|
|
7311
|
+
};
|
|
7312
|
+
if (candidate.phases.length === 0) {
|
|
7313
|
+
throw new Error("workflow must contain at least one phase or a non-empty body");
|
|
7314
|
+
}
|
|
7315
|
+
return { ...candidate, sourceHash: sourceHashFor(candidate) };
|
|
7316
|
+
}
|
|
7317
|
+
function parseWorkflowMarkdown(raw, input) {
|
|
7318
|
+
let parsed;
|
|
7319
|
+
try {
|
|
7320
|
+
parsed = matter3(raw);
|
|
7321
|
+
} catch (error) {
|
|
7322
|
+
throw new Error("Malformed workflow Markdown: " + (error instanceof Error ? error.message : String(error)));
|
|
7323
|
+
}
|
|
7324
|
+
return parsedWorkflow(parsed.data, parsed.content, {
|
|
7325
|
+
workspaceId: input.workspaceId,
|
|
7326
|
+
sourcePath: input.sourcePath,
|
|
7327
|
+
contentHash: hash3(raw)
|
|
7328
|
+
});
|
|
7329
|
+
}
|
|
7330
|
+
function workflowStoreDataDir(workspace) {
|
|
7331
|
+
if (!workspace.dataDir) throw new Error("Knowledge workspace is missing its local data directory");
|
|
7332
|
+
return workspace.dataDir;
|
|
7333
|
+
}
|
|
7334
|
+
async function storeFor2(workspace) {
|
|
7335
|
+
const store = new WorkflowStore();
|
|
7336
|
+
await store.init(workflowStoreDataDir(workspace));
|
|
7337
|
+
return store;
|
|
7338
|
+
}
|
|
7339
|
+
function materializeWorkflow(workflow) {
|
|
7340
|
+
const updatedAt = workflow.updatedAt || now2();
|
|
7341
|
+
const withDates = {
|
|
7342
|
+
...workflow,
|
|
7343
|
+
createdAt: workflow.createdAt || updatedAt,
|
|
7344
|
+
updatedAt
|
|
7345
|
+
};
|
|
7346
|
+
const sourceHash = sourceHashFor(withDates);
|
|
7347
|
+
const content = renderWorkflowMarkdown({ ...withDates, sourceHash, contentHash: "" });
|
|
4398
7348
|
return {
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
fileId: file.id,
|
|
4403
|
-
symbolId: symbol.id,
|
|
4404
|
-
capturedFileHash: file.contentHash,
|
|
4405
|
-
...symbol.contentHash ? { capturedSymbolHash: symbol.contentHash } : {},
|
|
4406
|
-
status: "current",
|
|
4407
|
-
reason: "bound by symbol mention",
|
|
4408
|
-
createdAt: obs.createdAt
|
|
7349
|
+
...withDates,
|
|
7350
|
+
sourceHash,
|
|
7351
|
+
contentHash: hash3(content)
|
|
4409
7352
|
};
|
|
4410
7353
|
}
|
|
4411
|
-
function
|
|
4412
|
-
const
|
|
4413
|
-
const
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
7354
|
+
async function writeCanonicalWorkflow(input) {
|
|
7355
|
+
const paths = getKnowledgeWorkspacePaths(input.workspace);
|
|
7356
|
+
const workflow = materializeWorkflow({
|
|
7357
|
+
...input.workflow,
|
|
7358
|
+
workspaceId: input.workspace.id,
|
|
7359
|
+
sourcePath: normalizeSourcePath(input.workflow.sourcePath)
|
|
7360
|
+
});
|
|
7361
|
+
const filePath = resolveKnowledgeWorkspaceFile(input.workspace, workflow.sourcePath);
|
|
7362
|
+
const content = renderWorkflowMarkdown(workflow);
|
|
7363
|
+
await withFileLock(paths.root, async () => {
|
|
7364
|
+
await fs7.mkdir(path10.dirname(filePath), { recursive: true });
|
|
7365
|
+
await atomicWriteFile(filePath, content);
|
|
7366
|
+
});
|
|
7367
|
+
const store = await storeFor2(input.workspace);
|
|
7368
|
+
return store.upsertWorkflow({ ...workflow, contentHash: hash3(content) });
|
|
7369
|
+
}
|
|
7370
|
+
async function markdownFiles2(directory) {
|
|
7371
|
+
let entries;
|
|
7372
|
+
try {
|
|
7373
|
+
entries = await fs7.readdir(directory, { withFileTypes: true });
|
|
7374
|
+
} catch {
|
|
7375
|
+
return [];
|
|
4421
7376
|
}
|
|
4422
|
-
const
|
|
4423
|
-
const
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
7377
|
+
const files = [];
|
|
7378
|
+
for (const entry of entries) {
|
|
7379
|
+
const absolutePath = path10.join(directory, entry.name);
|
|
7380
|
+
if (entry.isDirectory()) {
|
|
7381
|
+
files.push(...await markdownFiles2(absolutePath));
|
|
7382
|
+
} else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
|
|
7383
|
+
files.push(absolutePath);
|
|
7384
|
+
}
|
|
7385
|
+
}
|
|
7386
|
+
return files;
|
|
7387
|
+
}
|
|
7388
|
+
async function syncCanonicalWorkflows(workspace) {
|
|
7389
|
+
const paths = getKnowledgeWorkspacePaths(workspace);
|
|
7390
|
+
const store = await storeFor2(workspace);
|
|
7391
|
+
const workflows = [];
|
|
7392
|
+
const errors = [];
|
|
7393
|
+
for (const absolutePath of await markdownFiles2(paths.workflows)) {
|
|
7394
|
+
const sourcePath = path10.relative(paths.root, absolutePath).split(path10.sep).join("/");
|
|
7395
|
+
try {
|
|
7396
|
+
const raw = await fs7.readFile(absolutePath, "utf8");
|
|
7397
|
+
const workflow = parseWorkflowMarkdown(raw, { workspaceId: workspace.id, sourcePath });
|
|
7398
|
+
workflows.push(store.upsertWorkflow(workflow));
|
|
7399
|
+
} catch (error) {
|
|
7400
|
+
errors.push({
|
|
7401
|
+
sourcePath,
|
|
7402
|
+
message: error instanceof Error ? error.message : String(error)
|
|
7403
|
+
});
|
|
7404
|
+
}
|
|
4430
7405
|
}
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
7406
|
+
return { workflows, errors };
|
|
7407
|
+
}
|
|
7408
|
+
function inferTaskLenses(value) {
|
|
7409
|
+
const text = value.toLowerCase();
|
|
7410
|
+
return Object.entries(LENS_TERMS).filter(([, terms]) => terms.some((term) => text.includes(term))).map(([lens]) => lens);
|
|
7411
|
+
}
|
|
7412
|
+
function taskScore(workflow, task) {
|
|
7413
|
+
const text = task.toLowerCase();
|
|
7414
|
+
let score = 0;
|
|
7415
|
+
const reasons = [];
|
|
7416
|
+
for (const lens of workflow.taskLenses) {
|
|
7417
|
+
const terms = LENS_TERMS[lens.toLowerCase()] ?? [lens.toLowerCase()];
|
|
7418
|
+
if (terms.some((term) => text.includes(term))) {
|
|
7419
|
+
score += 20;
|
|
7420
|
+
reasons.push("matches " + lens + " workflow");
|
|
4440
7421
|
}
|
|
4441
7422
|
}
|
|
4442
|
-
|
|
7423
|
+
for (const trigger of workflow.triggers) {
|
|
7424
|
+
const term = trigger.toLowerCase().trim();
|
|
7425
|
+
if (term.length >= 2 && text.includes(term)) {
|
|
7426
|
+
score += 8;
|
|
7427
|
+
reasons.push('matches trigger "' + trigger + '"');
|
|
7428
|
+
}
|
|
7429
|
+
}
|
|
7430
|
+
return { score, reasons: [...new Set(reasons)] };
|
|
4443
7431
|
}
|
|
4444
|
-
function
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
7432
|
+
function selectWorkflows(input) {
|
|
7433
|
+
const selected = input.workflows.filter((workflow) => workflow.status === "active").map((workflow) => {
|
|
7434
|
+
const match = taskScore(workflow, input.task);
|
|
7435
|
+
const cautions = input.store && input.projectId ? input.store.recentFailureCautions(input.projectId, workflow.id) : [];
|
|
7436
|
+
return {
|
|
7437
|
+
workflow,
|
|
7438
|
+
score: match.score,
|
|
7439
|
+
reasons: match.reasons,
|
|
7440
|
+
firstPhase: workflow.phases[0],
|
|
7441
|
+
cautions
|
|
7442
|
+
};
|
|
7443
|
+
}).filter((item) => item.score > 0).sort((left, right) => right.score - left.score || left.workflow.title.localeCompare(right.workflow.title));
|
|
7444
|
+
return selected.slice(0, Math.max(1, Math.min(input.limit ?? 2, 2)));
|
|
7445
|
+
}
|
|
7446
|
+
function importedWorkflowSpec(input) {
|
|
7447
|
+
const entry = new WorkflowSyncer().parseWindsurfWorkflow(input.sourceName, input.raw);
|
|
7448
|
+
const content = entry.content.trim() || "## Execute\n\nFollow the imported workflow.";
|
|
7449
|
+
const lenses = inferTaskLenses(entry.name + "\n" + entry.description + "\n" + content);
|
|
7450
|
+
const createdAt = now2();
|
|
7451
|
+
const sourcePath = "workflows/" + slug(entry.name) + ".md";
|
|
7452
|
+
const spec = {
|
|
7453
|
+
id: "workflow:" + hash3(input.workspace.id + ":" + sourcePath).slice(0, 24),
|
|
7454
|
+
workspaceId: input.workspace.id,
|
|
7455
|
+
title: titleFromName(entry.name),
|
|
7456
|
+
description: entry.description || "Imported from " + input.sourcePath,
|
|
7457
|
+
status: "active",
|
|
7458
|
+
version: 1,
|
|
7459
|
+
taskLenses: lenses,
|
|
7460
|
+
triggers: lenses,
|
|
7461
|
+
assumptions: [],
|
|
7462
|
+
requiredContext: [],
|
|
7463
|
+
guardrails: [],
|
|
7464
|
+
allowedTools: [],
|
|
7465
|
+
phases: phasesFromBody(content),
|
|
7466
|
+
verificationGates: [],
|
|
7467
|
+
claimIds: [],
|
|
7468
|
+
evidenceRefs: [],
|
|
7469
|
+
codeRefs: [],
|
|
7470
|
+
compatibleAgents: WORKFLOW_ADAPTER_TARGETS,
|
|
7471
|
+
body: content,
|
|
7472
|
+
sourcePath,
|
|
7473
|
+
sourceHash: "",
|
|
7474
|
+
contentHash: "",
|
|
7475
|
+
createdAt,
|
|
7476
|
+
updatedAt: createdAt,
|
|
7477
|
+
importedFrom: input.sourcePath.replace(/\\/g, "/")
|
|
4448
7478
|
};
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
const
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
7479
|
+
return materializeWorkflow(spec);
|
|
7480
|
+
}
|
|
7481
|
+
async function importWindsurfWorkflows(input) {
|
|
7482
|
+
const sourceDirectory = path10.join(path10.resolve(input.projectRoot), ".windsurf", "workflows");
|
|
7483
|
+
const paths = getKnowledgeWorkspacePaths(input.workspace);
|
|
7484
|
+
const imported = [];
|
|
7485
|
+
const skipped = [];
|
|
7486
|
+
let entries;
|
|
7487
|
+
try {
|
|
7488
|
+
entries = await fs7.readdir(sourceDirectory, { withFileTypes: true });
|
|
7489
|
+
} catch {
|
|
7490
|
+
return { imported, skipped };
|
|
4457
7491
|
}
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
const
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
7492
|
+
for (const entry of entries.filter((item) => item.isFile() && item.name.toLowerCase().endsWith(".md"))) {
|
|
7493
|
+
const originalPath = path10.join(sourceDirectory, entry.name);
|
|
7494
|
+
const sourcePath = path10.relative(path10.resolve(input.projectRoot), originalPath).split(path10.sep).join("/");
|
|
7495
|
+
try {
|
|
7496
|
+
const raw = await fs7.readFile(originalPath, "utf8");
|
|
7497
|
+
const workflow = importedWorkflowSpec({
|
|
7498
|
+
workspace: input.workspace,
|
|
7499
|
+
sourceName: entry.name,
|
|
7500
|
+
sourcePath,
|
|
7501
|
+
raw
|
|
7502
|
+
});
|
|
7503
|
+
const targetPath = resolveKnowledgeWorkspaceFile(input.workspace, workflow.sourcePath);
|
|
7504
|
+
try {
|
|
7505
|
+
await fs7.access(targetPath);
|
|
7506
|
+
skipped.push({ sourcePath, reason: "canonical workflow already exists and was preserved" });
|
|
7507
|
+
continue;
|
|
7508
|
+
} catch {
|
|
4466
7509
|
}
|
|
4467
|
-
|
|
7510
|
+
imported.push(await writeCanonicalWorkflow({ workspace: input.workspace, workflow }));
|
|
7511
|
+
} catch (error) {
|
|
7512
|
+
skipped.push({
|
|
7513
|
+
sourcePath,
|
|
7514
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
7515
|
+
});
|
|
4468
7516
|
}
|
|
4469
|
-
}
|
|
7517
|
+
}
|
|
7518
|
+
await fs7.mkdir(paths.workflows, { recursive: true });
|
|
7519
|
+
return { imported, skipped };
|
|
7520
|
+
}
|
|
7521
|
+
function isWithin2(root, candidate) {
|
|
7522
|
+
const relative2 = path10.relative(root, candidate);
|
|
7523
|
+
return relative2 !== "" && relative2 !== ".." && !relative2.startsWith(".." + path10.sep) && !path10.isAbsolute(relative2);
|
|
7524
|
+
}
|
|
7525
|
+
function adapterRelativePath(workflow, agent) {
|
|
7526
|
+
const name = "memorix-workflow-" + slug(workflow.id.replace(/^workflow:/, ""));
|
|
7527
|
+
if (agent === "codex") return ".agents/skills/" + name + "/SKILL.md";
|
|
7528
|
+
if (agent === "claude-code") return ".claude/skills/" + name + "/SKILL.md";
|
|
7529
|
+
if (agent === "cursor") return ".cursor/rules/" + name + ".mdc";
|
|
7530
|
+
return ".windsurf/workflows/" + name + ".md";
|
|
7531
|
+
}
|
|
7532
|
+
function adapterMarker(workflow) {
|
|
7533
|
+
return '<!-- memorix:workflow-adapter id="' + workflow.id + '" -->';
|
|
7534
|
+
}
|
|
7535
|
+
function adapterContent(workflow, agent) {
|
|
7536
|
+
const marker = adapterMarker(workflow);
|
|
7537
|
+
const firstPhase = workflow.phases[0];
|
|
7538
|
+
const gates = workflow.verificationGates.length ? workflow.verificationGates : firstPhase.verificationGates;
|
|
7539
|
+
if (agent === "codex" || agent === "claude-code") {
|
|
7540
|
+
return matter3.stringify([
|
|
7541
|
+
marker,
|
|
7542
|
+
"",
|
|
7543
|
+
"# " + workflow.title,
|
|
7544
|
+
"",
|
|
7545
|
+
workflow.description,
|
|
7546
|
+
"",
|
|
7547
|
+
"## Start",
|
|
7548
|
+
"",
|
|
7549
|
+
firstPhase.instructions || firstPhase.title,
|
|
7550
|
+
"",
|
|
7551
|
+
"## Verification",
|
|
7552
|
+
"",
|
|
7553
|
+
...gates.length ? gates.map((gate) => "- " + gate) : ["- Follow the project verification standards."],
|
|
7554
|
+
"",
|
|
7555
|
+
"## Full workflow",
|
|
7556
|
+
"",
|
|
7557
|
+
workflow.body.trim(),
|
|
7558
|
+
""
|
|
7559
|
+
].join("\n"), {
|
|
7560
|
+
name: "memorix-workflow-" + slug(workflow.id.replace(/^workflow:/, "")),
|
|
7561
|
+
description: workflow.description || workflow.title
|
|
7562
|
+
});
|
|
7563
|
+
}
|
|
7564
|
+
if (agent === "cursor") {
|
|
7565
|
+
return matter3.stringify([
|
|
7566
|
+
marker,
|
|
7567
|
+
"",
|
|
7568
|
+
"# " + workflow.title,
|
|
7569
|
+
"",
|
|
7570
|
+
workflow.body.trim(),
|
|
7571
|
+
""
|
|
7572
|
+
].join("\n"), {
|
|
7573
|
+
description: workflow.description || workflow.title,
|
|
7574
|
+
globs: "",
|
|
7575
|
+
alwaysApply: false
|
|
7576
|
+
});
|
|
7577
|
+
}
|
|
7578
|
+
return matter3.stringify([
|
|
7579
|
+
marker,
|
|
7580
|
+
"",
|
|
7581
|
+
"# " + workflow.title,
|
|
7582
|
+
"",
|
|
7583
|
+
workflow.body.trim(),
|
|
7584
|
+
""
|
|
7585
|
+
].join("\n"), {
|
|
7586
|
+
description: workflow.description || workflow.title
|
|
7587
|
+
});
|
|
4470
7588
|
}
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
store.replaceObservationRefs(obs.projectId, obs.id, refs);
|
|
4474
|
-
return refs;
|
|
7589
|
+
function isWorkflowAdapterTarget(agent) {
|
|
7590
|
+
return WORKFLOW_ADAPTER_TARGETS.includes(agent);
|
|
4475
7591
|
}
|
|
4476
|
-
async function
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
new Set(store.listProjectObservationRefs(projectId).map((ref) => ref.observationId))
|
|
4485
|
-
);
|
|
4486
|
-
lookupByProject.set(
|
|
4487
|
-
projectId,
|
|
4488
|
-
createSnapshotLookup(store.listFiles(projectId), store.listSymbols(projectId))
|
|
4489
|
-
);
|
|
7592
|
+
async function previewWorkflowAdapter(input) {
|
|
7593
|
+
if (!isWorkflowAdapterTarget(input.agent)) {
|
|
7594
|
+
return {
|
|
7595
|
+
agent: input.agent,
|
|
7596
|
+
workflowId: input.workflow.id,
|
|
7597
|
+
status: "unsupported",
|
|
7598
|
+
reason: "This agent has no safe native workflow adapter. Use Memorix Project Context instead."
|
|
7599
|
+
};
|
|
4490
7600
|
}
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
if (!lookup) continue;
|
|
4499
|
-
const refs = resolveObservationCodeRefs(observation, lookup);
|
|
4500
|
-
if (refs.length === 0) continue;
|
|
4501
|
-
observationsBackfilled += 1;
|
|
4502
|
-
refsBackfilled += refs.length;
|
|
4503
|
-
refsToInsert.push(...refs);
|
|
7601
|
+
if (input.workflow.compatibleAgents.length > 0 && !input.workflow.compatibleAgents.includes(input.agent)) {
|
|
7602
|
+
return {
|
|
7603
|
+
agent: input.agent,
|
|
7604
|
+
workflowId: input.workflow.id,
|
|
7605
|
+
status: "unsupported",
|
|
7606
|
+
reason: "The canonical workflow does not declare compatibility with this agent."
|
|
7607
|
+
};
|
|
4504
7608
|
}
|
|
4505
|
-
|
|
7609
|
+
const projectRoot = path10.resolve(input.projectRoot);
|
|
7610
|
+
const targetPath = path10.resolve(projectRoot, adapterRelativePath(input.workflow, input.agent));
|
|
7611
|
+
if (!isWithin2(projectRoot, targetPath)) {
|
|
7612
|
+
throw new Error("Workflow adapter target escapes the project root");
|
|
7613
|
+
}
|
|
7614
|
+
const content = adapterContent(input.workflow, input.agent);
|
|
7615
|
+
let existing;
|
|
7616
|
+
try {
|
|
7617
|
+
existing = await fs7.readFile(targetPath, "utf8");
|
|
7618
|
+
} catch (error) {
|
|
7619
|
+
const code = error && typeof error === "object" && "code" in error ? error.code : void 0;
|
|
7620
|
+
if (code !== "ENOENT") throw error;
|
|
7621
|
+
}
|
|
7622
|
+
if (existing === void 0) {
|
|
7623
|
+
return { agent: input.agent, workflowId: input.workflow.id, targetPath, content, status: "create", reason: "No existing project adapter file." };
|
|
7624
|
+
}
|
|
7625
|
+
if (existing === content) {
|
|
7626
|
+
return { agent: input.agent, workflowId: input.workflow.id, targetPath, content, status: "unchanged", reason: "Existing Memorix adapter already matches the canonical workflow." };
|
|
7627
|
+
}
|
|
7628
|
+
if (existing.includes(adapterMarker(input.workflow))) {
|
|
7629
|
+
return { agent: input.agent, workflowId: input.workflow.id, targetPath, content, status: "update", reason: "Existing Memorix-owned adapter will be updated." };
|
|
7630
|
+
}
|
|
7631
|
+
return { agent: input.agent, workflowId: input.workflow.id, targetPath, content, status: "conflict", reason: "A user-owned project file already occupies this adapter path." };
|
|
7632
|
+
}
|
|
7633
|
+
async function applyWorkflowAdapter(input) {
|
|
7634
|
+
const initial = await previewWorkflowAdapter(input);
|
|
7635
|
+
if (initial.status === "unsupported" || initial.status === "conflict" || initial.status === "unchanged") {
|
|
7636
|
+
return initial;
|
|
7637
|
+
}
|
|
7638
|
+
const projectRoot = path10.resolve(input.projectRoot);
|
|
7639
|
+
return withFileLock(projectRoot, async () => {
|
|
7640
|
+
const current = await previewWorkflowAdapter(input);
|
|
7641
|
+
if (current.status !== "create" && current.status !== "update") return current;
|
|
7642
|
+
await fs7.mkdir(path10.dirname(current.targetPath), { recursive: true });
|
|
7643
|
+
await atomicWriteFile(current.targetPath, current.content);
|
|
7644
|
+
return current;
|
|
7645
|
+
});
|
|
7646
|
+
}
|
|
7647
|
+
async function recordWorkflowRun(input) {
|
|
7648
|
+
const store = await storeFor2(input.workspace);
|
|
7649
|
+
const workflow = store.getWorkflow(input.run.workflowId);
|
|
7650
|
+
if (!workflow || workflow.workspaceId !== input.workspace.id) {
|
|
7651
|
+
throw new Error("Workflow was not found for this workspace");
|
|
7652
|
+
}
|
|
7653
|
+
if (!input.run.task.trim()) throw new Error("Workflow run task is required");
|
|
7654
|
+
return store.recordRun(input.run);
|
|
7655
|
+
}
|
|
7656
|
+
async function selectWorkspaceWorkflows(input) {
|
|
7657
|
+
const synced = await syncCanonicalWorkflows(input.workspace);
|
|
7658
|
+
const store = await storeFor2(input.workspace);
|
|
4506
7659
|
return {
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
7660
|
+
selections: selectWorkflows({
|
|
7661
|
+
workflows: synced.workflows,
|
|
7662
|
+
task: input.task,
|
|
7663
|
+
projectId: input.workspace.projectId,
|
|
7664
|
+
store,
|
|
7665
|
+
limit: input.limit
|
|
7666
|
+
}),
|
|
7667
|
+
errors: synced.errors
|
|
4510
7668
|
};
|
|
4511
7669
|
}
|
|
4512
|
-
var
|
|
4513
|
-
|
|
7670
|
+
var KNOWN_AGENTS, WORKFLOW_ADAPTER_TARGETS, STATUS_VALUES, LENS_TERMS;
|
|
7671
|
+
var init_workflows = __esm({
|
|
7672
|
+
"src/knowledge/workflows.ts"() {
|
|
4514
7673
|
"use strict";
|
|
4515
7674
|
init_esm_shims();
|
|
4516
|
-
|
|
7675
|
+
init_file_lock();
|
|
7676
|
+
init_workflow_sync();
|
|
7677
|
+
init_workspace();
|
|
7678
|
+
init_workflow_store();
|
|
7679
|
+
KNOWN_AGENTS = [
|
|
7680
|
+
"windsurf",
|
|
7681
|
+
"cursor",
|
|
7682
|
+
"claude-code",
|
|
7683
|
+
"codex",
|
|
7684
|
+
"copilot",
|
|
7685
|
+
"antigravity",
|
|
7686
|
+
"gemini-cli",
|
|
7687
|
+
"openclaw",
|
|
7688
|
+
"hermes",
|
|
7689
|
+
"omp",
|
|
7690
|
+
"kiro",
|
|
7691
|
+
"opencode",
|
|
7692
|
+
"trae"
|
|
7693
|
+
];
|
|
7694
|
+
WORKFLOW_ADAPTER_TARGETS = [
|
|
7695
|
+
"codex",
|
|
7696
|
+
"claude-code",
|
|
7697
|
+
"cursor",
|
|
7698
|
+
"windsurf"
|
|
7699
|
+
];
|
|
7700
|
+
STATUS_VALUES = ["draft", "active", "archived"];
|
|
7701
|
+
LENS_TERMS = {
|
|
7702
|
+
release: ["release", "publish", "npm", "version", "\u53D1\u7248", "\u53D1\u5E03"],
|
|
7703
|
+
bugfix: ["bug", "fix", "error", "failure", "issue", "repair", "\u4FEE\u590D", "\u62A5\u9519", "\u6545\u969C"],
|
|
7704
|
+
migration: ["migration", "migrate", "upgrade", "\u8FC1\u79FB", "\u5347\u7EA7"],
|
|
7705
|
+
review: ["review", "audit", "pr", "code review", "\u5BA1\u67E5", "\u8BC4\u5BA1"],
|
|
7706
|
+
onboarding: ["onboard", "onboarding", "understand", "introduce", "\u63A5\u624B", "\u4E86\u89E3"],
|
|
7707
|
+
refactor: ["refactor", "cleanup", "restructure", "\u91CD\u6784", "\u6574\u7406"],
|
|
7708
|
+
test: ["test", "verify", "smoke", "\u6D4B\u8BD5", "\u9A8C\u8BC1"]
|
|
7709
|
+
};
|
|
4517
7710
|
}
|
|
4518
7711
|
});
|
|
4519
7712
|
|
|
@@ -4522,12 +7715,12 @@ var fastembed_provider_exports = {};
|
|
|
4522
7715
|
__export(fastembed_provider_exports, {
|
|
4523
7716
|
FastEmbedProvider: () => FastEmbedProvider
|
|
4524
7717
|
});
|
|
4525
|
-
import { createHash as
|
|
7718
|
+
import { createHash as createHash10 } from "crypto";
|
|
4526
7719
|
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
4527
7720
|
import { join as join3 } from "path";
|
|
4528
7721
|
import { homedir as homedir6 } from "os";
|
|
4529
7722
|
function textHash(text) {
|
|
4530
|
-
return
|
|
7723
|
+
return createHash10("sha256").update(text).digest("hex").slice(0, 16);
|
|
4531
7724
|
}
|
|
4532
7725
|
async function loadDiskCache() {
|
|
4533
7726
|
try {
|
|
@@ -4579,15 +7772,15 @@ var init_fastembed_provider = __esm({
|
|
|
4579
7772
|
return new _FastEmbedProvider(model);
|
|
4580
7773
|
}
|
|
4581
7774
|
async embed(text) {
|
|
4582
|
-
const
|
|
4583
|
-
const cached = cache.get(
|
|
7775
|
+
const hash4 = textHash(text);
|
|
7776
|
+
const cached = cache.get(hash4);
|
|
4584
7777
|
if (cached) return cached;
|
|
4585
7778
|
const raw = await this.model.queryEmbed(text);
|
|
4586
7779
|
const result = Array.from(raw);
|
|
4587
7780
|
if (result.length !== this.dimensions) {
|
|
4588
7781
|
throw new Error(`Expected ${this.dimensions}d embedding, got ${result.length}d`);
|
|
4589
7782
|
}
|
|
4590
|
-
this.cacheSet(
|
|
7783
|
+
this.cacheSet(hash4, result);
|
|
4591
7784
|
return result;
|
|
4592
7785
|
}
|
|
4593
7786
|
async embedBatch(texts) {
|
|
@@ -4595,8 +7788,8 @@ var init_fastembed_provider = __esm({
|
|
|
4595
7788
|
const uncachedIndices = [];
|
|
4596
7789
|
const uncachedTexts = [];
|
|
4597
7790
|
for (let i = 0; i < texts.length; i++) {
|
|
4598
|
-
const
|
|
4599
|
-
const cached = cache.get(
|
|
7791
|
+
const hash4 = textHash(texts[i]);
|
|
7792
|
+
const cached = cache.get(hash4);
|
|
4600
7793
|
if (cached) {
|
|
4601
7794
|
results[i] = cached;
|
|
4602
7795
|
} else {
|
|
@@ -4620,12 +7813,12 @@ var init_fastembed_provider = __esm({
|
|
|
4620
7813
|
}
|
|
4621
7814
|
return results;
|
|
4622
7815
|
}
|
|
4623
|
-
cacheSet(
|
|
7816
|
+
cacheSet(hash4, value) {
|
|
4624
7817
|
if (cache.size >= MAX_CACHE_SIZE) {
|
|
4625
7818
|
const firstKey = cache.keys().next().value;
|
|
4626
7819
|
if (firstKey !== void 0) cache.delete(firstKey);
|
|
4627
7820
|
}
|
|
4628
|
-
cache.set(
|
|
7821
|
+
cache.set(hash4, value);
|
|
4629
7822
|
diskCacheDirty = true;
|
|
4630
7823
|
}
|
|
4631
7824
|
};
|
|
@@ -4724,7 +7917,7 @@ var api_provider_exports = {};
|
|
|
4724
7917
|
__export(api_provider_exports, {
|
|
4725
7918
|
APIEmbeddingProvider: () => APIEmbeddingProvider
|
|
4726
7919
|
});
|
|
4727
|
-
import { createHash as
|
|
7920
|
+
import { createHash as createHash11 } from "crypto";
|
|
4728
7921
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
4729
7922
|
import { join as join4 } from "path";
|
|
4730
7923
|
import { homedir as homedir7 } from "os";
|
|
@@ -4740,7 +7933,7 @@ function cacheNamespace(config) {
|
|
|
4740
7933
|
].join("|");
|
|
4741
7934
|
}
|
|
4742
7935
|
function textHash2(text, namespace) {
|
|
4743
|
-
return
|
|
7936
|
+
return createHash11("sha256").update(`${namespace}\0${text}`).digest("hex").slice(0, 16);
|
|
4744
7937
|
}
|
|
4745
7938
|
async function loadDiskCache2() {
|
|
4746
7939
|
if (diskCacheLoaded) return;
|
|
@@ -4894,12 +8087,12 @@ function scheduleDiskSave() {
|
|
|
4894
8087
|
diskSaveTimer = null;
|
|
4895
8088
|
}, DISK_SAVE_DEBOUNCE_MS);
|
|
4896
8089
|
}
|
|
4897
|
-
function cacheSet(
|
|
8090
|
+
function cacheSet(hash4, value) {
|
|
4898
8091
|
if (cache3.size >= MAX_CACHE_SIZE3) {
|
|
4899
8092
|
const firstKey = cache3.keys().next().value;
|
|
4900
8093
|
if (firstKey !== void 0) cache3.delete(firstKey);
|
|
4901
8094
|
}
|
|
4902
|
-
cache3.set(
|
|
8095
|
+
cache3.set(hash4, value);
|
|
4903
8096
|
diskCacheDirty2 = true;
|
|
4904
8097
|
}
|
|
4905
8098
|
function resolveEnvEmbeddingApiKey() {
|
|
@@ -4946,18 +8139,18 @@ async function fetchWithRetry(url, apiKey, body, attempt = 0) {
|
|
|
4946
8139
|
if (response.ok) {
|
|
4947
8140
|
return response.json();
|
|
4948
8141
|
}
|
|
4949
|
-
if ((response.status === 429 || response.status >= 500) && attempt <
|
|
8142
|
+
if ((response.status === 429 || response.status >= 500) && attempt < MAX_RETRIES2) {
|
|
4950
8143
|
const delay = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
4951
8144
|
const retryAfter = response.headers.get("retry-after");
|
|
4952
8145
|
const waitMs = retryAfter ? parseInt(retryAfter, 10) * 1e3 : delay;
|
|
4953
|
-
console.error(`[memorix] Embedding API ${response.status}, retry ${attempt + 1}/${
|
|
8146
|
+
console.error(`[memorix] Embedding API ${response.status}, retry ${attempt + 1}/${MAX_RETRIES2} in ${waitMs}ms`);
|
|
4954
8147
|
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
4955
8148
|
return fetchWithRetry(url, apiKey, body, attempt + 1);
|
|
4956
8149
|
}
|
|
4957
8150
|
const errorText2 = await response.text().catch(() => "unknown error");
|
|
4958
8151
|
throw new Error(`Embedding API error (${response.status}): ${errorText2}`);
|
|
4959
8152
|
}
|
|
4960
|
-
var CACHE_DIR2, CACHE_FILE2, DIMS_CACHE_FILE, CACHE_META_FILE, cache3, MAX_CACHE_SIZE3, diskCacheDirty2, diskSaveTimer, diskCacheLoaded, diskCacheLoadPromise, MAX_INPUT_CHARS, MAX_CONCURRENCY, DISK_SAVE_DEBOUNCE_MS, DEFAULT_MAX_BATCH_SIZE, DASHSCOPE_MAX_BATCH_SIZE,
|
|
8153
|
+
var CACHE_DIR2, CACHE_FILE2, DIMS_CACHE_FILE, CACHE_META_FILE, cache3, MAX_CACHE_SIZE3, diskCacheDirty2, diskSaveTimer, diskCacheLoaded, diskCacheLoadPromise, MAX_INPUT_CHARS, MAX_CONCURRENCY, DISK_SAVE_DEBOUNCE_MS, DEFAULT_MAX_BATCH_SIZE, DASHSCOPE_MAX_BATCH_SIZE, MAX_RETRIES2, BASE_DELAY_MS, APIEmbeddingProvider;
|
|
4961
8154
|
var init_api_provider = __esm({
|
|
4962
8155
|
"src/embedding/api-provider.ts"() {
|
|
4963
8156
|
"use strict";
|
|
@@ -4977,7 +8170,7 @@ var init_api_provider = __esm({
|
|
|
4977
8170
|
DISK_SAVE_DEBOUNCE_MS = 5e3;
|
|
4978
8171
|
DEFAULT_MAX_BATCH_SIZE = 2048;
|
|
4979
8172
|
DASHSCOPE_MAX_BATCH_SIZE = 10;
|
|
4980
|
-
|
|
8173
|
+
MAX_RETRIES2 = 3;
|
|
4981
8174
|
BASE_DELAY_MS = 500;
|
|
4982
8175
|
APIEmbeddingProvider = class _APIEmbeddingProvider {
|
|
4983
8176
|
name;
|
|
@@ -5070,9 +8263,9 @@ var init_api_provider = __esm({
|
|
|
5070
8263
|
}
|
|
5071
8264
|
async embed(text) {
|
|
5072
8265
|
const normalized = normalizeText(text);
|
|
5073
|
-
const
|
|
8266
|
+
const hash4 = textHash2(normalized, this.cacheKeyNamespace);
|
|
5074
8267
|
if (diskCacheLoaded) {
|
|
5075
|
-
const cached = cache3.get(
|
|
8268
|
+
const cached = cache3.get(hash4);
|
|
5076
8269
|
if (cached) return cached;
|
|
5077
8270
|
}
|
|
5078
8271
|
const apiCall = async () => {
|
|
@@ -5098,7 +8291,7 @@ var init_api_provider = __esm({
|
|
|
5098
8291
|
let embedding;
|
|
5099
8292
|
if (!diskCacheLoaded && diskCacheLoadPromise) {
|
|
5100
8293
|
const cacheRace = diskCacheLoadPromise.then(() => {
|
|
5101
|
-
const cached = cache3.get(
|
|
8294
|
+
const cached = cache3.get(hash4);
|
|
5102
8295
|
if (cached) return cached;
|
|
5103
8296
|
return null;
|
|
5104
8297
|
});
|
|
@@ -5116,7 +8309,7 @@ var init_api_provider = __esm({
|
|
|
5116
8309
|
} else {
|
|
5117
8310
|
embedding = await apiCall();
|
|
5118
8311
|
}
|
|
5119
|
-
cacheSet(
|
|
8312
|
+
cacheSet(hash4, embedding);
|
|
5120
8313
|
scheduleDiskSave();
|
|
5121
8314
|
return embedding;
|
|
5122
8315
|
}
|
|
@@ -5127,8 +8320,8 @@ var init_api_provider = __esm({
|
|
|
5127
8320
|
const uncachedIndices = [];
|
|
5128
8321
|
const uncachedTexts = [];
|
|
5129
8322
|
for (let i = 0; i < normalizedTexts.length; i++) {
|
|
5130
|
-
const
|
|
5131
|
-
const cached = cache3.get(
|
|
8323
|
+
const hash4 = textHash2(normalizedTexts[i], this.cacheKeyNamespace);
|
|
8324
|
+
const cached = cache3.get(hash4);
|
|
5132
8325
|
if (cached) {
|
|
5133
8326
|
results[i] = cached;
|
|
5134
8327
|
} else {
|
|
@@ -5198,8 +8391,8 @@ var init_api_provider = __esm({
|
|
|
5198
8391
|
async getCachedEmbeddings(texts) {
|
|
5199
8392
|
await ensureDiskCacheLoaded();
|
|
5200
8393
|
return texts.map((text) => {
|
|
5201
|
-
const
|
|
5202
|
-
return cache3.get(
|
|
8394
|
+
const hash4 = textHash2(normalizeText(text), this.cacheKeyNamespace);
|
|
8395
|
+
return cache3.get(hash4) ?? null;
|
|
5203
8396
|
});
|
|
5204
8397
|
}
|
|
5205
8398
|
getStats() {
|
|
@@ -5228,12 +8421,12 @@ __export(provider_exports2, {
|
|
|
5228
8421
|
resetProvider: () => resetProvider
|
|
5229
8422
|
});
|
|
5230
8423
|
function warnOnce(message) {
|
|
5231
|
-
const
|
|
5232
|
-
if (message === lastEmbeddingWarning &&
|
|
8424
|
+
const now3 = Date.now();
|
|
8425
|
+
if (message === lastEmbeddingWarning && now3 - lastEmbeddingWarningAt < WARNING_COOLDOWN_MS) {
|
|
5233
8426
|
return;
|
|
5234
8427
|
}
|
|
5235
8428
|
lastEmbeddingWarning = message;
|
|
5236
|
-
lastEmbeddingWarningAt =
|
|
8429
|
+
lastEmbeddingWarningAt = now3;
|
|
5237
8430
|
console.error(message);
|
|
5238
8431
|
}
|
|
5239
8432
|
function getEmbeddingMode2() {
|
|
@@ -5512,8 +8705,8 @@ var init_types = __esm({
|
|
|
5512
8705
|
});
|
|
5513
8706
|
|
|
5514
8707
|
// src/store/mini-skill-store.ts
|
|
5515
|
-
import
|
|
5516
|
-
import
|
|
8708
|
+
import path11 from "path";
|
|
8709
|
+
import fs8 from "fs";
|
|
5517
8710
|
function isMiniSkillStoreInitialized() {
|
|
5518
8711
|
return _store2 !== null;
|
|
5519
8712
|
}
|
|
@@ -5532,18 +8725,6 @@ var init_mini_skill_store = __esm({
|
|
|
5532
8725
|
}
|
|
5533
8726
|
});
|
|
5534
8727
|
|
|
5535
|
-
// src/compact/token-budget.ts
|
|
5536
|
-
import { countTokens, isWithinTokenLimit } from "gpt-tokenizer";
|
|
5537
|
-
function countTextTokens(text) {
|
|
5538
|
-
return countTokens(text);
|
|
5539
|
-
}
|
|
5540
|
-
var init_token_budget = __esm({
|
|
5541
|
-
"src/compact/token-budget.ts"() {
|
|
5542
|
-
"use strict";
|
|
5543
|
-
init_esm_shims();
|
|
5544
|
-
}
|
|
5545
|
-
});
|
|
5546
|
-
|
|
5547
8728
|
// src/skills/mini-skills.ts
|
|
5548
8729
|
function resolveKnowledgeLayer(documentType, sourceDetail, source) {
|
|
5549
8730
|
if (documentType === "mini-skill") return "promoted";
|
|
@@ -5939,8 +9120,8 @@ __export(aliases_exports, {
|
|
|
5939
9120
|
resetAliasCache: () => resetAliasCache,
|
|
5940
9121
|
resolveAliases: () => resolveAliases
|
|
5941
9122
|
});
|
|
5942
|
-
import { promises as
|
|
5943
|
-
import
|
|
9123
|
+
import { promises as fs9 } from "fs";
|
|
9124
|
+
import path12 from "path";
|
|
5944
9125
|
import os from "os";
|
|
5945
9126
|
function normalizePath(p) {
|
|
5946
9127
|
let normalized = p.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
@@ -5955,12 +9136,12 @@ function idPriority(id) {
|
|
|
5955
9136
|
return 2;
|
|
5956
9137
|
}
|
|
5957
9138
|
function getRegistryPath(baseDir) {
|
|
5958
|
-
return
|
|
9139
|
+
return path12.join(baseDir ?? registryDir ?? DEFAULT_DATA_DIR, ALIAS_FILE);
|
|
5959
9140
|
}
|
|
5960
9141
|
async function loadRegistry(baseDir) {
|
|
5961
9142
|
if (registryCache) return registryCache;
|
|
5962
9143
|
try {
|
|
5963
|
-
const data = await
|
|
9144
|
+
const data = await fs9.readFile(getRegistryPath(baseDir), "utf-8");
|
|
5964
9145
|
const parsed = JSON.parse(data);
|
|
5965
9146
|
if (parsed.version === 1 && Array.isArray(parsed.groups)) {
|
|
5966
9147
|
registryCache = parsed;
|
|
@@ -5974,8 +9155,8 @@ async function loadRegistry(baseDir) {
|
|
|
5974
9155
|
async function saveRegistry(baseDir) {
|
|
5975
9156
|
if (!registryCache) return;
|
|
5976
9157
|
const filePath = getRegistryPath(baseDir);
|
|
5977
|
-
await
|
|
5978
|
-
await
|
|
9158
|
+
await fs9.mkdir(path12.dirname(filePath), { recursive: true });
|
|
9159
|
+
await fs9.writeFile(filePath, JSON.stringify(registryCache, null, 2), "utf-8");
|
|
5979
9160
|
}
|
|
5980
9161
|
function findMatchingGroup(registry, projectInfo) {
|
|
5981
9162
|
const normalizedRoot = normalizePath(projectInfo.rootPath);
|
|
@@ -6132,7 +9313,7 @@ var init_aliases = __esm({
|
|
|
6132
9313
|
"src/project/aliases.ts"() {
|
|
6133
9314
|
"use strict";
|
|
6134
9315
|
init_esm_shims();
|
|
6135
|
-
DEFAULT_DATA_DIR = process.env.MEMORIX_DATA_DIR ||
|
|
9316
|
+
DEFAULT_DATA_DIR = process.env.MEMORIX_DATA_DIR || path12.join(os.homedir(), ".memorix", "data");
|
|
6136
9317
|
ALIAS_FILE = ".project-aliases.json";
|
|
6137
9318
|
registryCache = null;
|
|
6138
9319
|
registryDir = null;
|
|
@@ -6406,11 +9587,11 @@ import { create, insert as insert2, search, remove as remove2, update, count, ge
|
|
|
6406
9587
|
function getLastSearchMode(projectId) {
|
|
6407
9588
|
return lastSearchModeByProject.get(projectId ?? SEARCH_MODE_DEFAULT_KEY) ?? "fulltext";
|
|
6408
9589
|
}
|
|
6409
|
-
function makeOramaObservationId(projectId,
|
|
6410
|
-
return `obs-${encodeURIComponent(projectId)}-${
|
|
9590
|
+
function makeOramaObservationId(projectId, observationId2) {
|
|
9591
|
+
return `obs-${encodeURIComponent(projectId)}-${observationId2}`;
|
|
6411
9592
|
}
|
|
6412
|
-
function makeEntryKey(projectId,
|
|
6413
|
-
return `${projectId ?? ""}::${
|
|
9593
|
+
function makeEntryKey(projectId, observationId2) {
|
|
9594
|
+
return `${projectId ?? ""}::${observationId2}`;
|
|
6414
9595
|
}
|
|
6415
9596
|
function rememberObservationDoc(doc) {
|
|
6416
9597
|
const publicDoc = { ...doc };
|
|
@@ -6639,9 +9820,9 @@ async function hydrateIndexForStartup(observations2) {
|
|
|
6639
9820
|
function getDeferredCachedVectorHydration() {
|
|
6640
9821
|
return deferredCachedVectorHydration;
|
|
6641
9822
|
}
|
|
6642
|
-
function hasObservationVector(projectId,
|
|
9823
|
+
function hasObservationVector(projectId, observationId2) {
|
|
6643
9824
|
if (!db || !embeddingEnabled || embeddingDimensions === null) return false;
|
|
6644
|
-
const document = getByID(db, makeOramaObservationId(projectId,
|
|
9825
|
+
const document = getByID(db, makeOramaObservationId(projectId, observationId2));
|
|
6645
9826
|
return Array.isArray(document?.embedding) && document.embedding.length === embeddingDimensions && document.embedding.every(Number.isFinite);
|
|
6646
9827
|
}
|
|
6647
9828
|
async function insertObservation(doc) {
|
|
@@ -6664,8 +9845,8 @@ async function searchObservations(options) {
|
|
|
6664
9845
|
const t0 = perf ? performance.now() : 0;
|
|
6665
9846
|
const mark = (label) => {
|
|
6666
9847
|
if (perf) {
|
|
6667
|
-
const
|
|
6668
|
-
process.stderr.write(` [search-perf] ${label}: ${(
|
|
9848
|
+
const now3 = performance.now();
|
|
9849
|
+
process.stderr.write(` [search-perf] ${label}: ${(now3 - t0).toFixed(0)}ms
|
|
6669
9850
|
`);
|
|
6670
9851
|
}
|
|
6671
9852
|
};
|
|
@@ -7129,13 +10310,13 @@ async function getTimeline(anchorId, projectId, depthBefore = 3, depthAfter = 3)
|
|
|
7129
10310
|
}
|
|
7130
10311
|
async function recordAccessBatch(hitDocs) {
|
|
7131
10312
|
const database = await getDb();
|
|
7132
|
-
const
|
|
10313
|
+
const now3 = (/* @__PURE__ */ new Date()).toISOString();
|
|
7133
10314
|
for (const { id, doc } of hitDocs) {
|
|
7134
10315
|
try {
|
|
7135
10316
|
await update(database, id, {
|
|
7136
10317
|
...doc,
|
|
7137
10318
|
accessCount: (doc.accessCount ?? 0) + 1,
|
|
7138
|
-
lastAccessedAt:
|
|
10319
|
+
lastAccessedAt: now3
|
|
7139
10320
|
});
|
|
7140
10321
|
} catch {
|
|
7141
10322
|
}
|
|
@@ -7333,10 +10514,10 @@ __export(observations_exports, {
|
|
|
7333
10514
|
withFreshObservations: () => withFreshObservations
|
|
7334
10515
|
});
|
|
7335
10516
|
function logEmbeddingFailureOnce(key, message) {
|
|
7336
|
-
const
|
|
10517
|
+
const now3 = Date.now();
|
|
7337
10518
|
const last = embeddingFailureLogTimestamps.get(key) ?? 0;
|
|
7338
|
-
if (
|
|
7339
|
-
embeddingFailureLogTimestamps.set(key,
|
|
10519
|
+
if (now3 - last < EMBEDDING_FAILURE_LOG_COOLDOWN_MS) return;
|
|
10520
|
+
embeddingFailureLogTimestamps.set(key, now3);
|
|
7340
10521
|
console.error(message);
|
|
7341
10522
|
}
|
|
7342
10523
|
function normalizeEmbeddingFailure(error) {
|
|
@@ -7382,6 +10563,20 @@ async function bindObservationCodeRefsBestEffort(observation) {
|
|
|
7382
10563
|
} catch {
|
|
7383
10564
|
}
|
|
7384
10565
|
}
|
|
10566
|
+
function queueClaimDerivation(observation) {
|
|
10567
|
+
const dataDir = projectDir;
|
|
10568
|
+
const isExplicit = observation.sourceDetail === "explicit";
|
|
10569
|
+
const isGit = observation.source === "git" || observation.sourceDetail === "git-ingest";
|
|
10570
|
+
if (!dataDir || !isExplicit && !isGit) return;
|
|
10571
|
+
try {
|
|
10572
|
+
enqueueClaimDerivation({
|
|
10573
|
+
dataDir,
|
|
10574
|
+
projectId: observation.projectId,
|
|
10575
|
+
observationId: observation.id
|
|
10576
|
+
});
|
|
10577
|
+
} catch {
|
|
10578
|
+
}
|
|
10579
|
+
}
|
|
7385
10580
|
function isVectorCompatibleWithCurrentIndex(embedding) {
|
|
7386
10581
|
if (!embedding) return false;
|
|
7387
10582
|
const vectorDimensions = getVectorDimensions();
|
|
@@ -7429,7 +10624,7 @@ async function withFreshObservations(fn) {
|
|
|
7429
10624
|
return fn();
|
|
7430
10625
|
}
|
|
7431
10626
|
async function storeObservation(input) {
|
|
7432
|
-
const
|
|
10627
|
+
const now3 = (/* @__PURE__ */ new Date()).toISOString();
|
|
7433
10628
|
input = { ...input, title: sanitizeCredentials(input.title), narrative: sanitizeCredentials(input.narrative), facts: input.facts?.map(sanitizeCredentials) };
|
|
7434
10629
|
await ensureFreshObservations();
|
|
7435
10630
|
if (input.topicKey) {
|
|
@@ -7437,7 +10632,7 @@ async function storeObservation(input) {
|
|
|
7437
10632
|
(o) => o.topicKey === input.topicKey && o.projectId === input.projectId
|
|
7438
10633
|
);
|
|
7439
10634
|
if (existing) {
|
|
7440
|
-
return { observation: await upsertObservation(existing, input,
|
|
10635
|
+
return { observation: await upsertObservation(existing, input, now3), upserted: true };
|
|
7441
10636
|
}
|
|
7442
10637
|
}
|
|
7443
10638
|
const contentForExtraction = [input.title, input.narrative, ...input.facts ?? []].join(" ");
|
|
@@ -7489,7 +10684,7 @@ async function storeObservation(input) {
|
|
|
7489
10684
|
filesModified: enrichedFiles,
|
|
7490
10685
|
concepts: enrichedConcepts,
|
|
7491
10686
|
tokens,
|
|
7492
|
-
createdAt:
|
|
10687
|
+
createdAt: now3,
|
|
7493
10688
|
projectId: input.projectId,
|
|
7494
10689
|
hasCausalLanguage: extracted.hasCausalLanguage,
|
|
7495
10690
|
topicKey: input.topicKey,
|
|
@@ -7535,7 +10730,7 @@ async function storeObservation(input) {
|
|
|
7535
10730
|
filesModified: enrichedFiles,
|
|
7536
10731
|
concepts: enrichedConcepts,
|
|
7537
10732
|
tokens,
|
|
7538
|
-
createdAt:
|
|
10733
|
+
createdAt: now3,
|
|
7539
10734
|
projectId: input.projectId,
|
|
7540
10735
|
hasCausalLanguage: extracted.hasCausalLanguage,
|
|
7541
10736
|
topicKey: input.topicKey,
|
|
@@ -7565,7 +10760,7 @@ async function storeObservation(input) {
|
|
|
7565
10760
|
filesModified: enrichedFiles.join("\n"),
|
|
7566
10761
|
concepts: enrichedConcepts.map((c) => c.replace(/-/g, " ")).join(", "),
|
|
7567
10762
|
tokens,
|
|
7568
|
-
createdAt:
|
|
10763
|
+
createdAt: now3,
|
|
7569
10764
|
projectId: input.projectId,
|
|
7570
10765
|
accessCount: 0,
|
|
7571
10766
|
lastAccessedAt: "",
|
|
@@ -7578,9 +10773,10 @@ async function storeObservation(input) {
|
|
|
7578
10773
|
};
|
|
7579
10774
|
await assignAndPersist();
|
|
7580
10775
|
if (upsertedInsideLock) {
|
|
7581
|
-
return { observation: await upsertObservation(observation, input,
|
|
10776
|
+
return { observation: await upsertObservation(observation, input, now3), upserted: true };
|
|
7582
10777
|
}
|
|
7583
10778
|
await bindObservationCodeRefsBestEffort(observation);
|
|
10779
|
+
queueClaimDerivation(observation);
|
|
7584
10780
|
const obsId = observation.id;
|
|
7585
10781
|
vectorMissingIds.add(obsId);
|
|
7586
10782
|
const searchableText = [input.title, input.narrative, ...input.facts ?? []].join(" ");
|
|
@@ -7625,7 +10821,7 @@ async function storeObservation(input) {
|
|
|
7625
10821
|
});
|
|
7626
10822
|
return { observation, upserted: false };
|
|
7627
10823
|
}
|
|
7628
|
-
async function upsertObservation(existing, input,
|
|
10824
|
+
async function upsertObservation(existing, input, now3) {
|
|
7629
10825
|
input = { ...input, title: sanitizeCredentials(input.title), narrative: sanitizeCredentials(input.narrative), facts: input.facts?.map(sanitizeCredentials) };
|
|
7630
10826
|
const contentForExtraction = [input.title, input.narrative, ...input.facts ?? []].join(" ");
|
|
7631
10827
|
const extracted = extractEntities(contentForExtraction);
|
|
@@ -7645,7 +10841,7 @@ async function upsertObservation(existing, input, now) {
|
|
|
7645
10841
|
existing.filesModified = enrichedFiles;
|
|
7646
10842
|
existing.concepts = enrichedConcepts;
|
|
7647
10843
|
existing.tokens = tokens;
|
|
7648
|
-
existing.updatedAt =
|
|
10844
|
+
existing.updatedAt = now3;
|
|
7649
10845
|
existing.hasCausalLanguage = extracted.hasCausalLanguage;
|
|
7650
10846
|
existing.revisionCount = (existing.revisionCount ?? 1) + 1;
|
|
7651
10847
|
existing.status = "active";
|
|
@@ -7694,6 +10890,7 @@ async function upsertObservation(existing, input, now) {
|
|
|
7694
10890
|
await store.update(existing);
|
|
7695
10891
|
}
|
|
7696
10892
|
await bindObservationCodeRefsBestEffort(existing);
|
|
10893
|
+
queueClaimDerivation(existing);
|
|
7697
10894
|
const searchableText = [input.title, input.narrative, ...input.facts ?? []].join(" ");
|
|
7698
10895
|
const obsId = existing.id;
|
|
7699
10896
|
vectorMissingIds.add(obsId);
|
|
@@ -7736,7 +10933,7 @@ async function resolveObservations(ids, status = "resolved") {
|
|
|
7736
10933
|
const resolved = [];
|
|
7737
10934
|
const notFound = [];
|
|
7738
10935
|
const changed = [];
|
|
7739
|
-
const
|
|
10936
|
+
const now3 = (/* @__PURE__ */ new Date()).toISOString();
|
|
7740
10937
|
for (const id of ids) {
|
|
7741
10938
|
const obs = observations.find((o) => o.id === id);
|
|
7742
10939
|
if (!obs) {
|
|
@@ -7744,7 +10941,7 @@ async function resolveObservations(ids, status = "resolved") {
|
|
|
7744
10941
|
continue;
|
|
7745
10942
|
}
|
|
7746
10943
|
obs.status = status;
|
|
7747
|
-
obs.updatedAt =
|
|
10944
|
+
obs.updatedAt = now3;
|
|
7748
10945
|
if (obs.progress) {
|
|
7749
10946
|
obs.progress.status = status === "resolved" ? "completed" : obs.progress.status;
|
|
7750
10947
|
}
|
|
@@ -7838,9 +11035,9 @@ function suggestTopicKey(type, title) {
|
|
|
7838
11035
|
break;
|
|
7839
11036
|
}
|
|
7840
11037
|
}
|
|
7841
|
-
const
|
|
7842
|
-
if (!
|
|
7843
|
-
return `${family}/${
|
|
11038
|
+
const slug2 = title.toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff\s-]/g, "").trim().replace(/\s+/g, "-").slice(0, 60);
|
|
11039
|
+
if (!slug2) return "";
|
|
11040
|
+
return `${family}/${slug2}`;
|
|
7844
11041
|
}
|
|
7845
11042
|
async function reindexObservations() {
|
|
7846
11043
|
if (observations.length === 0) return 0;
|
|
@@ -8078,6 +11275,7 @@ var init_observations = __esm({
|
|
|
8078
11275
|
init_entity_extractor();
|
|
8079
11276
|
init_provider2();
|
|
8080
11277
|
init_secret_filter();
|
|
11278
|
+
init_lifecycle();
|
|
8081
11279
|
observations = [];
|
|
8082
11280
|
nextId = 1;
|
|
8083
11281
|
projectDir = null;
|
|
@@ -8110,16 +11308,23 @@ var MAINTENANCE_RESULT_PREFIX = "__MEMORIX_MAINTENANCE_RESULT__";
|
|
|
8110
11308
|
var ISOLATED_MAINTENANCE_JOB_KINDS = [
|
|
8111
11309
|
"retention-archive",
|
|
8112
11310
|
"consolidation",
|
|
8113
|
-
"codegraph-refresh"
|
|
11311
|
+
"codegraph-refresh",
|
|
11312
|
+
"claim-derive",
|
|
11313
|
+
"claim-requalification",
|
|
11314
|
+
"knowledge-compile",
|
|
11315
|
+
"knowledge-lint",
|
|
11316
|
+
"workflow-index"
|
|
8114
11317
|
];
|
|
8115
11318
|
var DEFAULT_TIMEOUT_MS = 5 * 6e4;
|
|
8116
11319
|
|
|
8117
11320
|
// src/runtime/project-maintenance.ts
|
|
8118
11321
|
init_esm_shims();
|
|
11322
|
+
init_lifecycle();
|
|
8119
11323
|
var DEFAULT_VECTOR_BATCH_SIZE = 12;
|
|
8120
11324
|
var DEFAULT_RETENTION_BATCH_SIZE = 100;
|
|
8121
11325
|
var DEFAULT_CONSOLIDATION_BATCH_SIZE = 200;
|
|
8122
11326
|
var DEFAULT_CODEGRAPH_MAX_FILES = 5e3;
|
|
11327
|
+
var DEFAULT_CLAIM_DERIVATION_BATCH_SIZE = 100;
|
|
8123
11328
|
var VECTOR_RETRY_DELAY_MS = 5e3;
|
|
8124
11329
|
var DEDUP_PER_PAIR_TIMEOUT_MS = 5e3;
|
|
8125
11330
|
function vectorBatchSize(payload) {
|
|
@@ -8150,6 +11355,31 @@ function codeGraphMaxFiles(payload) {
|
|
|
8150
11355
|
if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_CODEGRAPH_MAX_FILES;
|
|
8151
11356
|
return Math.min(2e4, Math.max(1, Math.floor(value)));
|
|
8152
11357
|
}
|
|
11358
|
+
function claimDerivationBatchSize(payload) {
|
|
11359
|
+
const value = payload.limit;
|
|
11360
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_CLAIM_DERIVATION_BATCH_SIZE;
|
|
11361
|
+
return Math.min(500, Math.max(1, Math.floor(value)));
|
|
11362
|
+
}
|
|
11363
|
+
function claimDerivationCursor(payload) {
|
|
11364
|
+
const value = payload.cursor;
|
|
11365
|
+
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
|
|
11366
|
+
}
|
|
11367
|
+
function observationId(payload) {
|
|
11368
|
+
const value = payload.observationId;
|
|
11369
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : void 0;
|
|
11370
|
+
}
|
|
11371
|
+
function workspaceMode(payload) {
|
|
11372
|
+
return payload.workspaceMode === "local" || payload.workspaceMode === "versioned" ? payload.workspaceMode : void 0;
|
|
11373
|
+
}
|
|
11374
|
+
async function loadWorkspaceForMaintenance(projectId, projectDir2, mode) {
|
|
11375
|
+
const { loadKnowledgeWorkspace: loadKnowledgeWorkspace2 } = await Promise.resolve().then(() => (init_workspace(), workspace_exports));
|
|
11376
|
+
if (mode) return loadKnowledgeWorkspace2({ projectId, dataDir: projectDir2, mode });
|
|
11377
|
+
const [versioned, local] = await Promise.all([
|
|
11378
|
+
loadKnowledgeWorkspace2({ projectId, dataDir: projectDir2, mode: "versioned" }),
|
|
11379
|
+
loadKnowledgeWorkspace2({ projectId, dataDir: projectDir2, mode: "local" })
|
|
11380
|
+
]);
|
|
11381
|
+
return versioned ?? local;
|
|
11382
|
+
}
|
|
8153
11383
|
function withTimeout(promise, ms, label) {
|
|
8154
11384
|
return new Promise((resolve, reject) => {
|
|
8155
11385
|
const timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
@@ -8236,7 +11466,7 @@ async function runAutomaticConsolidation(projectId, projectDir2, options) {
|
|
|
8236
11466
|
}
|
|
8237
11467
|
return nextCursor === void 0 ? {} : { nextCursor };
|
|
8238
11468
|
}
|
|
8239
|
-
function createProjectMaintenanceHandler(projectId, projectDir2, projectRoot) {
|
|
11469
|
+
function createProjectMaintenanceHandler(projectId, projectDir2, projectRoot, options = {}) {
|
|
8240
11470
|
return async (job) => {
|
|
8241
11471
|
if (job.projectId !== projectId) {
|
|
8242
11472
|
return { action: "reschedule", delayMs: VECTOR_RETRY_DELAY_MS };
|
|
@@ -8304,6 +11534,144 @@ function createProjectMaintenanceHandler(projectId, projectDir2, projectRoot) {
|
|
|
8304
11534
|
});
|
|
8305
11535
|
const activeObservations = await getObservationStore2().loadByProject(projectId, { status: "active" });
|
|
8306
11536
|
await backfillMissingObservationCodeRefs2(store, activeObservations);
|
|
11537
|
+
const snapshot = store.latestSnapshot(projectId);
|
|
11538
|
+
enqueueClaimRequalification({
|
|
11539
|
+
projectId,
|
|
11540
|
+
dataDir: projectDir2,
|
|
11541
|
+
source: "codegraph-refresh",
|
|
11542
|
+
...snapshot?.id ? { snapshotId: snapshot.id } : {},
|
|
11543
|
+
...options.maintenanceQueue ? { queue: options.maintenanceQueue } : {}
|
|
11544
|
+
});
|
|
11545
|
+
return { action: "complete" };
|
|
11546
|
+
}
|
|
11547
|
+
if (job.kind === "claim-derive") {
|
|
11548
|
+
const id = observationId(job.payload);
|
|
11549
|
+
if (!id) throw new Error("Claim derivation requires a positive observationId");
|
|
11550
|
+
const [
|
|
11551
|
+
{ CodeGraphStore: CodeGraphStore2 },
|
|
11552
|
+
{ ClaimStore: ClaimStore2 },
|
|
11553
|
+
{ bindObservationToCode: bindObservationToCode2 },
|
|
11554
|
+
{ deriveLowRiskClaimsFromObservation: deriveLowRiskClaimsFromObservation2 },
|
|
11555
|
+
{ getObservationStore: getObservationStore2 }
|
|
11556
|
+
] = await Promise.all([
|
|
11557
|
+
Promise.resolve().then(() => (init_store(), store_exports)),
|
|
11558
|
+
Promise.resolve().then(() => (init_claim_store(), claim_store_exports)),
|
|
11559
|
+
Promise.resolve().then(() => (init_binder(), binder_exports)),
|
|
11560
|
+
Promise.resolve().then(() => (init_claims(), claims_exports)),
|
|
11561
|
+
Promise.resolve().then(() => (init_obs_store(), obs_store_exports))
|
|
11562
|
+
]);
|
|
11563
|
+
const observation = await getObservationStore2().getById(id);
|
|
11564
|
+
if (!observation || observation.projectId !== projectId) return { action: "complete" };
|
|
11565
|
+
const codeStore = new CodeGraphStore2();
|
|
11566
|
+
const claimStore = new ClaimStore2();
|
|
11567
|
+
await Promise.all([codeStore.init(projectDir2), claimStore.init(projectDir2)]);
|
|
11568
|
+
await bindObservationToCode2(codeStore, observation);
|
|
11569
|
+
const derived = deriveLowRiskClaimsFromObservation2(claimStore, observation, codeStore);
|
|
11570
|
+
if (derived.length > 0) {
|
|
11571
|
+
enqueueKnowledgeFollowups({
|
|
11572
|
+
projectId,
|
|
11573
|
+
dataDir: projectDir2,
|
|
11574
|
+
source: "claim-derive:" + id,
|
|
11575
|
+
includeCompile: true,
|
|
11576
|
+
...options.maintenanceQueue ? { queue: options.maintenanceQueue } : {}
|
|
11577
|
+
});
|
|
11578
|
+
}
|
|
11579
|
+
return { action: "complete" };
|
|
11580
|
+
}
|
|
11581
|
+
if (job.kind === "claim-requalification") {
|
|
11582
|
+
const [
|
|
11583
|
+
{ CodeGraphStore: CodeGraphStore2 },
|
|
11584
|
+
{ ClaimStore: ClaimStore2 },
|
|
11585
|
+
{ bindObservationToCode: bindObservationToCode2 },
|
|
11586
|
+
{ deriveLowRiskClaimsFromObservation: deriveLowRiskClaimsFromObservation2, requalifyClaimsForCodeState: requalifyClaimsForCodeState2 },
|
|
11587
|
+
{ getObservationStore: getObservationStore2 }
|
|
11588
|
+
] = await Promise.all([
|
|
11589
|
+
Promise.resolve().then(() => (init_store(), store_exports)),
|
|
11590
|
+
Promise.resolve().then(() => (init_claim_store(), claim_store_exports)),
|
|
11591
|
+
Promise.resolve().then(() => (init_binder(), binder_exports)),
|
|
11592
|
+
Promise.resolve().then(() => (init_claims(), claims_exports)),
|
|
11593
|
+
Promise.resolve().then(() => (init_obs_store(), obs_store_exports))
|
|
11594
|
+
]);
|
|
11595
|
+
const codeStore = new CodeGraphStore2();
|
|
11596
|
+
const claimStore = new ClaimStore2();
|
|
11597
|
+
await Promise.all([codeStore.init(projectDir2), claimStore.init(projectDir2)]);
|
|
11598
|
+
const limit = claimDerivationBatchSize(job.payload);
|
|
11599
|
+
const observations2 = await getObservationStore2().loadByProject(projectId, {
|
|
11600
|
+
status: "active",
|
|
11601
|
+
afterId: claimDerivationCursor(job.payload),
|
|
11602
|
+
limit
|
|
11603
|
+
});
|
|
11604
|
+
let derivedCount = 0;
|
|
11605
|
+
for (const observation of observations2) {
|
|
11606
|
+
await bindObservationToCode2(codeStore, observation);
|
|
11607
|
+
derivedCount += deriveLowRiskClaimsFromObservation2(claimStore, observation, codeStore).length;
|
|
11608
|
+
}
|
|
11609
|
+
requalifyClaimsForCodeState2(claimStore, codeStore, projectId);
|
|
11610
|
+
if (observations2.length === limit) {
|
|
11611
|
+
return {
|
|
11612
|
+
action: "reschedule",
|
|
11613
|
+
delayMs: 0,
|
|
11614
|
+
resetAttempts: true,
|
|
11615
|
+
payload: {
|
|
11616
|
+
...job.payload,
|
|
11617
|
+
cursor: observations2[observations2.length - 1].id,
|
|
11618
|
+
limit
|
|
11619
|
+
}
|
|
11620
|
+
};
|
|
11621
|
+
}
|
|
11622
|
+
enqueueKnowledgeFollowups({
|
|
11623
|
+
projectId,
|
|
11624
|
+
dataDir: projectDir2,
|
|
11625
|
+
source: "claim-requalification",
|
|
11626
|
+
includeCompile: derivedCount > 0,
|
|
11627
|
+
...options.maintenanceQueue ? { queue: options.maintenanceQueue } : {}
|
|
11628
|
+
});
|
|
11629
|
+
return { action: "complete" };
|
|
11630
|
+
}
|
|
11631
|
+
if (job.kind === "knowledge-compile") {
|
|
11632
|
+
const workspace = await loadWorkspaceForMaintenance(projectId, projectDir2, workspaceMode(job.payload));
|
|
11633
|
+
if (!workspace) return { action: "complete" };
|
|
11634
|
+
if (workspace.mode === "versioned" && job.payload.allowVersionedWrite !== true) {
|
|
11635
|
+
return { action: "complete" };
|
|
11636
|
+
}
|
|
11637
|
+
const [{ ClaimStore: ClaimStore2 }, { compileKnowledgeWorkspace: compileKnowledgeWorkspace2 }] = await Promise.all([
|
|
11638
|
+
Promise.resolve().then(() => (init_claim_store(), claim_store_exports)),
|
|
11639
|
+
Promise.resolve().then(() => (init_wiki(), wiki_exports))
|
|
11640
|
+
]);
|
|
11641
|
+
const claims = new ClaimStore2();
|
|
11642
|
+
await claims.init(projectDir2);
|
|
11643
|
+
await compileKnowledgeWorkspace2({ workspace, claims });
|
|
11644
|
+
enqueueKnowledgeFollowups({
|
|
11645
|
+
projectId,
|
|
11646
|
+
dataDir: projectDir2,
|
|
11647
|
+
source: "knowledge-compile",
|
|
11648
|
+
...options.maintenanceQueue ? { queue: options.maintenanceQueue } : {}
|
|
11649
|
+
});
|
|
11650
|
+
return { action: "complete" };
|
|
11651
|
+
}
|
|
11652
|
+
if (job.kind === "workflow-index") {
|
|
11653
|
+
const workspace = await loadWorkspaceForMaintenance(projectId, projectDir2, workspaceMode(job.payload));
|
|
11654
|
+
if (!workspace) return { action: "complete" };
|
|
11655
|
+
const { syncCanonicalWorkflows: syncCanonicalWorkflows2 } = await Promise.resolve().then(() => (init_workflows(), workflows_exports));
|
|
11656
|
+
await syncCanonicalWorkflows2(workspace);
|
|
11657
|
+
return { action: "complete" };
|
|
11658
|
+
}
|
|
11659
|
+
if (job.kind === "knowledge-lint") {
|
|
11660
|
+
const workspace = await loadWorkspaceForMaintenance(projectId, projectDir2, workspaceMode(job.payload));
|
|
11661
|
+
if (!workspace) return { action: "complete" };
|
|
11662
|
+
const [
|
|
11663
|
+
{ ClaimStore: ClaimStore2 },
|
|
11664
|
+
{ CodeGraphStore: CodeGraphStore2 },
|
|
11665
|
+
{ lintKnowledgeWorkspace: lintKnowledgeWorkspace2 }
|
|
11666
|
+
] = await Promise.all([
|
|
11667
|
+
Promise.resolve().then(() => (init_claim_store(), claim_store_exports)),
|
|
11668
|
+
Promise.resolve().then(() => (init_store(), store_exports)),
|
|
11669
|
+
Promise.resolve().then(() => (init_wiki(), wiki_exports))
|
|
11670
|
+
]);
|
|
11671
|
+
const claims = new ClaimStore2();
|
|
11672
|
+
const codeStore = new CodeGraphStore2();
|
|
11673
|
+
await Promise.all([claims.init(projectDir2), codeStore.init(projectDir2)]);
|
|
11674
|
+
await lintKnowledgeWorkspace2({ workspace, claims, codeStore });
|
|
8307
11675
|
return { action: "complete" };
|
|
8308
11676
|
}
|
|
8309
11677
|
if (job.kind !== "vector-backfill") {
|