@entrorove/skill 0.1.37 → 0.1.39

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 (58) hide show
  1. package/CAPABILITY-MAP.md +11 -0
  2. package/CHANGELOG.md +10 -0
  3. package/IDE-EXECUTION.md +97 -0
  4. package/REUSE-DISCIPLINE.md +18 -0
  5. package/SKILL.md +23 -92
  6. package/cli.mjs +15 -11
  7. package/cloud-task-client.mjs +3 -1
  8. package/ide-code-mode.mjs +38 -0
  9. package/ide-command.mjs +76 -0
  10. package/ide-hook.mjs +183 -0
  11. package/ide-install.mjs +60 -0
  12. package/ide-presentation.mjs +6 -0
  13. package/ide-queue.mjs +108 -0
  14. package/ide-results.mjs +59 -0
  15. package/ide-session.mjs +86 -0
  16. package/local-approved-plan.mjs +21 -6
  17. package/local-archguard-ast.mjs +6 -1
  18. package/local-archguard-language-ast.mjs +7 -0
  19. package/local-cli.mjs +6 -1
  20. package/local-contracts.mjs +4 -1
  21. package/local-discipline-actions.mjs +71 -0
  22. package/local-discipline-analysis.mjs +153 -0
  23. package/local-discipline-batch.mjs +32 -0
  24. package/local-discipline-contracts.d.mts +6 -0
  25. package/local-discipline-contracts.mjs +62 -0
  26. package/local-discipline-gate.mjs +85 -0
  27. package/local-discipline-native.mjs +33 -0
  28. package/local-discipline-operations.mjs +39 -0
  29. package/local-discipline-repository.mjs +87 -0
  30. package/local-discipline-snapshot.mjs +29 -0
  31. package/local-equivalence-runner.mjs +33 -0
  32. package/local-error.mjs +10 -0
  33. package/local-execution.mjs +1 -0
  34. package/local-integrity.d.mts +5 -0
  35. package/local-integrity.mjs +4 -0
  36. package/local-merge-controller.mjs +1 -0
  37. package/local-merge-rollback.mjs +2 -1
  38. package/local-mutation-authority.mjs +16 -7
  39. package/local-mutation-batch.mjs +7 -3
  40. package/local-mutation-files.mjs +22 -0
  41. package/local-mutation-snapshot.mjs +6 -3
  42. package/local-mutation-wire.mjs +6 -1
  43. package/local-runner-discipline.mjs +23 -0
  44. package/local-scope-repository.mjs +16 -0
  45. package/local-task-handoff-write-receipts.mjs +11 -0
  46. package/manifest.json +1 -1
  47. package/package.json +34 -4
  48. package/release-state.json +56 -28
  49. package/release.mjs +3 -1
  50. package/releases.json +12 -0
  51. package/remote-client.mjs +8 -0
  52. package/reuse-discipline-release.mjs +18 -0
  53. package/runtime/validator-runtime-v7.0.44.d.mts +3 -0
  54. package/runtime/validator-runtime-v7.0.44.mjs +1362 -0
  55. package/snapshot-cleanup-journal.mjs +32 -0
  56. package/snapshot-retention.mjs +95 -0
  57. package/task-entry-client.mjs +2 -2
  58. package/validator-runtime-build.mjs +16 -0
