@entrorove/skill 0.1.22 → 0.1.24

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.
Files changed (155) hide show
  1. package/AGREEMENT.md +6 -6
  2. package/CHANGELOG.md +10 -0
  3. package/SKILL.md +12 -4
  4. package/archguard-runtime-build.mjs +15 -0
  5. package/blueprint-criterion-runner.mjs +110 -0
  6. package/blueprint-criterion-wire.mjs +78 -0
  7. package/calctool-runtime-build.mjs +59 -0
  8. package/calctool-runtime-entry.mjs +92 -0
  9. package/cli.mjs +18 -83
  10. package/local-approved-plan.mjs +74 -15
  11. package/local-archguard-ast.mjs +371 -0
  12. package/local-archguard-checkpoint.mjs +123 -0
  13. package/local-archguard-components.mjs +177 -0
  14. package/local-archguard-contract.mjs +109 -0
  15. package/local-archguard-contracts.mjs +42 -0
  16. package/local-archguard-evaluate.mjs +120 -0
  17. package/local-archguard-language-ast.mjs +165 -0
  18. package/local-archguard-mutation-fence.mjs +28 -0
  19. package/local-archguard-operations.mjs +25 -0
  20. package/local-archguard-scope.mjs +64 -0
  21. package/local-archguard-signing.mjs +52 -0
  22. package/local-archguard-snapshot.mjs +126 -0
  23. package/local-archguard-state.mjs +200 -0
  24. package/local-archguard.mjs +74 -0
  25. package/local-budget-git-attributes.mjs +49 -0
  26. package/local-budget-git-loader.mjs +61 -0
  27. package/local-budget-git-native-attributes.mjs +81 -0
  28. package/local-budget-git-native-darwin.mjs +104 -0
  29. package/local-budget-git-native-linux.mjs +93 -0
  30. package/local-budget-git-native-reads.mjs +88 -0
  31. package/local-budget-git-native-request.mjs +53 -0
  32. package/local-budget-git-native-runtime.mjs +18 -0
  33. package/local-budget-git-native.c +313 -0
  34. package/local-budget-git-native.mjs +109 -0
  35. package/local-budget-git-outcome.mjs +33 -0
  36. package/local-budget-git-scratch-loader.mjs +158 -0
  37. package/local-budget-git-scratch.mjs +112 -0
  38. package/local-budget-git-system-attributes.mjs +44 -0
  39. package/local-budget-git.mjs +92 -0
  40. package/local-budget-grants.mjs +82 -0
  41. package/local-budget-handoff-completion.mjs +70 -0
  42. package/local-budget-handoff-contracts.mjs +53 -0
  43. package/local-budget-handoff.mjs +229 -0
  44. package/local-budget-lock.mjs +55 -0
  45. package/local-budget-reads.mjs +135 -0
  46. package/local-budget-renewal.mjs +86 -34
  47. package/local-budget-source-bindings.mjs +51 -0
  48. package/local-budget-store.mjs +131 -0
  49. package/local-budget-transition.mjs +46 -0
  50. package/local-budget.mjs +185 -0
  51. package/local-calctool-cli.mjs +22 -0
  52. package/local-calctool-contracts.mjs +42 -0
  53. package/local-calctool-runtime.mjs +10 -0
  54. package/local-calctool-storage.mjs +101 -0
  55. package/local-calctool.mjs +103 -0
  56. package/local-cli.mjs +49 -16
  57. package/local-contract-types.mjs +46 -0
  58. package/local-contracts.mjs +124 -0
  59. package/local-credential.mjs +101 -0
  60. package/local-decimal.mjs +85 -0
  61. package/local-evidence-verification.mjs +199 -0
  62. package/local-execution.mjs +115 -304
  63. package/local-final-gate16-bundle.mjs +21 -0
  64. package/local-final-gate16-checks.mjs +88 -0
  65. package/local-final-gate16-contract.mjs +15 -0
  66. package/local-final-gate16-plan.mjs +66 -0
  67. package/local-final-gate16-proof.mjs +75 -0
  68. package/local-final-gate16-release.mjs +17 -0
  69. package/local-final-gate16-state.mjs +70 -0
  70. package/local-final-gate16-worker.mjs +19 -0
  71. package/local-final-gate16.mjs +137 -0
  72. package/local-formula-ast.mjs +97 -0
  73. package/local-formula-engine.mjs +111 -0
  74. package/local-formula-tests.mjs +61 -0
  75. package/local-formula.mjs +25 -125
  76. package/local-integrity.mjs +22 -41
  77. package/local-invocations.mjs +107 -0
  78. package/local-ledger.mjs +2 -7
  79. package/local-merge-archguard-verification.d.mts +44 -0
  80. package/local-merge-archguard-verification.mjs +240 -0
  81. package/local-merge-archguard.mjs +138 -0
  82. package/local-merge-artifacts.mjs +86 -0
  83. package/local-merge-authority.mjs +89 -0
  84. package/local-merge-contracts.mjs +56 -0
  85. package/local-merge-controller.mjs +170 -0
  86. package/local-merge-head.mjs +56 -0
  87. package/local-merge-journal.mjs +77 -0
  88. package/local-merge-mutation.mjs +97 -0
  89. package/local-merge-rollback.mjs +126 -0
  90. package/local-merge-snapshot-rollback.mjs +26 -0
  91. package/local-merge-storage.mjs +103 -0
  92. package/local-merge-targets.mjs +47 -0
  93. package/local-merge-transaction.mjs +133 -0
  94. package/local-mutation-audit.mjs +34 -0
  95. package/local-mutation-authority.mjs +96 -0
  96. package/local-mutation-batch-receipt.mjs +84 -0
  97. package/local-mutation-batch.mjs +143 -0
  98. package/local-mutation-binding.mjs +86 -0
  99. package/local-mutation-files.mjs +245 -0
  100. package/local-mutation-pass-store.mjs +63 -0
  101. package/local-mutation-snapshot.mjs +200 -0
  102. package/local-mutation-wire.mjs +89 -0
  103. package/local-probe.mjs +133 -11
  104. package/local-scope-candidate.mjs +28 -0
  105. package/local-scope-context.mjs +28 -0
  106. package/local-scope-files.mjs +106 -0
  107. package/local-scope-git-attributes.mjs +200 -0
  108. package/local-scope-git-comparison.mjs +8 -0
  109. package/local-scope-git-history.mjs +72 -0
  110. package/local-scope-git-reads.mjs +66 -0
  111. package/local-scope-git.mjs +124 -0
  112. package/local-scope-repository.mjs +249 -0
  113. package/local-scope-signature.mjs +70 -0
  114. package/local-scope-snapshot.mjs +39 -0
  115. package/local-scope-state.mjs +204 -0
  116. package/local-scope-workspaces.mjs +83 -0
  117. package/local-swarm-authority.mjs +143 -0
  118. package/local-swarm-baseline.mjs +32 -0
  119. package/local-swarm-completion.mjs +102 -0
  120. package/local-swarm-contracts.mjs +47 -0
  121. package/local-swarm-mutation.mjs +142 -0
  122. package/local-swarm-priority.mjs +237 -0
  123. package/local-swarm-state.mjs +102 -0
  124. package/local-swarm-storage.mjs +135 -0
  125. package/local-swarm.mjs +179 -0
  126. package/local-task-handoff-completion-wire.mjs +71 -0
  127. package/local-task-handoff-contracts.mjs +66 -0
  128. package/local-task-handoff-fence.mjs +478 -0
  129. package/local-task-handoff-observation.mjs +38 -0
  130. package/local-task-handoff-overlay-wire.mjs +94 -0
  131. package/local-task-handoff-read-authority.mjs +236 -0
  132. package/local-task-handoff-storage.mjs +73 -0
  133. package/local-task-handoff-write-archive.mjs +80 -0
  134. package/local-task-handoff-write-constraints.mjs +66 -0
  135. package/local-task-handoff-write-receipts.mjs +99 -0
  136. package/local-task-handoff-write-wire.mjs +99 -0
  137. package/local-task-routing.mjs +202 -22
  138. package/manifest.json +1 -1
  139. package/package.json +153 -3
  140. package/release-state.json +153 -17
  141. package/release.mjs +238 -4
  142. package/releases.json +12 -0
  143. package/remote-client.mjs +127 -36
  144. package/remote-transport.mjs +80 -0
  145. package/review-client.mjs +38 -0
  146. package/runtime/archguard-runtime-v7.0.44.mjs +314 -0
  147. package/runtime/calctool-runtime-v7.0.43.mjs +1887 -0
  148. package/runtime/git-native-darwin-v1.mjs +5 -0
  149. package/runtime/git-native-linux-v1.mjs +5 -0
  150. package/task-message-cli.mjs +47 -0
  151. package/task-message-entry.mjs +159 -0
  152. package/workflow-local-client.mjs +35 -0
  153. package/workflow-local-contracts.mjs +58 -0
  154. package/workflow-local-request.mjs +53 -0
  155. package/workflow-local-runner.mjs +350 -0
