@rejot-dev/thalo 0.0.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/LICENSE +21 -0
- package/README.md +396 -0
- package/dist/ast/ast-types.d.ts +469 -0
- package/dist/ast/ast-types.d.ts.map +1 -0
- package/dist/ast/ast-types.js +11 -0
- package/dist/ast/ast-types.js.map +1 -0
- package/dist/ast/builder.js +158 -0
- package/dist/ast/builder.js.map +1 -0
- package/dist/ast/extract.js +748 -0
- package/dist/ast/extract.js.map +1 -0
- package/dist/ast/node-at-position.d.ts +147 -0
- package/dist/ast/node-at-position.d.ts.map +1 -0
- package/dist/ast/node-at-position.js +382 -0
- package/dist/ast/node-at-position.js.map +1 -0
- package/dist/ast/visitor.js +232 -0
- package/dist/ast/visitor.js.map +1 -0
- package/dist/checker/check.d.ts +53 -0
- package/dist/checker/check.d.ts.map +1 -0
- package/dist/checker/check.js +105 -0
- package/dist/checker/check.js.map +1 -0
- package/dist/checker/rules/actualize-missing-updated.js +34 -0
- package/dist/checker/rules/actualize-missing-updated.js.map +1 -0
- package/dist/checker/rules/actualize-unresolved-target.js +42 -0
- package/dist/checker/rules/actualize-unresolved-target.js.map +1 -0
- package/dist/checker/rules/alter-before-define.js +53 -0
- package/dist/checker/rules/alter-before-define.js.map +1 -0
- package/dist/checker/rules/alter-undefined-entity.js +32 -0
- package/dist/checker/rules/alter-undefined-entity.js.map +1 -0
- package/dist/checker/rules/create-requires-section.js +34 -0
- package/dist/checker/rules/create-requires-section.js.map +1 -0
- package/dist/checker/rules/define-entity-requires-section.js +31 -0
- package/dist/checker/rules/define-entity-requires-section.js.map +1 -0
- package/dist/checker/rules/duplicate-entity-definition.js +37 -0
- package/dist/checker/rules/duplicate-entity-definition.js.map +1 -0
- package/dist/checker/rules/duplicate-field-in-schema.js +38 -0
- package/dist/checker/rules/duplicate-field-in-schema.js.map +1 -0
- package/dist/checker/rules/duplicate-link-id.js +52 -0
- package/dist/checker/rules/duplicate-link-id.js.map +1 -0
- package/dist/checker/rules/duplicate-metadata-key.js +21 -0
- package/dist/checker/rules/duplicate-metadata-key.js.map +1 -0
- package/dist/checker/rules/duplicate-section-heading.js +41 -0
- package/dist/checker/rules/duplicate-section-heading.js.map +1 -0
- package/dist/checker/rules/duplicate-section-in-schema.js +38 -0
- package/dist/checker/rules/duplicate-section-in-schema.js.map +1 -0
- package/dist/checker/rules/duplicate-timestamp.js +104 -0
- package/dist/checker/rules/duplicate-timestamp.js.map +1 -0
- package/dist/checker/rules/empty-required-value.js +45 -0
- package/dist/checker/rules/empty-required-value.js.map +1 -0
- package/dist/checker/rules/empty-section.js +21 -0
- package/dist/checker/rules/empty-section.js.map +1 -0
- package/dist/checker/rules/invalid-date-range-value.js +56 -0
- package/dist/checker/rules/invalid-date-range-value.js.map +1 -0
- package/dist/checker/rules/invalid-default-value.js +86 -0
- package/dist/checker/rules/invalid-default-value.js.map +1 -0
- package/dist/checker/rules/invalid-field-type.js +45 -0
- package/dist/checker/rules/invalid-field-type.js.map +1 -0
- package/dist/checker/rules/missing-required-field.js +48 -0
- package/dist/checker/rules/missing-required-field.js.map +1 -0
- package/dist/checker/rules/missing-required-section.js +51 -0
- package/dist/checker/rules/missing-required-section.js.map +1 -0
- package/dist/checker/rules/missing-title.js +56 -0
- package/dist/checker/rules/missing-title.js.map +1 -0
- package/dist/checker/rules/remove-undefined-field.js +42 -0
- package/dist/checker/rules/remove-undefined-field.js.map +1 -0
- package/dist/checker/rules/remove-undefined-section.js +42 -0
- package/dist/checker/rules/remove-undefined-section.js.map +1 -0
- package/dist/checker/rules/rules.d.ts +71 -0
- package/dist/checker/rules/rules.d.ts.map +1 -0
- package/dist/checker/rules/rules.js +102 -0
- package/dist/checker/rules/rules.js.map +1 -0
- package/dist/checker/rules/synthesis-empty-query.js +35 -0
- package/dist/checker/rules/synthesis-empty-query.js.map +1 -0
- package/dist/checker/rules/synthesis-missing-prompt.js +42 -0
- package/dist/checker/rules/synthesis-missing-prompt.js.map +1 -0
- package/dist/checker/rules/synthesis-missing-sources.js +32 -0
- package/dist/checker/rules/synthesis-missing-sources.js.map +1 -0
- package/dist/checker/rules/synthesis-unknown-query-entity.js +39 -0
- package/dist/checker/rules/synthesis-unknown-query-entity.js.map +1 -0
- package/dist/checker/rules/timestamp-out-of-order.js +55 -0
- package/dist/checker/rules/timestamp-out-of-order.js.map +1 -0
- package/dist/checker/rules/unknown-entity.js +32 -0
- package/dist/checker/rules/unknown-entity.js.map +1 -0
- package/dist/checker/rules/unknown-field.js +40 -0
- package/dist/checker/rules/unknown-field.js.map +1 -0
- package/dist/checker/rules/unknown-section.js +47 -0
- package/dist/checker/rules/unknown-section.js.map +1 -0
- package/dist/checker/rules/unresolved-link.js +34 -0
- package/dist/checker/rules/unresolved-link.js.map +1 -0
- package/dist/checker/rules/update-without-create.js +65 -0
- package/dist/checker/rules/update-without-create.js.map +1 -0
- package/dist/checker/visitor.d.ts +69 -0
- package/dist/checker/visitor.d.ts.map +1 -0
- package/dist/checker/visitor.js +67 -0
- package/dist/checker/visitor.js.map +1 -0
- package/dist/checker/workspace-index.d.ts +50 -0
- package/dist/checker/workspace-index.d.ts.map +1 -0
- package/dist/checker/workspace-index.js +108 -0
- package/dist/checker/workspace-index.js.map +1 -0
- package/dist/commands/actualize.d.ts +113 -0
- package/dist/commands/actualize.d.ts.map +1 -0
- package/dist/commands/actualize.js +111 -0
- package/dist/commands/actualize.js.map +1 -0
- package/dist/commands/check.d.ts +65 -0
- package/dist/commands/check.d.ts.map +1 -0
- package/dist/commands/check.js +61 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/format.d.ts +90 -0
- package/dist/commands/format.d.ts.map +1 -0
- package/dist/commands/format.js +80 -0
- package/dist/commands/format.js.map +1 -0
- package/dist/commands/query.d.ts +152 -0
- package/dist/commands/query.d.ts.map +1 -0
- package/dist/commands/query.js +151 -0
- package/dist/commands/query.js.map +1 -0
- package/dist/constants.d.ts +31 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +51 -0
- package/dist/constants.js.map +1 -0
- package/dist/files.d.ts +58 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +103 -0
- package/dist/files.js.map +1 -0
- package/dist/formatters.d.ts +39 -0
- package/dist/formatters.d.ts.map +1 -0
- package/dist/formatters.js +200 -0
- package/dist/formatters.js.map +1 -0
- package/dist/fragment.d.ts +22 -0
- package/dist/fragment.d.ts.map +1 -0
- package/dist/git/git.js +240 -0
- package/dist/git/git.js.map +1 -0
- package/dist/merge/conflict-detector.d.ts +89 -0
- package/dist/merge/conflict-detector.d.ts.map +1 -0
- package/dist/merge/conflict-detector.js +352 -0
- package/dist/merge/conflict-detector.js.map +1 -0
- package/dist/merge/conflict-formatter.js +143 -0
- package/dist/merge/conflict-formatter.js.map +1 -0
- package/dist/merge/driver.d.ts +54 -0
- package/dist/merge/driver.d.ts.map +1 -0
- package/dist/merge/driver.js +112 -0
- package/dist/merge/driver.js.map +1 -0
- package/dist/merge/entry-matcher.d.ts +50 -0
- package/dist/merge/entry-matcher.d.ts.map +1 -0
- package/dist/merge/entry-matcher.js +141 -0
- package/dist/merge/entry-matcher.js.map +1 -0
- package/dist/merge/entry-merger.js +194 -0
- package/dist/merge/entry-merger.js.map +1 -0
- package/dist/merge/merge-result-builder.d.ts +62 -0
- package/dist/merge/merge-result-builder.d.ts.map +1 -0
- package/dist/merge/merge-result-builder.js +89 -0
- package/dist/merge/merge-result-builder.js.map +1 -0
- package/dist/mod.d.ts +31 -0
- package/dist/mod.js +23 -0
- package/dist/model/document.d.ts +134 -0
- package/dist/model/document.d.ts.map +1 -0
- package/dist/model/document.js +275 -0
- package/dist/model/document.js.map +1 -0
- package/dist/model/line-index.d.ts +85 -0
- package/dist/model/line-index.d.ts.map +1 -0
- package/dist/model/line-index.js +159 -0
- package/dist/model/line-index.js.map +1 -0
- package/dist/model/workspace.d.ts +296 -0
- package/dist/model/workspace.d.ts.map +1 -0
- package/dist/model/workspace.js +562 -0
- package/dist/model/workspace.js.map +1 -0
- package/dist/parser.js +27 -0
- package/dist/parser.js.map +1 -0
- package/dist/parser.native.d.ts +51 -0
- package/dist/parser.native.d.ts.map +1 -0
- package/dist/parser.native.js +62 -0
- package/dist/parser.native.js.map +1 -0
- package/dist/parser.shared.d.ts +99 -0
- package/dist/parser.shared.d.ts.map +1 -0
- package/dist/parser.shared.js +124 -0
- package/dist/parser.shared.js.map +1 -0
- package/dist/parser.web.d.ts +67 -0
- package/dist/parser.web.d.ts.map +1 -0
- package/dist/parser.web.js +49 -0
- package/dist/parser.web.js.map +1 -0
- package/dist/schema/registry.d.ts +108 -0
- package/dist/schema/registry.d.ts.map +1 -0
- package/dist/schema/registry.js +281 -0
- package/dist/schema/registry.js.map +1 -0
- package/dist/semantic/analyzer.d.ts +107 -0
- package/dist/semantic/analyzer.d.ts.map +1 -0
- package/dist/semantic/analyzer.js +261 -0
- package/dist/semantic/analyzer.js.map +1 -0
- package/dist/services/change-tracker/change-tracker.d.ts +111 -0
- package/dist/services/change-tracker/change-tracker.d.ts.map +1 -0
- package/dist/services/change-tracker/change-tracker.js +62 -0
- package/dist/services/change-tracker/change-tracker.js.map +1 -0
- package/dist/services/change-tracker/create-tracker.d.ts +42 -0
- package/dist/services/change-tracker/create-tracker.d.ts.map +1 -0
- package/dist/services/change-tracker/create-tracker.js +53 -0
- package/dist/services/change-tracker/create-tracker.js.map +1 -0
- package/dist/services/change-tracker/git-tracker.d.ts +59 -0
- package/dist/services/change-tracker/git-tracker.d.ts.map +1 -0
- package/dist/services/change-tracker/git-tracker.js +218 -0
- package/dist/services/change-tracker/git-tracker.js.map +1 -0
- package/dist/services/change-tracker/timestamp-tracker.d.ts +22 -0
- package/dist/services/change-tracker/timestamp-tracker.d.ts.map +1 -0
- package/dist/services/change-tracker/timestamp-tracker.js +74 -0
- package/dist/services/change-tracker/timestamp-tracker.js.map +1 -0
- package/dist/services/definition.d.ts +37 -0
- package/dist/services/definition.d.ts.map +1 -0
- package/dist/services/definition.js +43 -0
- package/dist/services/definition.js.map +1 -0
- package/dist/services/entity-navigation.d.ts +200 -0
- package/dist/services/entity-navigation.d.ts.map +1 -0
- package/dist/services/entity-navigation.js +211 -0
- package/dist/services/entity-navigation.js.map +1 -0
- package/dist/services/hover.d.ts +81 -0
- package/dist/services/hover.d.ts.map +1 -0
- package/dist/services/hover.js +669 -0
- package/dist/services/hover.js.map +1 -0
- package/dist/services/query.d.ts +116 -0
- package/dist/services/query.d.ts.map +1 -0
- package/dist/services/query.js +225 -0
- package/dist/services/query.js.map +1 -0
- package/dist/services/references.d.ts +52 -0
- package/dist/services/references.d.ts.map +1 -0
- package/dist/services/references.js +66 -0
- package/dist/services/references.js.map +1 -0
- package/dist/services/semantic-tokens.d.ts +54 -0
- package/dist/services/semantic-tokens.d.ts.map +1 -0
- package/dist/services/semantic-tokens.js +213 -0
- package/dist/services/semantic-tokens.js.map +1 -0
- package/dist/services/synthesis.d.ts +90 -0
- package/dist/services/synthesis.d.ts.map +1 -0
- package/dist/services/synthesis.js +113 -0
- package/dist/services/synthesis.js.map +1 -0
- package/dist/source-map.d.ts +42 -0
- package/dist/source-map.d.ts.map +1 -0
- package/dist/source-map.js +170 -0
- package/dist/source-map.js.map +1 -0
- package/package.json +128 -0
- package/tree-sitter-thalo.wasm +0 -0
- package/web-tree-sitter.wasm +0 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { ActualizeEntry, InstanceEntry, SchemaEntry, SynthesisEntry } from "../ast/ast-types.js";
|
|
2
|
+
import { SourceMap } from "../source-map.js";
|
|
3
|
+
import { SemanticModel } from "../semantic/analyzer.js";
|
|
4
|
+
import { Workspace } from "../model/workspace.js";
|
|
5
|
+
import { WorkspaceIndex } from "./workspace-index.js";
|
|
6
|
+
import { PartialDiagnostic } from "./check.js";
|
|
7
|
+
|
|
8
|
+
//#region src/checker/visitor.d.ts
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Context available to visitors during the check phase.
|
|
12
|
+
*/
|
|
13
|
+
interface VisitorContext {
|
|
14
|
+
/** The workspace being checked */
|
|
15
|
+
workspace: Workspace;
|
|
16
|
+
/** Pre-computed indices for efficient queries */
|
|
17
|
+
index: WorkspaceIndex;
|
|
18
|
+
/** Report a diagnostic */
|
|
19
|
+
report(diagnostic: PartialDiagnostic): void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Context available when visiting a specific entry.
|
|
23
|
+
*/
|
|
24
|
+
interface EntryContext extends VisitorContext {
|
|
25
|
+
/** The semantic model containing the entry */
|
|
26
|
+
model: SemanticModel;
|
|
27
|
+
/** The file path */
|
|
28
|
+
file: string;
|
|
29
|
+
/** Source map for position translation */
|
|
30
|
+
sourceMap: SourceMap;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Visitor interface for rule implementations.
|
|
34
|
+
*
|
|
35
|
+
* Rules implement this interface to receive callbacks during the check phase.
|
|
36
|
+
* Instead of each rule iterating over all entries, a single pass dispatches
|
|
37
|
+
* to all registered visitors.
|
|
38
|
+
*/
|
|
39
|
+
interface RuleVisitor {
|
|
40
|
+
/**
|
|
41
|
+
* Called once before visiting any entries.
|
|
42
|
+
* Use this for initialization or pre-processing.
|
|
43
|
+
*/
|
|
44
|
+
beforeCheck?(ctx: VisitorContext): void;
|
|
45
|
+
/**
|
|
46
|
+
* Called for each instance entry (create/update).
|
|
47
|
+
*/
|
|
48
|
+
visitInstanceEntry?(entry: InstanceEntry, ctx: EntryContext): void;
|
|
49
|
+
/**
|
|
50
|
+
* Called for each schema entry (define-entity/alter-entity).
|
|
51
|
+
*/
|
|
52
|
+
visitSchemaEntry?(entry: SchemaEntry, ctx: EntryContext): void;
|
|
53
|
+
/**
|
|
54
|
+
* Called for each synthesis entry (define-synthesis).
|
|
55
|
+
*/
|
|
56
|
+
visitSynthesisEntry?(entry: SynthesisEntry, ctx: EntryContext): void;
|
|
57
|
+
/**
|
|
58
|
+
* Called for each actualize entry (actualize-synthesis).
|
|
59
|
+
*/
|
|
60
|
+
visitActualizeEntry?(entry: ActualizeEntry, ctx: EntryContext): void;
|
|
61
|
+
/**
|
|
62
|
+
* Called once after visiting all entries.
|
|
63
|
+
* Use this for cross-entry checks that need all data collected first.
|
|
64
|
+
*/
|
|
65
|
+
afterCheck?(ctx: VisitorContext): void;
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
export { RuleVisitor };
|
|
69
|
+
//# sourceMappingURL=visitor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visitor.d.ts","names":[],"sources":["../../src/checker/visitor.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;AAgBA;AAEa,UAFI,cAAA,CAEJ;EAEJ;EAEY,SAAA,EAJR,SAIQ;EAAiB;EAMrB,KAAA,EARR,cAQqB;EAErB;EAII,MAAA,CAAA,UAAA,EAZQ,iBAYR,CAAA,EAAA,IAAA;;;AAUb;;AAU6B,UA1BZ,YAAA,SAAqB,cA0BT,CAAA;EAAoB;EAKtB,KAAA,EA7BlB,aA6BkB;EAAkB;EAKf,IAAA,EAAA,MAAA;EAAqB;EAKrB,SAAA,EAnCjB,SAmCiB;;;;;;;;;UAzBb,WAAA;;;;;oBAKG;;;;6BAKS,oBAAoB;;;;2BAKtB,kBAAkB;;;;8BAKf,qBAAqB;;;;8BAKrB,qBAAqB;;;;;mBAMhC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
//#region src/checker/visitor.ts
|
|
2
|
+
/**
|
|
3
|
+
* Dispatch an entry to the appropriate visitor method.
|
|
4
|
+
*/
|
|
5
|
+
function dispatchToVisitor(visitor, entry, ctx) {
|
|
6
|
+
switch (entry.type) {
|
|
7
|
+
case "instance_entry":
|
|
8
|
+
visitor.visitInstanceEntry?.(entry, ctx);
|
|
9
|
+
break;
|
|
10
|
+
case "schema_entry":
|
|
11
|
+
visitor.visitSchemaEntry?.(entry, ctx);
|
|
12
|
+
break;
|
|
13
|
+
case "synthesis_entry":
|
|
14
|
+
visitor.visitSynthesisEntry?.(entry, ctx);
|
|
15
|
+
break;
|
|
16
|
+
case "actualize_entry":
|
|
17
|
+
visitor.visitActualizeEntry?.(entry, ctx);
|
|
18
|
+
break;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Run a single pass over all entries, dispatching to all visitors.
|
|
23
|
+
*
|
|
24
|
+
* This is more efficient than each rule iterating independently.
|
|
25
|
+
*/
|
|
26
|
+
function runVisitors(visitors, workspace, index, report) {
|
|
27
|
+
const visitorCtx = {
|
|
28
|
+
workspace,
|
|
29
|
+
index,
|
|
30
|
+
report
|
|
31
|
+
};
|
|
32
|
+
for (const visitor of visitors) visitor.beforeCheck?.(visitorCtx);
|
|
33
|
+
for (const model of workspace.allModels()) {
|
|
34
|
+
const entryCtx = {
|
|
35
|
+
...visitorCtx,
|
|
36
|
+
model,
|
|
37
|
+
file: model.file,
|
|
38
|
+
sourceMap: model.sourceMap
|
|
39
|
+
};
|
|
40
|
+
for (const entry of model.ast.entries) for (const visitor of visitors) dispatchToVisitor(visitor, entry, entryCtx);
|
|
41
|
+
}
|
|
42
|
+
for (const visitor of visitors) visitor.afterCheck?.(visitorCtx);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Run visitors on a single model only.
|
|
46
|
+
* Useful for document-scoped checks.
|
|
47
|
+
*/
|
|
48
|
+
function runVisitorsOnModel(visitors, model, workspace, index, report) {
|
|
49
|
+
const visitorCtx = {
|
|
50
|
+
workspace,
|
|
51
|
+
index,
|
|
52
|
+
report
|
|
53
|
+
};
|
|
54
|
+
const entryCtx = {
|
|
55
|
+
...visitorCtx,
|
|
56
|
+
model,
|
|
57
|
+
file: model.file,
|
|
58
|
+
sourceMap: model.sourceMap
|
|
59
|
+
};
|
|
60
|
+
for (const visitor of visitors) visitor.beforeCheck?.(visitorCtx);
|
|
61
|
+
for (const entry of model.ast.entries) for (const visitor of visitors) dispatchToVisitor(visitor, entry, entryCtx);
|
|
62
|
+
for (const visitor of visitors) visitor.afterCheck?.(visitorCtx);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
//#endregion
|
|
66
|
+
export { runVisitors, runVisitorsOnModel };
|
|
67
|
+
//# sourceMappingURL=visitor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visitor.js","names":["visitorCtx: VisitorContext","entryCtx: EntryContext"],"sources":["../../src/checker/visitor.ts"],"sourcesContent":["import type {\n Entry,\n InstanceEntry,\n SchemaEntry,\n SynthesisEntry,\n ActualizeEntry,\n} from \"../ast/ast-types.js\";\nimport type { SemanticModel } from \"../semantic/analyzer.js\";\nimport type { SourceMap } from \"../source-map.js\";\nimport type { Workspace } from \"../model/workspace.js\";\nimport type { WorkspaceIndex } from \"./workspace-index.js\";\nimport type { PartialDiagnostic } from \"./check.js\";\n\n/**\n * Context available to visitors during the check phase.\n */\nexport interface VisitorContext {\n /** The workspace being checked */\n workspace: Workspace;\n /** Pre-computed indices for efficient queries */\n index: WorkspaceIndex;\n /** Report a diagnostic */\n report(diagnostic: PartialDiagnostic): void;\n}\n\n/**\n * Context available when visiting a specific entry.\n */\nexport interface EntryContext extends VisitorContext {\n /** The semantic model containing the entry */\n model: SemanticModel;\n /** The file path */\n file: string;\n /** Source map for position translation */\n sourceMap: SourceMap;\n}\n\n/**\n * Visitor interface for rule implementations.\n *\n * Rules implement this interface to receive callbacks during the check phase.\n * Instead of each rule iterating over all entries, a single pass dispatches\n * to all registered visitors.\n */\nexport interface RuleVisitor {\n /**\n * Called once before visiting any entries.\n * Use this for initialization or pre-processing.\n */\n beforeCheck?(ctx: VisitorContext): void;\n\n /**\n * Called for each instance entry (create/update).\n */\n visitInstanceEntry?(entry: InstanceEntry, ctx: EntryContext): void;\n\n /**\n * Called for each schema entry (define-entity/alter-entity).\n */\n visitSchemaEntry?(entry: SchemaEntry, ctx: EntryContext): void;\n\n /**\n * Called for each synthesis entry (define-synthesis).\n */\n visitSynthesisEntry?(entry: SynthesisEntry, ctx: EntryContext): void;\n\n /**\n * Called for each actualize entry (actualize-synthesis).\n */\n visitActualizeEntry?(entry: ActualizeEntry, ctx: EntryContext): void;\n\n /**\n * Called once after visiting all entries.\n * Use this for cross-entry checks that need all data collected first.\n */\n afterCheck?(ctx: VisitorContext): void;\n}\n\n/**\n * Dispatch an entry to the appropriate visitor method.\n */\nexport function dispatchToVisitor(visitor: RuleVisitor, entry: Entry, ctx: EntryContext): void {\n switch (entry.type) {\n case \"instance_entry\":\n visitor.visitInstanceEntry?.(entry, ctx);\n break;\n case \"schema_entry\":\n visitor.visitSchemaEntry?.(entry, ctx);\n break;\n case \"synthesis_entry\":\n visitor.visitSynthesisEntry?.(entry, ctx);\n break;\n case \"actualize_entry\":\n visitor.visitActualizeEntry?.(entry, ctx);\n break;\n }\n}\n\n/**\n * Run a single pass over all entries, dispatching to all visitors.\n *\n * This is more efficient than each rule iterating independently.\n */\nexport function runVisitors(\n visitors: RuleVisitor[],\n workspace: Workspace,\n index: WorkspaceIndex,\n report: (diagnostic: PartialDiagnostic) => void,\n): void {\n const visitorCtx: VisitorContext = {\n workspace,\n index,\n report,\n };\n\n // Call beforeCheck on all visitors\n for (const visitor of visitors) {\n visitor.beforeCheck?.(visitorCtx);\n }\n\n // Single pass over all models and entries\n for (const model of workspace.allModels()) {\n const entryCtx: EntryContext = {\n ...visitorCtx,\n model,\n file: model.file,\n sourceMap: model.sourceMap,\n };\n\n for (const entry of model.ast.entries) {\n // Dispatch to all visitors\n for (const visitor of visitors) {\n dispatchToVisitor(visitor, entry, entryCtx);\n }\n }\n }\n\n // Call afterCheck on all visitors\n for (const visitor of visitors) {\n visitor.afterCheck?.(visitorCtx);\n }\n}\n\n/**\n * Run visitors on a single model only.\n * Useful for document-scoped checks.\n */\nexport function runVisitorsOnModel(\n visitors: RuleVisitor[],\n model: SemanticModel,\n workspace: Workspace,\n index: WorkspaceIndex,\n report: (diagnostic: PartialDiagnostic) => void,\n): void {\n const visitorCtx: VisitorContext = {\n workspace,\n index,\n report,\n };\n\n const entryCtx: EntryContext = {\n ...visitorCtx,\n model,\n file: model.file,\n sourceMap: model.sourceMap,\n };\n\n // Call beforeCheck on all visitors\n for (const visitor of visitors) {\n visitor.beforeCheck?.(visitorCtx);\n }\n\n // Iterate over entries in this model only\n for (const entry of model.ast.entries) {\n for (const visitor of visitors) {\n dispatchToVisitor(visitor, entry, entryCtx);\n }\n }\n\n // Call afterCheck on all visitors\n for (const visitor of visitors) {\n visitor.afterCheck?.(visitorCtx);\n }\n}\n\n/**\n * Run visitors on specific entries only.\n * Useful for incremental checks on changed entries.\n */\nexport function runVisitorsOnEntries(\n visitors: RuleVisitor[],\n entries: Entry[],\n model: SemanticModel,\n workspace: Workspace,\n index: WorkspaceIndex,\n report: (diagnostic: PartialDiagnostic) => void,\n): void {\n const visitorCtx: VisitorContext = {\n workspace,\n index,\n report,\n };\n\n const entryCtx: EntryContext = {\n ...visitorCtx,\n model,\n file: model.file,\n sourceMap: model.sourceMap,\n };\n\n // Note: We don't call beforeCheck/afterCheck for incremental checks\n // as they typically need full workspace data\n\n for (const entry of entries) {\n for (const visitor of visitors) {\n dispatchToVisitor(visitor, entry, entryCtx);\n }\n }\n}\n"],"mappings":";;;;AAiFA,SAAgB,kBAAkB,SAAsB,OAAc,KAAyB;AAC7F,SAAQ,MAAM,MAAd;EACE,KAAK;AACH,WAAQ,qBAAqB,OAAO,IAAI;AACxC;EACF,KAAK;AACH,WAAQ,mBAAmB,OAAO,IAAI;AACtC;EACF,KAAK;AACH,WAAQ,sBAAsB,OAAO,IAAI;AACzC;EACF,KAAK;AACH,WAAQ,sBAAsB,OAAO,IAAI;AACzC;;;;;;;;AASN,SAAgB,YACd,UACA,WACA,OACA,QACM;CACN,MAAMA,aAA6B;EACjC;EACA;EACA;EACD;AAGD,MAAK,MAAM,WAAW,SACpB,SAAQ,cAAc,WAAW;AAInC,MAAK,MAAM,SAAS,UAAU,WAAW,EAAE;EACzC,MAAMC,WAAyB;GAC7B,GAAG;GACH;GACA,MAAM,MAAM;GACZ,WAAW,MAAM;GAClB;AAED,OAAK,MAAM,SAAS,MAAM,IAAI,QAE5B,MAAK,MAAM,WAAW,SACpB,mBAAkB,SAAS,OAAO,SAAS;;AAMjD,MAAK,MAAM,WAAW,SACpB,SAAQ,aAAa,WAAW;;;;;;AAQpC,SAAgB,mBACd,UACA,OACA,WACA,OACA,QACM;CACN,MAAMD,aAA6B;EACjC;EACA;EACA;EACD;CAED,MAAMC,WAAyB;EAC7B,GAAG;EACH;EACA,MAAM,MAAM;EACZ,WAAW,MAAM;EAClB;AAGD,MAAK,MAAM,WAAW,SACpB,SAAQ,cAAc,WAAW;AAInC,MAAK,MAAM,SAAS,MAAM,IAAI,QAC5B,MAAK,MAAM,WAAW,SACpB,mBAAkB,SAAS,OAAO,SAAS;AAK/C,MAAK,MAAM,WAAW,SACpB,SAAQ,aAAa,WAAW"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ActualizeEntry, Entry, InstanceEntry, SchemaEntry, SynthesisEntry } from "../ast/ast-types.js";
|
|
2
|
+
import { SourceMap } from "../source-map.js";
|
|
3
|
+
import { SemanticModel } from "../semantic/analyzer.js";
|
|
4
|
+
|
|
5
|
+
//#region src/checker/workspace-index.d.ts
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* An entry with its context (model, file, sourceMap) for reporting diagnostics.
|
|
9
|
+
*/
|
|
10
|
+
interface IndexedEntry<T extends Entry> {
|
|
11
|
+
/** The AST entry */
|
|
12
|
+
entry: T;
|
|
13
|
+
/** The semantic model containing this entry */
|
|
14
|
+
model: SemanticModel;
|
|
15
|
+
/** The file path */
|
|
16
|
+
file: string;
|
|
17
|
+
/** Source map for position translation */
|
|
18
|
+
sourceMap: SourceMap;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Pre-computed indices for efficient rule execution.
|
|
22
|
+
*
|
|
23
|
+
* Instead of each rule iterating over all models and entries,
|
|
24
|
+
* rules can query this index for pre-grouped data.
|
|
25
|
+
*/
|
|
26
|
+
interface WorkspaceIndex {
|
|
27
|
+
/** All instance entries (create/update) */
|
|
28
|
+
readonly instanceEntries: IndexedEntry<InstanceEntry>[];
|
|
29
|
+
/** All schema entries (define-entity/alter-entity) */
|
|
30
|
+
readonly schemaEntries: IndexedEntry<SchemaEntry>[];
|
|
31
|
+
/** All synthesis entries (define-synthesis) */
|
|
32
|
+
readonly synthesisEntries: IndexedEntry<SynthesisEntry>[];
|
|
33
|
+
/** All actualize entries (actualize-synthesis) */
|
|
34
|
+
readonly actualizeEntries: IndexedEntry<ActualizeEntry>[];
|
|
35
|
+
/** define-entity entries grouped by entity name */
|
|
36
|
+
readonly defineEntitiesByName: ReadonlyMap<string, IndexedEntry<SchemaEntry>[]>;
|
|
37
|
+
/** alter-entity entries grouped by entity name */
|
|
38
|
+
readonly alterEntitiesByName: ReadonlyMap<string, IndexedEntry<SchemaEntry>[]>;
|
|
39
|
+
/** Instance entries grouped by entity type */
|
|
40
|
+
readonly instancesByEntity: ReadonlyMap<string, IndexedEntry<InstanceEntry>[]>;
|
|
41
|
+
/** Instance entries by their link ID (for entries that define a link) */
|
|
42
|
+
readonly instancesByLinkId: ReadonlyMap<string, IndexedEntry<InstanceEntry>>;
|
|
43
|
+
/** Entries that reference a specific link ID (in metadata or as actualize target) */
|
|
44
|
+
readonly entriesReferencingLink: ReadonlyMap<string, IndexedEntry<Entry>[]>;
|
|
45
|
+
/** Entries that use a specific entity type (instance entries) */
|
|
46
|
+
readonly entriesUsingEntity: ReadonlyMap<string, IndexedEntry<Entry>[]>;
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
export { WorkspaceIndex };
|
|
50
|
+
//# sourceMappingURL=workspace-index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-index.d.ts","names":[],"sources":["../../src/checker/workspace-index.ts"],"sourcesContent":[],"mappings":";;;;;;;AAcA;;AAES,UAFQ,YAER,CAAA,UAF+B,KAE/B,CAAA,CAAA;EAEA;EAII,KAAA,EANJ,CAMI;EAAS;EASL,KAAA,EAbR,aAasB;EAGU;EAAb,IAAA,EAAA,MAAA;EAEW;EAAb,SAAA,EAdb,SAca;;;;;;;;AAUuC,UAfhD,cAAA,CAegD;EAAb;EAApB,SAAA,eAAA,EAZJ,YAYI,CAZS,aAYT,CAAA,EAAA;EAI+B;EAAb,SAAA,aAAA,EAdxB,YAcwB,CAdX,WAcW,CAAA,EAAA;EAApB;EAEiC,SAAA,gBAAA,EAdlC,YAckC,CAdrB,cAcqB,CAAA,EAAA;EAAb;EAApB,SAAA,gBAAA,EAZD,YAYC,CAZY,cAYZ,CAAA,EAAA;EAIsC;EAAb,SAAA,oBAAA,EAZtB,WAYsB,CAAA,MAAA,EAZF,YAYE,CAZW,WAYX,CAAA,EAAA,CAAA;EAApB;EAE6B,SAAA,mBAAA,EAZhC,WAYgC,CAAA,MAAA,EAZZ,YAYY,CAZC,WAYD,CAAA,EAAA,CAAA;EAAb;EAApB,SAAA,iBAAA,EARD,WAQC,CAAA,MAAA,EARmB,YAQnB,CARgC,aAQhC,CAAA,EAAA,CAAA;EAAW;8BANZ,oBAAoB,aAAa;;mCAI5B,oBAAoB,aAAa;;+BAErC,oBAAoB,aAAa"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
//#region src/checker/workspace-index.ts
|
|
2
|
+
/**
|
|
3
|
+
* Create an empty workspace index builder.
|
|
4
|
+
*/
|
|
5
|
+
function createEmptyBuilder() {
|
|
6
|
+
return {
|
|
7
|
+
instanceEntries: [],
|
|
8
|
+
schemaEntries: [],
|
|
9
|
+
synthesisEntries: [],
|
|
10
|
+
actualizeEntries: [],
|
|
11
|
+
defineEntitiesByName: /* @__PURE__ */ new Map(),
|
|
12
|
+
alterEntitiesByName: /* @__PURE__ */ new Map(),
|
|
13
|
+
instancesByEntity: /* @__PURE__ */ new Map(),
|
|
14
|
+
instancesByLinkId: /* @__PURE__ */ new Map(),
|
|
15
|
+
entriesReferencingLink: /* @__PURE__ */ new Map(),
|
|
16
|
+
entriesUsingEntity: /* @__PURE__ */ new Map()
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Add an item to a map of arrays.
|
|
21
|
+
*/
|
|
22
|
+
function addToMapArray(map, key, value) {
|
|
23
|
+
const arr = map.get(key);
|
|
24
|
+
if (arr) arr.push(value);
|
|
25
|
+
else map.set(key, [value]);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Extract link IDs referenced in an entry's metadata.
|
|
29
|
+
*/
|
|
30
|
+
function getReferencedLinkIds(entry) {
|
|
31
|
+
const linkIds = [];
|
|
32
|
+
if (entry.type === "instance_entry" || entry.type === "synthesis_entry") for (const m of entry.metadata) {
|
|
33
|
+
const content = m.value.content;
|
|
34
|
+
if (content.type === "link_value") linkIds.push(content.link.id);
|
|
35
|
+
else if (content.type === "value_array") {
|
|
36
|
+
for (const element of content.elements) if (element.type === "link") linkIds.push(element.id);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else if (entry.type === "actualize_entry") {
|
|
40
|
+
linkIds.push(entry.header.target.id);
|
|
41
|
+
for (const m of entry.metadata) {
|
|
42
|
+
const content = m.value.content;
|
|
43
|
+
if (content.type === "link_value") linkIds.push(content.link.id);
|
|
44
|
+
else if (content.type === "value_array") {
|
|
45
|
+
for (const element of content.elements) if (element.type === "link") linkIds.push(element.id);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return linkIds;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Build a WorkspaceIndex from a Workspace in a single pass.
|
|
53
|
+
*
|
|
54
|
+
* This is more efficient than having each rule iterate over all models
|
|
55
|
+
* and entries independently. Rules can query the pre-built index instead.
|
|
56
|
+
*/
|
|
57
|
+
function buildWorkspaceIndex(workspace) {
|
|
58
|
+
const builder = createEmptyBuilder();
|
|
59
|
+
for (const model of workspace.allModels()) {
|
|
60
|
+
const file = model.file;
|
|
61
|
+
const sourceMap = model.sourceMap;
|
|
62
|
+
for (const entry of model.ast.entries) {
|
|
63
|
+
const indexed = {
|
|
64
|
+
entry,
|
|
65
|
+
model,
|
|
66
|
+
file,
|
|
67
|
+
sourceMap
|
|
68
|
+
};
|
|
69
|
+
switch (entry.type) {
|
|
70
|
+
case "instance_entry": {
|
|
71
|
+
const instanceIndexed = indexed;
|
|
72
|
+
builder.instanceEntries.push(instanceIndexed);
|
|
73
|
+
const entityName = entry.header.entity;
|
|
74
|
+
addToMapArray(builder.instancesByEntity, entityName, instanceIndexed);
|
|
75
|
+
addToMapArray(builder.entriesUsingEntity, entityName, indexed);
|
|
76
|
+
if (entry.header.link) builder.instancesByLinkId.set(entry.header.link.id, instanceIndexed);
|
|
77
|
+
for (const linkId of getReferencedLinkIds(entry)) addToMapArray(builder.entriesReferencingLink, linkId, indexed);
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
case "schema_entry": {
|
|
81
|
+
const schemaIndexed = indexed;
|
|
82
|
+
builder.schemaEntries.push(schemaIndexed);
|
|
83
|
+
const entityName = entry.header.entityName.value;
|
|
84
|
+
if (entry.header.directive === "define-entity") addToMapArray(builder.defineEntitiesByName, entityName, schemaIndexed);
|
|
85
|
+
else addToMapArray(builder.alterEntitiesByName, entityName, schemaIndexed);
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
case "synthesis_entry": {
|
|
89
|
+
const synthesisIndexed = indexed;
|
|
90
|
+
builder.synthesisEntries.push(synthesisIndexed);
|
|
91
|
+
for (const linkId of getReferencedLinkIds(entry)) addToMapArray(builder.entriesReferencingLink, linkId, indexed);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
case "actualize_entry": {
|
|
95
|
+
const actualizeIndexed = indexed;
|
|
96
|
+
builder.actualizeEntries.push(actualizeIndexed);
|
|
97
|
+
for (const linkId of getReferencedLinkIds(entry)) addToMapArray(builder.entriesReferencingLink, linkId, indexed);
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return builder;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
//#endregion
|
|
107
|
+
export { buildWorkspaceIndex };
|
|
108
|
+
//# sourceMappingURL=workspace-index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-index.js","names":["linkIds: string[]"],"sources":["../../src/checker/workspace-index.ts"],"sourcesContent":["import type {\n Entry,\n InstanceEntry,\n SchemaEntry,\n SynthesisEntry,\n ActualizeEntry,\n} from \"../ast/ast-types.js\";\nimport type { SemanticModel } from \"../semantic/analyzer.js\";\nimport type { SourceMap } from \"../source-map.js\";\nimport type { Workspace } from \"../model/workspace.js\";\n\n/**\n * An entry with its context (model, file, sourceMap) for reporting diagnostics.\n */\nexport interface IndexedEntry<T extends Entry> {\n /** The AST entry */\n entry: T;\n /** The semantic model containing this entry */\n model: SemanticModel;\n /** The file path */\n file: string;\n /** Source map for position translation */\n sourceMap: SourceMap;\n}\n\n/**\n * Pre-computed indices for efficient rule execution.\n *\n * Instead of each rule iterating over all models and entries,\n * rules can query this index for pre-grouped data.\n */\nexport interface WorkspaceIndex {\n // Entry indices by type\n /** All instance entries (create/update) */\n readonly instanceEntries: IndexedEntry<InstanceEntry>[];\n /** All schema entries (define-entity/alter-entity) */\n readonly schemaEntries: IndexedEntry<SchemaEntry>[];\n /** All synthesis entries (define-synthesis) */\n readonly synthesisEntries: IndexedEntry<SynthesisEntry>[];\n /** All actualize entries (actualize-synthesis) */\n readonly actualizeEntries: IndexedEntry<ActualizeEntry>[];\n\n // Pre-grouped indices for schema entries\n /** define-entity entries grouped by entity name */\n readonly defineEntitiesByName: ReadonlyMap<string, IndexedEntry<SchemaEntry>[]>;\n /** alter-entity entries grouped by entity name */\n readonly alterEntitiesByName: ReadonlyMap<string, IndexedEntry<SchemaEntry>[]>;\n\n // Pre-grouped indices for instance entries\n /** Instance entries grouped by entity type */\n readonly instancesByEntity: ReadonlyMap<string, IndexedEntry<InstanceEntry>[]>;\n /** Instance entries by their link ID (for entries that define a link) */\n readonly instancesByLinkId: ReadonlyMap<string, IndexedEntry<InstanceEntry>>;\n\n // Cross-reference indices\n /** Entries that reference a specific link ID (in metadata or as actualize target) */\n readonly entriesReferencingLink: ReadonlyMap<string, IndexedEntry<Entry>[]>;\n /** Entries that use a specific entity type (instance entries) */\n readonly entriesUsingEntity: ReadonlyMap<string, IndexedEntry<Entry>[]>;\n}\n\n/**\n * Mutable builder for WorkspaceIndex.\n */\ninterface WorkspaceIndexBuilder {\n instanceEntries: IndexedEntry<InstanceEntry>[];\n schemaEntries: IndexedEntry<SchemaEntry>[];\n synthesisEntries: IndexedEntry<SynthesisEntry>[];\n actualizeEntries: IndexedEntry<ActualizeEntry>[];\n defineEntitiesByName: Map<string, IndexedEntry<SchemaEntry>[]>;\n alterEntitiesByName: Map<string, IndexedEntry<SchemaEntry>[]>;\n instancesByEntity: Map<string, IndexedEntry<InstanceEntry>[]>;\n instancesByLinkId: Map<string, IndexedEntry<InstanceEntry>>;\n entriesReferencingLink: Map<string, IndexedEntry<Entry>[]>;\n entriesUsingEntity: Map<string, IndexedEntry<Entry>[]>;\n}\n\n/**\n * Create an empty workspace index builder.\n */\nfunction createEmptyBuilder(): WorkspaceIndexBuilder {\n return {\n instanceEntries: [],\n schemaEntries: [],\n synthesisEntries: [],\n actualizeEntries: [],\n defineEntitiesByName: new Map(),\n alterEntitiesByName: new Map(),\n instancesByEntity: new Map(),\n instancesByLinkId: new Map(),\n entriesReferencingLink: new Map(),\n entriesUsingEntity: new Map(),\n };\n}\n\n/**\n * Add an item to a map of arrays.\n */\nfunction addToMapArray<K, V>(map: Map<K, V[]>, key: K, value: V): void {\n const arr = map.get(key);\n if (arr) {\n arr.push(value);\n } else {\n map.set(key, [value]);\n }\n}\n\n/**\n * Extract link IDs referenced in an entry's metadata.\n */\nfunction getReferencedLinkIds(entry: Entry): string[] {\n const linkIds: string[] = [];\n\n if (entry.type === \"instance_entry\" || entry.type === \"synthesis_entry\") {\n for (const m of entry.metadata) {\n const content = m.value.content;\n if (content.type === \"link_value\") {\n linkIds.push(content.link.id);\n } else if (content.type === \"value_array\") {\n for (const element of content.elements) {\n if (element.type === \"link\") {\n linkIds.push(element.id);\n }\n }\n }\n }\n } else if (entry.type === \"actualize_entry\") {\n // Actualize target is a reference\n linkIds.push(entry.header.target.id);\n // Plus any metadata links\n for (const m of entry.metadata) {\n const content = m.value.content;\n if (content.type === \"link_value\") {\n linkIds.push(content.link.id);\n } else if (content.type === \"value_array\") {\n for (const element of content.elements) {\n if (element.type === \"link\") {\n linkIds.push(element.id);\n }\n }\n }\n }\n }\n\n return linkIds;\n}\n\n/**\n * Build a WorkspaceIndex from a Workspace in a single pass.\n *\n * This is more efficient than having each rule iterate over all models\n * and entries independently. Rules can query the pre-built index instead.\n */\nexport function buildWorkspaceIndex(workspace: Workspace): WorkspaceIndex {\n const builder = createEmptyBuilder();\n\n // Single iteration over all models and entries\n for (const model of workspace.allModels()) {\n const file = model.file;\n const sourceMap = model.sourceMap;\n\n for (const entry of model.ast.entries) {\n const indexed = { entry, model, file, sourceMap };\n\n // Safe casts below: switch narrows entry.type, so the indexed cast is valid\n switch (entry.type) {\n case \"instance_entry\": {\n const instanceIndexed = indexed as IndexedEntry<InstanceEntry>;\n builder.instanceEntries.push(instanceIndexed);\n\n // Group by entity\n const entityName = entry.header.entity;\n addToMapArray(builder.instancesByEntity, entityName, instanceIndexed);\n addToMapArray(builder.entriesUsingEntity, entityName, indexed as IndexedEntry<Entry>);\n\n // Index by link ID if present\n if (entry.header.link) {\n builder.instancesByLinkId.set(entry.header.link.id, instanceIndexed);\n }\n\n // Track link references\n for (const linkId of getReferencedLinkIds(entry)) {\n addToMapArray(builder.entriesReferencingLink, linkId, indexed as IndexedEntry<Entry>);\n }\n break;\n }\n\n case \"schema_entry\": {\n const schemaIndexed = indexed as IndexedEntry<SchemaEntry>;\n builder.schemaEntries.push(schemaIndexed);\n\n const entityName = entry.header.entityName.value;\n if (entry.header.directive === \"define-entity\") {\n addToMapArray(builder.defineEntitiesByName, entityName, schemaIndexed);\n } else {\n addToMapArray(builder.alterEntitiesByName, entityName, schemaIndexed);\n }\n break;\n }\n\n case \"synthesis_entry\": {\n const synthesisIndexed = indexed as IndexedEntry<SynthesisEntry>;\n builder.synthesisEntries.push(synthesisIndexed);\n\n // Track link references\n for (const linkId of getReferencedLinkIds(entry)) {\n addToMapArray(builder.entriesReferencingLink, linkId, indexed as IndexedEntry<Entry>);\n }\n break;\n }\n\n case \"actualize_entry\": {\n const actualizeIndexed = indexed as IndexedEntry<ActualizeEntry>;\n builder.actualizeEntries.push(actualizeIndexed);\n\n // Track link references (including target)\n for (const linkId of getReferencedLinkIds(entry)) {\n addToMapArray(builder.entriesReferencingLink, linkId, indexed as IndexedEntry<Entry>);\n }\n break;\n }\n }\n }\n }\n\n return builder as WorkspaceIndex;\n}\n\n/**\n * Get instance entries for a specific entity type from the index.\n */\nexport function getInstancesForEntity(\n index: WorkspaceIndex,\n entityName: string,\n): IndexedEntry<InstanceEntry>[] {\n return index.instancesByEntity.get(entityName) ?? [];\n}\n\n/**\n * Get all define-entity entries for a specific entity name.\n */\nexport function getDefineEntriesForEntity(\n index: WorkspaceIndex,\n entityName: string,\n): IndexedEntry<SchemaEntry>[] {\n return index.defineEntitiesByName.get(entityName) ?? [];\n}\n\n/**\n * Get all alter-entity entries for a specific entity name.\n */\nexport function getAlterEntriesForEntity(\n index: WorkspaceIndex,\n entityName: string,\n): IndexedEntry<SchemaEntry>[] {\n return index.alterEntitiesByName.get(entityName) ?? [];\n}\n\n/**\n * Get all entries that reference a specific link ID.\n */\nexport function getEntriesReferencingLink(\n index: WorkspaceIndex,\n linkId: string,\n): IndexedEntry<Entry>[] {\n return index.entriesReferencingLink.get(linkId) ?? [];\n}\n"],"mappings":";;;;AAgFA,SAAS,qBAA4C;AACnD,QAAO;EACL,iBAAiB,EAAE;EACnB,eAAe,EAAE;EACjB,kBAAkB,EAAE;EACpB,kBAAkB,EAAE;EACpB,sCAAsB,IAAI,KAAK;EAC/B,qCAAqB,IAAI,KAAK;EAC9B,mCAAmB,IAAI,KAAK;EAC5B,mCAAmB,IAAI,KAAK;EAC5B,wCAAwB,IAAI,KAAK;EACjC,oCAAoB,IAAI,KAAK;EAC9B;;;;;AAMH,SAAS,cAAoB,KAAkB,KAAQ,OAAgB;CACrE,MAAM,MAAM,IAAI,IAAI,IAAI;AACxB,KAAI,IACF,KAAI,KAAK,MAAM;KAEf,KAAI,IAAI,KAAK,CAAC,MAAM,CAAC;;;;;AAOzB,SAAS,qBAAqB,OAAwB;CACpD,MAAMA,UAAoB,EAAE;AAE5B,KAAI,MAAM,SAAS,oBAAoB,MAAM,SAAS,kBACpD,MAAK,MAAM,KAAK,MAAM,UAAU;EAC9B,MAAM,UAAU,EAAE,MAAM;AACxB,MAAI,QAAQ,SAAS,aACnB,SAAQ,KAAK,QAAQ,KAAK,GAAG;WACpB,QAAQ,SAAS,eAC1B;QAAK,MAAM,WAAW,QAAQ,SAC5B,KAAI,QAAQ,SAAS,OACnB,SAAQ,KAAK,QAAQ,GAAG;;;UAKvB,MAAM,SAAS,mBAAmB;AAE3C,UAAQ,KAAK,MAAM,OAAO,OAAO,GAAG;AAEpC,OAAK,MAAM,KAAK,MAAM,UAAU;GAC9B,MAAM,UAAU,EAAE,MAAM;AACxB,OAAI,QAAQ,SAAS,aACnB,SAAQ,KAAK,QAAQ,KAAK,GAAG;YACpB,QAAQ,SAAS,eAC1B;SAAK,MAAM,WAAW,QAAQ,SAC5B,KAAI,QAAQ,SAAS,OACnB,SAAQ,KAAK,QAAQ,GAAG;;;;AAOlC,QAAO;;;;;;;;AAST,SAAgB,oBAAoB,WAAsC;CACxE,MAAM,UAAU,oBAAoB;AAGpC,MAAK,MAAM,SAAS,UAAU,WAAW,EAAE;EACzC,MAAM,OAAO,MAAM;EACnB,MAAM,YAAY,MAAM;AAExB,OAAK,MAAM,SAAS,MAAM,IAAI,SAAS;GACrC,MAAM,UAAU;IAAE;IAAO;IAAO;IAAM;IAAW;AAGjD,WAAQ,MAAM,MAAd;IACE,KAAK,kBAAkB;KACrB,MAAM,kBAAkB;AACxB,aAAQ,gBAAgB,KAAK,gBAAgB;KAG7C,MAAM,aAAa,MAAM,OAAO;AAChC,mBAAc,QAAQ,mBAAmB,YAAY,gBAAgB;AACrE,mBAAc,QAAQ,oBAAoB,YAAY,QAA+B;AAGrF,SAAI,MAAM,OAAO,KACf,SAAQ,kBAAkB,IAAI,MAAM,OAAO,KAAK,IAAI,gBAAgB;AAItE,UAAK,MAAM,UAAU,qBAAqB,MAAM,CAC9C,eAAc,QAAQ,wBAAwB,QAAQ,QAA+B;AAEvF;;IAGF,KAAK,gBAAgB;KACnB,MAAM,gBAAgB;AACtB,aAAQ,cAAc,KAAK,cAAc;KAEzC,MAAM,aAAa,MAAM,OAAO,WAAW;AAC3C,SAAI,MAAM,OAAO,cAAc,gBAC7B,eAAc,QAAQ,sBAAsB,YAAY,cAAc;SAEtE,eAAc,QAAQ,qBAAqB,YAAY,cAAc;AAEvE;;IAGF,KAAK,mBAAmB;KACtB,MAAM,mBAAmB;AACzB,aAAQ,iBAAiB,KAAK,iBAAiB;AAG/C,UAAK,MAAM,UAAU,qBAAqB,MAAM,CAC9C,eAAc,QAAQ,wBAAwB,QAAQ,QAA+B;AAEvF;;IAGF,KAAK,mBAAmB;KACtB,MAAM,mBAAmB;AACzB,aAAQ,iBAAiB,KAAK,iBAAiB;AAG/C,UAAK,MAAM,UAAU,qBAAqB,MAAM,CAC9C,eAAc,QAAQ,wBAAwB,QAAQ,QAA+B;AAEvF;;;;;AAMR,QAAO"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { Workspace } from "../model/workspace.js";
|
|
2
|
+
import { ChangeMarker, ChangeTracker } from "../services/change-tracker/change-tracker.js";
|
|
3
|
+
|
|
4
|
+
//#region src/commands/actualize.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Default instructions template for actualization.
|
|
8
|
+
* Uses placeholders: {file}, {linkId}, {checkpoint}, {timestamp}
|
|
9
|
+
*/
|
|
10
|
+
declare const DEFAULT_INSTRUCTIONS_TEMPLATE = "1. Update only the synthesis content directly below the ```thalo block in {file}\n2. Do NOT modify the ```thalo block or the define-synthesis entry; the only change inside the block is appending the actualize entry in step 4\n3. Place output BEFORE any subsequent ```thalo blocks\n4. Append to the thalo block: {timestamp} actualize-synthesis ^{linkId}\n with metadata: checkpoint: \"{checkpoint}\"";
|
|
11
|
+
/**
|
|
12
|
+
* Parameters for generating instructions.
|
|
13
|
+
*/
|
|
14
|
+
interface InstructionsParams {
|
|
15
|
+
/** Relative file path */
|
|
16
|
+
file: string;
|
|
17
|
+
/** Link ID for the synthesis */
|
|
18
|
+
linkId: string;
|
|
19
|
+
/** Checkpoint value */
|
|
20
|
+
checkpoint: string;
|
|
21
|
+
/** Current timestamp for the actualize entry */
|
|
22
|
+
timestamp: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Generate instructions from a template with placeholder substitution.
|
|
26
|
+
*/
|
|
27
|
+
declare function generateInstructions(template: string, params: InstructionsParams): string;
|
|
28
|
+
/**
|
|
29
|
+
* Generate a timestamp string suitable for thalo entries.
|
|
30
|
+
* Format: ISO 8601 with minute precision (e.g., "2026-01-13T10:30Z")
|
|
31
|
+
*/
|
|
32
|
+
declare function generateTimestamp(date?: Date): string;
|
|
33
|
+
/**
|
|
34
|
+
* Information about a matching entry for a synthesis.
|
|
35
|
+
*/
|
|
36
|
+
interface ActualizeEntryInfo {
|
|
37
|
+
/** File path containing the entry */
|
|
38
|
+
file: string;
|
|
39
|
+
/** Formatted timestamp string */
|
|
40
|
+
timestamp: string;
|
|
41
|
+
/** Entity type */
|
|
42
|
+
entity: string;
|
|
43
|
+
/** Entry title */
|
|
44
|
+
title: string;
|
|
45
|
+
/** Link ID if present */
|
|
46
|
+
linkId: string | null;
|
|
47
|
+
/** Tags on the entry */
|
|
48
|
+
tags: string[];
|
|
49
|
+
/** Raw source text of the entry */
|
|
50
|
+
rawText: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Information about a synthesis and its pending updates.
|
|
54
|
+
*/
|
|
55
|
+
interface SynthesisOutputInfo {
|
|
56
|
+
/** File path containing the synthesis definition */
|
|
57
|
+
file: string;
|
|
58
|
+
/** Title of the synthesis */
|
|
59
|
+
title: string;
|
|
60
|
+
/** Link ID for the synthesis */
|
|
61
|
+
linkId: string;
|
|
62
|
+
/** Source queries as formatted strings */
|
|
63
|
+
sources: string[];
|
|
64
|
+
/** Last checkpoint marker, or null if never actualized */
|
|
65
|
+
lastCheckpoint: ChangeMarker | null;
|
|
66
|
+
/** The prompt text for the LLM */
|
|
67
|
+
prompt: string | null;
|
|
68
|
+
/** Entries that have changed since last checkpoint */
|
|
69
|
+
entries: ActualizeEntryInfo[];
|
|
70
|
+
/** Current checkpoint marker to store */
|
|
71
|
+
currentCheckpoint: ChangeMarker;
|
|
72
|
+
/** Whether the synthesis is up to date (no new entries) */
|
|
73
|
+
isUpToDate: boolean;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Result of running the actualize command.
|
|
77
|
+
*/
|
|
78
|
+
interface ActualizeResult {
|
|
79
|
+
/** Information about each synthesis in the workspace */
|
|
80
|
+
syntheses: SynthesisOutputInfo[];
|
|
81
|
+
/** Type of change tracker used */
|
|
82
|
+
trackerType: "git" | "ts";
|
|
83
|
+
/** Link IDs that were not found (when filtering) */
|
|
84
|
+
notFoundLinkIds: string[];
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Options for running the actualize command.
|
|
88
|
+
*/
|
|
89
|
+
interface RunActualizeOptions {
|
|
90
|
+
/** Only process syntheses with these link IDs (optional ^ prefix is stripped) */
|
|
91
|
+
targetLinkIds?: string[];
|
|
92
|
+
/**
|
|
93
|
+
* Pre-created change tracker to use.
|
|
94
|
+
* If not provided, a browser-safe TimestampChangeTracker is used.
|
|
95
|
+
* CLI should pass a GitChangeTracker for proper git-based tracking.
|
|
96
|
+
*/
|
|
97
|
+
tracker?: ChangeTracker;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Parse link IDs, stripping optional ^ prefix.
|
|
101
|
+
*/
|
|
102
|
+
declare function parseLinkIds(ids: string[]): string[];
|
|
103
|
+
/**
|
|
104
|
+
* Run the actualize command on a workspace.
|
|
105
|
+
*
|
|
106
|
+
* @param workspace - The workspace containing synthesis definitions
|
|
107
|
+
* @param options - Actualize options
|
|
108
|
+
* @returns Structured actualize results
|
|
109
|
+
*/
|
|
110
|
+
declare function runActualize(workspace: Workspace, options?: RunActualizeOptions): Promise<ActualizeResult>;
|
|
111
|
+
//#endregion
|
|
112
|
+
export { ActualizeEntryInfo, ActualizeResult, DEFAULT_INSTRUCTIONS_TEMPLATE, InstructionsParams, RunActualizeOptions, SynthesisOutputInfo, generateInstructions, generateTimestamp, parseLinkIds, runActualize };
|
|
113
|
+
//# sourceMappingURL=actualize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actualize.d.ts","names":[],"sources":["../../src/commands/actualize.ts"],"sourcesContent":[],"mappings":";;;;;AAqDA;AAYA;AAWA;AAoBA;AAUkB,cA5EL,6BAAA,GA4EK,kZAAA;;;;AAcD,UAjFA,kBAAA,CAmFJ;EAUI;EAcD,IAAA,EAAA,MAAA;EAsFM;EACT,MAAA,EAAA,MAAA;EACF;EACA,UAAA,EAAA,MAAA;EAAR;EAAO,SAAA,EAAA,MAAA;;;;;iBAtLM,oBAAA,2BAA+C;;;;;iBAY/C,iBAAA,QAAwB;;;;UAWvB,kBAAA;;;;;;;;;;;;;;;;;;;UAoBA,mBAAA;;;;;;;;;;kBAUC;;;;WAIP;;qBAEU;;;;;;;UAQJ,eAAA;;aAEJ;;;;;;;;;UAUI,mBAAA;;;;;;;;YAQL;;;;;iBAMI,YAAA;;;;;;;;iBAsFM,YAAA,YACT,qBACF,sBACR,QAAQ"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { formatQuery } from "../services/query.js";
|
|
2
|
+
import { TimestampChangeTracker } from "../services/change-tracker/timestamp-tracker.js";
|
|
3
|
+
import { parseCheckpoint } from "../services/change-tracker/change-tracker.js";
|
|
4
|
+
import { formatTimestamp } from "../formatters.js";
|
|
5
|
+
import { findAllSyntheses, findEntryFile, findLatestActualize, getEntrySourceText } from "../services/synthesis.js";
|
|
6
|
+
|
|
7
|
+
//#region src/commands/actualize.ts
|
|
8
|
+
/**
|
|
9
|
+
* Default instructions template for actualization.
|
|
10
|
+
* Uses placeholders: {file}, {linkId}, {checkpoint}, {timestamp}
|
|
11
|
+
*/
|
|
12
|
+
const DEFAULT_INSTRUCTIONS_TEMPLATE = `1. Update only the synthesis content directly below the \`\`\`thalo block in {file}
|
|
13
|
+
2. Do NOT modify the \`\`\`thalo block or the define-synthesis entry; the only change inside the block is appending the actualize entry in step 4
|
|
14
|
+
3. Place output BEFORE any subsequent \`\`\`thalo blocks
|
|
15
|
+
4. Append to the thalo block: {timestamp} actualize-synthesis ^{linkId}
|
|
16
|
+
with metadata: checkpoint: "{checkpoint}"`;
|
|
17
|
+
/**
|
|
18
|
+
* Generate instructions from a template with placeholder substitution.
|
|
19
|
+
*/
|
|
20
|
+
function generateInstructions(template, params) {
|
|
21
|
+
return template.replace(/\{file\}/g, params.file).replace(/\{linkId\}/g, params.linkId).replace(/\{checkpoint\}/g, params.checkpoint).replace(/\{timestamp\}/g, params.timestamp);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Generate a timestamp string suitable for thalo entries.
|
|
25
|
+
* Format: ISO 8601 with minute precision (e.g., "2026-01-13T10:30Z")
|
|
26
|
+
*/
|
|
27
|
+
function generateTimestamp(date = /* @__PURE__ */ new Date()) {
|
|
28
|
+
return date.toISOString().slice(0, 16) + "Z";
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Parse link IDs, stripping optional ^ prefix.
|
|
32
|
+
*/
|
|
33
|
+
function parseLinkIds(ids) {
|
|
34
|
+
return ids.map((id) => id.startsWith("^") ? id.slice(1) : id);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Get the change marker from an actualize entry.
|
|
38
|
+
* Reads from the checkpoint metadata field.
|
|
39
|
+
*/
|
|
40
|
+
function getActualizeMarker(actualize) {
|
|
41
|
+
if (!actualize) return null;
|
|
42
|
+
return parseCheckpoint(actualize.entry.metadata.find((m) => m.key.value === "checkpoint")?.value.raw);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Convert an InstanceEntry to ActualizeEntryInfo.
|
|
46
|
+
*/
|
|
47
|
+
function toActualizeEntryInfo(entry, file, workspace) {
|
|
48
|
+
const model = workspace.getModel(file);
|
|
49
|
+
const rawText = model ? getEntrySourceText(entry, model.source) : "";
|
|
50
|
+
return {
|
|
51
|
+
file,
|
|
52
|
+
timestamp: formatTimestamp(entry.header.timestamp),
|
|
53
|
+
entity: entry.header.entity,
|
|
54
|
+
title: entry.header.title.value,
|
|
55
|
+
linkId: entry.header.link?.id ?? null,
|
|
56
|
+
tags: entry.header.tags.map((t) => t.name),
|
|
57
|
+
rawText
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Process a single synthesis and get its output info.
|
|
62
|
+
*/
|
|
63
|
+
async function processSynthesis(synthesis, workspace, tracker) {
|
|
64
|
+
const lastCheckpoint = getActualizeMarker(findLatestActualize(workspace, synthesis.linkId));
|
|
65
|
+
const { entries: changedEntries, currentMarker } = await tracker.getChangedEntries(workspace, synthesis.sources, lastCheckpoint);
|
|
66
|
+
const entries = [];
|
|
67
|
+
for (const entry of changedEntries) {
|
|
68
|
+
const file = findEntryFile(workspace, entry);
|
|
69
|
+
if (file) entries.push(toActualizeEntryInfo(entry, file, workspace));
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
file: synthesis.file,
|
|
73
|
+
title: synthesis.title,
|
|
74
|
+
linkId: synthesis.linkId,
|
|
75
|
+
sources: synthesis.sources.map(formatQuery),
|
|
76
|
+
lastCheckpoint,
|
|
77
|
+
prompt: synthesis.prompt,
|
|
78
|
+
entries,
|
|
79
|
+
currentCheckpoint: currentMarker,
|
|
80
|
+
isUpToDate: entries.length === 0
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Run the actualize command on a workspace.
|
|
85
|
+
*
|
|
86
|
+
* @param workspace - The workspace containing synthesis definitions
|
|
87
|
+
* @param options - Actualize options
|
|
88
|
+
* @returns Structured actualize results
|
|
89
|
+
*/
|
|
90
|
+
async function runActualize(workspace, options = {}) {
|
|
91
|
+
const { targetLinkIds, tracker: providedTracker } = options;
|
|
92
|
+
const tracker = providedTracker ?? new TimestampChangeTracker();
|
|
93
|
+
let syntheses = findAllSyntheses(workspace);
|
|
94
|
+
const notFoundLinkIds = [];
|
|
95
|
+
if (targetLinkIds && targetLinkIds.length > 0) {
|
|
96
|
+
const normalizedIds = parseLinkIds(targetLinkIds);
|
|
97
|
+
const filtered = syntheses.filter((s) => normalizedIds.includes(s.linkId));
|
|
98
|
+
const foundIds = new Set(filtered.map((s) => s.linkId));
|
|
99
|
+
for (const id of normalizedIds) if (!foundIds.has(id)) notFoundLinkIds.push(id);
|
|
100
|
+
syntheses = filtered;
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
syntheses: await Promise.all(syntheses.map((s) => processSynthesis(s, workspace, tracker))),
|
|
104
|
+
trackerType: tracker.type,
|
|
105
|
+
notFoundLinkIds
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
//#endregion
|
|
110
|
+
export { DEFAULT_INSTRUCTIONS_TEMPLATE, generateInstructions, generateTimestamp, parseLinkIds, runActualize };
|
|
111
|
+
//# sourceMappingURL=actualize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actualize.js","names":["entries: ActualizeEntryInfo[]","notFoundLinkIds: string[]"],"sources":["../../src/commands/actualize.ts"],"sourcesContent":["/**\n * Actualize command - finds syntheses and returns information about pending updates.\n * Uses git-based or timestamp-based change tracking to identify modified entries.\n */\n\nimport type { Workspace } from \"../model/workspace.js\";\nimport type { InstanceEntry } from \"../ast/ast-types.js\";\nimport {\n findAllSyntheses,\n findLatestActualize,\n findEntryFile,\n getEntrySourceText,\n type SynthesisInfo,\n type ActualizeInfo,\n} from \"../services/synthesis.js\";\nimport { formatQuery } from \"../services/query.js\";\nimport { formatTimestamp } from \"../formatters.js\";\n// Import only browser-safe parts - types and timestamp tracker\n// The git tracker must be passed in from the caller (CLI) to avoid Node.js deps\nimport {\n parseCheckpoint,\n type ChangeTracker,\n type ChangeMarker,\n} from \"../services/change-tracker/change-tracker.js\";\nimport { TimestampChangeTracker } from \"../services/change-tracker/timestamp-tracker.js\";\n\n/**\n * Default instructions template for actualization.\n * Uses placeholders: {file}, {linkId}, {checkpoint}, {timestamp}\n */\nexport const DEFAULT_INSTRUCTIONS_TEMPLATE = `1. Update only the synthesis content directly below the \\`\\`\\`thalo block in {file}\n2. Do NOT modify the \\`\\`\\`thalo block or the define-synthesis entry; the only change inside the block is appending the actualize entry in step 4\n3. Place output BEFORE any subsequent \\`\\`\\`thalo blocks\n4. Append to the thalo block: {timestamp} actualize-synthesis ^{linkId}\n with metadata: checkpoint: \"{checkpoint}\"`;\n\n/**\n * Parameters for generating instructions.\n */\nexport interface InstructionsParams {\n /** Relative file path */\n file: string;\n /** Link ID for the synthesis */\n linkId: string;\n /** Checkpoint value */\n checkpoint: string;\n /** Current timestamp for the actualize entry */\n timestamp: string;\n}\n\n/**\n * Generate instructions from a template with placeholder substitution.\n */\nexport function generateInstructions(template: string, params: InstructionsParams): string {\n return template\n .replace(/\\{file\\}/g, params.file)\n .replace(/\\{linkId\\}/g, params.linkId)\n .replace(/\\{checkpoint\\}/g, params.checkpoint)\n .replace(/\\{timestamp\\}/g, params.timestamp);\n}\n\n/**\n * Generate a timestamp string suitable for thalo entries.\n * Format: ISO 8601 with minute precision (e.g., \"2026-01-13T10:30Z\")\n */\nexport function generateTimestamp(date: Date = new Date()): string {\n return date.toISOString().slice(0, 16) + \"Z\";\n}\n\n// ===================\n// Types\n// ===================\n\n/**\n * Information about a matching entry for a synthesis.\n */\nexport interface ActualizeEntryInfo {\n /** File path containing the entry */\n file: string;\n /** Formatted timestamp string */\n timestamp: string;\n /** Entity type */\n entity: string;\n /** Entry title */\n title: string;\n /** Link ID if present */\n linkId: string | null;\n /** Tags on the entry */\n tags: string[];\n /** Raw source text of the entry */\n rawText: string;\n}\n\n/**\n * Information about a synthesis and its pending updates.\n */\nexport interface SynthesisOutputInfo {\n /** File path containing the synthesis definition */\n file: string;\n /** Title of the synthesis */\n title: string;\n /** Link ID for the synthesis */\n linkId: string;\n /** Source queries as formatted strings */\n sources: string[];\n /** Last checkpoint marker, or null if never actualized */\n lastCheckpoint: ChangeMarker | null;\n /** The prompt text for the LLM */\n prompt: string | null;\n /** Entries that have changed since last checkpoint */\n entries: ActualizeEntryInfo[];\n /** Current checkpoint marker to store */\n currentCheckpoint: ChangeMarker;\n /** Whether the synthesis is up to date (no new entries) */\n isUpToDate: boolean;\n}\n\n/**\n * Result of running the actualize command.\n */\nexport interface ActualizeResult {\n /** Information about each synthesis in the workspace */\n syntheses: SynthesisOutputInfo[];\n /** Type of change tracker used */\n trackerType: \"git\" | \"ts\";\n /** Link IDs that were not found (when filtering) */\n notFoundLinkIds: string[];\n}\n\n/**\n * Options for running the actualize command.\n */\nexport interface RunActualizeOptions {\n /** Only process syntheses with these link IDs (optional ^ prefix is stripped) */\n targetLinkIds?: string[];\n /**\n * Pre-created change tracker to use.\n * If not provided, a browser-safe TimestampChangeTracker is used.\n * CLI should pass a GitChangeTracker for proper git-based tracking.\n */\n tracker?: ChangeTracker;\n}\n\n/**\n * Parse link IDs, stripping optional ^ prefix.\n */\nexport function parseLinkIds(ids: string[]): string[] {\n return ids.map((id) => (id.startsWith(\"^\") ? id.slice(1) : id));\n}\n\n/**\n * Get the change marker from an actualize entry.\n * Reads from the checkpoint metadata field.\n */\nfunction getActualizeMarker(actualize: ActualizeInfo | null): ChangeMarker | null {\n if (!actualize) {\n return null;\n }\n const checkpoint = actualize.entry.metadata.find((m) => m.key.value === \"checkpoint\");\n return parseCheckpoint(checkpoint?.value.raw);\n}\n\n/**\n * Convert an InstanceEntry to ActualizeEntryInfo.\n */\nfunction toActualizeEntryInfo(\n entry: InstanceEntry,\n file: string,\n workspace: Workspace,\n): ActualizeEntryInfo {\n const model = workspace.getModel(file);\n const rawText = model ? getEntrySourceText(entry, model.source) : \"\";\n\n return {\n file,\n timestamp: formatTimestamp(entry.header.timestamp),\n entity: entry.header.entity,\n title: entry.header.title.value,\n linkId: entry.header.link?.id ?? null,\n tags: entry.header.tags.map((t) => t.name),\n rawText,\n };\n}\n\n/**\n * Process a single synthesis and get its output info.\n */\nasync function processSynthesis(\n synthesis: SynthesisInfo,\n workspace: Workspace,\n tracker: ChangeTracker,\n): Promise<SynthesisOutputInfo> {\n // Find latest actualize entry and its checkpoint\n const lastActualize = findLatestActualize(workspace, synthesis.linkId);\n const lastCheckpoint = getActualizeMarker(lastActualize);\n\n // Get changed entries using the tracker\n const { entries: changedEntries, currentMarker } = await tracker.getChangedEntries(\n workspace,\n synthesis.sources,\n lastCheckpoint,\n );\n\n // Convert entries to ActualizeEntryInfo\n const entries: ActualizeEntryInfo[] = [];\n for (const entry of changedEntries) {\n const file = findEntryFile(workspace, entry);\n if (file) {\n entries.push(toActualizeEntryInfo(entry, file, workspace));\n }\n }\n\n return {\n file: synthesis.file,\n title: synthesis.title,\n linkId: synthesis.linkId,\n sources: synthesis.sources.map(formatQuery),\n lastCheckpoint,\n prompt: synthesis.prompt,\n entries,\n currentCheckpoint: currentMarker,\n isUpToDate: entries.length === 0,\n };\n}\n\n/**\n * Run the actualize command on a workspace.\n *\n * @param workspace - The workspace containing synthesis definitions\n * @param options - Actualize options\n * @returns Structured actualize results\n */\nexport async function runActualize(\n workspace: Workspace,\n options: RunActualizeOptions = {},\n): Promise<ActualizeResult> {\n const { targetLinkIds, tracker: providedTracker } = options;\n\n // Use provided tracker or fall back to browser-safe timestamp tracker\n const tracker = providedTracker ?? new TimestampChangeTracker();\n\n // Find all synthesis definitions\n let syntheses = findAllSyntheses(workspace);\n const notFoundLinkIds: string[] = [];\n\n // Filter to targets if specified\n if (targetLinkIds && targetLinkIds.length > 0) {\n const normalizedIds = parseLinkIds(targetLinkIds);\n const filtered = syntheses.filter((s) => normalizedIds.includes(s.linkId));\n\n // Track which IDs weren't found\n const foundIds = new Set(filtered.map((s) => s.linkId));\n for (const id of normalizedIds) {\n if (!foundIds.has(id)) {\n notFoundLinkIds.push(id);\n }\n }\n\n syntheses = filtered;\n }\n\n // Process each synthesis\n const synthesisOutputs: SynthesisOutputInfo[] = await Promise.all(\n syntheses.map((s) => processSynthesis(s, workspace, tracker)),\n );\n\n return {\n syntheses: synthesisOutputs,\n trackerType: tracker.type,\n notFoundLinkIds,\n };\n}\n"],"mappings":";;;;;;;;;;;AA8BA,MAAa,gCAAgC;;;;;;;;AAuB7C,SAAgB,qBAAqB,UAAkB,QAAoC;AACzF,QAAO,SACJ,QAAQ,aAAa,OAAO,KAAK,CACjC,QAAQ,eAAe,OAAO,OAAO,CACrC,QAAQ,mBAAmB,OAAO,WAAW,CAC7C,QAAQ,kBAAkB,OAAO,UAAU;;;;;;AAOhD,SAAgB,kBAAkB,uBAAa,IAAI,MAAM,EAAU;AACjE,QAAO,KAAK,aAAa,CAAC,MAAM,GAAG,GAAG,GAAG;;;;;AAgF3C,SAAgB,aAAa,KAAyB;AACpD,QAAO,IAAI,KAAK,OAAQ,GAAG,WAAW,IAAI,GAAG,GAAG,MAAM,EAAE,GAAG,GAAI;;;;;;AAOjE,SAAS,mBAAmB,WAAsD;AAChF,KAAI,CAAC,UACH,QAAO;AAGT,QAAO,gBADY,UAAU,MAAM,SAAS,MAAM,MAAM,EAAE,IAAI,UAAU,aAAa,EAClD,MAAM,IAAI;;;;;AAM/C,SAAS,qBACP,OACA,MACA,WACoB;CACpB,MAAM,QAAQ,UAAU,SAAS,KAAK;CACtC,MAAM,UAAU,QAAQ,mBAAmB,OAAO,MAAM,OAAO,GAAG;AAElE,QAAO;EACL;EACA,WAAW,gBAAgB,MAAM,OAAO,UAAU;EAClD,QAAQ,MAAM,OAAO;EACrB,OAAO,MAAM,OAAO,MAAM;EAC1B,QAAQ,MAAM,OAAO,MAAM,MAAM;EACjC,MAAM,MAAM,OAAO,KAAK,KAAK,MAAM,EAAE,KAAK;EAC1C;EACD;;;;;AAMH,eAAe,iBACb,WACA,WACA,SAC8B;CAG9B,MAAM,iBAAiB,mBADD,oBAAoB,WAAW,UAAU,OAAO,CACd;CAGxD,MAAM,EAAE,SAAS,gBAAgB,kBAAkB,MAAM,QAAQ,kBAC/D,WACA,UAAU,SACV,eACD;CAGD,MAAMA,UAAgC,EAAE;AACxC,MAAK,MAAM,SAAS,gBAAgB;EAClC,MAAM,OAAO,cAAc,WAAW,MAAM;AAC5C,MAAI,KACF,SAAQ,KAAK,qBAAqB,OAAO,MAAM,UAAU,CAAC;;AAI9D,QAAO;EACL,MAAM,UAAU;EAChB,OAAO,UAAU;EACjB,QAAQ,UAAU;EAClB,SAAS,UAAU,QAAQ,IAAI,YAAY;EAC3C;EACA,QAAQ,UAAU;EAClB;EACA,mBAAmB;EACnB,YAAY,QAAQ,WAAW;EAChC;;;;;;;;;AAUH,eAAsB,aACpB,WACA,UAA+B,EAAE,EACP;CAC1B,MAAM,EAAE,eAAe,SAAS,oBAAoB;CAGpD,MAAM,UAAU,mBAAmB,IAAI,wBAAwB;CAG/D,IAAI,YAAY,iBAAiB,UAAU;CAC3C,MAAMC,kBAA4B,EAAE;AAGpC,KAAI,iBAAiB,cAAc,SAAS,GAAG;EAC7C,MAAM,gBAAgB,aAAa,cAAc;EACjD,MAAM,WAAW,UAAU,QAAQ,MAAM,cAAc,SAAS,EAAE,OAAO,CAAC;EAG1E,MAAM,WAAW,IAAI,IAAI,SAAS,KAAK,MAAM,EAAE,OAAO,CAAC;AACvD,OAAK,MAAM,MAAM,cACf,KAAI,CAAC,SAAS,IAAI,GAAG,CACnB,iBAAgB,KAAK,GAAG;AAI5B,cAAY;;AAQd,QAAO;EACL,WAL8C,MAAM,QAAQ,IAC5D,UAAU,KAAK,MAAM,iBAAiB,GAAG,WAAW,QAAQ,CAAC,CAC9D;EAIC,aAAa,QAAQ;EACrB;EACD"}
|