@@ -0,0 +1,87 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { canonical, sha256 } from './local-integrity.mjs';
4
+ import { mutationRead14, mutationPath14, mutationTarget14, mutationFail14 } from './local-mutation-files.mjs';
5
+ import { analyzeDisciplineSources } from './local-discipline-analysis.mjs';
6
+ import { importGraph, scopeCandidateImports } from './local-scope-repository.mjs';
7
+ import { nativeDisciplineDefinitions } from './local-discipline-native.mjs';
8
+
9
+ const script = /\.(?:[cm]?[jt]sx?|php|py|java|rs|vue|svelte|css)$/i;
10
+ const excluded = new Set(['.git', 'node_modules']);
11
+ async function inventory(root, prefix = '') {
12
+ const entries = await fs.readdir(path.join(root, prefix), { withFileTypes: true }), files = [];
13
+ for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
14
+ if (excluded.has(entry.name)) continue;
15
+ const relative = mutationPath14(prefix ? `${prefix}/${entry.name}` : entry.name);
16
+ if (entry.isDirectory()) files.push(...await inventory(root, relative));
17
+ else if (entry.isFile() || entry.isSymbolicLink()) files.push(relative);
18
+ else mutationFail14('ENTROROVE_DISCIPLINE_SCAN_UNKNOWN', 'Repository comparison encountered an unsupported filesystem entry');
19
+ }
20
+ return files.sort();
21
+ }
22
+ export async function disciplineRepositoryObservation(input, root, { analyze = false, overrides = new Map() } = {}) {
23
+ const paths = await inventory(root), files = [], sources = [], unresolved = [];
24
+ for (const relative of paths) {
25
+ const stat = await fs.lstat(path.join(root, relative));
26
+ if (stat.isSymbolicLink()) {
27
+ files.push({ path: relative, sha256: sha256(await fs.readlink(path.join(root, relative))), mode: stat.mode & 0o777, kind: 'symlink' });
28
+ unresolved.push({ path: relative, reason: 'symlink-reference' }); continue;
29
+ }
30
+ const read = await mutationRead14(input, root, relative);
31
+ files.push({ path: relative, sha256: read.sha256, mode: read.mode, kind: 'file' });
32
+ if (analyze && script.test(relative)) sources.push({ path: relative, content: overrides.get(relative) ?? read.content.toString('utf8') });
33
+ }
34
+ if (canonical(paths) !== canonical(await inventory(root))) mutationFail14('ENTROROVE_DISCIPLINE_SCAN_CHANGED', 'Repository inventory changed while observing its original baseline');
35
+ const comparison = { schemaVersion: 'entrorove.repository-comparison/1.0', files,
36
+ scope: 'whole-repository-including-ignored-files', excludedDirectoryNames: [...excluded].sort() };
37
+ if (!analyze) return { comparison };
38
+ // The existing resolver supplies actual aliases, package exports and import edges.
39
+ // Unsupported/dynamic references stay unknown; they are never converted to unused.
40
+ const roots = sources.filter(file => /\.[cm]?[jt]sx?$/i.test(file.path)).map(file => ({ path: file.path, exists: true }));
41
+ for (const [relative, content] of overrides) if (!paths.includes(relative) && script.test(relative)) sources.push({ path: relative, content });
42
+ let graph;
43
+ try { graph = roots.length ? await importGraph(root, roots, input) : { edges: [], dynamicSources: [], complete: true }; }
44
+ catch (error) {
45
+ if (!['ENTROROVE_IMPORT_UNRESOLVED', 'ENTROROVE_IMPORT_INVALID', 'ENTROROVE_IMPORT_UNSUPPORTED', 'ENTROROVE_SCOPE_LIMIT'].includes(error.code)) throw error;
46
+ graph = { edges: [], dynamicSources: [], complete: false }; unresolved.push({ reason: error.code });
47
+ }
48
+ for (const [relative, content] of overrides) if (/\.[cm]?[jt]sx?$/i.test(relative)) {
49
+ try {
50
+ const candidate = await scopeCandidateImports(root, { path: relative, content }, input);
51
+ graph.edges = [...graph.edges.filter(edge => edge.from !== relative), ...candidate.edges];
52
+ if (candidate.dynamic) unresolved.push({ path: relative, reason: 'candidate-dynamic-import' });
53
+ } catch (error) {
54
+ if (!['ENTROROVE_IMPORT_UNRESOLVED', 'ENTROROVE_IMPORT_INVALID', 'ENTROROVE_IMPORT_UNSUPPORTED'].includes(error.code)) throw error;
55
+ unresolved.push({ path: relative, reason: error.code });
56
+ }
57
+ }
58
+ const analysis = analyzeDisciplineSources(sources, { edges: graph.edges,
59
+ unresolved: [...unresolved, ...graph.dynamicSources.map(item => ({ path: item.path, reason: 'dynamic-import' }))] });
60
+ analysis.definitions.push(...await nativeDisciplineDefinitions(sources));
61
+ return { comparison, analysis, scanComplete: graph.complete && analysis.unresolved.length === 0 };
62
+ }
63
+ export function disciplineRepositoryDifferences(before, after, authorized = new Map()) {
64
+ const left = new Map(before.files.map(file => [file.path, file])), right = new Map(after.files.map(file => [file.path, file]));
65
+ const changed = [];
66
+ for (const relative of [...new Set([...left.keys(), ...right.keys()])].sort()) {
67
+ const expected = authorized.has(relative) ? authorized.get(relative) : left.get(relative) ?? null;
68
+ if (canonical(expected) !== canonical(right.get(relative) ?? null)) changed.push(relative);
69
+ }
70
+ return changed;
71
+ }
72
+ export async function assertDisciplineRepository(input, root, review, ledger) {
73
+ const expected = new Map();
74
+ for (const [relative, write] of Object.entries(ledger.writes ?? {})) {
75
+ if (write.status !== 'written') mutationFail14('ENTROROVE_MUTATION_PASS_CONSUMED', 'Repository comparison found an unresolved physical action');
76
+ if (write.kind === 'delete') expected.set(relative, null);
77
+ else {
78
+ const baseline = review.comparison.files.find(file => file.path === relative);
79
+ expected.set(relative, { path: relative, sha256: write.contentDigest, mode: write.mode ?? baseline?.mode ?? 0o644,
80
+ kind: baseline?.kind ?? 'file' });
81
+ }
82
+ }
83
+ const current = await disciplineRepositoryObservation(input, root);
84
+ const changed = disciplineRepositoryDifferences(review.comparison, current.comparison, expected);
85
+ if (changed.length) mutationFail14('ENTROROVE_DISCIPLINE_REPOSITORY_DRIFT', `Whole-repository comparison differs outside completed declared actions: ${changed.join(', ')}`);
86
+ return current.comparison;
87
+ }
@@ -0,0 +1,29 @@
1
+ import path from 'node:path';
2
+ import { canonical, sha256 } from './local-integrity.mjs';
3
+ import { atomicMutationJson14, mutationFail14, readMutationJson14 } from './local-mutation-files.mjs';
4
+ import { disciplineRepositoryObservation } from './local-discipline-repository.mjs';
5
+
6
+ export function snapshotDisciplineBinding(value) {
7
+ if (!Object.hasOwn(value, 'disciplineReviewDigest')) return {};
8
+ if (!/^[0-9a-f]{64}$/.test(value.disciplineReviewDigest ?? '')) mutationFail14('ENTROROVE_DISCIPLINE_REVIEW_INVALID', 'Snapshot discipline review digest is invalid');
9
+ return { disciplineReviewDigest: value.disciplineReviewDigest };
10
+ }
11
+ export async function captureDisciplineSnapshot(input, root, location, binding) {
12
+ const observed = await disciplineRepositoryObservation(input, root, { analyze: true });
13
+ const review = { schemaVersion: 'entrorove.discipline-review/1.0', ...binding,
14
+ repositoryDigest: sha256(root).slice(0, 32), createdAt: new Date().toISOString(), ...observed,
15
+ thresholds: { deletionAdditionRatio: null, semanticOverlap: null, directLines: null, usageScopeLimit: null } };
16
+ const digest = sha256(canonical(review));
17
+ await atomicMutationJson14(path.join(location.snapshotRoot, 'discipline-review.json'), review, { privateFrom: location.stateRoot });
18
+ return { disciplineReviewDigest: digest };
19
+ }
20
+ export async function readDisciplineSnapshot(snapshot) {
21
+ const manifest = snapshot.manifest;
22
+ if (!manifest.disciplineReviewDigest) mutationFail14('ENTROROVE_DISCIPLINE_REVIEW_REQUIRED', 'A new write requires its original deterministic repository and usage review; reconnect the original snapshot');
23
+ const review = await readMutationJson14(path.join(snapshot.snapshotRoot, 'discipline-review.json'));
24
+ if (review.schemaVersion !== 'entrorove.discipline-review/1.0' || sha256(canonical(review)) !== manifest.disciplineReviewDigest
25
+ || ['snapshotId', 'flowId', 'stepId', 'repositoryDigest'].some(key => review[key] !== manifest[key])) {
26
+ mutationFail14('ENTROROVE_DISCIPLINE_REVIEW_INVALID', 'Repository or usage review differs from the original signed snapshot');
27
+ }
28
+ return review;
29
+ }
@@ -0,0 +1,33 @@
1
+ import { validatorBindEquivalenceResult, validatorParallelDeclarationsValid } from './runtime/validator-runtime-v7.0.44.mjs';
2
+
3
+ export function validateRunnerParallelPaths(plan) {
4
+ const pairs = plan.policy.parallelPaths;
5
+ if (pairs === undefined) return;
6
+ if (!validatorParallelDeclarationsValid(pairs) || pairs.some(pair =>
7
+ ![pair.originalPath, pair.candidatePath].every(path => plan.files.some(file => file.path === path))
8
+ || !plan.files.some(file => file.path === pair.baselinePath && file.sha256 === pair.baselineSha256)
9
+ || plan.goldenBaseline.source.kind !== 'artifact' || plan.goldenBaseline.source.locator !== pair.baselinePath
10
+ || plan.goldenBaseline.source.digestSha256 !== pair.baselineSha256)) {
11
+ throw Object.assign(new Error('Parallel paths must bind the original frozen manifest and real prior output baseline'), { code: 'ENTROROVE_EQ_PLAN_INVALID' });
12
+ }
13
+ }
14
+ export async function captureRunnerEquivalence(authority, result, readFile) {
15
+ const pairs = authority.plan.policy.parallelPaths;
16
+ if (!pairs) return undefined;
17
+ let output;
18
+ try { output = JSON.parse(result.stdout); } catch { return undefined; }
19
+ if (!Array.isArray(output?.equivalence)) return undefined;
20
+ if (output.equivalence.some(item => !item || Object.keys(item).sort().join('|') !== 'cases|comparisonId')
21
+ || new Set(output.equivalence.map(item => item.comparisonId)).size !== output.equivalence.length
22
+ || output.equivalence.some(item => !pairs.some(pair => pair.comparisonId === item.comparisonId))) {
23
+ throw Object.assign(new Error('Actual comparison output contains undeclared identities'), { code: 'ENTROROVE_EQ_OUTPUT_INVALID' });
24
+ }
25
+ const results = [];
26
+ for (const pair of pairs) {
27
+ const actual = output.equivalence.find(item => item.comparisonId === pair.comparisonId);
28
+ if (!actual) continue;
29
+ const baseline = await readFile(authority.root, pair.baselinePath);
30
+ results.push(validatorBindEquivalenceResult(pair, baseline.bytes, actual.cases, authority.plan.goldenBaseline.frozenAt));
31
+ }
32
+ return results;
33
+ }
package/local-error.mjs CHANGED
@@ -9,9 +9,19 @@ export const LOCAL_ERROR_CODE_CATALOG = Object.freeze({
9
9
  ENTROROVE_BUDGET_LEASE_EXPIRED: Object.freeze({ disposition: 'retriable', stability: 'stable' }),
10
10
  ENTROROVE_GIT_READER_UNSUPPORTED: Object.freeze({ disposition: 'terminal', stability: 'stable' }),
11
11
  ENTROROVE_GIT_UNAVAILABLE: Object.freeze({ disposition: 'needs-human', stability: 'stable' }),
12
+ ENTROROVE_DISCIPLINE_NEEDS_HUMAN: Object.freeze({ disposition: 'needs-human', stability: 'stable' }),
13
+ ENTROROVE_DISCIPLINE_USAGE_UNKNOWN: Object.freeze({ disposition: 'needs-human', stability: 'stable' }),
14
+ ENTROROVE_DISCIPLINE_REVIEW_REQUIRED: Object.freeze({ disposition: 'needs-human', stability: 'stable' }),
15
+ ENTROROVE_DISCIPLINE_DATA_SNAPSHOT_REQUIRED: Object.freeze({ disposition: 'needs-human', stability: 'stable' }),
16
+ ENTROROVE_IDE_GUARD_REQUIRED: Object.freeze({ disposition: 'needs-human', stability: 'stable' }),
17
+ ENTROROVE_IDE_GUARD_UNSUPPORTED: Object.freeze({ disposition: 'terminal', stability: 'stable' }),
12
18
  });