@@ -0,0 +1,177 @@
1
+ import { parse as parseVue, compileTemplate } from '@vue/compiler-sfc';
2
+ import { parse as parseSvelte } from 'svelte/compiler';
3
+ import { analyzeArchguardLanguage } from './local-archguard-language-ast.mjs';
4
+
5
+ export function archguardComponentLanguage(path) {
6
+ return new Map([['vue', 'vue'], ['svelte', 'svelte'], ['wxml', 'wxml']]).get(path.split('.').at(-1)) ?? null;
7
+ }
8
+ function offsets(source) {
9
+ const output = [0];
10
+ for (let index = 0; index < source.length; index += 1) {
11
+ if (source[index] === '\r' && source[index + 1] === '\n') { output.push(index + 2); index += 1; }
12
+ else if (source[index] === '\n' || source[index] === '\r') output.push(index + 1);
13
+ }
14
+ return output;
15
+ }
16
+ function position(index, lineOffsets) {
17
+ let low = 0; let high = lineOffsets.length;
18
+ while (low + 1 < high) { const middle = Math.floor((low + high) / 2); if (lineOffsets[middle] <= index) low = middle; else high = middle; }
19
+ return { line: low + 1, column: index - lineOffsets[low] + 1 };
20
+ }
21
+ function merge(target, part, base, prefix, lineOffsets) {
22
+ const rebase = location => {
23
+ const start = Math.max(0, (location.start ?? 0) - prefix) + base;
24
+ return { ...location, ...position(start, lineOffsets), start, end: Math.max(0, (location.end ?? 0) - prefix) + base };
25
+ };
26
+ if (part.diagnostics?.length) {
27
+ target.diagnostics.push(...part.diagnostics.map(item => {
28
+ const start = item.start ?? 0; return { ...item, ...rebase({ ...item, start, end: item.end ?? start }) };
29
+ }));
30
+ return;
31
+ }
32
+ const sourceLocations = locations => locations.filter(location => (location.start ?? 0) >= prefix).map(rebase);
33
+ for (const [id, locations] of part.builtin) target.builtin.get(id).push(...sourceLocations(locations));
34
+ for (const [id, selected] of part.selected) target.selected.get(id).locations.push(...sourceLocations(selected.locations));
35
+ target.dependencies.push(...sourceLocations(part.dependencies));
36
+ for (const key of ['maxNestingDepth', 'maxFunctionLines']) target[key] = Math.max(target[key], part[key]);
37
+ for (const key of ['animations', 'transitions']) target[key] += part[key];
38
+ }
39
+ function scriptLanguage(block) {
40
+ const language = block.lang ?? 'js';
41
+ if (!['js', 'javascript', 'ts', 'typescript'].includes(language) || block.src) throw new Error('ARCH-AST-EMBEDDED-LANGUAGE-UNSUPPORTED');
42
+ return ['ts', 'typescript'].includes(language) ? 'ts' : 'js';
43
+ }
44
+ function styleLanguage(block) {
45
+ if ((block.lang && block.lang !== 'css') || block.src) throw new Error('ARCH-AST-EMBEDDED-LANGUAGE-UNSUPPORTED');
46
+ }
47
+ function walk(value, visit, depth = 0, seen = new WeakSet()) {
48
+ if (!value || typeof value !== 'object' || seen.has(value)) return;
49
+ seen.add(value); const next = visit(value, depth);
50
+ for (const [key, child] of Object.entries(value)) if (!['loc', 'ast', 'parent', 'codegenNode'].includes(key)) {
51
+ if (Array.isArray(child)) child.forEach(item => walk(item, visit, next, seen));
52
+ else walk(child, visit, next, seen);
53
+ }
54
+ }
55
+ function expressions(ranges) {
56
+ const unique = [...new Map(ranges.map(item => [`${item.start}:${item.end}`, item])).values()];
57
+ return unique.filter(item => !unique.some(other => other !== item && other.start <= item.start && other.end >= item.end));
58
+ }
59
+ function syntaxError(error, parser, lineOffsets) {
60
+ const start = error.loc?.start?.offset ?? error.position?.[0] ?? error.start?.character ?? 0;
61
+ return { code: error.code ?? 'COMPONENT-SYNTAX', parser, message: error.message ?? String(error),
62
+ start, end: start, ...position(start, lineOffsets) };
63
+ }
64
+ function addStyle(target, source, rules, content, base, lineOffsets, property = '') {
65
+ const header = `.archguard{${property}`;
66
+ const analysis = analyzeArchguardLanguage({ path: 'fragment.css', content: `${header}${content}}` }, rules);
67
+ analysis.maxNestingDepth = Math.max(0, analysis.maxNestingDepth - 1);
68
+ merge(target, analysis, base, header.length, lineOffsets);
69
+ }
70
+ function vueSections(file) {
71
+ const parsed = parseVue(file.content, { filename: file.path, sourceMap: false,
72
+ templateParseOptions: { expressionPlugins: ['typescript'] } });
73
+ if (parsed.errors.length) return { errors: parsed.errors };
74
+ const descriptor = parsed.descriptor;
75
+ if (descriptor.customBlocks.length || descriptor.template?.lang) throw new Error('ARCH-AST-EMBEDDED-LANGUAGE-UNSUPPORTED');
76
+ return { scripts: [descriptor.script, descriptor.scriptSetup].filter(Boolean), styles: descriptor.styles,
77
+ template: descriptor.template?.ast, errors: [] };
78
+ }
79
+ function wxmlAttributeRanges(value, file, analyzeScript) {
80
+ const quoted = ['"', "'"].includes(value.loc.source[0]);
81
+ const base = value.loc.start.offset + (quoted ? 1 : 0);
82
+ const source = file.content.slice(base, value.loc.end.offset - (quoted ? 1 : 0));
83
+ const ranges = []; let cursor = 0;
84
+ while ((cursor = source.indexOf('{{', cursor)) !== -1) {
85
+ const start = cursor + 2; let end = source.indexOf('}}', start); let accepted = false;
86
+ while (end !== -1) {
87
+ const parsed = analyzeScript({ path: 'binding.ts', content: `void (${source.slice(start, end)});` }, []);
88
+ if (!parsed.diagnostics.length) { ranges.push({ start: base + start, end: base + end }); accepted = true; break; }
89
+ end = source.indexOf('}}', end + 1);
90
+ }
91
+ if (!accepted) throw Object.assign(new Error('WXML attribute binding is not a valid expression'), { position: [base + start] });
92
+ cursor = end + 2;
93
+ }
94
+ return ranges;
95
+ }
96
+ function vueTemplate(target, root, file, rules, lineOffsets, analyzeScript) {
97
+ const ranges = [];
98
+ walk(root, (node, depth) => {
99
+ const next = node.type === 1 ? depth + 1 : depth; target.maxNestingDepth = Math.max(target.maxNestingDepth, next);
100
+ if (((node.type === 4 && !node.isStatic && node.content) || (node.type === 8 && node.ast)) && node.loc) ranges.push({ start: node.loc.start.offset,
101
+ end: node.loc.end.offset, statement: node.archguardStatement === true });
102
+ if (node.type === 7 && node.name === 'on' && node.exp) node.exp.archguardStatement = true;
103
+ if (file.path.endsWith('.wxml') && node.type === 6 && node.value) ranges.push(...wxmlAttributeRanges(node.value, file, analyzeScript));
104
+ const style = (node.type === 6 && node.name === 'style') || (node.type === 7 && node.name === 'bind' && node.arg?.content === 'style');
105
+ if (style) target.builtin.get('no-inline-style').push({ ...position(node.loc.start.offset, lineOffsets),
106
+ start: node.loc.start.offset, end: node.loc.end.offset, syntaxKind: 'Attribute', parser: 'vue/compiler-sfc' });
107
+ if (node.type === 6 && node.name === 'style' && node.value) {
108
+ const base = node.value.loc.start.offset + (/^["']/.test(node.value.loc.source) ? 1 : 0);
109
+ addStyle(target, file.content, rules, node.value.content, base, lineOffsets);
110
+ }
111
+ return next;
112
+ });
113
+ return expressions(ranges);
114
+ }
115
+ function svelteAttribute(node, name) {
116
+ return node.attributes?.find(item => item.type === 'Attribute' && item.name === name)?.value?.[0]?.data;
117
+ }
118
+ function svelteTemplate(target, root, file, rules, lineOffsets) {
119
+ const ranges = [];
120
+ walk(root, (node, depth) => {
121
+ const next = ['RegularElement', 'Component', 'IfBlock', 'EachBlock', 'AwaitBlock', 'SnippetBlock', 'KeyBlock'].includes(node.type) ? depth + 1 : depth;
122
+ target.maxNestingDepth = Math.max(target.maxNestingDepth, next);
123
+ if (typeof node.type === 'string' && (node.type.endsWith('Expression') || ['Literal', 'Identifier'].includes(node.type))
124
+ && Number.isInteger(node.start) && Number.isInteger(node.end)) ranges.push({ start: node.start, end: node.end, statement: false });
125
+ if ((node.type === 'Attribute' && node.name === 'style') || node.type === 'StyleDirective') {
126
+ target.builtin.get('no-inline-style').push({ ...position(node.start, lineOffsets), start: node.start,
127
+ end: node.end, syntaxKind: node.type, parser: 'svelte/compiler' });
128
+ const text = Array.isArray(node.value) && node.value.length === 1 && node.value[0].type === 'Text' ? node.value[0] : null;
129
+ if (text) addStyle(target, file.content, rules, text.data, text.start, lineOffsets, node.type === 'StyleDirective' ? `${node.name}:` : '');
130
+ }
131
+ return next;
132
+ });
133
+ return expressions(ranges);
134
+ }
135
+ function wxmlSections(file) {
136
+ const compiled = compileTemplate({ source: file.content, filename: file.path, id: 'archguard-inspection',
137
+ transformAssetUrls: false, compilerOptions: { comments: true } });
138
+ return { template: compiled.ast, scripts: [], styles: [], errors: compiled.errors };
139
+ }
140
+ export function analyzeArchguardComponent(file, rules, analyzeScript) {
141
+ const language = archguardComponentLanguage(file.path); const lineOffsets = offsets(file.content);
142
+ const target = { selected: new Map(rules.filter(rule => rule.engine === 'ast').map(rule => [rule.id, { rule, locations: [] }])),
143
+ builtin: new Map(['no-eval', 'no-debug', 'no-inline-style', 'no-hardcoded-color', 'no-magic-number'].map(id => [id, []])),
144
+ dependencies: [], maxFunctionLines: 0, maxNestingDepth: 0, animations: 0, transitions: 0, diagnostics: [] };
145
+ try {
146
+ let sections; let ranges;
147
+ if (language === 'svelte') {
148
+ const parsed = parseSvelte(file.content, { filename: file.path, modern: true });
149
+ const scripts = [...new Set([parsed.instance, parsed.module, ...(parsed.js ?? [])].filter(Boolean))];
150
+ sections = { scripts: scripts.map(node => ({ content: file.content.slice(node.content.start, node.content.end),
151
+ start: node.content.start, lang: svelteAttribute(node, 'lang'), src: svelteAttribute(node, 'src') })),
152
+ styles: parsed.css ? [{ content: parsed.css.content.styles, start: parsed.css.content.start, lang: svelteAttribute(parsed.css, 'lang') }] : [], errors: [] };
153
+ ranges = svelteTemplate(target, parsed.fragment, file, rules, lineOffsets);
154
+ } else {
155
+ sections = language === 'vue' ? vueSections(file) : wxmlSections(file);
156
+ ranges = vueTemplate(target, sections.template, file, rules, lineOffsets, analyzeScript);
157
+ }
158
+ if (sections.errors.length) { target.diagnostics.push(...sections.errors.map(error => syntaxError(error, language, lineOffsets))); return target; }
159
+ for (const block of sections.scripts) {
160
+ const kind = scriptLanguage(block); const start = block.start ?? block.loc.start.offset;
161
+ merge(target, analyzeScript({ path: `${file.path}.${kind}`, content: block.content }, rules), start, 0, lineOffsets);
162
+ }
163
+ for (const block of sections.styles) {
164
+ styleLanguage(block); const start = block.start ?? block.loc.start.offset;
165
+ merge(target, analyzeArchguardLanguage({ path: `${file.path}.css`, content: block.content }, rules), start, 0, lineOffsets);
166
+ }
167
+ for (const range of ranges) {
168
+ const source = file.content.slice(range.start, range.end); const prefix = range.statement ? '' : 'void (';
169
+ merge(target, analyzeScript({ path: `${file.path}.ts`, content: `${prefix}${source}${range.statement ? '' : ');'}` }, rules),
170
+ range.start, prefix.length, lineOffsets);
171
+ }
172
+ return target;
173
+ } catch (error) {
174
+ if (error.message?.startsWith('ARCH-AST-SELECTOR-')) throw error;
175
+ target.diagnostics.push(syntaxError(error, language, lineOffsets)); return target;
176
+ }
177
+ }
@@ -0,0 +1,109 @@
1
+ import { withArchguardMutationFence } from './local-archguard-mutation-fence.mjs';
2
+ import { promises as fs } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { context, canonical, sha256, readBytes, readJson, atomicJson, seal, unseal,
5
+ withExclusiveLock, verifyAuthority, appendLedger, readLedger, atomicBytes, fail, safeId } from './local-archguard-state.mjs';
6
+
7
+ async function evaluator() { return import('./local-archguard-evaluate.mjs'); }
8
+ const CONTRACT_PATH = 'arch.contract.yaml';
9
+ export async function contractFile(ctx) {
10
+ const bytes = await readBytes(path.join(ctx.root, CONTRACT_PATH), false, ctx);
11
+ const contract = await (await evaluator()).parseArchguardContract(bytes.content.toString('utf8'));
12
+ return { contract, contractDigest: sha256(canonical(contract)), rawDigest: sha256(bytes.content), raw: bytes.content };
13
+ }
14
+ export async function loadContractLock(ctx, required = true) {
15
+ try { return await unseal(ctx, await readJson(path.join(ctx.repositoryState, 'contract-lock.json'))); }
16
+ catch (error) { if (!required && error.code === 'ENOENT') return null; throw error; }
17
+ }
18
+ export async function freezeContract(ctx) {
19
+ const current = await contractFile(ctx); const lock = await loadContractLock(ctx, false);
20
+ if (lock && (lock.contractDigest !== current.contractDigest || lock.rawDigest !== current.rawDigest)) fail('ARCH_CONTRACT_DRIFT', 'architecture contract changed outside an explicitly confirmed version update');
21
+ if (!lock) await atomicJson(path.join(ctx.repositoryState, 'contract-lock.json'), await seal(ctx, {
22
+ repositoryDigest: ctx.repositoryDigest, contractDigest: current.contractDigest, rawDigest: current.rawDigest,
23
+ revision: current.contract.revision, usedConfirmations: [] }));
24
+ return current;
25
+ }
26
+ async function confirmation(ctx, input, action, next, previous) {
27
+ const verified = await verifyAuthority(ctx, input.confirmation, 'entrorove.archguard-contract-confirmation/1.0');
28
+ const value = verified.value; const nextDigest = sha256(canonical(next));
29
+ if (!verified.authority.actors.includes(value.actor) || typeof value.reason !== 'string' || !value.reason.trim()) fail('ARCH_CONFIRMATION_ACTOR', 'confirmation actor and reason must be explicit and trusted');
30
+ if (value.action !== action || value.contractDigest !== nextDigest || value.contractRevision !== next.revision
31
+ || value.previousDigest !== previous) fail('ARCH_CONFIRMATION_BINDING', 'confirmation does not bind the exact contract digest and revision');
32
+ return value;
33
+ }
34
+ async function saveContract(ctx, next, permit, lock, exclusive) {
35
+ const raw = Buffer.from(canonical(next) + '\n'); const contractDigest = sha256(canonical(next));
36
+ const file = path.join(ctx.root, CONTRACT_PATH); const temporary = path.join(ctx.root, `.arch-contract-${permit.nonce}.tmp`);
37
+ const old = await readBytes(file, true, ctx);
38
+ if (exclusive && old.exists) fail('ARCH_CONTRACT_EXISTS', 'architecture contract already exists');
39
+ await readLedger(ctx);
40
+ const protectedPaths = [file, path.join(ctx.repositoryState, 'contract-lock.json'), path.join(ctx.directory, 'ledger.jsonl'), path.join(ctx.directory, 'head.json')];
41
+ const protectedFiles = await Promise.all(protectedPaths.map(async target => ({ target, original: await readBytes(target, true, target === file ? ctx : null) })));
42
+ let projectWritten = false;
43
+ try {
44
+ await fs.writeFile(temporary, raw, { flag: 'wx', mode: old.exists ? old.mode : 0o600 });
45
+ if (exclusive) { await fs.link(temporary, file); projectWritten = true; await fs.unlink(temporary); }
46
+ else await fs.rename(temporary, file);
47
+ projectWritten = true;
48
+ await atomicJson(path.join(ctx.repositoryState, 'contract-lock.json'), await seal(ctx, {
49
+ repositoryDigest: ctx.repositoryDigest, contractDigest, rawDigest: sha256(raw), revision: next.revision,
50
+ usedConfirmations: [...(lock?.usedConfirmations ?? []), permit.nonce] }));
51
+ const ledgerEntry = await appendLedger(ctx, `contract-${exclusive ? 'create' : 'update'}`, {
52
+ contractDigest, revision: next.revision, previousDigest: permit.previousDigest,
53
+ actor: permit.actor, reason: permit.reason, confirmationDigest: sha256(canonical(permit)) });
54
+ return { schemaVersion: 'entrorove.archguard-contract-result/1.0', contract: next, contractDigest, ledgerEntry };
55
+ } catch (error) {
56
+ for (const item of protectedFiles) {
57
+ if (item.target === file && !projectWritten) continue;
58
+ try {
59
+ if (item.original.exists) await atomicBytes(item.target, item.original.content, item.original.mode);
60
+ else await fs.rm(item.target, { force: true });
61
+ } catch { fail('ARCH_CONTRACT_RECOVERY_FAILED', 'contract transaction recovery failed; explicit repair is required'); }
62
+ }
63
+ throw error;
64
+ } finally { await fs.rm(temporary, { force: true }); }
65
+ }
66
+ async function assertNoBlockedCheckpoints(ctx) {
67
+ const directory = path.join(ctx.stateRoot, 'flows', ctx.repositoryDigest);
68
+ let flows; try { flows = await fs.readdir(directory); } catch (error) { if (error.code === 'ENOENT') return; throw error; }
69
+ for (const flowId of flows) {
70
+ safeId(flowId, 'stored flowId');
71
+ const ledger = await readLedger({ ...ctx, flowId, directory: path.join(directory, flowId) });
72
+ const last = ledger.entries.filter(item => item.event === 'checkpoint').at(-1);
73
+ if (last?.data.status === 'blocked') fail('ARCH_CONTRACT_BLOCKED_UPDATE', 'a blocked checkpoint must be corrected under its locked contract before a version update');
74
+ }
75
+ }
76
+ export async function archguardContractCreate(input) {
77
+ const next = await (await evaluator()).normalizeArchguardContract(input.contract);
78
+ if (next.revision !== 1) fail('ARCH_CONTRACT_REVISION', 'initial contract revision must be 1');
79
+ return withArchguardMutationFence(input, async () => {
80
+ const ctx = await context(input);
81
+ return withExclusiveLock(path.join(ctx.repositoryState, 'mutation.lock'), async () => {
82
+ const permit = await confirmation(ctx, input, 'create', next, null);
83
+ const lock = await loadContractLock(ctx, false);
84
+ if (lock) fail('ARCH_CONTRACT_EXISTS', 'architecture contract authority already exists');
85
+ return saveContract(ctx, next, permit, null, true);
86
+ });
87
+ }, { bootstrap: true, paths: [CONTRACT_PATH] });
88
+ }
89
+ export async function archguardContractGet(input) {
90
+ const ctx = await context(input); const current = await contractFile(ctx); const lock = await loadContractLock(ctx, false);
91
+ if (lock && (lock.contractDigest !== current.contractDigest || lock.rawDigest !== current.rawDigest)) fail('ARCH_CONTRACT_DRIFT', 'architecture contract digest no longer matches its authority lock');
92
+ return { schemaVersion: 'entrorove.archguard-contract-result/1.0', contract: current.contract, contractDigest: current.contractDigest,
93
+ authoritative: Boolean(lock), contractPath: CONTRACT_PATH };
94
+ }
95
+ export async function archguardContractUpdate(input) {
96
+ const next = await (await evaluator()).normalizeArchguardContract(input.contract);
97
+ return withArchguardMutationFence(input, async () => {
98
+ const ctx = await context(input);
99
+ return withExclusiveLock(path.join(ctx.repositoryState, 'mutation.lock'), async () => {
100
+ const current = await freezeContract(ctx); const lock = await loadContractLock(ctx);
101
+ await assertNoBlockedCheckpoints(ctx);
102
+ if (input.expectedDigest !== current.contractDigest) fail('ARCH_CONTRACT_STALE', 'expected contract digest is stale');
103
+ if (next.revision !== current.contract.revision + 1) fail('ARCH_CONTRACT_REVISION', 'confirmed update must increment contract revision by exactly one');
104
+ const permit = await confirmation(ctx, input, 'update', next, current.contractDigest);
105
+ if (lock.usedConfirmations.includes(permit.nonce)) fail('ARCH_CONFIRMATION_REPLAY', 'confirmation nonce already consumed');
106
+ return saveContract(ctx, next, permit, lock, false);
107
+ });
108
+ }, { bootstrap: true, paths: [CONTRACT_PATH] });
109
+ }
@@ -0,0 +1,42 @@
1
+ import { array, integer, ledgerIdentifier as id, nullable, object, paths, relativePath, sha256, text } from './local-contract-types.mjs';
2
+ import { WORKFLOW_ACCEPTED_PLAN_SCHEMA, WORKFLOW_LOCAL_LEASE_SCHEMA } from './workflow-local-contracts.mjs';
3
+ import { archguardOperationSchemas } from './local-archguard-evaluate.mjs';
4
+
5
+ const native = await archguardOperationSchemas();
6
+ const repositoryDigest = { type: 'string', pattern: '^[0-9a-f]{32}$' };
7
+ const signature = { type: 'string', pattern: '^[A-Za-z0-9_-]{86}$' };
8
+ const timestamp = { type: 'string', pattern: '^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$' };
9
+ const exact = fields => object(fields, Object.keys(fields));
10
+ const signed = { repositoryDigest, flowId: id, issuedAt: timestamp, expiresAt: timestamp, nonce: id, keyId: sha256, signature };
11
+ const confirmation = exact({ ...signed, schemaVersion: { const: 'entrorove.archguard-contract-confirmation/1.0' },
12
+ action: { enum: ['create', 'update'] }, actor: text, reason: text, previousDigest: nullable(sha256), contractDigest: sha256, contractRevision: integer(1) });
13
+ const direction = exact({ ...signed, schemaVersion: { const: 'entrorove.archguard-human-direction/1.0' },
14
+ action: { const: 'resume-correction' }, actor: text, reason: text, ledgerHeadDigest: sha256, contractDigest: sha256 });
15
+ const scope = exact({ ...signed, schemaVersion: { const: 'entrorove.archguard-scope-authorization/1.0' },
16
+ stepId: id, ownerId: id, leaseId: id, generation: integer(1), taskDigest: sha256, chainDigest: sha256,
17
+ scopeArtifactDigest: sha256, contractDigest: sha256, paths, snapshotDigest: sha256, passDigest: sha256 });
18
+ const base = { repositoryRoot: text, home: text, flowId: id };
19
+ const requiredBase = ['repositoryRoot', 'flowId']; const step = { ...base, stepId: id }; const requiredStep = [...requiredBase, 'stepId'];
20
+ const contract = native['contract-get'].input.properties.contract;
21
+ export const ARCHGUARD_LOCAL_OPERATION_SCHEMAS = Object.freeze({
22
+ 'archguard-capabilities': object({}), 'archguard-help': object({}),
23
+ 'archguard-intake': native.intake.input, 'archguard-template-list': object({}),
24
+ 'archguard-contract-create': object({ ...base, contract, confirmation }, [...requiredBase, 'contract', 'confirmation']),
25
+ 'archguard-contract-get': object(base, requiredBase),
26
+ 'archguard-contract-update': object({ ...base, contract, expectedDigest: sha256, confirmation }, [...requiredBase, 'contract', 'expectedDigest', 'confirmation']),
27
+ 'archguard-snapshot': object({ ...step, paths, snapshotId: id }, [...requiredStep, 'paths']),
28
+ 'archguard-checkpoint': object({ ...step, snapshotId: id, gatePassPath: text, scopeAuthorization: scope, path: relativePath, blockId: id,
29
+ workerId: id, leaseId: id }, [...requiredStep, 'snapshotId', 'gatePassPath', 'scopeAuthorization', 'path', 'blockId']),
30
+ 'archguard-scope-request': object({ ...step, snapshotId: id, gatePassPath: text, acceptedPlan: WORKFLOW_ACCEPTED_PLAN_SCHEMA,
31
+ lease: WORKFLOW_LOCAL_LEASE_SCHEMA, scopeArtifactDigest: sha256, workerId: id, leaseId: id },
32
+ [...requiredStep, 'snapshotId', 'gatePassPath', 'acceptedPlan', 'lease', 'scopeArtifactDigest']),
33
+ 'archguard-rules-scan': object({ ...base, paths }, [...requiredBase, 'paths']),
34
+ 'archguard-complexity-report': object({ ...base, paths }, [...requiredBase, 'paths']),
35
+ 'archguard-drift-status': object(base, requiredBase),
36
+ 'archguard-ledger-init': object(base, requiredBase),
37
+ 'archguard-ledger-query': object({ ...base, event: { enum: ['contract-create', 'contract-update', 'ledger-init', 'snapshot', 'checkpoint', 'human-resume'] },
38
+ status: { enum: ['passed', 'blocked'] }, ruleId: id }, requiredBase),
39
+ 'archguard-human-resume': object({ ...base, confirmation: direction }, [...requiredBase, 'confirmation']),
40
+ 'archguard-human-direction-request': object({ ...step, acceptedPlan: WORKFLOW_ACCEPTED_PLAN_SCHEMA,
41
+ lease: WORKFLOW_LOCAL_LEASE_SCHEMA, scopeArtifactDigest: sha256 }, [...requiredStep, 'acceptedPlan', 'lease', 'scopeArtifactDigest']),
42
+ });
@@ -0,0 +1,120 @@
1
+ import { createHash } from 'node:crypto'
2
+ import { readFile } from 'node:fs/promises'
3
+ import Ajv from 'ajv'
4
+ import { evaluateArchguardFiles, validateArchguardAstRules } from './local-archguard-ast.mjs'
5
+
6
+ const ASSET = 'archguard-runtime-v7.0.44.mjs'
7
+ const ASSET_SHA256 = '836f20b270139371bef43e3934fa7232371e42d46371c513db2a5e3c5622b215'
8
+ const ajv = new Ajv({ allErrors: true, strict: false })
9
+ const validators = new Map()
10
+ let runtimePromise
11
+
12
+ function fail(message) { throw new Error(`ENTROROVE_ARCHGUARD_INVALID: ${message}`) }
13
+ function canonical(value) {
14
+ if (value === null || ['string', 'boolean'].includes(typeof value)) return JSON.stringify(value)
15
+ if (typeof value === 'number' && Number.isFinite(value)) return JSON.stringify(value)
16
+ if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`
17
+ if (!value || typeof value !== 'object') fail('Finite JSON is required')
18
+ return `{${Object.keys(value).sort().map(key => `${JSON.stringify(key)}:${canonical(value[key])}`).join(',')}}`
19
+ }
20
+ const digest = value => createHash('sha256').update(canonical(value)).digest('hex')
21
+
22
+ async function loadPinnedRuntime() {
23
+ for (const url of [new URL(`./runtime/${ASSET}`, import.meta.url), new URL(`../../assets/${ASSET}`, import.meta.url)]) {
24
+ let source
25
+ try { source = await readFile(url) } catch (error) { if (error.code === 'ENOENT') continue; throw error }
26
+ if (createHash('sha256').update(source).digest('hex') !== ASSET_SHA256) fail('Pinned runtime digest mismatch')
27
+ return import(url.href)
28
+ }
29
+ fail('Pinned Archguard runtime is unavailable')
30
+ }
31
+ const runtime = () => runtimePromise ??= loadPinnedRuntime()
32
+
33
+ function assertSchema(key, schema, value) {
34
+ let validate = validators.get(key)
35
+ if (!validate) { validate = ajv.compile(schema); validators.set(key, validate) }
36
+ if (!validate(value)) fail(`${key}: ${ajv.errorsText(validate.errors, { dataVar: key })}`)
37
+ }
38
+
39
+ export async function normalizeArchguardContract(value) {
40
+ const module = await runtime()
41
+ assertSchema('contract', module.OPERATION_SCHEMAS['contract-get'].input.properties.contract, value)
42
+ validateArchguardAstRules(value.rules.custom)
43
+ const result = await module.run({ schemaVersion: 'archguard.skill.request/1.0', requestId: digest(value), operation: 'contract-get', input: { contract: value } })
44
+ return result.output.contract
45
+ }
46
+
47
+ export async function parseArchguardContract(source) {
48
+ if (typeof source !== 'string' || Buffer.byteLength(source) > 1_000_000) fail('Contract text must be at most 1 MB')
49
+ const { parseDocument } = await import('yaml')
50
+ const document = parseDocument(source, { uniqueKeys: true, version: '1.2' })
51
+ if (document.errors.length) fail(`Contract YAML is invalid: ${document.errors[0].message}`)
52
+ const value = document.toJS({ maxAliasCount: 0 })
53
+ return normalizeArchguardContract(value)
54
+ }
55
+
56
+ export async function archguardOperationSchemas() { return (await runtime()).OPERATION_SCHEMAS }
57
+
58
+ export async function runPureArchguardOperation(operation, input, requestId = digest({ operation, input })) {
59
+ const module = await runtime()
60
+ const schema = module.OPERATION_SCHEMAS[operation]
61
+ if (!schema) fail('Unknown Archguard operation')
62
+ assertSchema(`input.${operation}`, schema.input, input)
63
+ if (input.contract) input = { ...input, contract: await normalizeArchguardContract(input.contract) }
64
+ const result = await module.run({ schemaVersion: 'archguard.skill.request/1.0', requestId, operation, input })
65
+ if (result.output.contract) await normalizeArchguardContract(result.output.contract)
66
+ if (['rules-scan', 'complexity-report'].includes(operation)) {
67
+ const scan = evaluateArchguardFiles(input.contract, input.files)
68
+ const findings = operation === 'rules-scan' ? scan.findings : scan.findings.filter(item => item.category === 'complexity')
69
+ result.status = scan.findings.some(item => item.blocking) ? 'blocked' : 'succeeded'
70
+ result.findings = scan.findings
71
+ result.output = operation === 'rules-scan' ? { findings, summary: { total: findings.length, blocking: findings.filter(item => item.blocking).length } }
72
+ : { metrics: scan.metrics, findings }
73
+ }
74
+ return result
75
+ }
76
+
77
+ export async function createArchguardContract(input) {
78
+ return (await runPureArchguardOperation('contract-create', input)).output
79
+ }
80
+
81
+ export async function evaluateArchguardDrift(ledger) {
82
+ await runPureArchguardOperation('drift-status', { ledger })
83
+ const recent = ledger.slice(-10)
84
+ let shared = new Set(); let consecutiveBlocks = 0
85
+ for (let index = recent.length - 1; index >= 0; index--) {
86
+ const entry = recent[index]
87
+ if (entry.status !== 'blocked') break
88
+ const rules = new Set(entry.blockingRuleIds)
89
+ const intersection = consecutiveBlocks ? new Set([...shared].filter(id => rules.has(id))) : rules
90
+ if (!intersection.size) break
91
+ shared = intersection; consecutiveBlocks++
92
+ }
93
+ const blockedBlocks = recent.filter(entry => entry.status === 'blocked').length
94
+ const trend = consecutiveBlocks >= 3 ? 'red' : blockedBlocks || !ledger.length ? 'yellow' : 'green'
95
+ return { trend, totalBlocks: ledger.length, recentBlocks: recent.length, blockedBlocks,
96
+ violationRate: recent.length ? Math.round(blockedBlocks / recent.length * 1e3) / 10 : 0,
97
+ consecutiveBlocks, repeatedRuleIds: [...shared].sort(), action: trend === 'red'
98
+ ? 'interrupt-and-request-human-confirmation' : trend === 'yellow' ? 'warn-and-correct-next-block' : 'continue' }
99
+ }
100
+
101
+ export async function evaluateArchguardCheckpoint(value, block, history) {
102
+ const contract = await normalizeArchguardContract(value)
103
+ const module = await runtime()
104
+ assertSchema('checkpoint', module.OPERATION_SCHEMAS.checkpoint.input, { contract, block, history })
105
+ if (block.path !== block.path.normalize('NFC') || block.path.startsWith('/') || block.path.includes('\\')
106
+ || /[\u0000-\u001f\u007f:]/.test(block.path) || block.path.split('/').some(part => !part || part === '.' || part === '..')) fail('Unsafe checkpoint path')
107
+ const scan = evaluateArchguardFiles(contract, [{ path: block.path, content: block.content }])
108
+ const native = await module.runTrustedLocalCheckpoint({ schemaVersion: 'archguard.skill.request/1.0', requestId: digest({ contract, block, history }),
109
+ operation: 'checkpoint', input: { contract, block, history } }, scan.findings)
110
+ const blocking = scan.findings.filter(item => item.blocking)
111
+ const ledgerEntry = { ...native.output.ledgerEntry, status: blocking.length ? 'blocked' : 'passed',
112
+ ruleIds: [...new Set(scan.findings.map(item => item.ruleId))].sort(), blockingRuleIds: [...new Set(blocking.map(item => item.ruleId))].sort(), rollbackRequired: blocking.length > 0 }
113
+ const drift = await evaluateArchguardDrift([...history, ledgerEntry])
114
+ if (drift.trend === 'red') { ledgerEntry.status = 'blocked'; ledgerEntry.rollbackRequired = true }
115
+ return { checkpoint: { status: ledgerEntry.status, writeAllowed: !ledgerEntry.rollbackRequired,
116
+ rollbackRequired: ledgerEntry.rollbackRequired, findings: scan.findings, metrics: scan.metrics[0], drift,
117
+ instruction: drift.trend === 'red' ? 'rollback-block-and-interrupt-aimlock' : ledgerEntry.rollbackRequired ? 'rollback-block-and-correct' : 'accept-block' }, ledgerEntry }
118
+ }
119
+
120
+ export { evaluateArchguardFiles }
@@ -0,0 +1,165 @@
1
+ import Parser from 'web-tree-sitter';
2
+ import { createRequire } from 'node:module';
3
+
4
+ const require = createRequire(import.meta.url);
5
+ await Parser.init({ locateFile: name => require.resolve(`web-tree-sitter/${name}`) });
6
+ const grammars = new Map();
7
+ for (const language of ['css', 'python', 'java', 'rust']) {
8
+ grammars.set(language, await Parser.Language.load(require.resolve(`tree-sitter-wasms/out/tree-sitter-${language}.wasm`)));
9
+ }
10
+ const EXTENSIONS = new Map([['css', 'css'], ['wxss', 'css'], ['py', 'python'], ['java', 'java'], ['rs', 'rust']]);
11
+ const ROOTS = { css: 'stylesheet', python: 'module', java: 'program', rust: 'source_file' };
12
+ const FUNCTIONS = { css: [], python: ['function_definition', 'lambda'], java: ['method_declaration', 'constructor_declaration', 'lambda_expression'],
13
+ rust: ['function_item', 'closure_expression'] };
14
+ const CONTROLS = new Set(['if_statement', 'for_statement', 'while_statement', 'do_statement', 'enhanced_for_statement',
15
+ 'switch_expression', 'switch_statement', 'try_statement', 'catch_clause', 'except_clause', 'with_statement',
16
+ 'match_statement', 'case_clause', 'if_expression', 'for_expression', 'while_expression', 'loop_expression', 'match_expression']);
17
+ const SELECTORS = {
18
+ css: { FunctionDeclaration: [], FunctionExpression: [], ArrowFunction: [], MethodDeclaration: [], Constructor: [],
19
+ ClassDeclaration: [], ImportDeclaration: ['import_statement'], ExportDeclaration: [], CallExpression: ['call_expression'],
20
+ NewExpression: [], PropertyAssignment: ['declaration'], Block: ['block', 'keyframe_block_list'],
21
+ StringLiteral: ['string_value'], NumericLiteral: ['integer_value', 'float_value'], Identifier: ['identifier', 'property_name'],
22
+ IfStatement: [], ForStatement: [], WhileStatement: [], ReturnStatement: [] },
23
+ python: { FunctionDeclaration: ['function_definition'], MethodDeclaration: ['function_definition'], FunctionExpression: ['lambda'],
24
+ ArrowFunction: ['lambda'], ClassDeclaration: ['class_definition'], ImportDeclaration: ['import_statement', 'import_from_statement'],
25
+ ExportDeclaration: [], CallExpression: ['call'], NewExpression: [], Block: ['block'], StringLiteral: ['string'],
26
+ NumericLiteral: ['integer', 'float'], Identifier: ['identifier'], IfStatement: ['if_statement'],
27
+ ForStatement: ['for_statement'], WhileStatement: ['while_statement'], ReturnStatement: ['return_statement'],
28
+ ThrowStatement: ['raise_statement'], TryStatement: ['try_statement'], AwaitExpression: ['await'],
29
+ PropertyAccessExpression: ['attribute'], BinaryExpression: ['binary_operator', 'comparison_operator', 'boolean_operator'] },
30
+ java: { FunctionDeclaration: [], MethodDeclaration: ['method_declaration'], Constructor: ['constructor_declaration'],
31
+ FunctionExpression: ['lambda_expression'], ArrowFunction: ['lambda_expression'], ClassDeclaration: ['class_declaration'],
32
+ InterfaceDeclaration: ['interface_declaration'], ImportDeclaration: ['import_declaration'], ExportDeclaration: [],
33
+ CallExpression: ['method_invocation'], NewExpression: ['object_creation_expression'], Block: ['block', 'constructor_body'],
34
+ StringLiteral: ['string_literal'], NumericLiteral: ['decimal_integer_literal', 'decimal_floating_point_literal', 'hex_integer_literal'],
35
+ Identifier: ['identifier'], IfStatement: ['if_statement'], ForStatement: ['for_statement', 'enhanced_for_statement'],
36
+ WhileStatement: ['while_statement'], ReturnStatement: ['return_statement'], ThrowStatement: ['throw_statement'],
37
+ TryStatement: ['try_statement'], PropertyAccessExpression: ['field_access'], BinaryExpression: ['binary_expression'] },
38
+ rust: { FunctionDeclaration: ['function_item'], MethodDeclaration: ['function_item'], FunctionExpression: ['closure_expression'],
39
+ ArrowFunction: ['closure_expression'], ImportDeclaration: ['use_declaration', 'extern_crate_declaration'], ExportDeclaration: ['use_declaration'],
40
+ CallExpression: ['call_expression'], NewExpression: [], Block: ['block'], StringLiteral: ['string_literal', 'raw_string_literal'],
41
+ NumericLiteral: ['integer_literal', 'float_literal'], Identifier: ['identifier'], IfStatement: ['if_expression'],
42
+ ForStatement: ['for_expression'], WhileStatement: ['while_expression'], ReturnStatement: ['return_expression'],
43
+ AwaitExpression: ['await_expression'], PropertyAccessExpression: ['field_expression'], BinaryExpression: ['binary_expression'] },
44
+ };
45
+ export function archguardNativeLanguage(path) { return EXTENSIONS.get(path.split('.').at(-1)) ?? null; }
46
+ function location(node, language) {
47
+ return { line: node.startPosition.row + 1, column: node.startPosition.column + 1, start: node.startIndex,
48
+ end: node.endIndex, syntaxKind: node.type, parser: `tree-sitter/${language}` };
49
+ }
50
+ function method(node, language) {
51
+ for (let parent = node.parent; parent; parent = parent.parent) {
52
+ if (language === 'python' && parent.type === 'class_definition') return true;
53
+ if (language === 'rust' && parent.type === 'impl_item') return true;
54
+ if (FUNCTIONS[language].includes(parent.type)) return false;
55
+ }
56
+ return false;
57
+ }
58
+ function selectorMatch(node, pattern, language, types) {
59
+ if (!types.includes(node.type)) return false;
60
+ if (['python', 'rust'].includes(language) && ['FunctionDeclaration', 'MethodDeclaration'].includes(pattern)) {
61
+ return method(node, language) === (pattern === 'MethodDeclaration');
62
+ }
63
+ if (language === 'rust' && pattern === 'ExportDeclaration') return node.namedChildren.some(child => child.type === 'visibility_modifier');
64
+ return true;
65
+ }
66
+ function nodeDepth(node, language) {
67
+ if (language === 'css') return ['block', 'keyframe_block_list'].includes(node.type) ? 1 : 0;
68
+ if (CONTROLS.has(node.type)) return 1;
69
+ if (!['block', 'constructor_body'].includes(node.type)) return 0;
70
+ if (language === 'python' && node.parent?.type === 'class_definition') return 0;
71
+ return CONTROLS.has(node.parent?.type) || ['else_clause', 'finally_clause'].includes(node.parent?.type) ? 0 : 1;
72
+ }
73
+ function stringValue(node) {
74
+ if (node.text.startsWith('"')) { try { return JSON.parse(node.text); } catch { return node.text.slice(1, -1); } }
75
+ return node.text.startsWith("'") ? node.text.slice(1, -1) : node.text;
76
+ }
77
+ function dependencies(node, language) {
78
+ if (language === 'python' && node.type === 'import_from_statement') {
79
+ const source = node.childForFieldName('module_name'); return source ? [{ dependency: source.text.split('.')[0], namespace: source.text }] : [];
80
+ }
81
+ if (language === 'python' && node.type === 'import_statement') return node.namedChildren.map(child => {
82
+ const source = child.type === 'aliased_import' ? child.childForFieldName('name') : child;
83
+ return { dependency: source.text.split('.')[0], namespace: source.text };
84
+ });
85
+ if (language === 'java' && node.type === 'import_declaration') {
86
+ const source = node.namedChildren.find(child => ['scoped_identifier', 'identifier'].includes(child.type));
87
+ if (!source) return [];
88
+ const dependency = source.text.startsWith('org.apache.commons.logging.') ? 'commons-logging' : source.text;
89
+ return [{ dependency, namespace: source.text }];
90
+ }
91
+ if (language === 'rust' && ['use_declaration', 'extern_crate_declaration'].includes(node.type)) {
92
+ const source = node.childForFieldName(node.type === 'use_declaration' ? 'argument' : 'name');
93
+ return source ? [{ dependency: source.text.replace(/^::/, '').split('::')[0], namespace: source.text }] : [];
94
+ }
95
+ if (language === 'css' && node.type === 'import_statement') {
96
+ const output = []; function collect(child) { if (child.type === 'string_value') output.push({ dependency: stringValue(child) });
97
+ else child.namedChildren.forEach(collect); } collect(node); return output;
98
+ }
99
+ return [];
100
+ }
101
+ function callName(node, language) {
102
+ if (language === 'python' && node.type === 'call') return node.childForFieldName('function')?.text ?? '';
103
+ if (language === 'java' && node.type === 'method_invocation') {
104
+ const object = node.childForFieldName('object')?.text; const name = node.childForFieldName('name')?.text;
105
+ return object ? `${object}.${name}` : name ?? '';
106
+ }
107
+ if (language === 'rust' && ['call_expression', 'macro_invocation'].includes(node.type)) {
108
+ return node.childForFieldName(node.type === 'macro_invocation' ? 'macro' : 'function')?.text ?? '';
109
+ }
110
+ if (language === 'css' && node.type === 'call_expression') return node.namedChildren[0]?.text ?? '';
111
+ return '';
112
+ }
113
+ function builtin(node, language) {
114
+ const output = []; const name = callName(node, language);
115
+ if ((language === 'python' && ['eval', 'exec', 'builtins.eval', 'builtins.exec'].includes(name))
116
+ || (language === 'java' && (name === 'eval' || name.endsWith('.eval')))
117
+ || (language === 'rust' && name === 'eval')) output.push('no-eval');
118
+ if ((language === 'python' && ['print', 'breakpoint'].includes(name))
119
+ || (language === 'java' && ['System.out.print', 'System.out.println', 'System.err.print', 'System.err.println'].includes(name))
120
+ || (language === 'rust' && node.type === 'macro_invocation' && ['dbg', 'println', 'eprintln'].includes(name))) output.push('no-debug');
121
+ if (language === 'css' && (node.type === 'color_value' || ['rgb', 'rgba', 'hsl', 'hsla'].includes(name))) output.push('no-hardcoded-color');
122
+ if (['integer', 'float', 'decimal_integer_literal', 'decimal_floating_point_literal', 'integer_literal', 'float_literal'].includes(node.type)
123
+ && Number(node.text.replace(/_/g, '')) > 1) output.push('no-magic-number');
124
+ return output;
125
+ }
126
+ export function analyzeArchguardLanguage(file, rules) {
127
+ const language = archguardNativeLanguage(file.path); if (!language) throw new Error('ARCH-AST-LANGUAGE-UNSUPPORTED');
128
+ const selected = new Map();
129
+ for (const rule of rules.filter(rule => rule.engine === 'ast')) {
130
+ const types = rule.pattern === 'SourceFile' ? [ROOTS[language]] : SELECTORS[language][rule.pattern];
131
+ if (!types) throw new Error(`ARCH-AST-SELECTOR-LANGUAGE-UNSUPPORTED: ${rule.pattern} in ${language}`);
132
+ selected.set(rule.id, { rule, types, locations: [] });
133
+ }
134
+ const parser = new Parser(); parser.setLanguage(grammars.get(language));
135
+ const tree = parser.parse(file.content);
136
+ try {
137
+ const diagnostics = [];
138
+ function errors(node) {
139
+ if (node.type === 'ERROR' || node.isMissing) diagnostics.push({ ...location(node, language), code: node.type === 'ERROR' ? 'ERROR' : 'MISSING' });
140
+ node.children.forEach(errors);
141
+ }
142
+ if (tree.rootNode.hasError) { errors(tree.rootNode); return { diagnostics: diagnostics.length ? diagnostics : [{ code: 'ERROR', line: 1, column: 1 }] }; }
143
+ const result = { selected, builtin: new Map(['no-eval', 'no-debug', 'no-inline-style', 'no-hardcoded-color', 'no-magic-number'].map(id => [id, []])),
144
+ dependencies: [], maxFunctionLines: 0, maxNestingDepth: 0, animations: 0, transitions: 0, diagnostics: [] };
145
+ function visit(node, depth) {
146
+ const next = depth + nodeDepth(node, language); result.maxNestingDepth = Math.max(result.maxNestingDepth, next);
147
+ if (FUNCTIONS[language].includes(node.type)) result.maxFunctionLines = Math.max(result.maxFunctionLines, node.endPosition.row - node.startPosition.row + 1);
148
+ for (const item of selected.values()) if (selectorMatch(node, item.rule.pattern, language, item.types)) {
149
+ item.locations.push({ ...location(node, language), semanticSyntaxKind: item.rule.pattern });
150
+ }
151
+ for (const id of builtin(node, language)) result.builtin.get(id).push(location(node, language));
152
+ for (const dependency of dependencies(node, language)) result.dependencies.push({ ...dependency, ...location(node, language) });
153
+ if (language === 'css') {
154
+ if (node.type === 'keyframes_statement') result.animations += 1;
155
+ if (node.type === 'declaration') {
156
+ const property = node.namedChildren.find(child => child.type === 'property_name')?.text;
157
+ if (['animation', 'animation-name'].includes(property)) result.animations += 1;
158
+ if (['transition', 'transition-property'].includes(property)) result.transitions += 1;
159
+ }
160
+ }
161
+ node.namedChildren.forEach(child => visit(child, next));
162
+ }
163
+ visit(tree.rootNode, 0); return result;
164
+ } finally { tree.delete(); parser.delete(); }
165
+ }