@rengler33/prov 0.1.1

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 (146) hide show
  1. package/README.md +314 -0
  2. package/dist/cli.d.ts +26 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +381 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/commands/agent.d.ts +107 -0
  7. package/dist/commands/agent.d.ts.map +1 -0
  8. package/dist/commands/agent.js +197 -0
  9. package/dist/commands/agent.js.map +1 -0
  10. package/dist/commands/agent.test.d.ts +5 -0
  11. package/dist/commands/agent.test.d.ts.map +1 -0
  12. package/dist/commands/agent.test.js +199 -0
  13. package/dist/commands/agent.test.js.map +1 -0
  14. package/dist/commands/constraint.d.ts +100 -0
  15. package/dist/commands/constraint.d.ts.map +1 -0
  16. package/dist/commands/constraint.js +763 -0
  17. package/dist/commands/constraint.js.map +1 -0
  18. package/dist/commands/constraint.test.d.ts +9 -0
  19. package/dist/commands/constraint.test.d.ts.map +1 -0
  20. package/dist/commands/constraint.test.js +470 -0
  21. package/dist/commands/constraint.test.js.map +1 -0
  22. package/dist/commands/graph.d.ts +99 -0
  23. package/dist/commands/graph.d.ts.map +1 -0
  24. package/dist/commands/graph.js +552 -0
  25. package/dist/commands/graph.js.map +1 -0
  26. package/dist/commands/graph.test.d.ts +2 -0
  27. package/dist/commands/graph.test.d.ts.map +1 -0
  28. package/dist/commands/graph.test.js +258 -0
  29. package/dist/commands/graph.test.js.map +1 -0
  30. package/dist/commands/impact.d.ts +83 -0
  31. package/dist/commands/impact.d.ts.map +1 -0
  32. package/dist/commands/impact.js +319 -0
  33. package/dist/commands/impact.js.map +1 -0
  34. package/dist/commands/impact.test.d.ts +2 -0
  35. package/dist/commands/impact.test.d.ts.map +1 -0
  36. package/dist/commands/impact.test.js +234 -0
  37. package/dist/commands/impact.test.js.map +1 -0
  38. package/dist/commands/init.d.ts +45 -0
  39. package/dist/commands/init.d.ts.map +1 -0
  40. package/dist/commands/init.js +94 -0
  41. package/dist/commands/init.js.map +1 -0
  42. package/dist/commands/init.test.d.ts +7 -0
  43. package/dist/commands/init.test.d.ts.map +1 -0
  44. package/dist/commands/init.test.js +174 -0
  45. package/dist/commands/init.test.js.map +1 -0
  46. package/dist/commands/integration.test.d.ts +10 -0
  47. package/dist/commands/integration.test.d.ts.map +1 -0
  48. package/dist/commands/integration.test.js +456 -0
  49. package/dist/commands/integration.test.js.map +1 -0
  50. package/dist/commands/mcp.d.ts +21 -0
  51. package/dist/commands/mcp.d.ts.map +1 -0
  52. package/dist/commands/mcp.js +616 -0
  53. package/dist/commands/mcp.js.map +1 -0
  54. package/dist/commands/mcp.test.d.ts +7 -0
  55. package/dist/commands/mcp.test.d.ts.map +1 -0
  56. package/dist/commands/mcp.test.js +132 -0
  57. package/dist/commands/mcp.test.js.map +1 -0
  58. package/dist/commands/plan.d.ts +218 -0
  59. package/dist/commands/plan.d.ts.map +1 -0
  60. package/dist/commands/plan.js +1307 -0
  61. package/dist/commands/plan.js.map +1 -0
  62. package/dist/commands/plan.test.d.ts +9 -0
  63. package/dist/commands/plan.test.d.ts.map +1 -0
  64. package/dist/commands/plan.test.js +569 -0
  65. package/dist/commands/plan.test.js.map +1 -0
  66. package/dist/commands/spec.d.ts +94 -0
  67. package/dist/commands/spec.d.ts.map +1 -0
  68. package/dist/commands/spec.js +635 -0
  69. package/dist/commands/spec.js.map +1 -0
  70. package/dist/commands/spec.test.d.ts +9 -0
  71. package/dist/commands/spec.test.d.ts.map +1 -0
  72. package/dist/commands/spec.test.js +407 -0
  73. package/dist/commands/spec.test.js.map +1 -0
  74. package/dist/commands/trace.d.ts +157 -0
  75. package/dist/commands/trace.d.ts.map +1 -0
  76. package/dist/commands/trace.js +847 -0
  77. package/dist/commands/trace.js.map +1 -0
  78. package/dist/commands/trace.test.d.ts +9 -0
  79. package/dist/commands/trace.test.d.ts.map +1 -0
  80. package/dist/commands/trace.test.js +524 -0
  81. package/dist/commands/trace.test.js.map +1 -0
  82. package/dist/graph.d.ts +204 -0
  83. package/dist/graph.d.ts.map +1 -0
  84. package/dist/graph.js +496 -0
  85. package/dist/graph.js.map +1 -0
  86. package/dist/graph.test.d.ts +2 -0
  87. package/dist/graph.test.d.ts.map +1 -0
  88. package/dist/graph.test.js +382 -0
  89. package/dist/graph.test.js.map +1 -0
  90. package/dist/hash.d.ts +72 -0
  91. package/dist/hash.d.ts.map +1 -0
  92. package/dist/hash.js +137 -0
  93. package/dist/hash.js.map +1 -0
  94. package/dist/hash.test.d.ts +2 -0
  95. package/dist/hash.test.d.ts.map +1 -0
  96. package/dist/hash.test.js +227 -0
  97. package/dist/hash.test.js.map +1 -0
  98. package/dist/index.d.ts +18 -0
  99. package/dist/index.d.ts.map +1 -0
  100. package/dist/index.js +64 -0
  101. package/dist/index.js.map +1 -0
  102. package/dist/index.test.d.ts +2 -0
  103. package/dist/index.test.d.ts.map +1 -0
  104. package/dist/index.test.js +11 -0
  105. package/dist/index.test.js.map +1 -0
  106. package/dist/output.d.ts +84 -0
  107. package/dist/output.d.ts.map +1 -0
  108. package/dist/output.js +175 -0
  109. package/dist/output.js.map +1 -0
  110. package/dist/output.test.d.ts +7 -0
  111. package/dist/output.test.d.ts.map +1 -0
  112. package/dist/output.test.js +146 -0
  113. package/dist/output.test.js.map +1 -0
  114. package/dist/staleness.d.ts +162 -0
  115. package/dist/staleness.d.ts.map +1 -0
  116. package/dist/staleness.js +309 -0
  117. package/dist/staleness.js.map +1 -0
  118. package/dist/staleness.test.d.ts +2 -0
  119. package/dist/staleness.test.d.ts.map +1 -0
  120. package/dist/staleness.test.js +448 -0
  121. package/dist/staleness.test.js.map +1 -0
  122. package/dist/storage.d.ts +267 -0
  123. package/dist/storage.d.ts.map +1 -0
  124. package/dist/storage.js +623 -0
  125. package/dist/storage.js.map +1 -0
  126. package/dist/storage.test.d.ts +5 -0
  127. package/dist/storage.test.d.ts.map +1 -0
  128. package/dist/storage.test.js +434 -0
  129. package/dist/storage.test.js.map +1 -0
  130. package/dist/types.d.ts +270 -0
  131. package/dist/types.d.ts.map +1 -0
  132. package/dist/types.js +12 -0
  133. package/dist/types.js.map +1 -0
  134. package/dist/types.test.d.ts +2 -0
  135. package/dist/types.test.d.ts.map +1 -0
  136. package/dist/types.test.js +232 -0
  137. package/dist/types.test.js.map +1 -0
  138. package/dist/watcher.d.ts +139 -0
  139. package/dist/watcher.d.ts.map +1 -0
  140. package/dist/watcher.js +406 -0
  141. package/dist/watcher.js.map +1 -0
  142. package/dist/watcher.test.d.ts +5 -0
  143. package/dist/watcher.test.d.ts.map +1 -0
  144. package/dist/watcher.test.js +327 -0
  145. package/dist/watcher.test.js.map +1 -0
  146. package/package.json +53 -0