13
19
 
14
20
  const BLOCKED_CODES = new Set([
21
+ 'ENTROROVE_DISCIPLINE_NEEDS_HUMAN', 'ENTROROVE_DISCIPLINE_USAGE_UNKNOWN', 'ENTROROVE_DISCIPLINE_REVIEW_REQUIRED',
22
+ 'ENTROROVE_DISCIPLINE_DATA_SNAPSHOT_REQUIRED', 'ENTROROVE_DISCIPLINE_REPOSITORY_DRIFT', 'ENTROROVE_DISCIPLINE_FORMAT_SCOPE',
23
+ 'ENTROROVE_DISCIPLINE_SPECULATIVE', 'ENTROROVE_DISCIPLINE_REVIEW_INVALID',
24
+ 'ENTROROVE_IDE_GUARD_REQUIRED', 'ENTROROVE_IDE_GUARD_UNSUPPORTED', 'ENTROROVE_IDE_ACCOUNT',
15
25
  'ENTROROVE_GIT_UNAVAILABLE',
16
26
  'ENTROROVE_MUTATE_NOT_ACCEPTED', 'ENTROROVE_MUTATION_AUTHORITY_REQUIRED',
17
27
  'ENTROROVE_MUTATION_PASS_INVALID', 'ENTROROVE_MUTATION_PASS_EXPIRED',
@@ -277,6 +277,7 @@ export async function finalGate(input) {
277
277
 
278
278
  const LOCAL_OPERATION_GROUPS = Object.freeze(['core', 'budget', 'scope', 'merge', 'chain', 'handoff', 'archguard', 'calctool', 'swarm', 'validator', 'formula', 'evidence']);
279
279
  const PASS_REQUIRED_OPERATIONS = new Set(['mutation-snapshot-attest', 'mutate-gate', 'gate-issue', 'gate-verify', 'guarded-write',
280
+ 'discipline-review', 'discipline-check', 'guarded-delete', 'guarded-move',
280
281
  'merge-verified', 'rollback', 'archguard-contract-create', 'archguard-contract-update', 'archguard-checkpoint', 'archguard-scope-request',
281
282
  'budget-handoff', 'budget-transition', 'spawn-bind', 'peer-complete', 'complete']);
282
283
  function operationGroup(operation) {
@@ -0,0 +1,5 @@
1
+ export function canonical(value: unknown): string;
2
+ export function sha256(value: string | Buffer): string;
3
+ export function verifySnapshotManifest(manifest: unknown, binding: {
4
+ snapshotId: string; flowId: string; stepId: string; repositoryDigest: string;
5
+ }): { verified: boolean; reason?: string };
@@ -48,6 +48,10 @@ export function verifySnapshotManifest(manifest, binding) {
48
48
  }
49
49
  const core = { snapshotId: manifest.snapshotId, flowId: manifest.flowId, stepId: manifest.stepId,
50
50
  paths: manifest.paths, files: manifest.files, contractDigest: manifest.contractDigest };
51
+ if (Object.hasOwn(manifest, 'disciplineReviewDigest')) {
52
+ if (!SHA256.test(manifest.disciplineReviewDigest ?? '')) return { verified: false, reason: 'snapshot discipline digest is invalid' };
53
+ core.disciplineReviewDigest = manifest.disciplineReviewDigest;
54
+ }
51
55
  try { Object.assign(core, snapshotHandoffBinding(manifest)); }
52
56
  catch { return { verified: false, reason: 'snapshot handoff binding is invalid' }; }
53
57
  return manifest.snapshotDigest === sha256(canonical(core))
@@ -85,6 +85,7 @@ async function commit(ctx, input, state, registration, images, assertCurrent) {
85
85
  token = await bridge.beginMutationBatch14({ ...ctx.input, signerConfigPath: input.runnerSignerConfigPath }, {
86
86
  mutationPass: images.mutation.pass, contract: input.contract, contractDigest, transactionId,
87
87
  beforeFiles: journal.before.filter(file => input.preparation.paths.includes(file.path)),
88
+ contents: images.afterWide.filter(file => input.preparation.paths.includes(file.path) && file.exists).map(file => ({ path: file.path, contentBase64: file.bytes.toString('base64') })),
88
89
  afterFiles: journal.after.filter(file => input.preparation.paths.includes(file.path)) }); },
89
90
  progress: async progress => { journal = { ...journal, ...progress }; await saveMergeJournal(ctx, journal); },
90
91
  finish: async () => {
@@ -65,7 +65,8 @@ async function rollbackCommit(ctx, input, state, journal, images, assertCurrent)
65
65
  latest = await saveMergeJournal(ctx, { ...journal, rollback: attempt });
66
66
  token = await bridge.beginMutationBatch14({ ...ctx.input, signerConfigPath: input.runnerSignerConfigPath }, {
67
67
  mutationPass: images.mutation.pass, contract: auth, contractDigest: binding.contractDigest, transactionId: auth.transactionId,
68
- beforeFiles: selected(before), afterFiles: selected(after) });
68
+ beforeFiles: selected(before), afterFiles: selected(after),
69
+ contents: after.filter(file => auth.paths.includes(file.path) && file.exists).map(file => ({ path: file.path, contentBase64: file.bytes.toString('base64') })) });
69
70
  },
70
71
  progress: async progress => { attempt = { ...attempt, ...progress }; latest = await saveMergeJournal(ctx, { ...journal, rollback: attempt }); },
71
72
  finish: async () => {
@@ -4,13 +4,16 @@ import { appendLedgerRecord } from './local-ledger.mjs';
4
4
  import { withStateLock } from './local-state-lock.mjs';
5
5
  import { withTaskHandoffIssueFence, withTaskHandoffWriteFence, assertTaskHandoffSnapshotBinding } from './local-task-handoff-fence.mjs';
6
6
  import { mutationFail14, mutationPath14, mutationStateRoot14,
7
- mutationRead14, privateMutationDirectory14, writeMutationProjectFile14 } from './local-mutation-files.mjs';
7
+ mutationRead14, mutationTarget14, privateMutationDirectory14, writeMutationProjectFile14 } from './local-mutation-files.mjs';
8
8
  import { mutationPass14, mutationLifetime14 } from './local-mutation-wire.mjs';
9
9
  import { mutationOriginalBinding14, mutationSnapshotBinding14 } from './local-mutation-binding.mjs';
10
10
  import { installMutationPass14, mutationPassLocations14, readMutationConsumption14,
11
11
  readStoredMutationPass14, saveMutationConsumption14 } from './local-mutation-pass-store.mjs';
12
12
  import { auditMutation14, withMutationAudit14 } from './local-mutation-audit.mjs';
13
13
  import { withSwarmMutationLease } from './local-swarm-mutation.mjs';
14
+ import { disciplineWritePreflight } from './local-discipline-gate.mjs';
15
+ import { readDisciplineSnapshot } from './local-discipline-snapshot.mjs';
16
+ import { assertDisciplineRepository } from './local-discipline-repository.mjs';
14
17
  export { beginMutationBatch14, finishMutationBatch14, failMutationBatch14 } from './local-mutation-batch.mjs';
15
18
 
16
19
  const SCHEMA = 'entrorove.local-execution/1.0';
@@ -42,7 +45,7 @@ export async function issueMutationPass14(input) {
42
45
  });
43
46
  });
