@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,261 @@
|
|
|
1
|
+
//#region src/semantic/analyzer.ts
|
|
2
|
+
/**
|
|
3
|
+
* Analyze an AST to produce a SemanticModel.
|
|
4
|
+
*
|
|
5
|
+
* This function traverses the AST and builds indexes:
|
|
6
|
+
* - Link definitions (entries with explicit ^linkId)
|
|
7
|
+
* - Link references (^linkId in metadata values or actualize targets)
|
|
8
|
+
* - Schema entries for later resolution by SchemaRegistry
|
|
9
|
+
*/
|
|
10
|
+
function analyze(ast, options) {
|
|
11
|
+
const { file, source, sourceMap, blocks } = options;
|
|
12
|
+
return {
|
|
13
|
+
ast,
|
|
14
|
+
file,
|
|
15
|
+
source,
|
|
16
|
+
sourceMap,
|
|
17
|
+
blocks,
|
|
18
|
+
linkIndex: buildLinkIndex(ast, file),
|
|
19
|
+
schemaEntries: collectSchemaEntries(ast)
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Build a link index from an AST.
|
|
24
|
+
* Iterates over all entries and indexes link definitions and references.
|
|
25
|
+
*/
|
|
26
|
+
function buildLinkIndex(ast, file) {
|
|
27
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
28
|
+
const references = /* @__PURE__ */ new Map();
|
|
29
|
+
for (const entry of ast.entries) indexEntryLinks(entry, file, definitions, references);
|
|
30
|
+
return {
|
|
31
|
+
definitions,
|
|
32
|
+
references
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Index all links from an entry.
|
|
37
|
+
*/
|
|
38
|
+
function indexEntryLinks(entry, file, definitions, references) {
|
|
39
|
+
const linkDef = getEntryLinkDefinition(entry);
|
|
40
|
+
if (linkDef) definitions.set(linkDef.id, {
|
|
41
|
+
id: linkDef.id,
|
|
42
|
+
file,
|
|
43
|
+
location: linkDef.location,
|
|
44
|
+
entry
|
|
45
|
+
});
|
|
46
|
+
if (entry.type === "instance_entry" || entry.type === "synthesis_entry") indexMetadataLinks(entry.metadata, entry, file, references);
|
|
47
|
+
else if (entry.type === "actualize_entry") {
|
|
48
|
+
addReference(references, {
|
|
49
|
+
id: entry.header.target.id,
|
|
50
|
+
file,
|
|
51
|
+
location: entry.header.target.location,
|
|
52
|
+
entry,
|
|
53
|
+
context: "target"
|
|
54
|
+
});
|
|
55
|
+
indexMetadataLinks(entry.metadata, entry, file, references);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Get the link definition from an entry's header (if any).
|
|
60
|
+
*/
|
|
61
|
+
function getEntryLinkDefinition(entry) {
|
|
62
|
+
switch (entry.type) {
|
|
63
|
+
case "instance_entry": return entry.header.link ? {
|
|
64
|
+
id: entry.header.link.id,
|
|
65
|
+
location: entry.header.link.location
|
|
66
|
+
} : null;
|
|
67
|
+
case "schema_entry": return entry.header.link ? {
|
|
68
|
+
id: entry.header.link.id,
|
|
69
|
+
location: entry.header.link.location
|
|
70
|
+
} : null;
|
|
71
|
+
case "synthesis_entry": return {
|
|
72
|
+
id: entry.header.linkId.id,
|
|
73
|
+
location: entry.header.linkId.location
|
|
74
|
+
};
|
|
75
|
+
case "actualize_entry": return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Index link references from metadata values.
|
|
80
|
+
*/
|
|
81
|
+
function indexMetadataLinks(metadata, entry, file, references) {
|
|
82
|
+
for (const m of metadata) {
|
|
83
|
+
const content = m.value.content;
|
|
84
|
+
if (content.type === "link_value") addReference(references, {
|
|
85
|
+
id: content.link.id,
|
|
86
|
+
file,
|
|
87
|
+
location: content.link.location,
|
|
88
|
+
entry,
|
|
89
|
+
context: m.key.value
|
|
90
|
+
});
|
|
91
|
+
else if (content.type === "value_array") {
|
|
92
|
+
for (const element of content.elements) if (element.type === "link") addReference(references, {
|
|
93
|
+
id: element.id,
|
|
94
|
+
file,
|
|
95
|
+
location: element.location,
|
|
96
|
+
entry,
|
|
97
|
+
context: m.key.value
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Add a reference to the references map.
|
|
104
|
+
*/
|
|
105
|
+
function addReference(references, ref) {
|
|
106
|
+
const refs = references.get(ref.id) ?? [];
|
|
107
|
+
refs.push(ref);
|
|
108
|
+
references.set(ref.id, refs);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Collect all schema entries from an AST.
|
|
112
|
+
*/
|
|
113
|
+
function collectSchemaEntries(ast) {
|
|
114
|
+
return ast.entries.filter((e) => e.type === "schema_entry");
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Update a semantic model incrementally when the AST changes.
|
|
118
|
+
*
|
|
119
|
+
* This function compares the old and new ASTs, identifies what changed,
|
|
120
|
+
* and updates the model's indexes accordingly. This is more efficient
|
|
121
|
+
* than rebuilding the entire model from scratch.
|
|
122
|
+
*
|
|
123
|
+
* @param model - The existing semantic model to update
|
|
124
|
+
* @param newAst - The new AST after changes
|
|
125
|
+
* @param newSource - The new source text
|
|
126
|
+
* @param newSourceMap - The new source map
|
|
127
|
+
* @param newBlocks - The new parsed blocks
|
|
128
|
+
* @returns Information about what changed
|
|
129
|
+
*/
|
|
130
|
+
function updateSemanticModel(model, newAst, newSource, newSourceMap, newBlocks) {
|
|
131
|
+
const oldAst = model.ast;
|
|
132
|
+
const file = model.file;
|
|
133
|
+
const result = {
|
|
134
|
+
addedLinkDefinitions: [],
|
|
135
|
+
removedLinkDefinitions: [],
|
|
136
|
+
changedLinkReferences: [],
|
|
137
|
+
schemaEntriesChanged: false,
|
|
138
|
+
changedEntityNames: []
|
|
139
|
+
};
|
|
140
|
+
const oldEntryKeys = new Set(oldAst.entries.map(entryKey));
|
|
141
|
+
const newEntryKeys = new Set(newAst.entries.map(entryKey));
|
|
142
|
+
const addedEntries = [];
|
|
143
|
+
const removedEntries = [];
|
|
144
|
+
for (const entry of oldAst.entries) if (!newEntryKeys.has(entryKey(entry))) removedEntries.push(entry);
|
|
145
|
+
for (const entry of newAst.entries) if (!oldEntryKeys.has(entryKey(entry))) addedEntries.push(entry);
|
|
146
|
+
const linkResult = updateLinkIndex(model.linkIndex, file, removedEntries, addedEntries);
|
|
147
|
+
result.addedLinkDefinitions = linkResult.addedDefinitions;
|
|
148
|
+
result.removedLinkDefinitions = linkResult.removedDefinitions;
|
|
149
|
+
result.changedLinkReferences = linkResult.changedReferences;
|
|
150
|
+
const oldSchemaEntries = model.schemaEntries;
|
|
151
|
+
const newSchemaEntries = collectSchemaEntries(newAst);
|
|
152
|
+
if (!schemaEntriesEqual(oldSchemaEntries, newSchemaEntries)) {
|
|
153
|
+
result.schemaEntriesChanged = true;
|
|
154
|
+
const oldEntityNames = new Set(oldSchemaEntries.map((e) => e.header.entityName.value));
|
|
155
|
+
const newEntityNames = new Set(newSchemaEntries.map((e) => e.header.entityName.value));
|
|
156
|
+
for (const name of oldEntityNames) if (!newEntityNames.has(name)) result.changedEntityNames.push(name);
|
|
157
|
+
for (const name of newEntityNames) if (!oldEntityNames.has(name)) result.changedEntityNames.push(name);
|
|
158
|
+
for (const oldEntry of oldSchemaEntries) {
|
|
159
|
+
const newEntry = newSchemaEntries.find((e) => e.header.entityName.value === oldEntry.header.entityName.value);
|
|
160
|
+
if (newEntry && !schemaEntryEqual(oldEntry, newEntry)) {
|
|
161
|
+
if (!result.changedEntityNames.includes(oldEntry.header.entityName.value)) result.changedEntityNames.push(oldEntry.header.entityName.value);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
model.ast = newAst;
|
|
166
|
+
model.source = newSource;
|
|
167
|
+
model.sourceMap = newSourceMap;
|
|
168
|
+
model.blocks = newBlocks;
|
|
169
|
+
model.schemaEntries = newSchemaEntries;
|
|
170
|
+
model.dirty = {
|
|
171
|
+
linkIndex: false,
|
|
172
|
+
schemaEntries: false
|
|
173
|
+
};
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Create a unique key for an entry based on its type and position.
|
|
178
|
+
*/
|
|
179
|
+
function entryKey(entry) {
|
|
180
|
+
return `${entry.type}:${entry.location.startIndex}:${entry.location.endIndex}`;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Update the link index based on removed and added entries.
|
|
184
|
+
*/
|
|
185
|
+
function updateLinkIndex(linkIndex, file, removedEntries, addedEntries) {
|
|
186
|
+
const addedDefinitions = [];
|
|
187
|
+
const removedDefinitions = [];
|
|
188
|
+
const changedReferences = /* @__PURE__ */ new Set();
|
|
189
|
+
for (const entry of removedEntries) {
|
|
190
|
+
const linkDef = getEntryLinkDefinition(entry);
|
|
191
|
+
if (linkDef) {
|
|
192
|
+
linkIndex.definitions.delete(linkDef.id);
|
|
193
|
+
removedDefinitions.push(linkDef.id);
|
|
194
|
+
}
|
|
195
|
+
removeEntryReferences(linkIndex.references, entry, changedReferences);
|
|
196
|
+
}
|
|
197
|
+
for (const entry of addedEntries) {
|
|
198
|
+
indexEntryLinks(entry, file, linkIndex.definitions, linkIndex.references);
|
|
199
|
+
const linkDef = getEntryLinkDefinition(entry);
|
|
200
|
+
if (linkDef) addedDefinitions.push(linkDef.id);
|
|
201
|
+
trackEntryReferences(entry, changedReferences);
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
addedDefinitions,
|
|
205
|
+
removedDefinitions,
|
|
206
|
+
changedReferences: Array.from(changedReferences)
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Remove all references from a specific entry.
|
|
211
|
+
*/
|
|
212
|
+
function removeEntryReferences(references, entry, changedReferences) {
|
|
213
|
+
for (const [linkId, refs] of references) {
|
|
214
|
+
const filtered = refs.filter((ref) => ref.entry !== entry);
|
|
215
|
+
if (filtered.length !== refs.length) {
|
|
216
|
+
changedReferences.add(linkId);
|
|
217
|
+
if (filtered.length === 0) references.delete(linkId);
|
|
218
|
+
else references.set(linkId, filtered);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Track which link IDs are referenced by an entry.
|
|
224
|
+
*/
|
|
225
|
+
function trackEntryReferences(entry, changedReferences) {
|
|
226
|
+
if (entry.type === "instance_entry" || entry.type === "synthesis_entry") for (const m of entry.metadata) {
|
|
227
|
+
const content = m.value.content;
|
|
228
|
+
if (content.type === "link_value") changedReferences.add(content.link.id);
|
|
229
|
+
else if (content.type === "value_array") {
|
|
230
|
+
for (const element of content.elements) if (element.type === "link") changedReferences.add(element.id);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
else if (entry.type === "actualize_entry") {
|
|
234
|
+
changedReferences.add(entry.header.target.id);
|
|
235
|
+
for (const m of entry.metadata) {
|
|
236
|
+
const content = m.value.content;
|
|
237
|
+
if (content.type === "link_value") changedReferences.add(content.link.id);
|
|
238
|
+
else if (content.type === "value_array") {
|
|
239
|
+
for (const element of content.elements) if (element.type === "link") changedReferences.add(element.id);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Check if two arrays of schema entries are equal.
|
|
246
|
+
*/
|
|
247
|
+
function schemaEntriesEqual(a, b) {
|
|
248
|
+
if (a.length !== b.length) return false;
|
|
249
|
+
for (let i = 0; i < a.length; i++) if (!schemaEntryEqual(a[i], b[i])) return false;
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Check if two schema entries are equal (by entity name and directive).
|
|
254
|
+
*/
|
|
255
|
+
function schemaEntryEqual(a, b) {
|
|
256
|
+
return a.header.entityName.value === b.header.entityName.value && a.header.directive === b.header.directive && a.location.startIndex === b.location.startIndex && a.location.endIndex === b.location.endIndex;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
//#endregion
|
|
260
|
+
export { analyze, updateSemanticModel };
|
|
261
|
+
//# sourceMappingURL=analyzer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzer.js","names":["result: SemanticUpdateResult","addedEntries: Entry[]","removedEntries: Entry[]","addedDefinitions: string[]","removedDefinitions: string[]"],"sources":["../../src/semantic/analyzer.ts"],"sourcesContent":["import type { SourceFile, Entry, SchemaEntry, Metadata, Location } from \"../ast/ast-types.js\";\nimport type { SourceMap } from \"../source-map.js\";\nimport type { ParsedBlock, GenericTree } from \"../parser.shared.js\";\n\n// ===================\n// Link Index Types\n// ===================\n\n/**\n * A link definition in an entry header.\n * Created when an entry has an explicit ^linkId in its header.\n */\nexport interface LinkDefinition {\n /** The link ID (without ^ prefix) */\n id: string;\n /** The file path containing this definition */\n file: string;\n /** Location of the link in the source */\n location: Location;\n /** The entry that defines this link */\n entry: Entry;\n}\n\n/**\n * A link reference in metadata or as an actualize target.\n * Created when an entry references another entry via ^linkId.\n */\nexport interface LinkReference {\n /** The link ID (without ^ prefix) */\n id: string;\n /** The file path containing this reference */\n file: string;\n /** Location of the link in the source */\n location: Location;\n /** The entry containing this reference */\n entry: Entry;\n /** The context where this reference appears (metadata key or \"target\") */\n context: string;\n}\n\n/**\n * Index for link definitions and references within a document.\n */\nexport interface LinkIndex {\n /** Map from link ID to its definition (if any) */\n definitions: Map<string, LinkDefinition>;\n /** Map from link ID to all references */\n references: Map<string, LinkReference[]>;\n}\n\n// ===================\n// Semantic Model\n// ===================\n\n/**\n * A semantic model for a single document.\n * Contains the AST plus indexes built during semantic analysis.\n *\n * This is the per-document semantic representation. The Workspace\n * aggregates multiple SemanticModels and combines their indexes.\n */\nexport interface SemanticModel {\n /** The AST for this document */\n ast: SourceFile;\n /** The file path */\n file: string;\n /** The original source text */\n source: string;\n /** Source map for position translation (block-relative to file-absolute) */\n sourceMap: SourceMap;\n /** The parsed blocks (containing tree-sitter trees) for CST operations */\n blocks: ParsedBlock<GenericTree>[];\n\n /**\n * Link index for this document.\n * Contains definitions and references found in this document only.\n */\n linkIndex: LinkIndex;\n\n /**\n * Schema entries from this document.\n * These are used by the SchemaRegistry at the workspace level\n * to build resolved entity schemas.\n */\n schemaEntries: SchemaEntry[];\n\n /**\n * Dirty flags for incremental updates.\n * When true, the corresponding index needs to be rebuilt.\n */\n dirty?: SemanticModelDirtyFlags;\n}\n\n/**\n * Dirty flags indicating which parts of the semantic model need rebuilding.\n */\nexport interface SemanticModelDirtyFlags {\n /** Link index needs rebuild (definitions or references changed) */\n linkIndex: boolean;\n /** Schema entries need re-extraction (schema entries changed) */\n schemaEntries: boolean;\n}\n\n/**\n * Result of an incremental semantic model update.\n */\nexport interface SemanticUpdateResult {\n /** Link IDs that were added (new definitions) */\n addedLinkDefinitions: string[];\n /** Link IDs that were removed (deleted definitions) */\n removedLinkDefinitions: string[];\n /** Link IDs whose references changed */\n changedLinkReferences: string[];\n /** Whether schema entries changed */\n schemaEntriesChanged: boolean;\n /** Entity names whose schema definitions changed */\n changedEntityNames: string[];\n}\n\n/**\n * Options for analyzing a document\n */\nexport interface AnalyzeOptions {\n /** The file path for this document */\n file: string;\n /** The original source text */\n source: string;\n /** Source map for position translation */\n sourceMap: SourceMap;\n /** The parsed blocks (containing tree-sitter trees) */\n blocks: ParsedBlock<GenericTree>[];\n}\n\n/**\n * Analyze an AST to produce a SemanticModel.\n *\n * This function traverses the AST and builds indexes:\n * - Link definitions (entries with explicit ^linkId)\n * - Link references (^linkId in metadata values or actualize targets)\n * - Schema entries for later resolution by SchemaRegistry\n */\nexport function analyze(ast: SourceFile, options: AnalyzeOptions): SemanticModel {\n const { file, source, sourceMap, blocks } = options;\n\n return {\n ast,\n file,\n source,\n sourceMap,\n blocks,\n linkIndex: buildLinkIndex(ast, file),\n schemaEntries: collectSchemaEntries(ast),\n };\n}\n\n/**\n * Build a link index from an AST.\n * Iterates over all entries and indexes link definitions and references.\n */\nfunction buildLinkIndex(ast: SourceFile, file: string): LinkIndex {\n const definitions = new Map<string, LinkDefinition>();\n const references = new Map<string, LinkReference[]>();\n\n for (const entry of ast.entries) {\n indexEntryLinks(entry, file, definitions, references);\n }\n\n return { definitions, references };\n}\n\n/**\n * Index all links from an entry.\n */\nfunction indexEntryLinks(\n entry: Entry,\n file: string,\n definitions: Map<string, LinkDefinition>,\n references: Map<string, LinkReference[]>,\n): void {\n // Index header link definition\n const linkDef = getEntryLinkDefinition(entry);\n if (linkDef) {\n definitions.set(linkDef.id, {\n id: linkDef.id,\n file,\n location: linkDef.location,\n entry,\n });\n }\n\n // Index references based on entry type\n if (entry.type === \"instance_entry\" || entry.type === \"synthesis_entry\") {\n indexMetadataLinks(entry.metadata, entry, file, references);\n } else if (entry.type === \"actualize_entry\") {\n // Actualize target is a reference\n addReference(references, {\n id: entry.header.target.id,\n file,\n location: entry.header.target.location,\n entry,\n context: \"target\",\n });\n indexMetadataLinks(entry.metadata, entry, file, references);\n }\n}\n\n/**\n * Get the link definition from an entry's header (if any).\n */\nfunction getEntryLinkDefinition(\n entry: Entry,\n): { id: string; location: import(\"../ast/ast-types.js\").Location } | null {\n switch (entry.type) {\n case \"instance_entry\":\n return entry.header.link\n ? { id: entry.header.link.id, location: entry.header.link.location }\n : null;\n case \"schema_entry\":\n return entry.header.link\n ? { id: entry.header.link.id, location: entry.header.link.location }\n : null;\n case \"synthesis_entry\":\n return { id: entry.header.linkId.id, location: entry.header.linkId.location };\n case \"actualize_entry\":\n return null; // Actualize entries don't define links\n }\n}\n\n/**\n * Index link references from metadata values.\n */\nfunction indexMetadataLinks(\n metadata: Metadata[],\n entry: Entry,\n file: string,\n references: Map<string, LinkReference[]>,\n): void {\n for (const m of metadata) {\n const content = m.value.content;\n\n if (content.type === \"link_value\") {\n addReference(references, {\n id: content.link.id,\n file,\n location: content.link.location,\n entry,\n context: m.key.value,\n });\n } else if (content.type === \"value_array\") {\n for (const element of content.elements) {\n if (element.type === \"link\") {\n addReference(references, {\n id: element.id,\n file,\n location: element.location,\n entry,\n context: m.key.value,\n });\n }\n }\n }\n }\n}\n\n/**\n * Add a reference to the references map.\n */\nfunction addReference(references: Map<string, LinkReference[]>, ref: LinkReference): void {\n const refs = references.get(ref.id) ?? [];\n refs.push(ref);\n references.set(ref.id, refs);\n}\n\n/**\n * Collect all schema entries from an AST.\n */\nfunction collectSchemaEntries(ast: SourceFile): SchemaEntry[] {\n return ast.entries.filter((e): e is SchemaEntry => e.type === \"schema_entry\");\n}\n\n// ===================\n// Incremental Updates\n// ===================\n\n/**\n * Update a semantic model incrementally when the AST changes.\n *\n * This function compares the old and new ASTs, identifies what changed,\n * and updates the model's indexes accordingly. This is more efficient\n * than rebuilding the entire model from scratch.\n *\n * @param model - The existing semantic model to update\n * @param newAst - The new AST after changes\n * @param newSource - The new source text\n * @param newSourceMap - The new source map\n * @param newBlocks - The new parsed blocks\n * @returns Information about what changed\n */\nexport function updateSemanticModel(\n model: SemanticModel,\n newAst: SourceFile,\n newSource: string,\n newSourceMap: SourceMap,\n newBlocks: ParsedBlock<GenericTree>[],\n): SemanticUpdateResult {\n const oldAst = model.ast;\n const file = model.file;\n\n // Track what changed\n const result: SemanticUpdateResult = {\n addedLinkDefinitions: [],\n removedLinkDefinitions: [],\n changedLinkReferences: [],\n schemaEntriesChanged: false,\n changedEntityNames: [],\n };\n\n // Build entry sets for comparison (using position as identity)\n const oldEntryKeys = new Set(oldAst.entries.map(entryKey));\n const newEntryKeys = new Set(newAst.entries.map(entryKey));\n\n // Find added and removed entries\n const addedEntries: Entry[] = [];\n const removedEntries: Entry[] = [];\n\n for (const entry of oldAst.entries) {\n if (!newEntryKeys.has(entryKey(entry))) {\n removedEntries.push(entry);\n }\n }\n\n for (const entry of newAst.entries) {\n if (!oldEntryKeys.has(entryKey(entry))) {\n addedEntries.push(entry);\n }\n }\n\n // Update link index\n const linkResult = updateLinkIndex(model.linkIndex, file, removedEntries, addedEntries);\n result.addedLinkDefinitions = linkResult.addedDefinitions;\n result.removedLinkDefinitions = linkResult.removedDefinitions;\n result.changedLinkReferences = linkResult.changedReferences;\n\n // Update schema entries\n const oldSchemaEntries = model.schemaEntries;\n const newSchemaEntries = collectSchemaEntries(newAst);\n\n // Check if schema entries changed\n if (!schemaEntriesEqual(oldSchemaEntries, newSchemaEntries)) {\n result.schemaEntriesChanged = true;\n\n // Find changed entity names\n const oldEntityNames = new Set(oldSchemaEntries.map((e) => e.header.entityName.value));\n const newEntityNames = new Set(newSchemaEntries.map((e) => e.header.entityName.value));\n\n for (const name of oldEntityNames) {\n if (!newEntityNames.has(name)) {\n result.changedEntityNames.push(name);\n }\n }\n for (const name of newEntityNames) {\n if (!oldEntityNames.has(name)) {\n result.changedEntityNames.push(name);\n }\n }\n\n // Also check for modified entries (same name but different content)\n for (const oldEntry of oldSchemaEntries) {\n const newEntry = newSchemaEntries.find(\n (e) => e.header.entityName.value === oldEntry.header.entityName.value,\n );\n if (newEntry && !schemaEntryEqual(oldEntry, newEntry)) {\n if (!result.changedEntityNames.includes(oldEntry.header.entityName.value)) {\n result.changedEntityNames.push(oldEntry.header.entityName.value);\n }\n }\n }\n }\n\n // Update the model in place\n model.ast = newAst;\n model.source = newSource;\n model.sourceMap = newSourceMap;\n model.blocks = newBlocks;\n model.schemaEntries = newSchemaEntries;\n model.dirty = {\n linkIndex: false,\n schemaEntries: false,\n };\n\n return result;\n}\n\n/**\n * Create a unique key for an entry based on its type and position.\n */\nfunction entryKey(entry: Entry): string {\n return `${entry.type}:${entry.location.startIndex}:${entry.location.endIndex}`;\n}\n\n/**\n * Update the link index based on removed and added entries.\n */\nfunction updateLinkIndex(\n linkIndex: LinkIndex,\n file: string,\n removedEntries: Entry[],\n addedEntries: Entry[],\n): { addedDefinitions: string[]; removedDefinitions: string[]; changedReferences: string[] } {\n const addedDefinitions: string[] = [];\n const removedDefinitions: string[] = [];\n const changedReferences = new Set<string>();\n\n // Remove links from removed entries\n for (const entry of removedEntries) {\n const linkDef = getEntryLinkDefinition(entry);\n if (linkDef) {\n linkIndex.definitions.delete(linkDef.id);\n removedDefinitions.push(linkDef.id);\n }\n\n // Remove references from this entry\n removeEntryReferences(linkIndex.references, entry, changedReferences);\n }\n\n // Add links from added entries\n for (const entry of addedEntries) {\n indexEntryLinks(entry, file, linkIndex.definitions, linkIndex.references);\n\n const linkDef = getEntryLinkDefinition(entry);\n if (linkDef) {\n addedDefinitions.push(linkDef.id);\n }\n\n // Track changed references\n trackEntryReferences(entry, changedReferences);\n }\n\n return {\n addedDefinitions,\n removedDefinitions,\n changedReferences: Array.from(changedReferences),\n };\n}\n\n/**\n * Remove all references from a specific entry.\n */\nfunction removeEntryReferences(\n references: Map<string, LinkReference[]>,\n entry: Entry,\n changedReferences: Set<string>,\n): void {\n for (const [linkId, refs] of references) {\n const filtered = refs.filter((ref) => ref.entry !== entry);\n if (filtered.length !== refs.length) {\n changedReferences.add(linkId);\n if (filtered.length === 0) {\n references.delete(linkId);\n } else {\n references.set(linkId, filtered);\n }\n }\n }\n}\n\n/**\n * Track which link IDs are referenced by an entry.\n */\nfunction trackEntryReferences(entry: Entry, changedReferences: Set<string>): void {\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 changedReferences.add(content.link.id);\n } else if (content.type === \"value_array\") {\n for (const element of content.elements) {\n if (element.type === \"link\") {\n changedReferences.add(element.id);\n }\n }\n }\n }\n } else if (entry.type === \"actualize_entry\") {\n changedReferences.add(entry.header.target.id);\n for (const m of entry.metadata) {\n const content = m.value.content;\n if (content.type === \"link_value\") {\n changedReferences.add(content.link.id);\n } else if (content.type === \"value_array\") {\n for (const element of content.elements) {\n if (element.type === \"link\") {\n changedReferences.add(element.id);\n }\n }\n }\n }\n }\n}\n\n/**\n * Check if two arrays of schema entries are equal.\n */\nfunction schemaEntriesEqual(a: SchemaEntry[], b: SchemaEntry[]): boolean {\n if (a.length !== b.length) {\n return false;\n }\n for (let i = 0; i < a.length; i++) {\n if (!schemaEntryEqual(a[i], b[i])) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * Check if two schema entries are equal (by entity name and directive).\n */\nfunction schemaEntryEqual(a: SchemaEntry, b: SchemaEntry): boolean {\n return (\n a.header.entityName.value === b.header.entityName.value &&\n a.header.directive === b.header.directive &&\n a.location.startIndex === b.location.startIndex &&\n a.location.endIndex === b.location.endIndex\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { InstanceEntry } from "../../ast/ast-types.js";
|
|
2
|
+
import { Workspace } from "../../model/workspace.js";
|
|
3
|
+
import { Query } from "../query.js";
|
|
4
|
+
import { TimestampChangeTracker } from "./timestamp-tracker.js";
|
|
5
|
+
|
|
6
|
+
//#region src/services/change-tracker/change-tracker.d.ts
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Known checkpoint provider types.
|
|
10
|
+
*
|
|
11
|
+
* - "git": Git commit hash
|
|
12
|
+
* - "ts": ISO timestamp
|
|
13
|
+
*
|
|
14
|
+
* This is extensible - future providers could include "db" for database, etc.
|
|
15
|
+
*/
|
|
16
|
+
type CheckpointProvider = "git" | "ts";
|
|
17
|
+
/**
|
|
18
|
+
* A marker representing a point in time for change tracking.
|
|
19
|
+
*
|
|
20
|
+
* Stored as `checkpoint: "git:abc123"` or `checkpoint: "ts:2026-01-08T15:00Z"`.
|
|
21
|
+
*/
|
|
22
|
+
interface ChangeMarker {
|
|
23
|
+
/** Provider type (git, ts, etc.) */
|
|
24
|
+
type: CheckpointProvider;
|
|
25
|
+
/** The marker value - commit hash or ISO timestamp */
|
|
26
|
+
value: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Result of getting changed entries, including the marker to store
|
|
30
|
+
*/
|
|
31
|
+
interface ChangedEntriesResult {
|
|
32
|
+
/** Entries that have changed since the marker */
|
|
33
|
+
entries: InstanceEntry[];
|
|
34
|
+
/** The current marker to store for next comparison */
|
|
35
|
+
currentMarker: ChangeMarker;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Options for creating a ChangeTracker
|
|
39
|
+
*/
|
|
40
|
+
interface ChangeTrackerOptions {
|
|
41
|
+
/** Working directory for git operations */
|
|
42
|
+
cwd?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Force operation even if there are uncommitted changes.
|
|
45
|
+
* When false (default), git tracker will error if source files have uncommitted changes.
|
|
46
|
+
*/
|
|
47
|
+
force?: boolean;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Error thrown when source files have uncommitted changes.
|
|
51
|
+
*
|
|
52
|
+
* This prevents incorrect change tracking since uncommitted changes
|
|
53
|
+
* are not captured in the checkpoint.
|
|
54
|
+
*/
|
|
55
|
+
declare class UncommittedChangesError extends Error {
|
|
56
|
+
/** Files with uncommitted changes */
|
|
57
|
+
readonly files: string[];
|
|
58
|
+
constructor(files: string[]);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Interface for tracking changes to entries.
|
|
62
|
+
*
|
|
63
|
+
* Implementations can use different backends (git, timestamps, database, etc.)
|
|
64
|
+
* to determine which entries have changed since a marker point.
|
|
65
|
+
*/
|
|
66
|
+
interface ChangeTracker {
|
|
67
|
+
/**
|
|
68
|
+
* Type of tracker - matches the checkpoint provider type.
|
|
69
|
+
*/
|
|
70
|
+
readonly type: CheckpointProvider;
|
|
71
|
+
/**
|
|
72
|
+
* Get the current position marker (HEAD commit or current time).
|
|
73
|
+
*
|
|
74
|
+
* @returns Current marker to store for future comparisons
|
|
75
|
+
*/
|
|
76
|
+
getCurrentMarker(): Promise<ChangeMarker>;
|
|
77
|
+
/**
|
|
78
|
+
* Get entries that have changed since a marker.
|
|
79
|
+
*
|
|
80
|
+
* For git tracker: Finds files modified since the commit, parses them,
|
|
81
|
+
* and compares entry content to identify changed entries.
|
|
82
|
+
*
|
|
83
|
+
* For timestamp tracker: Filters entries with timestamps after the marker.
|
|
84
|
+
*
|
|
85
|
+
* @param workspace - The workspace containing current entries
|
|
86
|
+
* @param queries - Queries to filter which entries to consider
|
|
87
|
+
* @param marker - The marker to compare against (from previous actualization)
|
|
88
|
+
* @returns Changed entries and the current marker to store
|
|
89
|
+
*/
|
|
90
|
+
getChangedEntries(workspace: Workspace, queries: Query[], marker: ChangeMarker | null): Promise<ChangedEntriesResult>;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Parse a checkpoint value into a ChangeMarker.
|
|
94
|
+
*
|
|
95
|
+
* Expected format: "git:abc123" or "ts:2026-01-08T15:00Z"
|
|
96
|
+
* Strips quotes from the raw value as metadata raw values include quotes.
|
|
97
|
+
*
|
|
98
|
+
* @param checkpointRaw - Raw checkpoint metadata value (may include quotes)
|
|
99
|
+
* @returns Parsed marker or null if invalid/missing
|
|
100
|
+
*/
|
|
101
|
+
declare function parseCheckpoint(checkpointRaw: string | undefined): ChangeMarker | null;
|
|
102
|
+
/**
|
|
103
|
+
* Format a ChangeMarker as a checkpoint string.
|
|
104
|
+
*
|
|
105
|
+
* @param marker - The marker to format
|
|
106
|
+
* @returns Formatted string like "git:abc123" or "ts:2026-01-08T15:00Z"
|
|
107
|
+
*/
|
|
108
|
+
declare function formatCheckpoint(marker: ChangeMarker): string;
|
|
109
|
+
//#endregion
|
|
110
|
+
export { ChangeMarker, ChangeTracker, ChangeTrackerOptions, ChangedEntriesResult, CheckpointProvider, TimestampChangeTracker, UncommittedChangesError, formatCheckpoint, parseCheckpoint };
|
|
111
|
+
//# sourceMappingURL=change-tracker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"change-tracker.d.ts","names":[],"sources":["../../../src/services/change-tracker/change-tracker.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;AAaA;AAOA;AAUA;AAUA;AAgBA;AAoBiB,KA/DL,kBAAA,GA+DkB,KAAA,GAAA,IAAA;;;;;;AA6BlB,UArFK,YAAA,CAqFL;EACC;EAAR,IAAA,EApFG,kBAoFH;EAAO;EA0BI,KAAA,EAAA,MAAA;AAiChB;;;;UAvIiB,oBAAA;;WAEN;;iBAEM;;;;;UAMA,oBAAA;;;;;;;;;;;;;;;cAgBJ,uBAAA,SAAgC,KAAA;;;;;;;;;;;UAoB5B,aAAA;;;;iBAIA;;;;;;sBAOK,QAAQ;;;;;;;;;;;;;;+BAgBf,oBACF,iBACD,sBACP,QAAQ;;;;;;;;;;;iBA0BG,eAAA,qCAAoD;;;;;;;iBAiCpD,gBAAA,SAAyB"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { TimestampChangeTracker } from "./timestamp-tracker.js";
|
|
2
|
+
|
|
3
|
+
//#region src/services/change-tracker/change-tracker.ts
|
|
4
|
+
/**
|
|
5
|
+
* Error thrown when source files have uncommitted changes.
|
|
6
|
+
*
|
|
7
|
+
* This prevents incorrect change tracking since uncommitted changes
|
|
8
|
+
* are not captured in the checkpoint.
|
|
9
|
+
*/
|
|
10
|
+
var UncommittedChangesError = class extends Error {
|
|
11
|
+
/** Files with uncommitted changes */
|
|
12
|
+
files;
|
|
13
|
+
constructor(files) {
|
|
14
|
+
super(`Source files have uncommitted changes: ${files.join(", ")}. Commit your changes or use --force to proceed anyway.`);
|
|
15
|
+
this.name = "UncommittedChangesError";
|
|
16
|
+
this.files = files;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Strip quotes from a string value if present.
|
|
21
|
+
* Handles both single and double quotes.
|
|
22
|
+
*/
|
|
23
|
+
function stripQuotes(value) {
|
|
24
|
+
if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Parse a checkpoint value into a ChangeMarker.
|
|
29
|
+
*
|
|
30
|
+
* Expected format: "git:abc123" or "ts:2026-01-08T15:00Z"
|
|
31
|
+
* Strips quotes from the raw value as metadata raw values include quotes.
|
|
32
|
+
*
|
|
33
|
+
* @param checkpointRaw - Raw checkpoint metadata value (may include quotes)
|
|
34
|
+
* @returns Parsed marker or null if invalid/missing
|
|
35
|
+
*/
|
|
36
|
+
function parseCheckpoint(checkpointRaw) {
|
|
37
|
+
if (!checkpointRaw) return null;
|
|
38
|
+
const value = stripQuotes(checkpointRaw);
|
|
39
|
+
const colonIndex = value.indexOf(":");
|
|
40
|
+
if (colonIndex === -1) return null;
|
|
41
|
+
const provider = value.slice(0, colonIndex);
|
|
42
|
+
const markerValue = value.slice(colonIndex + 1);
|
|
43
|
+
if (!markerValue) return null;
|
|
44
|
+
if (provider === "git" || provider === "ts") return {
|
|
45
|
+
type: provider,
|
|
46
|
+
value: markerValue
|
|
47
|
+
};
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Format a ChangeMarker as a checkpoint string.
|
|
52
|
+
*
|
|
53
|
+
* @param marker - The marker to format
|
|
54
|
+
* @returns Formatted string like "git:abc123" or "ts:2026-01-08T15:00Z"
|
|
55
|
+
*/
|
|
56
|
+
function formatCheckpoint(marker) {
|
|
57
|
+
return `${marker.type}:${marker.value}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
//#endregion
|
|
61
|
+
export { TimestampChangeTracker, UncommittedChangesError, formatCheckpoint, parseCheckpoint };
|
|
62
|
+
//# sourceMappingURL=change-tracker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"change-tracker.js","names":[],"sources":["../../../src/services/change-tracker/change-tracker.ts"],"sourcesContent":["import type { InstanceEntry } from \"../../ast/ast-types.js\";\nimport type { Query } from \"../query.js\";\nimport type { Workspace } from \"../../model/workspace.js\";\nimport { TimestampChangeTracker } from \"./timestamp-tracker.js\";\n\n/**\n * Known checkpoint provider types.\n *\n * - \"git\": Git commit hash\n * - \"ts\": ISO timestamp\n *\n * This is extensible - future providers could include \"db\" for database, etc.\n */\nexport type CheckpointProvider = \"git\" | \"ts\";\n\n/**\n * A marker representing a point in time for change tracking.\n *\n * Stored as `checkpoint: \"git:abc123\"` or `checkpoint: \"ts:2026-01-08T15:00Z\"`.\n */\nexport interface ChangeMarker {\n /** Provider type (git, ts, etc.) */\n type: CheckpointProvider;\n /** The marker value - commit hash or ISO timestamp */\n value: string;\n}\n\n/**\n * Result of getting changed entries, including the marker to store\n */\nexport interface ChangedEntriesResult {\n /** Entries that have changed since the marker */\n entries: InstanceEntry[];\n /** The current marker to store for next comparison */\n currentMarker: ChangeMarker;\n}\n\n/**\n * Options for creating a ChangeTracker\n */\nexport interface ChangeTrackerOptions {\n /** Working directory for git operations */\n cwd?: string;\n /**\n * Force operation even if there are uncommitted changes.\n * When false (default), git tracker will error if source files have uncommitted changes.\n */\n force?: boolean;\n}\n\n/**\n * Error thrown when source files have uncommitted changes.\n *\n * This prevents incorrect change tracking since uncommitted changes\n * are not captured in the checkpoint.\n */\nexport class UncommittedChangesError extends Error {\n /** Files with uncommitted changes */\n readonly files: string[];\n\n constructor(files: string[]) {\n super(\n `Source files have uncommitted changes: ${files.join(\", \")}. ` +\n `Commit your changes or use --force to proceed anyway.`,\n );\n this.name = \"UncommittedChangesError\";\n this.files = files;\n }\n}\n\n/**\n * Interface for tracking changes to entries.\n *\n * Implementations can use different backends (git, timestamps, database, etc.)\n * to determine which entries have changed since a marker point.\n */\nexport interface ChangeTracker {\n /**\n * Type of tracker - matches the checkpoint provider type.\n */\n readonly type: CheckpointProvider;\n\n /**\n * Get the current position marker (HEAD commit or current time).\n *\n * @returns Current marker to store for future comparisons\n */\n getCurrentMarker(): Promise<ChangeMarker>;\n\n /**\n * Get entries that have changed since a marker.\n *\n * For git tracker: Finds files modified since the commit, parses them,\n * and compares entry content to identify changed entries.\n *\n * For timestamp tracker: Filters entries with timestamps after the marker.\n *\n * @param workspace - The workspace containing current entries\n * @param queries - Queries to filter which entries to consider\n * @param marker - The marker to compare against (from previous actualization)\n * @returns Changed entries and the current marker to store\n */\n getChangedEntries(\n workspace: Workspace,\n queries: Query[],\n marker: ChangeMarker | null,\n ): Promise<ChangedEntriesResult>;\n}\n\n/**\n * Strip quotes from a string value if present.\n * Handles both single and double quotes.\n */\nfunction stripQuotes(value: string): string {\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n return value.slice(1, -1);\n }\n return value;\n}\n\n/**\n * Parse a checkpoint value into a ChangeMarker.\n *\n * Expected format: \"git:abc123\" or \"ts:2026-01-08T15:00Z\"\n * Strips quotes from the raw value as metadata raw values include quotes.\n *\n * @param checkpointRaw - Raw checkpoint metadata value (may include quotes)\n * @returns Parsed marker or null if invalid/missing\n */\nexport function parseCheckpoint(checkpointRaw: string | undefined): ChangeMarker | null {\n if (!checkpointRaw) {\n return null;\n }\n\n const value = stripQuotes(checkpointRaw);\n const colonIndex = value.indexOf(\":\");\n\n if (colonIndex === -1) {\n return null;\n }\n\n const provider = value.slice(0, colonIndex);\n const markerValue = value.slice(colonIndex + 1);\n\n if (!markerValue) {\n return null;\n }\n\n // Validate known providers\n if (provider === \"git\" || provider === \"ts\") {\n return { type: provider, value: markerValue };\n }\n\n return null;\n}\n\n/**\n * Format a ChangeMarker as a checkpoint string.\n *\n * @param marker - The marker to format\n * @returns Formatted string like \"git:abc123\" or \"ts:2026-01-08T15:00Z\"\n */\nexport function formatCheckpoint(marker: ChangeMarker): string {\n return `${marker.type}:${marker.value}`;\n}\n\nexport { TimestampChangeTracker } from \"./timestamp-tracker.js\";\n\n// For Node.js environments that need git-based tracking, use:\n// import { createChangeTracker, GitChangeTracker } from \"@rejot-dev/thalo/change-tracker/node\";\n"],"mappings":";;;;;;;;;AAwDA,IAAa,0BAAb,cAA6C,MAAM;;CAEjD,AAAS;CAET,YAAY,OAAiB;AAC3B,QACE,0CAA0C,MAAM,KAAK,KAAK,CAAC,yDAE5D;AACD,OAAK,OAAO;AACZ,OAAK,QAAQ;;;;;;;AA+CjB,SAAS,YAAY,OAAuB;AAC1C,KACG,MAAM,WAAW,KAAI,IAAI,MAAM,SAAS,KAAI,IAC5C,MAAM,WAAW,IAAI,IAAI,MAAM,SAAS,IAAI,CAE7C,QAAO,MAAM,MAAM,GAAG,GAAG;AAE3B,QAAO;;;;;;;;;;;AAYT,SAAgB,gBAAgB,eAAwD;AACtF,KAAI,CAAC,cACH,QAAO;CAGT,MAAM,QAAQ,YAAY,cAAc;CACxC,MAAM,aAAa,MAAM,QAAQ,IAAI;AAErC,KAAI,eAAe,GACjB,QAAO;CAGT,MAAM,WAAW,MAAM,MAAM,GAAG,WAAW;CAC3C,MAAM,cAAc,MAAM,MAAM,aAAa,EAAE;AAE/C,KAAI,CAAC,YACH,QAAO;AAIT,KAAI,aAAa,SAAS,aAAa,KACrC,QAAO;EAAE,MAAM;EAAU,OAAO;EAAa;AAG/C,QAAO;;;;;;;;AAST,SAAgB,iBAAiB,QAA8B;AAC7D,QAAO,GAAG,OAAO,KAAK,GAAG,OAAO"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ChangeTracker, ChangeTrackerOptions, UncommittedChangesError } from "./change-tracker.js";
|
|
2
|
+
import { GitChangeTracker } from "./git-tracker.js";
|
|
3
|
+
|
|
4
|
+
//#region src/services/change-tracker/create-tracker.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Error thrown when a git tracker is requested but the current directory
|
|
8
|
+
* is not inside a git repository.
|
|
9
|
+
*/
|
|
10
|
+
declare class NotInGitRepoError extends Error {
|
|
11
|
+
/** The directory that was checked */
|
|
12
|
+
readonly cwd: string;
|
|
13
|
+
constructor(cwd: string);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Options for creating a change tracker
|
|
17
|
+
*/
|
|
18
|
+
interface CreateChangeTrackerOptions extends ChangeTrackerOptions {
|
|
19
|
+
/**
|
|
20
|
+
* Preferred tracker type.
|
|
21
|
+
* - "auto": Use git if in a git repo, otherwise timestamp (default)
|
|
22
|
+
* - "git": Force git tracker (throws if not in git repo)
|
|
23
|
+
* - "timestamp": Force timestamp tracker
|
|
24
|
+
*/
|
|
25
|
+
preferredType?: "auto" | "git" | "timestamp";
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create a change tracker based on environment and options.
|
|
29
|
+
*
|
|
30
|
+
* By default, auto-detects git repository and uses GitChangeTracker
|
|
31
|
+
* if available, otherwise falls back to TimestampChangeTracker.
|
|
32
|
+
*
|
|
33
|
+
* NOTE: This is a Node.js-only function. For browser environments,
|
|
34
|
+
* use TimestampChangeTracker directly.
|
|
35
|
+
*
|
|
36
|
+
* @param options - Tracker creation options
|
|
37
|
+
* @returns Appropriate change tracker implementation
|
|
38
|
+
*/
|
|
39
|
+
declare function createChangeTracker(options?: CreateChangeTrackerOptions): Promise<ChangeTracker>;
|
|
40
|
+
//#endregion
|
|
41
|
+
export { CreateChangeTrackerOptions, GitChangeTracker, NotInGitRepoError, UncommittedChangesError, createChangeTracker };
|
|
42
|
+
//# sourceMappingURL=create-tracker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-tracker.d.ts","names":[],"sources":["../../../src/services/change-tracker/create-tracker.ts"],"sourcesContent":[],"mappings":";;;;;;;;;cAiBa,iBAAA,SAA0B,KAAA;;;;;;;;UActB,0BAAA,SAAmC;;;;;;;;;;;;;;;;;;;;;iBAsB9B,mBAAA,WACX,6BACR,QAAQ"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { TimestampChangeTracker } from "./timestamp-tracker.js";
|
|
2
|
+
import { UncommittedChangesError } from "./change-tracker.js";
|
|
3
|
+
import { detectGitContext } from "../../git/git.js";
|
|
4
|
+
import { GitChangeTracker } from "./git-tracker.js";
|
|
5
|
+
|
|
6
|
+
//#region src/services/change-tracker/create-tracker.ts
|
|
7
|
+
/**
|
|
8
|
+
* Error thrown when a git tracker is requested but the current directory
|
|
9
|
+
* is not inside a git repository.
|
|
10
|
+
*/
|
|
11
|
+
var NotInGitRepoError = class extends Error {
|
|
12
|
+
/** The directory that was checked */
|
|
13
|
+
cwd;
|
|
14
|
+
constructor(cwd) {
|
|
15
|
+
super(`Git tracker requested but "${cwd}" is not in a git repository`);
|
|
16
|
+
this.name = "NotInGitRepoError";
|
|
17
|
+
this.cwd = cwd;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Create a change tracker based on environment and options.
|
|
22
|
+
*
|
|
23
|
+
* By default, auto-detects git repository and uses GitChangeTracker
|
|
24
|
+
* if available, otherwise falls back to TimestampChangeTracker.
|
|
25
|
+
*
|
|
26
|
+
* NOTE: This is a Node.js-only function. For browser environments,
|
|
27
|
+
* use TimestampChangeTracker directly.
|
|
28
|
+
*
|
|
29
|
+
* @param options - Tracker creation options
|
|
30
|
+
* @returns Appropriate change tracker implementation
|
|
31
|
+
*/
|
|
32
|
+
async function createChangeTracker(options = {}) {
|
|
33
|
+
const { preferredType = "auto", cwd, force } = options;
|
|
34
|
+
if (preferredType === "timestamp") return new TimestampChangeTracker();
|
|
35
|
+
const nodeCwd = cwd ?? process.cwd();
|
|
36
|
+
const gitContext = await detectGitContext(nodeCwd);
|
|
37
|
+
if (preferredType === "git") {
|
|
38
|
+
if (!gitContext.isGitRepo) throw new NotInGitRepoError(nodeCwd);
|
|
39
|
+
return new GitChangeTracker({
|
|
40
|
+
cwd: nodeCwd,
|
|
41
|
+
force
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
if (gitContext.isGitRepo) return new GitChangeTracker({
|
|
45
|
+
cwd: nodeCwd,
|
|
46
|
+
force
|
|
47
|
+
});
|
|
48
|
+
return new TimestampChangeTracker();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
//#endregion
|
|
52
|
+
export { GitChangeTracker, NotInGitRepoError, UncommittedChangesError, createChangeTracker };
|
|
53
|
+
//# sourceMappingURL=create-tracker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-tracker.js","names":[],"sources":["../../../src/services/change-tracker/create-tracker.ts"],"sourcesContent":["/**\n * Node.js-only module for creating change trackers.\n *\n * This module imports git functionality directly and should only be used\n * in Node.js environments (CLI, scripts). For browser code, use\n * TimestampChangeTracker directly from \"./change-tracker.js\".\n */\n\nimport { detectGitContext } from \"../../git/git.js\";\nimport type { ChangeTracker, ChangeTrackerOptions } from \"./change-tracker.js\";\nimport { GitChangeTracker } from \"./git-tracker.js\";\nimport { TimestampChangeTracker } from \"./timestamp-tracker.js\";\n\n/**\n * Error thrown when a git tracker is requested but the current directory\n * is not inside a git repository.\n */\nexport class NotInGitRepoError extends Error {\n /** The directory that was checked */\n readonly cwd: string;\n\n constructor(cwd: string) {\n super(`Git tracker requested but \"${cwd}\" is not in a git repository`);\n this.name = \"NotInGitRepoError\";\n this.cwd = cwd;\n }\n}\n\n/**\n * Options for creating a change tracker\n */\nexport interface CreateChangeTrackerOptions extends ChangeTrackerOptions {\n /**\n * Preferred tracker type.\n * - \"auto\": Use git if in a git repo, otherwise timestamp (default)\n * - \"git\": Force git tracker (throws if not in git repo)\n * - \"timestamp\": Force timestamp tracker\n */\n preferredType?: \"auto\" | \"git\" | \"timestamp\";\n}\n\n/**\n * Create a change tracker based on environment and options.\n *\n * By default, auto-detects git repository and uses GitChangeTracker\n * if available, otherwise falls back to TimestampChangeTracker.\n *\n * NOTE: This is a Node.js-only function. For browser environments,\n * use TimestampChangeTracker directly.\n *\n * @param options - Tracker creation options\n * @returns Appropriate change tracker implementation\n */\nexport async function createChangeTracker(\n options: CreateChangeTrackerOptions = {},\n): Promise<ChangeTracker> {\n const { preferredType = \"auto\", cwd, force } = options;\n\n if (preferredType === \"timestamp\") {\n return new TimestampChangeTracker();\n }\n\n const nodeCwd = cwd ?? process.cwd();\n const gitContext = await detectGitContext(nodeCwd);\n\n if (preferredType === \"git\") {\n if (!gitContext.isGitRepo) {\n throw new NotInGitRepoError(nodeCwd);\n }\n return new GitChangeTracker({ cwd: nodeCwd, force });\n }\n\n // Auto mode: use git if available\n if (gitContext.isGitRepo) {\n return new GitChangeTracker({ cwd: nodeCwd, force });\n }\n\n return new TimestampChangeTracker();\n}\n\n// Re-export things CLI needs\nexport { GitChangeTracker } from \"./git-tracker.js\";\nexport { UncommittedChangesError } from \"./change-tracker.js\";\n"],"mappings":";;;;;;;;;;AAiBA,IAAa,oBAAb,cAAuC,MAAM;;CAE3C,AAAS;CAET,YAAY,KAAa;AACvB,QAAM,8BAA8B,IAAI,8BAA8B;AACtE,OAAK,OAAO;AACZ,OAAK,MAAM;;;;;;;;;;;;;;;AA6Bf,eAAsB,oBACpB,UAAsC,EAAE,EAChB;CACxB,MAAM,EAAE,gBAAgB,QAAQ,KAAK,UAAU;AAE/C,KAAI,kBAAkB,YACpB,QAAO,IAAI,wBAAwB;CAGrC,MAAM,UAAU,OAAO,QAAQ,KAAK;CACpC,MAAM,aAAa,MAAM,iBAAiB,QAAQ;AAElD,KAAI,kBAAkB,OAAO;AAC3B,MAAI,CAAC,WAAW,UACd,OAAM,IAAI,kBAAkB,QAAQ;AAEtC,SAAO,IAAI,iBAAiB;GAAE,KAAK;GAAS;GAAO,CAAC;;AAItD,KAAI,WAAW,UACb,QAAO,IAAI,iBAAiB;EAAE,KAAK;EAAS;EAAO,CAAC;AAGtD,QAAO,IAAI,wBAAwB"}
|