package/dist/hash.js ADDED
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Canonical YAML serialization and SHA-256 content hashing.
3
+ *
4
+ * All entities in prov are content-addressable via their hash.
5
+ * This module provides deterministic serialization and hashing.
6
+ *
7
+ * @see req:hashing
8
+ */
9
+ import { createHash } from 'node:crypto';
10
+ import YAML from 'yaml';
11
+ /**
12
+ * Default fields to exclude from hash computation.
13
+ * These are computed/derived fields that shouldn't affect identity.
14
+ */
15
+ const DEFAULT_EXCLUDE_FIELDS = ['hash', 'stale', 'status'];
16
+ /**
17
+ * Recursively sort object keys for deterministic serialization.
18
+ */
19
+ function sortKeys(value) {
20
+ if (value === null || value === undefined) {
21
+ return value;
22
+ }
23
+ if (Array.isArray(value)) {
24
+ return value.map(sortKeys);
25
+ }
26
+ if (typeof value === 'object') {
27
+ const sorted = {};
28
+ const keys = Object.keys(value).sort();
29
+ for (const key of keys) {
30
+ sorted[key] = sortKeys(value[key]);
31
+ }
32
+ return sorted;
33
+ }
34
+ return value;
35
+ }
36
+ /**
37
+ * Remove specified fields from an object (shallow copy).
38
+ */
39
+ function excludeFields(obj, fields) {
40
+ const result = {};
41
+ for (const [key, value] of Object.entries(obj)) {
42
+ if (!fields.includes(key)) {
43
+ result[key] = value;
44
+ }
45
+ }
46
+ return result;
47
+ }
48
+ /**
49
+ * Serialize an object to canonical YAML format.
50
+ *
51
+ * Canonical format guarantees:
52
+ * - Keys are sorted alphabetically at all levels
53
+ * - Consistent whitespace (2-space indent, no trailing spaces)
54
+ * - No document markers (---)
55
+ * - Strings are quoted only when necessary
56
+ *
57
+ * @param value - The value to serialize
58
+ * @param options - Serialization options
59
+ * @returns Canonical YAML string
60
+ */
61
+ export function serializeCanonical(value, options = {}) {
62
+ const { excludeFields: exclude = [] } = options;
63
+ // Remove excluded fields if value is an object
64
+ let processedValue = value;
65
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
66
+ processedValue = excludeFields(value, [...DEFAULT_EXCLUDE_FIELDS, ...exclude]);
67
+ }
68
+ // Sort keys recursively for deterministic output
69
+ const sorted = sortKeys(processedValue);
70
+ // Serialize to YAML with consistent formatting
71
+ return YAML.stringify(sorted, {
72
+ indent: 2,
73
+ lineWidth: 0, // Don't wrap lines
74
+ minContentWidth: 0,
75
+ singleQuote: false,
76
+ }).trim();
77
+ }
78
+ /**
79
+ * Compute SHA-256 hash of content.
80
+ *
81
+ * @param content - String content to hash
82
+ * @returns Full SHA-256 hash as hex string
83
+ */
84
+ export function sha256(content) {
85
+ return createHash('sha256').update(content, 'utf8').digest('hex');
86
+ }
87
+ /**
88
+ * Compute content hash for an entity.
89
+ *
90
+ * The hash is computed from the canonical YAML representation,
91
+ * excluding computed fields (hash, stale, status).
92
+ *
93
+ * @param entity - The entity to hash
94
+ * @param options - Serialization options
95
+ * @returns Hash in format `sha256:{first-12-hex-chars}`
96
+ */
97
+ export function computeHash(entity, options = {}) {
98
+ const canonical = serializeCanonical(entity, options);
99
+ const fullHash = sha256(canonical);
100
+ // Use first 12 hex characters (48 bits) - enough for practical uniqueness
101
+ return `sha256:${fullHash.slice(0, 12)}`;
102
+ }
103
+ /**
104
+ * Verify that an entity's stored hash matches its computed hash.
105
+ *
106
+ * @param entity - The entity with a hash field
107
+ * @returns true if hash matches or no hash is stored, false if mismatch
108
+ */
109
+ export function verifyHash(entity) {
110
+ if (entity.hash === undefined || entity.hash === null) {
111
+ return true; // No hash to verify
112
+ }
113
+ const computed = computeHash(entity);
114
+ return computed === entity.hash;
115
+ }
116
+ /**
117
+ * Parse YAML content to an object.
118
+ *
119
+ * @param content - YAML string to parse
120
+ * @returns Parsed object
121
+ */
122
+ export function parseYaml(content) {
123
+ return YAML.parse(content);
124
+ }
125
+ /**
126
+ * Serialize an object to YAML (non-canonical, for human readability).
127
+ *
128
+ * @param value - The value to serialize
129
+ * @returns YAML string
130
+ */
131
+ export function toYaml(value) {
132
+ return YAML.stringify(value, {
133
+ indent: 2,
134
+ lineWidth: 80,
135
+ }).trim();
136
+ }
137
+ //# sourceMappingURL=hash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.js","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=hash.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.test.d.ts","sourceRoot":"","sources":["../src/hash.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,227 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { serializeCanonical, sha256, computeHash, verifyHash, parseYaml, toYaml, } from './hash.js';
3
+ describe('hash module', () => {
4
+ describe('serializeCanonical', () => {
5
+ it('sorts object keys alphabetically', () => {
6
+ const yaml = serializeCanonical({ zebra: 1, apple: 2, mango: 3 });
7
+ const lines = yaml.split('\n');
8
+ expect(lines[0]).toBe('apple: 2');
9
+ expect(lines[1]).toBe('mango: 3');
10
+ expect(lines[2]).toBe('zebra: 1');
11
+ });
12
+ it('sorts nested object keys', () => {
13
+ const yaml = serializeCanonical({
14
+ outer: { zebra: 1, apple: 2 },
15
+ });
16
+ expect(yaml).toContain('apple: 2');
17
+ expect(yaml.indexOf('apple')).toBeLessThan(yaml.indexOf('zebra'));
18
+ });
19
+ it('handles arrays preserving order', () => {
20
+ const yaml = serializeCanonical({ items: ['c', 'a', 'b'] });
21
+ // Arrays should maintain their order
22
+ expect(yaml).toBe('items:\n - c\n - a\n - b');
23
+ });
24
+ it('sorts keys within array objects', () => {
25
+ const yaml = serializeCanonical({
26
+ items: [{ z: 1, a: 2 }],
27
+ });
28
+ // Object keys within arrays should still be sorted
29
+ expect(yaml.indexOf('a: 2')).toBeLessThan(yaml.indexOf('z: 1'));
30
+ });
31
+ it('excludes default computed fields', () => {
32
+ const yaml = serializeCanonical({
33
+ id: 'test',
34
+ hash: 'sha256:should-be-excluded',
35
+ stale: true,
36
+ status: 'draft',
37
+ });
38
+ expect(yaml).not.toContain('hash');
39
+ expect(yaml).not.toContain('stale');
40
+ expect(yaml).not.toContain('status');
41
+ expect(yaml).toContain('id: test');
42
+ });
43
+ it('excludes custom fields when specified', () => {
44
+ const yaml = serializeCanonical({ id: 'test', custom: 'value', keep: 'this' }, { excludeFields: ['custom'] });
45
+ expect(yaml).not.toContain('custom');
46
+ expect(yaml).toContain('id: test');
47
+ expect(yaml).toContain('keep: this');
48
+ });
49
+ it('handles null and undefined values', () => {
50
+ const yaml = serializeCanonical({ a: null, b: undefined });
51
+ expect(yaml).toContain('a: null');
52
+ // undefined values are typically omitted in YAML
53
+ });
54
+ it('produces consistent output for same input', () => {
55
+ const obj = { b: 2, a: 1, c: { z: 26, y: 25 } };
56
+ const yaml1 = serializeCanonical(obj);
57
+ const yaml2 = serializeCanonical(obj);
58
+ expect(yaml1).toBe(yaml2);
59
+ });
60
+ it('produces same output regardless of input key order', () => {
61
+ const obj1 = { a: 1, b: 2, c: 3 };
62
+ const obj2 = { c: 3, a: 1, b: 2 };
63
+ const obj3 = { b: 2, c: 3, a: 1 };
64
+ const yaml1 = serializeCanonical(obj1);
65
+ const yaml2 = serializeCanonical(obj2);
66
+ const yaml3 = serializeCanonical(obj3);
67
+ expect(yaml1).toBe(yaml2);
68
+ expect(yaml2).toBe(yaml3);
69
+ });
70
+ });
71
+ describe('sha256', () => {
72
+ it('computes correct SHA-256 hash', () => {
73
+ // Known test vector
74
+ const hash = sha256('hello');
75
+ expect(hash).toBe('2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824');
76
+ });
77
+ it('produces different hashes for different inputs', () => {
78
+ const hash1 = sha256('hello');
79
+ const hash2 = sha256('world');
80
+ expect(hash1).not.toBe(hash2);
81
+ });
82
+ it('produces same hash for same input', () => {
83
+ const hash1 = sha256('test input');
84
+ const hash2 = sha256('test input');
85
+ expect(hash1).toBe(hash2);
86
+ });
87
+ it('is sensitive to whitespace', () => {
88
+ const hash1 = sha256('hello');
89
+ const hash2 = sha256('hello ');
90
+ const hash3 = sha256(' hello');
91
+ expect(hash1).not.toBe(hash2);
92
+ expect(hash1).not.toBe(hash3);
93
+ expect(hash2).not.toBe(hash3);
94
+ });
95
+ });
96
+ describe('computeHash', () => {
97
+ it('returns hash in sha256:{12-chars} format', () => {
98
+ const hash = computeHash({ id: 'test' });
99
+ expect(hash).toMatch(/^sha256:[a-f0-9]{12}$/);
100
+ });
101
+ it('produces consistent hash for same entity', () => {
102
+ const entity = { id: 'spec:test:v1', title: 'Test' };
103
+ const hash1 = computeHash(entity);
104
+ const hash2 = computeHash(entity);
105
+ expect(hash1).toBe(hash2);
106
+ });
107
+ it('produces same hash regardless of property order', () => {
108
+ const entity1 = { id: 'test', title: 'Test', version: '1.0' };
109
+ const entity2 = { version: '1.0', id: 'test', title: 'Test' };
110
+ expect(computeHash(entity1)).toBe(computeHash(entity2));
111
+ });
112
+ it('produces different hash for different content', () => {
113
+ const entity1 = { id: 'test', value: 1 };
114
+ const entity2 = { id: 'test', value: 2 };
115
+ expect(computeHash(entity1)).not.toBe(computeHash(entity2));
116
+ });
117
+ it('ignores hash field in computation', () => {
118
+ const entity = { id: 'test', hash: 'sha256:old-hash-val' };
119
+ const entityWithoutHash = { id: 'test' };
120
+ expect(computeHash(entity)).toBe(computeHash(entityWithoutHash));
121
+ });
122
+ it('ignores status field in computation', () => {
123
+ const entity1 = { id: 'test', title: 'Test' };
124
+ const entity2 = { id: 'test', title: 'Test', status: 'draft' };
125
+ const entity3 = { id: 'test', title: 'Test', status: 'active' };
126
+ expect(computeHash(entity1)).toBe(computeHash(entity2));
127
+ expect(computeHash(entity2)).toBe(computeHash(entity3));
128
+ });
129
+ });
130
+ describe('verifyHash', () => {
131
+ it('returns true when hash matches', () => {
132
+ const entity = { id: 'test', title: 'Test' };
133
+ const hash = computeHash(entity);
134
+ const entityWithHash = { ...entity, hash };
135
+ expect(verifyHash(entityWithHash)).toBe(true);
136
+ });
137
+ it('returns false when hash does not match', () => {
138
+ const entity = {
139
+ id: 'test',
140
+ title: 'Test',
141
+ hash: 'sha256:wrong-hash00',
142
+ };
143
+ expect(verifyHash(entity)).toBe(false);
144
+ });
145
+ it('returns true when no hash is present', () => {
146
+ const entity = {
147
+ id: 'test',
148
+ title: 'Test',
149
+ };
150
+ expect(verifyHash(entity)).toBe(true);
151
+ });
152
+ });
153
+ describe('parseYaml and toYaml', () => {
154
+ it('round-trips objects correctly', () => {
155
+ const original = {
156
+ id: 'spec:test:v1',
157
+ title: 'Test Spec',
158
+ requirements: [{ id: 'req:test:1', description: 'First' }],
159
+ };
160
+ const yaml = toYaml(original);
161
+ const parsed = parseYaml(yaml);
162
+ expect(parsed).toEqual(original);
163
+ });
164
+ it('parses YAML strings', () => {
165
+ const yaml = `
166
+ id: spec:test:v1
167
+ title: Test
168
+ `.trim();
169
+ const parsed = parseYaml(yaml);
170
+ expect(parsed.id).toBe('spec:test:v1');
171
+ expect(parsed.title).toBe('Test');
172
+ });
173
+ });
174
+ describe('real-world entity hashing', () => {
175
+ it('computes stable hash for Spec entity', () => {
176
+ const spec = {
177
+ id: 'spec:core-model:v1',
178
+ version: '1.0.0',
179
+ title: 'Core Data Model',
180
+ status: 'draft',
181
+ intent: 'Define the fundamental entities',
182
+ requirements: [
183
+ {
184
+ id: 'req:core-model:entity-spec',
185
+ description: 'Specification entity represents declarative intent',
186
+ acceptance: ['Specs have a unique id'],
187
+ },
188
+ ],
189
+ };
190
+ const hash1 = computeHash(spec);
191
+ const hash2 = computeHash(spec);
192
+ expect(hash1).toBe(hash2);
193
+ expect(hash1).toMatch(/^sha256:[a-f0-9]{12}$/);
194
+ });
195
+ it('detects changes in Spec content', () => {
196
+ const spec1 = {
197
+ id: 'spec:test:v1',
198
+ version: '1.0.0',
199
+ title: 'Original',
200
+ status: 'draft',
201
+ intent: 'Original intent',
202
+ requirements: [],
203
+ };
204
+ const spec2 = {
205
+ ...spec1,
206
+ title: 'Modified',
207
+ };
208
+ expect(computeHash(spec1)).not.toBe(computeHash(spec2));
209
+ });
210
+ it('ignores status changes for hash computation', () => {
211
+ const specDraft = {
212
+ id: 'spec:test:v1',
213
+ version: '1.0.0',
214
+ title: 'Test',
215
+ status: 'draft',
216
+ intent: 'Test intent',
217
+ requirements: [],
218
+ };
219
+ const specActive = {
220
+ ...specDraft,
221
+ status: 'active',
222
+ };
223
+ expect(computeHash(specDraft)).toBe(computeHash(specActive));
224
+ });
225
+ });
226
+ });
227
+ //# sourceMappingURL=hash.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.test.js","sourceRoot":"","sources":["../src/hash.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * prov - Provenance-first planning CLI for AI agent workflows
3
+ *
4
+ * This module exports the core prov API for programmatic usage.
5
+ */
6
+ export declare const VERSION = "0.1.0";
7
+ export type { Hash, Timestamp, EntityId, EntityStatus, SpecId, RequirementId, ConstraintId, InvariantId, PlanId, DecisionId, StepId, TraceId, AcceptanceCriterion, Requirement, Spec, VerificationMethod, Invariant, Constraint, Decision, Step, SourceReference, Plan, FileLocation, Trace, NodeType, EdgeType, GraphNode, GraphEdge, } from './types.js';
8
+ export { serializeCanonical, sha256, computeHash, verifyHash, parseYaml, toYaml, type SerializeOptions, } from './hash.js';
9
+ export { IntentGraph, addSpecToGraph, addConstraintToGraph, addPlanToGraph, addTraceToGraph, type EntityData, type TraversalOptions, type ValidationResult, } from './graph.js';
10
+ export { PROV_DIR, GRAPH_FILE, MAPPINGS_DIR, CACHE_DIR, SCHEMA_VERSION, getProvDir, getGraphPath, getMappingsDir, getCacheDir, getMappingPath, isInitialized, initialize, acquireLock, releaseLock, withLock, atomicWrite, saveGraph, loadGraph, saveMapping, loadMapping, deleteMapping, listMappings, getCachedHash, setCachedHash, clearCache, Storage, type GraphFile, type SerializedNode, type SerializedEdge, type LineMapping, type FileMapping, type StorageOptions, type StorageResult, } from './storage.js';
11
+ export { getFileType, extractEntityId, computeFileHash, hasFileChanged, getTrackedFiles, scanForChanges, updateHashes, getStaleEntities, FileWatcher, type TrackedFileType, type TrackedFile, type ChangeResult, type WatcherEvents, type WatcherOptions, } from './watcher.js';
12
+ export { computeArtifactHash, checkPlanStaleness, checkTraceStaleness, findTransitivelyStale, detectStaleness, isNodeStale, getStaleNodes, getStalePlans, getStaleTraces, formatStalenessReason, type StalenessReason, type StalenessInfo, type StalenessResult, type StalenessOptions, } from './staleness.js';
13
+ /**
14
+ * Placeholder for core prov functionality.
15
+ * Will be implemented in subsequent steps.
16
+ */
17
+ export declare function getVersion(): string;
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,eAAO,MAAM,OAAO,UAAU,CAAC;AAG/B,YAAY,EAEV,IAAI,EACJ,SAAS,EACT,QAAQ,EACR,YAAY,EAEZ,MAAM,EACN,aAAa,EACb,YAAY,EACZ,WAAW,EACX,MAAM,EACN,UAAU,EACV,MAAM,EACN,OAAO,EAEP,mBAAmB,EACnB,WAAW,EACX,IAAI,EAEJ,kBAAkB,EAClB,SAAS,EACT,UAAU,EAEV,QAAQ,EACR,IAAI,EACJ,eAAe,EACf,IAAI,EAEJ,YAAY,EACZ,KAAK,EAEL,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,SAAS,GACV,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,kBAAkB,EAClB,MAAM,EACN,WAAW,EACX,UAAU,EACV,SAAS,EACT,MAAM,EACN,KAAK,gBAAgB,GACtB,MAAM,WAAW,CAAC;AAGnB,OAAO,EACL,WAAW,EACX,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,GACtB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAEL,QAAQ,EACR,UAAU,EACV,YAAY,EACZ,SAAS,EACT,cAAc,EAEd,UAAU,EACV,YAAY,EACZ,cAAc,EACd,WAAW,EACX,cAAc,EAEd,aAAa,EACb,UAAU,EAEV,WAAW,EACX,WAAW,EACX,QAAQ,EAER,WAAW,EAEX,SAAS,EACT,SAAS,EAET,WAAW,EACX,WAAW,EACX,aAAa,EACb,YAAY,EAEZ,aAAa,EACb,aAAa,EACb,UAAU,EAEV,OAAO,EAEP,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,aAAa,GACnB,MAAM,cAAc,CAAC;AAGtB,OAAO,EAEL,WAAW,EACX,eAAe,EAEf,eAAe,EACf,cAAc,EAEd,eAAe,EACf,cAAc,EACd,YAAY,EAEZ,gBAAgB,EAEhB,WAAW,EAEX,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,cAAc,GACpB,MAAM,cAAc,CAAC;AAGtB,OAAO,EAEL,mBAAmB,EAEnB,kBAAkB,EAClB,mBAAmB,EAEnB,qBAAqB,EAErB,eAAe,EAEf,WAAW,EACX,aAAa,EACb,aAAa,EACb,cAAc,EAEd,qBAAqB,EAErB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACtB,MAAM,gBAAgB,CAAC;AAExB;;;GAGG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAEnC"}
package/dist/index.js ADDED
@@ -0,0 +1,64 @@
1
+ /**
2
+ * prov - Provenance-first planning CLI for AI agent workflows
3
+ *
4
+ * This module exports the core prov API for programmatic usage.
5
+ */
6
+ export const VERSION = '0.1.0';
7
+ // Hash and serialization utilities
8
+ export { serializeCanonical, sha256, computeHash, verifyHash, parseYaml, toYaml, } from './hash.js';
9
+ // Intent graph
10
+ export { IntentGraph, addSpecToGraph, addConstraintToGraph, addPlanToGraph, addTraceToGraph, } from './graph.js';
11
+ // Storage layer
12
+ export {
13
+ // Constants
14
+ PROV_DIR, GRAPH_FILE, MAPPINGS_DIR, CACHE_DIR, SCHEMA_VERSION,
15
+ // Path utilities
16
+ getProvDir, getGraphPath, getMappingsDir, getCacheDir, getMappingPath,
17
+ // Directory management
18
+ isInitialized, initialize,
19
+ // Lock management
20
+ acquireLock, releaseLock, withLock,
21
+ // Atomic write
22
+ atomicWrite,
23
+ // Graph persistence
24
+ saveGraph, loadGraph,
25
+ // Mapping persistence
26
+ saveMapping, loadMapping, deleteMapping, listMappings,
27
+ // Cache management
28
+ getCachedHash, setCachedHash, clearCache,
29
+ // High-level interface
30
+ Storage, } from './storage.js';
31
+ // File watching and change detection
32
+ export {
33
+ // File type detection
34
+ getFileType, extractEntityId,
35
+ // Hash utilities
36
+ computeFileHash, hasFileChanged,
37
+ // File scanning
38
+ getTrackedFiles, scanForChanges, updateHashes,
39
+ // Staleness detection
40
+ getStaleEntities,
41
+ // Watch mode
42
+ FileWatcher, } from './watcher.js';
43
+ // Staleness detection
44
+ export {
45
+ // File hash utilities
46
+ computeArtifactHash,
47
+ // Direct staleness checks
48
+ checkPlanStaleness, checkTraceStaleness,
49
+ // Transitive staleness
50
+ findTransitivelyStale,
51
+ // Full graph detection
52
+ detectStaleness,
53
+ // Convenience functions
54
+ isNodeStale, getStaleNodes, getStalePlans, getStaleTraces,
55
+ // Formatting
56
+ formatStalenessReason, } from './staleness.js';
57
+ /**
58
+ * Placeholder for core prov functionality.
59
+ * Will be implemented in subsequent steps.
60
+ */
61
+ export function getVersion() {
62
+ return VERSION;
63
+ }
64
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC;AAyC/B,mCAAmC;AACnC,OAAO,EACL,kBAAkB,EAClB,MAAM,EACN,WAAW,EACX,UAAU,EACV,SAAS,EACT,MAAM,GAEP,MAAM,WAAW,CAAC;AAEnB,eAAe;AACf,OAAO,EACL,WAAW,EACX,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,eAAe,GAIhB,MAAM,YAAY,CAAC;AAEpB,gBAAgB;AAChB,OAAO;AACL,YAAY;AACZ,QAAQ,EACR,UAAU,EACV,YAAY,EACZ,SAAS,EACT,cAAc;AACd,iBAAiB;AACjB,UAAU,EACV,YAAY,EACZ,cAAc,EACd,WAAW,EACX,cAAc;AACd,uBAAuB;AACvB,aAAa,EACb,UAAU;AACV,kBAAkB;AAClB,WAAW,EACX,WAAW,EACX,QAAQ;AACR,eAAe;AACf,WAAW;AACX,oBAAoB;AACpB,SAAS,EACT,SAAS;AACT,sBAAsB;AACtB,WAAW,EACX,WAAW,EACX,aAAa,EACb,YAAY;AACZ,mBAAmB;AACnB,aAAa,EACb,aAAa,EACb,UAAU;AACV,uBAAuB;AACvB,OAAO,GASR,MAAM,cAAc,CAAC;AAEtB,qCAAqC;AACrC,OAAO;AACL,sBAAsB;AACtB,WAAW,EACX,eAAe;AACf,iBAAiB;AACjB,eAAe,EACf,cAAc;AACd,gBAAgB;AAChB,eAAe,EACf,cAAc,EACd,YAAY;AACZ,sBAAsB;AACtB,gBAAgB;AAChB,aAAa;AACb,WAAW,GAOZ,MAAM,cAAc,CAAC;AAEtB,sBAAsB;AACtB,OAAO;AACL,sBAAsB;AACtB,mBAAmB;AACnB,0BAA0B;AAC1B,kBAAkB,EAClB,mBAAmB;AACnB,uBAAuB;AACvB,qBAAqB;AACrB,uBAAuB;AACvB,eAAe;AACf,wBAAwB;AACxB,WAAW,EACX,aAAa,EACb,aAAa,EACb,cAAc;AACd,aAAa;AACb,qBAAqB,GAMtB,MAAM,gBAAgB,CAAC;AAExB;;;GAGG;AACH,MAAM,UAAU,UAAU;IACxB,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,11 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { getVersion, VERSION } from './index.js';
3
+ describe('prov core', () => {
4
+ it('exports VERSION constant', () => {
5
+ expect(VERSION).toBe('0.1.0');
6
+ });
7
+ it('getVersion returns the version string', () => {
8
+ expect(getVersion()).toBe('0.1.0');
9
+ });
10
+ });
11
+ //# sourceMappingURL=index.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAEjD,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;IACzB,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;QAClC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Output formatting utilities for CLI commands.
3
+ *
4
+ * Supports multiple output formats:
5
+ * - table: Human-readable columnar format (default for TTY)
6
+ * - yaml: YAML format (default for pipes)
7
+ * - json: Machine-parseable JSON format
8
+ *
9
+ * @see req:cli:output-formats
10
+ */
11
+ /**
12
+ * Supported output format types.
13
+ */
14
+ export type OutputFormat = 'table' | 'yaml' | 'json';
15
+ /**
16
+ * Column definition for table output.
17
+ */
18
+ export interface TableColumn {
19
+ /** Column header text */
20
+ header: string;
21
+ /** Key to extract from row data */
22
+ key: string;
23
+ /** Minimum width (defaults to header length) */
24
+ minWidth?: number;
25
+ /** Maximum width (truncates with ellipsis) */
26
+ maxWidth?: number;
27
+ /** Alignment: left, right, center (defaults to left) */
28
+ align?: 'left' | 'right' | 'center';
29
+ }
30
+ /**
31
+ * Options for output formatting.
32
+ */
33
+ export interface OutputOptions {
34
+ /** Output format to use */
35
+ format?: OutputFormat | undefined;
36
+ /** Whether output is to a TTY (auto-detected if not specified) */
37
+ isTty?: boolean | undefined;
38
+ /** Custom table columns (for table format) */
39
+ columns?: TableColumn[] | undefined;
40
+ /** Whether to include headers in table output */
41
+ headers?: boolean | undefined;
42
+ }
43
+ /**
44
+ * Detect the default output format based on environment.
45
+ * Returns 'table' for TTY, 'yaml' for pipes/redirects.
46
+ */
47
+ export declare function detectFormat(isTty?: boolean): OutputFormat;
48
+ /**
49
+ * Resolve the format to use, considering explicit format and TTY detection.
50
+ */
51
+ export declare function resolveFormat(options?: OutputOptions): OutputFormat;
52
+ /**
53
+ * Format data as a table.
54
+ */
55
+ export declare function formatTable(data: readonly Record<string, unknown>[], columns: TableColumn[], includeHeaders?: boolean): string;
56
+ /**
57
+ * Format data as YAML.
58
+ */
59
+ export declare function formatYaml(data: unknown): string;
60
+ /**
61
+ * Format data as JSON.
62
+ */
63
+ export declare function formatJson(data: unknown): string;
64
+ /**
65
+ * Format data in the specified format.
66
+ */
67
+ export declare function format(data: unknown, options?: OutputOptions): string;
68
+ /**
69
+ * Output data to stdout in the specified format.
70
+ */
71
+ export declare function output(data: unknown, options?: OutputOptions): void;
72
+ /**
73
+ * Output an error message to stderr.
74
+ */
75
+ export declare function error(message: string): void;
76
+ /**
77
+ * Output a success message to stdout.
78
+ */
79
+ export declare function success(message: string): void;
80
+ /**
81
+ * Output a warning message to stderr.
82
+ */
83
+ export declare function warn(message: string): void;
84
+ //# sourceMappingURL=output.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;AAErD;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,yBAAyB;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,gDAAgD;IAChD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;CACrC;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,2BAA2B;IAC3B,MAAM,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;IAClC,kEAAkE;IAClE,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B,8CAA8C;IAC9C,OAAO,CAAC,EAAE,WAAW,EAAE,GAAG,SAAS,CAAC;IACpC,iDAAiD;IACjD,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC/B;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,CAAC,EAAE,OAAO,GAAG,YAAY,CAG1D;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE,aAAkB,GAAG,YAAY,CAKvE;AA4BD;;GAEG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EACxC,OAAO,EAAE,WAAW,EAAE,EACtB,cAAc,UAAO,GACpB,MAAM,CA6CR;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAEhD;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAEhD;AAED;;GAEG;AACH,wBAAgB,MAAM,CACpB,IAAI,EAAE,OAAO,EACb,OAAO,GAAE,aAAkB,GAC1B,MAAM,CAmCR;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,GAAE,aAAkB,GAAG,IAAI,CAOvE;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE3C;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE7C;AAED;;GAEG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE1C"}