44
47
  }
45
- async function checkedPass(input, state, { allowTargetDrift = false } = {}) {
48
+ export async function checkedPass(input, state, { allowTargetDrift = false } = {}) {
46
49
  const pass = await readStoredMutationPass14(input), target = mutationPath14(input.targetPath, 'targetPath');
47
50
  if (!pass.paths.includes(target)) mutationFail14('ENTROROVE_MUTATION_WRITE_SCOPE', 'Guarded writing is outside the cloud pass path set');
48
51
  const binding = await mutationOriginalBinding14(input, pass, state);
@@ -53,8 +56,10 @@ async function checkedPass(input, state, { allowTargetDrift = false } = {}) {
53
56
  const consumedWrite = ledger.writes[target];
54
57
  if (allowTargetDrift) {
55
58
  if (consumedWrite?.status !== 'written') mutationFail14('ENTROROVE_MUTATION_PASS_CONSUMED', 'Checkpoint requires a completed guarded write');
56
- const current = await mutationRead14(input, binding.root, target);
57
- if (current.sha256 !== consumedWrite.contentDigest) mutationFail14('ENTROROVE_MUTATION_WRITE_CHANGED', 'Checkpoint target differs from its completed guarded write');
59
+ if (consumedWrite.kind !== 'delete') {
60
+ const current = await mutationRead14(input, binding.root, target);
61
+ if (current.sha256 !== consumedWrite.contentDigest) mutationFail14('ENTROROVE_MUTATION_WRITE_CHANGED', 'Checkpoint target differs from its completed guarded write');
62
+ } else if ((await mutationTarget14(binding.root, target, true)).exists) mutationFail14('ENTROROVE_MUTATION_WRITE_CHANGED', 'Deleted checkpoint target was recreated');
58
63
  } else if (consumedWrite) {
59
64
  mutationFail14('ENTROROVE_MUTATION_PASS_CONSUMED', 'Mutation pass was already consumed or has an unresolved physical write');
60
65
  }
@@ -80,12 +85,16 @@ async function performWrite(input, state, value) {
80
85
  if (canonical(value.pass) !== canonical(pass)) mutationFail14('ENTROROVE_MUTATION_PASS_CHANGED', 'Mutation pass changed while waiting for its original write lock');
81
86
  return withSwarmMutationLease(input, pass, async () => {
82
87
  const baseline = snapshot.manifest.files.find(file => file.path === target), content = contentFor(input, baseline);
83
- const entry = { status: 'pending', contentDigest: sha256(content), at: new Date().toISOString() };
88
+ const discipline = await disciplineWritePreflight(input, current, content);
89
+ const entry = { status: 'pending', contentDigest: sha256(content), mode: baseline.mode ?? 0o644, at: new Date().toISOString() };
84
90
  await saveMutationConsumption14(locations, { ...ledger, writes: { ...ledger.writes, [target]: entry } });
85
- await auditMutation14(input, 'guarded-write-authorized', { passId: pass.passId, sha256: entry.contentDigest });
91
+ await auditMutation14(input, 'guarded-write-authorized', { passId: pass.passId, sha256: entry.contentDigest,
92
+ disciplineReviewDigest: discipline.reviewDigest, declaredKind: discipline.declaredKind });
86
93
  mutationLifetime14(pass);
87
94
  const written = await writeMutationProjectFile14(input, binding.root, target, content, baseline);
88
- await saveMutationConsumption14(locations, { ...ledger, writes: { ...ledger.writes, [target]: { ...entry, status: 'written' } } });
95
+ const completed = { ...ledger, writes: { ...ledger.writes, [target]: { ...entry, status: 'written' } } };
96
+ await saveMutationConsumption14(locations, completed);
97
+ await assertDisciplineRepository(input, binding.root, await readDisciplineSnapshot(snapshot), completed);
89
98
  await auditMutation14(input, 'guarded-write', { passId: pass.passId, sha256: written.sha256 });
90
99
  const stateRoot = await mutationStateRoot14(input);
91
100
  await privateMutationDirectory14(path.join(stateRoot, 'ledgers', pass.repositoryDigest), { privateFrom: stateRoot });
@@ -9,6 +9,8 @@ import { readStoredMutationPass14, mutationPassLocations14, readMutationConsumpt
9
9
  saveMutationConsumption14 } from './local-mutation-pass-store.mjs';
10
10
  import { auditMutation14, withMutationAudit14 } from './local-mutation-audit.mjs';
11
11
  import { mutationBatchReceiptBinding14, signMutationBatchReceipt14 } from './local-mutation-batch-receipt.mjs';
12
+ import { disciplineBatchBefore } from './local-discipline-batch.mjs';
13
+ import { assertDisciplineRepository } from './local-discipline-repository.mjs';
12
14
 
13
15
  const tokens = new WeakMap(), MISSING = sha256('entrorove.missing-file/1.0');
14
16
  function clone(value) {
@@ -26,7 +28,7 @@ function files(value, paths) {
26
28
  }
27
29
  function options(value) {
28
30
  if (!value || typeof value !== 'object' || Array.isArray(value)
29
- || Object.keys(value).some(field => !['mutationPass', 'beforeFiles', 'afterFiles', 'contract', 'contractDigest', 'transactionId'].includes(field))) {
31
+ || Object.keys(value).some(field => !['mutationPass', 'beforeFiles', 'afterFiles', 'contents', 'contract', 'contractDigest', 'transactionId'].includes(field))) {
30
32
  mutationFail14('ENTROROVE_MUTATION_BATCH_INPUT', 'Batch begin requires its original pass and exact before and after images');
31
33
  }
32
34
  return value;
@@ -47,7 +49,7 @@ async function authority(input, supplied) {
47
49
  return { pass, binding, snapshot, locations: await mutationPassLocations14(input, binding, pass) };
48
50
  }
49
51
  function batchEntry(state, file, status = 'pending') {
50
- return { status, contentDigest: file.sha256, at: state.at, batchId: state.batchId, batchDigest: state.batchDigest,
52
+ return { status, contentDigest: file.sha256, kind: file.exists ? 'modify' : 'delete', mode: file.mode, at: state.at, batchId: state.batchId, batchDigest: state.batchDigest,
51
53
  beforeDigest: state.beforeDigest, afterDigest: state.afterDigest };
52
54
  }
53
55
  async function originalLedger(state) {
@@ -85,8 +87,9 @@ export async function beginMutationBatch14(rawInput, rawOptions) {
85
87
  }
86
88
  const snapshot = await mutationSnapshotBinding14(input, fresh.pass, { allSources: true });
87
89
  if (canonical(before) !== canonical(snapshot.manifest.files)) mutationFail14('ENTROROVE_MUTATION_BATCH_BEFORE', 'Batch before image differs from the real original signed snapshot');
90
+ const review = await disciplineBatchBefore(input, { ...fresh, snapshot }, value, before, after, ledger);
88
91
  mutationLifetime14(fresh.pass);
89
- const state = { ...fresh, ...receiptBinding, input, before, after, status: 'pending', batchId: randomUUID(), at: new Date().toISOString(),
92
+ const state = { ...fresh, ...receiptBinding, input, before, after, review, status: 'pending', batchId: randomUUID(), at: new Date().toISOString(),
90
93
  passDigest: sha256(canonical(fresh.pass)), beforeDigest: sha256(canonical(before)), afterDigest: sha256(canonical(after)) };
91
94
  state.batchDigest = sha256(canonical({ passDigest: state.passDigest, beforeDigest: state.beforeDigest,
92
95
  afterDigest: state.afterDigest, batchId: state.batchId }));
@@ -114,6 +117,7 @@ export async function finishMutationBatch14(token) {
114
117
  mutationLifetime14(fresh.pass);
115
118
  const writes = { ...ledger.writes };
116
119
  for (const file of state.after) writes[file.path] = batchEntry(state, file, 'written');
120
+ await assertDisciplineRepository(state.input, state.binding.root, state.review, { ...ledger, writes });
117
121
  await saveMutationConsumption14(state.locations, { ...ledger, writes });
118
122
  state.status = 'completed';
119
123
  await auditMutation14(state.input, 'mutation-batch-finished', { passId: state.pass.passId, batchId: state.batchId,
@@ -268,3 +268,25 @@ export async function writeMutationProjectFile14(input, root, relative, content,
268
268
  return { target: published.target, sha256: sha256(content), mode: published.stat.mode & 0o777 };
269
269
  } finally { await handle.close(); await cleanupTemporary(temporary, saved); }
270
270
  }
271
+ export async function removeMutationProjectFile14(input, root, expected) {
272
+ if (!expected?.exists) mutationFail14('ENTROROVE_DISCIPLINE_BASELINE', 'Deletion requires an existing original snapshot copy');
273
+ const before = await expectedProjectFile(input, root, expected.path, expected);
274
+ await assertAncestors(before, 'ENTROROVE_FILE_CHANGED');
275
+ const latest = await expectedProjectFile(input, root, expected.path, expected);
276
+ if (!sameNode(before.stat, latest.stat)) mutationFail14('ENTROROVE_FILE_CHANGED', 'Deletion target changed before removal');
277
+ await fs.unlink(before.target);
278
+ await assertAncestors(before, 'ENTROROVE_FILE_CHANGED');
279
+ if ((await mutationTarget14(root, expected.path, true)).exists) mutationFail14('ENTROROVE_FILE_CHANGED', 'Deleted file was replaced during removal');
280
+ }
281
+ export async function moveMutationProjectFile14(input, root, source, destination) {
282
+ if (!source?.exists || destination?.exists) mutationFail14('ENTROROVE_DISCIPLINE_BASELINE', 'Move requires an existing original source and an absent planned destination');
283
+ await expectedProjectFile(input, root, source.path, source);
284
+ await projectParents(root, destination.path);
285
+ const before = await expectedProjectFile(input, root, source.path, source), after = await expectedProjectFile(input, root, destination.path, destination);
286
+ await assertAncestors(before, 'ENTROROVE_FILE_CHANGED'); await assertAncestors(after, 'ENTROROVE_FILE_CHANGED');
287
+ await fs.link(before.target, after.target);
288
+ const linked = await mutationTarget14(root, destination.path);
289
+ if (!sameNode(before.stat, linked.stat)) mutationFail14('ENTROROVE_FILE_CHANGED', 'Move destination changed during publication');
290
+ await removeMutationProjectFile14(input, root, source);
291
+ await assertAncestors(after, 'ENTROROVE_FILE_CHANGED');
292
+ }
@@ -3,6 +3,7 @@ import { promises as fs } from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { canonical, sha256, snapshotHandoffBinding, verifySnapshotManifest } from './local-integrity.mjs';
5
5
  import { budgetBindingForRead } from './local-budget-reads.mjs';
6
+ import { captureDisciplineSnapshot, snapshotDisciplineBinding } from './local-discipline-snapshot.mjs';
6
7
  import { atomicMutationJson14, cleanupMutationDirectory14, mutationFail14, mutationId14, mutationPaths14,
7
8
  mutationPath14, mutationRead14, mutationRoot14, mutationStateRoot14, mutationTarget14, privateMutationDirectory14,
8
9
  readMutationJson14, writeMutationFile14 } from './local-mutation-files.mjs';
@@ -26,7 +27,7 @@ function handoffBinding(value) {
26
27
  function receiptCore(receipt) {
27
28
  return { schemaVersion: RECEIPT_SCHEMA, snapshotId: receipt.snapshotId, flowId: receipt.flowId, stepId: receipt.stepId,
28
29
  repositoryDigest: receipt.repositoryDigest, snapshotDigest: receipt.snapshotDigest, contractDigest: receipt.contractDigest ?? null,
29
- paths: receipt.paths, fileManifestDigest: receipt.fileManifestDigest, ...handoffBinding(receipt), ...layoutBinding(receipt) };
30
+ paths: receipt.paths, fileManifestDigest: receipt.fileManifestDigest, ...handoffBinding(receipt), ...layoutBinding(receipt), ...snapshotDisciplineBinding(receipt) };
30
31
  }
31
32
  export function loadSnapshotReceipt14(receipt) {
32
33
  if (!receipt || typeof receipt !== 'object' || Array.isArray(receipt) || receipt.schemaVersion !== RECEIPT_SCHEMA
@@ -161,7 +162,8 @@ export async function snapshotPlan14(input, { handoffBinding: binding = {} } = {
161
162
  await privateMutationDirectory14(path.join(loc.snapshotRoot, 'files'), { privateFrom: loc.stateRoot });
162
163
  const files = [];
163
164
  for (const relative of paths) files.push(await copySource(input, root, loc.snapshotRoot, relative, loc.stateRoot));
164
- const core = { snapshotId, flowId, stepId, paths, files, contractDigest, ...handoff };
165
+ const discipline = await captureDisciplineSnapshot(input, root, loc, { snapshotId, flowId, stepId });
166
+ const core = { snapshotId, flowId, stepId, paths, files, contractDigest, ...handoff, ...discipline };
165
167
  const manifest = { schemaVersion: SNAPSHOT_SCHEMA, ...core, repositoryDigest: repoKey(root), copyLayout: COPY_LAYOUT,
166
168
  createdAt: new Date().toISOString(), snapshotDigest: sha256(canonical(core)) };
167
169
  await atomicMutationJson14(loc.manifestPath, manifest, { privateFrom: loc.stateRoot });
@@ -193,7 +195,8 @@ export async function snapshotAuthority14(input, root, value) {
193
195
  const manifest = checkManifest(await readMutationJson14(loc.manifestPath), root, receipt);
194
196
  if (manifest.snapshotDigest !== receipt.snapshotDigest || canonical(manifest.paths) !== canonical(receipt.paths)
195
197
  || manifest.contractDigest !== receipt.contractDigest || canonical(handoffBinding(manifest)) !== canonical(handoffBinding(receipt))
196
- || canonical(layoutBinding(manifest)) !== canonical(layoutBinding(receipt))) mutationFail14('ENTROROVE_SNAPSHOT_BINDING', 'snapshot receipt does not match its manifest or layout');
198
+ || canonical(layoutBinding(manifest)) !== canonical(layoutBinding(receipt))
199
+ || canonical(snapshotDisciplineBinding(manifest)) !== canonical(snapshotDisciplineBinding(receipt))) mutationFail14('ENTROROVE_SNAPSHOT_BINDING', 'snapshot receipt does not match its manifest or layout');
197
200
  const files = await copyFacts(input, loc.snapshotRoot, manifest);
198
201
  if (receipt.fileManifestDigest !== sha256(canonical(files))) mutationFail14('ENTROROVE_SNAPSHOT_BINDING', 'snapshot receipt file manifest digest is invalid');
199
202
  return { manifest, manifestPath: loc.manifestPath, snapshotRoot: loc.snapshotRoot, copyRoot: copyRoot(loc.snapshotRoot, manifest) };
@@ -2,6 +2,8 @@ import { createPublicKey, verify } from 'node:crypto';
2
2
  import { canonical, sha256 } from './local-integrity.mjs';
3
3
  import { canonicalHandoffSignature } from './local-task-handoff-overlay-wire.mjs';
4
4
  import { mutationFail14, mutationPaths14 } from './local-mutation-files.mjs';
5
+ import { disciplineApprovedPolicy } from './local-discipline-contracts.mjs';
6
+ import { validatorParallelDeclarationsValid } from './runtime/validator-runtime-v7.0.44.mjs';
5
7
 
6
8
  export const MUTATION_IDENTITY_FIELDS14 = ['flowId', 'taskId', 'ownerId', 'taskDigest', 'chainDigest', 'chainRevision',
7
9
  'acceptanceReceiptId', 'acceptanceDigest', 'routeDigest', 'receiver', 'receiverDigest', 'stepId', 'leaseId', 'leaseDigest',
@@ -80,7 +82,10 @@ export function mutationChallenge14(value, options) {
80
82
  return authority(value, CHALLENGE_FIELDS, 'entrorove.mutation-snapshot-challenge/1.0', options);
81
83
  }
82
84
  export function mutationPass14(value, options) {
83
- const pass = authority(value, PASS_FIELDS, 'entrorove.mutation-pass/1.0', options);
85
+ const fields = [...PASS_FIELDS, ...['disciplinePolicy', 'parallelPaths'].filter(key => Object.hasOwn(value ?? {}, key))];
86
+ const pass = authority(value, fields, 'entrorove.mutation-pass/1.0', options);
87
+ if (pass.disciplinePolicy) disciplineApprovedPolicy(pass.disciplinePolicy);
88
+ if (pass.parallelPaths && !validatorParallelDeclarationsValid(pass.parallelPaths)) mutationFail14('ENTROROVE_MUTATION_AUTHORITY_INVALID', 'Parallel paths must match the frozen equivalence contract');
84
89
  if (!ID.test(pass.passId) || !ID.test(pass.challengeNonce) || pass.handoffEpoch !== pass.epoch || pass.coordinationRequired !== false
85
90
  || ['challengeDigest', 'snapshotDigest', 'snapshotReceiptDigest', 'snapshotManifestDigest', 'attestationDigest'].some(field => !SHA.test(pass[field]))) {
86
91
  mutationFail14('ENTROROVE_MUTATION_AUTHORITY_INVALID', 'Mutation pass has invalid snapshot or original epoch bindings');
@@ -0,0 +1,23 @@
1
+ import { canonical, sha256 } from './local-integrity.mjs';
2
+ import { disciplineRepositoryObservation, disciplineRepositoryDifferences } from './local-discipline-repository.mjs';
3
+ import { atomicMutationJson14, privateMutationDirectory14, mutationStateRoot14 } from './local-mutation-files.mjs';
4
+ import path from 'node:path';
5
+
6
+ export async function runnerDisciplineBefore(input, authority) {
7
+ if (!authority.plan.policy.discipline) return null;
8
+ const original = await disciplineRepositoryObservation(input, authority.root);
9
+ return { input, original: original.comparison };
10
+ }
11
+ export async function runnerDisciplineAfter(before, authority) {
12
+ if (!before) return null;
13
+ const after = (await disciplineRepositoryObservation(before.input, authority.root)).comparison;
14
+ const changed = disciplineRepositoryDifferences(before.original, after);
15
+ const report = { schemaVersion: 'entrorove.discipline-runner-diff/1.0', planId: authority.plan.planId,
16
+ comparisonScope: after.scope, originalManifestDigest: sha256(canonical(before.original)), actualManifestDigest: sha256(canonical(after)),
17
+ changedCount: changed.length, changedPaths: changed.slice(0, 256), gates: { repositoryUnchanged: changed.length === 0 } };
18
+ const reportDigest = sha256(canonical(report)), state = await mutationStateRoot14(before.input);
19
+ const directory = await privateMutationDirectory14(path.join(state, 'discipline-reports'));
20
+ await atomicMutationJson14(path.join(directory.target, `${reportDigest}.json`), { ...report, reportDigest,
21
+ original: before.original, actual: after }, { replace: true });
22
+ return { ...report, reportDigest };
23
+ }
@@ -260,6 +260,22 @@ export async function importGraph(root, originals, input = { repositoryRoot: roo
260
260
  return { nodes, edges: edges.sort((a, b) => `${a.from}:${a.specifier}`.localeCompare(`${b.from}:${b.specifier}`)),
261
261
  dynamic, complete: !dynamic, dynamicSources: dynamicSources.sort((a, b) => a.path.localeCompare(b.path)), workspaceMetadata };
262
262
  }
263
+ export async function scopeCandidateImports(root, file, input = { repositoryRoot: root }) {
264
+ const parsed = importSpecifiers(Buffer.from(file.content), file.path);
265
+ const workspace = await workspaceIndex(root, input);
266
+ const context = (input.importContexts ?? []).filter(item => file.path === item.sourcePrefix || file.path.startsWith(`${item.sourcePrefix}/`))
267
+ .sort((a, b) => b.sourcePrefix.length - a.sourcePrefix.length)[0];
268
+ const aliases = await scopeTsconfigImports(async relative => {
269
+ const read = await scopeRead(root, relative, input);
270
+ return { path: relative, sha256: read.sha256, contentBase64: read.content.toString('base64') };
271
+ }, file.path, context?.configPath);
272
+ const declarations = declarationModules(Buffer.from(file.content), file.path), edges = [];
273
+ for (const specifier of parsed.specifiers) {
274
+ const target = await resolveImport(root, file.path, specifier, workspace, input, aliases, context?.configPath, declarations);
275
+ edges.push({ from: file.path, to: target, external: target === null, specifier });
276
+ }
277
+ return { edges, dynamic: parsed.dynamic };
278
+ }
263
279
  export async function collectScopeFacts(root, requested, baselineRef, knownFiles = [], input = { repositoryRoot: root }) {
264
280
  const selected = await scopeFiles(root, requested, knownFiles); const files = [];
265
281
  for (const relative of selected) {
@@ -6,6 +6,9 @@ import { receiverClaim } from './local-task-handoff-overlay-wire.mjs';
6
6
  import { checkHandoffWriteConstraints } from './local-task-handoff-write-constraints.mjs';
7
7
  import { verifyHandoffWriteArchive, verifyTaskHandoffWriteArchives } from './local-task-handoff-write-archive.mjs';
8
8
  import { handoffWriteFail as fail, writeReceiptIdentity, verifyWriteReceipt, verifyWriteNativeResult, assertWriteReceiptFields } from './local-task-handoff-write-wire.mjs';
9
+ import { disciplineWritePreflight } from './local-discipline-gate.mjs';
10
+ import { readDisciplineSnapshot } from './local-discipline-snapshot.mjs';
11
+ import { assertDisciplineRepository } from './local-discipline-repository.mjs';
9
12
 
10
13
  function sourceReadContext(ctx) { return Object.freeze({ home: ctx.home, repositoryRoot: ctx.root, flowId: ctx.flowId }); }
11
14
  async function fileObservation(ctx, relative) {
@@ -50,6 +53,11 @@ export async function runHandoffTrustedWrite({ input, context: ctx, state, persi
50
53
  const key = await loadTaskHandoffSigner(ctx, process.env.ENTROROVE_TASK_HANDOFF_WRITE_SIGNER_CONFIG, pass.runnerKeyId);
51
54
  const content = typeof input.content === 'string' ? input.content : JSON.stringify(input.content);
52
55
  if (typeof content !== 'string') fail('actual proposed content must be text or JSON');
56
+ const snapshot = { manifest, snapshotRoot: snapshotDirectory };
57
+ const writes = Object.fromEntries([...verified.files].filter(([name, file]) => file.exists
58
+ && manifest.files.find(original => original.path === name)?.sha256 !== file.sha256).map(([name, file]) =>
59
+ [name, { status: 'written', contentDigest: file.sha256, mode: manifest.files.find(original => original.path === name)?.mode ?? 0o644 }]));
60
+ await disciplineWritePreflight(input, { pass, snapshot, binding: { root: ctx.root }, ledger: { writes }, target: input.targetPath }, content);
53
61
  const last = archive.receipts.at(-1), nonce = randomUUID();
54
62
  const pending = { blockId: `${pass.passId}:${nonce}`, path: input.targetPath, beforeSha256: actual.sha256,
55
63
  beforeExists: actual.exists, proposedAfterSha256: sha256(content), observedAt: new Date().toISOString(), nonce,
@@ -92,6 +100,9 @@ export async function runHandoffTrustedWrite({ input, context: ctx, state, persi
92
100
  const finalScope = new Map(verified.files); finalScope.set(input.targetPath, projected);
93
101
  await verifyActualScope(ctx, finalScope);
94
102
  const core = receiptCore(archive, ctx, pending, rejected ? 'blocked' : 'retained', after, result, key);
103
+ const review = await readDisciplineSnapshot(snapshot);
104
+ await assertDisciplineRepository(input, ctx.root, review, { writes: rejected ? writes : { ...writes,
105
+ [input.targetPath]: { status: 'written', contentDigest: after.sha256, mode: manifest.files.find(file => file.path === input.targetPath)?.mode ?? 0o644 } } });
95
106
  const receipt = { ...core, signature: sign(null, Buffer.from(canonical(core)), key.privateKey).toString('base64url') };
96
107
  await saveCompleted(state, ctx, persist, archive, receipt);
97
108
  if (rejected) throw rejected;
package/manifest.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": "entrorove.skill-manifest/1.0",
3
3
  "name": "entrorove",
4
- "version": "0.1.37",
4
+ "version": "0.1.39",
5
5
  "endpoint": "https://entrorove.com",
6
6
  "requestSchemaVersion": "entrorove.skill.request/2.0",
7
7
  "supportedRequestSchemaVersions": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@entrorove/skill",
3
- "version": "0.1.37",
3
+ "version": "0.1.39",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "entrorove": "cli.mjs"
@@ -43,6 +43,7 @@
43
43
  "local-mutation-batch.mjs",
44
44
  "local-mutation-batch-receipt.mjs",
45
45
  "local-integrity.mjs",
46
+ "local-integrity.d.mts",
46
47
  "local-evidence-verification.mjs",
47
48
  "local-error.mjs",
48
49
  "local-formula.mjs",
@@ -196,16 +197,45 @@
196
197
  "local-final-gate16-contract.mjs",
197
198
  "local-final-gate16-release.mjs",
198
199
  "native-client.mjs",
199
- "native-notification.mjs"
200
+ "native-notification.mjs",
201
+ "ide-code-mode.mjs",
202
+ "ide-command.mjs",
203
+ "ide-hook.mjs",
204
+ "ide-install.mjs",
205
+ "ide-session.mjs",
206
+ "ide-results.mjs",
207
+ "ide-queue.mjs",
208
+ "local-discipline-actions.mjs",
209
+ "local-discipline-analysis.mjs",
210
+ "local-discipline-batch.mjs",
211
+ "local-discipline-contracts.mjs",
212
+ "local-discipline-gate.mjs",
213
+ "local-discipline-native.mjs",
214
+ "local-discipline-operations.mjs",
215
+ "local-discipline-repository.mjs",
216
+ "local-discipline-snapshot.mjs",
217
+ "local-equivalence-runner.mjs",
218
+ "local-runner-discipline.mjs",
219
+ "snapshot-retention.mjs",
220
+ "snapshot-cleanup-journal.mjs",
221
+ "validator-runtime-build.mjs",
222
+ "local-discipline-contracts.d.mts",
223
+ "runtime/validator-runtime-v7.0.44.mjs",
224
+ "runtime/validator-runtime-v7.0.44.d.mts",
225
+ "IDE-EXECUTION.md",
226
+ "REUSE-DISCIPLINE.md",
227
+ "reuse-discipline-release.mjs"
200
228
  ],
201
229
  "scripts": {
202
230
  "test": "node --test --test-concurrency=1 test/*.test.mjs",
203
231
  "prepublishOnly": "node release.mjs",
204
232
  "prepare:archguard": "node archguard-runtime-build.mjs",
205
233
  "check:archguard": "node archguard-runtime-build.mjs --check",
206
- "prepack": "node archguard-runtime-build.mjs && node calctool-runtime-build.mjs",
234
+ "prepack": "node archguard-runtime-build.mjs && node calctool-runtime-build.mjs && node validator-runtime-build.mjs",
207
235
  "prepare:calctool": "node calctool-runtime-build.mjs",
208
- "check:calctool": "node calctool-runtime-build.mjs --check"
236
+ "check:calctool": "node calctool-runtime-build.mjs --check",
237
+ "prepare:validator": "node validator-runtime-build.mjs",
238
+ "check:validator": "node validator-runtime-build.mjs --check"
209
239
  },
210
240
  "engines": {
211
241
  "node": ">=20"