@eddacraft/anvil-aps 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/AGENTS.md +155 -0
  2. package/LICENSE +14 -0
  3. package/README.md +57 -0
  4. package/TODO.md +40 -0
  5. package/dist/filter/context-bundle.d.ts +81 -0
  6. package/dist/filter/context-bundle.d.ts.map +1 -0
  7. package/dist/filter/context-bundle.js +230 -0
  8. package/dist/filter/index.d.ts +85 -0
  9. package/dist/filter/index.d.ts.map +1 -0
  10. package/dist/filter/index.js +169 -0
  11. package/dist/index.d.ts +16 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +15 -0
  14. package/dist/loader/index.d.ts +80 -0
  15. package/dist/loader/index.d.ts.map +1 -0
  16. package/dist/loader/index.js +253 -0
  17. package/dist/parser/index.d.ts +24 -0
  18. package/dist/parser/index.d.ts.map +1 -0
  19. package/dist/parser/index.js +22 -0
  20. package/dist/parser/parse-document.d.ts +17 -0
  21. package/dist/parser/parse-document.d.ts.map +1 -0
  22. package/dist/parser/parse-document.js +219 -0
  23. package/dist/parser/parse-index.d.ts +31 -0
  24. package/dist/parser/parse-index.d.ts.map +1 -0
  25. package/dist/parser/parse-index.js +251 -0
  26. package/dist/parser/parse-task.d.ts +30 -0
  27. package/dist/parser/parse-task.d.ts.map +1 -0
  28. package/dist/parser/parse-task.js +261 -0
  29. package/dist/state/index.d.ts +307 -0
  30. package/dist/state/index.d.ts.map +1 -0
  31. package/dist/state/index.js +689 -0
  32. package/dist/templates/generator.d.ts +71 -0
  33. package/dist/templates/generator.d.ts.map +1 -0
  34. package/dist/templates/generator.js +723 -0
  35. package/dist/templates/index.d.ts +5 -0
  36. package/dist/templates/index.d.ts.map +1 -0
  37. package/dist/templates/index.js +4 -0
  38. package/dist/types/index.d.ts +131 -0
  39. package/dist/types/index.d.ts.map +1 -0
  40. package/dist/types/index.js +107 -0
  41. package/dist/validator/index.d.ts +83 -0
  42. package/dist/validator/index.d.ts.map +1 -0
  43. package/dist/validator/index.js +611 -0
  44. package/docs/APS-Anvil-Integration.md +750 -0
  45. package/docs/APS-Conventions.md +635 -0
  46. package/docs/APS-NonGoals.md +455 -0
  47. package/docs/APS-Planning-Spec-v0.1.md +362 -0
  48. package/examples/README.md +170 -0
  49. package/examples/feature-auth.aps.md +87 -0
  50. package/examples/refactor-error-handling.aps.md +119 -0
  51. package/examples/system-ecommerce/APS.md +57 -0
  52. package/examples/system-ecommerce/modules/auth.aps.md +38 -0
  53. package/examples/system-ecommerce/modules/cart.aps.md +53 -0
  54. package/examples/system-ecommerce/modules/payments.aps.md +68 -0
  55. package/examples/system-ecommerce/modules/products.aps.md +53 -0
  56. package/package.json +34 -0
  57. package/project.json +37 -0
  58. package/scripts/generate-templates.js +33 -0
  59. package/src/filter/context-bundle.ts +312 -0
  60. package/src/filter/filter.test.ts +317 -0
  61. package/src/filter/index.ts +249 -0
  62. package/src/index.ts +16 -0
  63. package/src/loader/index.ts +364 -0
  64. package/src/loader/loader.test.ts +224 -0
  65. package/src/parser/__fixtures__/invalid-task-id-not-padded.aps.md +7 -0
  66. package/src/parser/__fixtures__/invalid-task-id.aps.md +8 -0
  67. package/src/parser/__fixtures__/minimal-task.aps.md +7 -0
  68. package/src/parser/__fixtures__/non-scope-hyphenated.aps.md +10 -0
  69. package/src/parser/__fixtures__/simple-index.aps.md +35 -0
  70. package/src/parser/__fixtures__/simple-plan.aps.md +19 -0
  71. package/src/parser/index.ts +30 -0
  72. package/src/parser/parse-document.test.ts +603 -0
  73. package/src/parser/parse-document.ts +262 -0
  74. package/src/parser/parse-index.test.ts +316 -0
  75. package/src/parser/parse-index.ts +298 -0
  76. package/src/parser/parse-task.test.ts +476 -0
  77. package/src/parser/parse-task.ts +325 -0
  78. package/src/state/__fixtures__/invalid-plan.aps.md +9 -0
  79. package/src/state/__fixtures__/test-plan.aps.md +20 -0
  80. package/src/state/index.ts +879 -0
  81. package/src/state/state.test.ts +645 -0
  82. package/src/templates/generator.test.ts +378 -0
  83. package/src/templates/generator.ts +776 -0
  84. package/src/templates/index.ts +5 -0
  85. package/src/types/index.ts +168 -0
  86. package/src/validator/__fixtures__/broken-links.aps.md +10 -0
  87. package/src/validator/__fixtures__/circular-deps-index.aps.md +26 -0
  88. package/src/validator/__fixtures__/circular-modules/module-a.aps.md +9 -0
  89. package/src/validator/__fixtures__/circular-modules/module-b.aps.md +9 -0
  90. package/src/validator/__fixtures__/circular-modules/module-c.aps.md +9 -0
  91. package/src/validator/__fixtures__/dup-modules/module-a.aps.md +9 -0
  92. package/src/validator/__fixtures__/dup-modules/module-b.aps.md +9 -0
  93. package/src/validator/__fixtures__/duplicate-ids-index.aps.md +15 -0
  94. package/src/validator/__fixtures__/invalid-task-id.aps.md +17 -0
  95. package/src/validator/__fixtures__/missing-confidence.aps.md +9 -0
  96. package/src/validator/__fixtures__/missing-h1.aps.md +5 -0
  97. package/src/validator/__fixtures__/missing-intent.aps.md +9 -0
  98. package/src/validator/__fixtures__/missing-modules-section.aps.md +7 -0
  99. package/src/validator/__fixtures__/missing-tasks-section.aps.md +7 -0
  100. package/src/validator/__fixtures__/modules/auth.aps.md +17 -0
  101. package/src/validator/__fixtures__/modules/payments.aps.md +13 -0
  102. package/src/validator/__fixtures__/scope-mismatch.aps.md +14 -0
  103. package/src/validator/__fixtures__/valid-index.aps.md +24 -0
  104. package/src/validator/__fixtures__/valid-leaf.aps.md +22 -0
  105. package/src/validator/index.ts +776 -0
  106. package/src/validator/validator.test.ts +269 -0
  107. package/templates/index-full.md +94 -0
  108. package/templates/index-minimal.md +16 -0
  109. package/templates/index-template.md +63 -0
  110. package/templates/leaf-full.md +76 -0
  111. package/templates/leaf-minimal.md +14 -0
  112. package/templates/leaf-template.md +55 -0
  113. package/templates/simple-full.md +56 -0
  114. package/templates/simple-minimal.md +14 -0
  115. package/templates/simple-template.md +30 -0
  116. package/tsconfig.json +19 -0
  117. package/tsconfig.lib.json +14 -0
  118. package/tsconfig.lib.tsbuildinfo +1 -0
  119. package/tsconfig.spec.json +9 -0
  120. package/tsconfig.tsbuildinfo +1 -0
  121. package/vitest.config.ts +15 -0
@@ -0,0 +1,378 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import type { TemplateBundle, TemplateVariant } from './generator.js';
3
+ import {
4
+ generateAllTemplates,
5
+ generateIndexTemplate,
6
+ generateLeafTemplate,
7
+ generateSimplePlanTemplate,
8
+ generateActionsTemplate,
9
+ } from './generator.js';
10
+
11
+ describe('Template Generator', () => {
12
+ describe('generateIndexTemplate', () => {
13
+ describe('standard variant (default)', () => {
14
+ it('should generate a valid index template', () => {
15
+ const template = generateIndexTemplate();
16
+
17
+ expect(template).toContain('# [Plan Title]');
18
+ expect(template).toContain('## Problem & Success Criteria');
19
+ expect(template).toContain('## System Map');
20
+ expect(template).toContain('## Milestones');
21
+ expect(template).toContain('## Modules');
22
+ expect(template).toContain('### [module-id]');
23
+ expect(template).toContain('**Path:**');
24
+ expect(template).toContain('**Scope:**');
25
+ expect(template).toContain('**Owner:**');
26
+ expect(template).toContain('**Status:**');
27
+ expect(template).toContain('**Priority:**');
28
+ expect(template).toContain('**Tags:**');
29
+ expect(template).toContain('**Dependencies:**');
30
+ expect(template).toContain('## Decisions');
31
+ expect(template).toContain('## Open Questions');
32
+ });
33
+
34
+ it('should include module metadata fields', () => {
35
+ const template = generateIndexTemplate();
36
+
37
+ expect(template).toMatch(/\*\*Path:\*\*/);
38
+ expect(template).toMatch(/\*\*Scope:\*\*/);
39
+ expect(template).toMatch(/\*\*Owner:\*\*/);
40
+ expect(template).toMatch(/\*\*Status:\*\*/);
41
+ expect(template).toMatch(/\*\*Priority:\*\*/);
42
+ expect(template).toMatch(/\*\*Tags:\*\*/);
43
+ expect(template).toMatch(/\*\*Dependencies:\*\*/);
44
+ });
45
+
46
+ it('should use markdown link syntax for paths', () => {
47
+ const template = generateIndexTemplate();
48
+
49
+ expect(template).toMatch(/\[.*\]\(\.\/modules\/.*\.aps\.md\)/);
50
+ });
51
+
52
+ it('should include decision IDs', () => {
53
+ const template = generateIndexTemplate();
54
+
55
+ expect(template).toContain('**D-001:**');
56
+ expect(template).toContain('**D-002:**');
57
+ });
58
+ });
59
+
60
+ describe('minimal variant', () => {
61
+ it('should generate a minimal index template', () => {
62
+ const template = generateIndexTemplate({ variant: 'minimal' });
63
+
64
+ expect(template).toContain('# [Plan Title]');
65
+ expect(template).toContain('## Modules');
66
+ expect(template).toContain('**Path:**');
67
+ expect(template).toContain('**Scope:**');
68
+ expect(template).toContain('**Owner:**');
69
+ // Should NOT have extra sections
70
+ expect(template).not.toContain('## Problem');
71
+ expect(template).not.toContain('## Milestones');
72
+ expect(template).not.toContain('## Decisions');
73
+ });
74
+ });
75
+
76
+ describe('full variant', () => {
77
+ it('should generate a comprehensive index template', () => {
78
+ const template = generateIndexTemplate({ variant: 'full' });
79
+
80
+ expect(template).toContain('# APS Index');
81
+ expect(template).toContain('## Problem & Success Criteria');
82
+ expect(template).toContain('## Scope');
83
+ expect(template).toContain('**In Scope:**');
84
+ expect(template).toContain('**Out of Scope:**');
85
+ expect(template).toContain('## System Map');
86
+ expect(template).toContain('## Milestones');
87
+ expect(template).toContain('## Modules');
88
+ expect(template).toContain('## Epics');
89
+ expect(template).toContain('## Decisions');
90
+ expect(template).toContain('## Risks');
91
+ expect(template).toContain('## Open Questions');
92
+ });
93
+ });
94
+ });
95
+
96
+ describe('generateLeafTemplate', () => {
97
+ describe('standard variant (default)', () => {
98
+ it('should generate a valid leaf spec template', () => {
99
+ const template = generateLeafTemplate();
100
+
101
+ expect(template).toContain('# [Module Title]');
102
+ expect(template).toContain('**Scope:**');
103
+ expect(template).toContain('**Owner:**');
104
+ expect(template).toContain('**Priority:**');
105
+ expect(template).toContain('## Purpose');
106
+ expect(template).toContain('## In Scope / Out of Scope');
107
+ expect(template).toContain('## Interfaces');
108
+ expect(template).toContain('**Depends on:**');
109
+ expect(template).toContain('**Exposes:**');
110
+ expect(template).toContain('## Tasks');
111
+ expect(template).toContain('### [SCOPE]-001:');
112
+ expect(template).toContain('**Intent:**');
113
+ expect(template).toContain('**Expected Outcome:**');
114
+ expect(template).toContain('**Confidence:**');
115
+ expect(template).toContain('**Link:**');
116
+ expect(template).toContain('**Scopes:**');
117
+ expect(template).toContain('**Tags:**');
118
+ expect(template).toContain('**Dependencies:**');
119
+ expect(template).toContain('**Inputs:**');
120
+ expect(template).toContain('## Decisions');
121
+ expect(template).toContain('## Notes');
122
+ });
123
+
124
+ it('should use task ID format SCOPE-NUMBER', () => {
125
+ const template = generateLeafTemplate();
126
+
127
+ expect(template).toMatch(/### \[SCOPE\]-001:/);
128
+ expect(template).toMatch(/### \[SCOPE\]-002:/);
129
+ });
130
+
131
+ it('should include all task fields', () => {
132
+ const template = generateLeafTemplate();
133
+
134
+ expect(template).toMatch(/\*\*Intent:\*\*/);
135
+ expect(template).toMatch(/\*\*Expected Outcome:\*\*/);
136
+ expect(template).toMatch(/\*\*Confidence:\*\*/);
137
+ expect(template).toMatch(/\*\*Link:\*\*/);
138
+ expect(template).toMatch(/\*\*Scopes:\*\*/);
139
+ expect(template).toMatch(/\*\*Tags:\*\*/);
140
+ expect(template).toMatch(/\*\*Dependencies:\*\*/);
141
+ expect(template).toMatch(/\*\*Inputs:\*\*/);
142
+ });
143
+
144
+ it('should include external link placeholder', () => {
145
+ const template = generateLeafTemplate();
146
+
147
+ expect(template).toContain('**Link:**');
148
+ expect(template).toContain('jira.example.com');
149
+ });
150
+ });
151
+
152
+ describe('minimal variant', () => {
153
+ it('should generate a minimal leaf template', () => {
154
+ const template = generateLeafTemplate({ variant: 'minimal' });
155
+
156
+ expect(template).toContain('# [Module Title]');
157
+ expect(template).toContain('## Tasks');
158
+ expect(template).toContain('**Intent:**');
159
+ expect(template).toContain('**Confidence:**');
160
+ // Should NOT have extra sections
161
+ expect(template).not.toContain('## Purpose');
162
+ expect(template).not.toContain('## Interfaces');
163
+ expect(template).not.toContain('**Link:**');
164
+ });
165
+ });
166
+
167
+ describe('full variant', () => {
168
+ it('should generate a comprehensive leaf template', () => {
169
+ const template = generateLeafTemplate({ variant: 'full' });
170
+
171
+ expect(template).toContain('# Module APS');
172
+ expect(template).toContain('## Purpose');
173
+ expect(template).toContain('## In Scope / Out of Scope');
174
+ expect(template).toContain('## Assumptions');
175
+ expect(template).toContain('## Interfaces');
176
+ expect(template).toContain('## Tasks');
177
+ expect(template).toContain('## Decisions');
178
+ expect(template).toContain('## Risks');
179
+ expect(template).toContain('## Open Questions');
180
+ expect(template).toContain('## Notes');
181
+ });
182
+ });
183
+ });
184
+
185
+ describe('generateSimplePlanTemplate', () => {
186
+ describe('standard variant (default)', () => {
187
+ it('should generate a valid single-file plan template', () => {
188
+ const template = generateSimplePlanTemplate();
189
+
190
+ expect(template).toContain('# Feature:');
191
+ expect(template).toContain('**Scope:**');
192
+ expect(template).toContain('**Owner:**');
193
+ expect(template).toContain('**Priority:**');
194
+ expect(template).toContain('## Purpose');
195
+ expect(template).toContain('## Success Criteria');
196
+ expect(template).toContain('## Tasks');
197
+ expect(template).toContain('### [SCOPE]-001:');
198
+ expect(template).toContain('**Intent:**');
199
+ expect(template).toContain('**Link:**');
200
+ expect(template).toContain('## Notes');
201
+ });
202
+
203
+ it('should include task dependencies example', () => {
204
+ const template = generateSimplePlanTemplate();
205
+
206
+ expect(template).toMatch(/\*\*Dependencies:\*\* \[SCOPE\]-001/);
207
+ });
208
+ });
209
+
210
+ describe('minimal variant', () => {
211
+ it('should generate a minimal simple template', () => {
212
+ const template = generateSimplePlanTemplate({ variant: 'minimal' });
213
+
214
+ expect(template).toContain('# [Feature Name]');
215
+ expect(template).toContain('## Tasks');
216
+ // Should NOT have extra sections
217
+ expect(template).not.toContain('## Purpose');
218
+ expect(template).not.toContain('## Success Criteria');
219
+ });
220
+ });
221
+
222
+ describe('full variant', () => {
223
+ it('should generate a comprehensive simple template', () => {
224
+ const template = generateSimplePlanTemplate({ variant: 'full' });
225
+
226
+ expect(template).toContain('# Feature:');
227
+ expect(template).toContain('## Purpose');
228
+ expect(template).toContain('## Success Criteria');
229
+ expect(template).toContain('## In Scope / Out of Scope');
230
+ expect(template).toContain('## Assumptions');
231
+ expect(template).toContain('## Tasks');
232
+ expect(template).toContain('## Decisions');
233
+ expect(template).toContain('## Open Questions');
234
+ expect(template).toContain('## Notes');
235
+ });
236
+ });
237
+ });
238
+
239
+ describe('generateActionsTemplate', () => {
240
+ describe('standard variant (default)', () => {
241
+ it('should generate a valid action plan template', () => {
242
+ const template = generateActionsTemplate();
243
+
244
+ expect(template).toContain('# Actions: [SCOPE-NNN]');
245
+ expect(template).toContain('| Source | Work Item | Created by | Status |');
246
+ expect(template).toContain('## Prerequisites');
247
+ expect(template).toContain('## Actions');
248
+ expect(template).toContain('### 1.');
249
+ expect(template).toContain('**Purpose:**');
250
+ expect(template).toContain('**Produces:**');
251
+ expect(template).toContain('**Checkpoint:**');
252
+ expect(template).toContain('**Validate:**');
253
+ expect(template).toContain('## Completion');
254
+ });
255
+
256
+ it('should include action structure fields', () => {
257
+ const template = generateActionsTemplate();
258
+
259
+ expect(template).toMatch(/\*\*Purpose:\*\*/);
260
+ expect(template).toMatch(/\*\*Produces:\*\*/);
261
+ expect(template).toMatch(/\*\*Checkpoint:\*\*/);
262
+ expect(template).toMatch(/\*\*Validate:\*\*/);
263
+ });
264
+ });
265
+
266
+ describe('minimal variant', () => {
267
+ it('should generate a minimal action plan template', () => {
268
+ const template = generateActionsTemplate({ variant: 'minimal' });
269
+
270
+ expect(template).toContain('# Actions: [SCOPE-NNN]');
271
+ expect(template).toContain('## Actions');
272
+ expect(template).toContain('**Checkpoint:**');
273
+ expect(template).toContain('**Validate:**');
274
+ expect(template).toContain('## Completion');
275
+ // Should NOT have extra sections
276
+ expect(template).not.toContain('## Prerequisites');
277
+ expect(template).not.toContain('**Purpose:**');
278
+ });
279
+ });
280
+
281
+ describe('full variant', () => {
282
+ it('should generate a comprehensive action plan template', () => {
283
+ const template = generateActionsTemplate({ variant: 'full' });
284
+
285
+ expect(template).toContain('# Actions: [SCOPE-NNN]');
286
+ expect(template).toContain('## Overview');
287
+ expect(template).toContain('**Intent:**');
288
+ expect(template).toContain('**Expected Outcome:**');
289
+ expect(template).toContain('## Prerequisites');
290
+ expect(template).toContain('## Actions');
291
+ expect(template).toContain('## Blocked/Deferred');
292
+ expect(template).toContain('## Notes');
293
+ expect(template).toContain('## Completion');
294
+ });
295
+ });
296
+ });
297
+
298
+ describe('generateAllTemplates', () => {
299
+ it('should generate all template types', () => {
300
+ const templates = generateAllTemplates();
301
+
302
+ expect(templates).toHaveProperty('index');
303
+ expect(templates).toHaveProperty('leaf');
304
+ expect(templates).toHaveProperty('simple');
305
+ expect(templates).toHaveProperty('actions');
306
+ });
307
+
308
+ it('should return a properly typed TemplateBundle', () => {
309
+ const templates: TemplateBundle = generateAllTemplates();
310
+
311
+ // Type-safe access to known keys
312
+ expect(typeof templates.index).toBe('string');
313
+ expect(typeof templates.leaf).toBe('string');
314
+ expect(typeof templates.simple).toBe('string');
315
+ expect(typeof templates.actions).toBe('string');
316
+
317
+ // TypeScript will error if trying to access unknown keys
318
+ // @ts-expect-error - 'unknown' does not exist on type 'TemplateBundle'
319
+ const _ = templates.unknown;
320
+ });
321
+
322
+ it('should return non-empty templates', () => {
323
+ const templates = generateAllTemplates();
324
+
325
+ expect(templates.index).toBeTruthy();
326
+ expect(templates.leaf).toBeTruthy();
327
+ expect(templates.simple).toBeTruthy();
328
+ expect(templates.actions).toBeTruthy();
329
+
330
+ expect(templates.index.length).toBeGreaterThan(0);
331
+ expect(templates.leaf.length).toBeGreaterThan(0);
332
+ expect(templates.simple.length).toBeGreaterThan(0);
333
+ expect(templates.actions.length).toBeGreaterThan(0);
334
+ });
335
+
336
+ it('should return distinct templates', () => {
337
+ const templates = generateAllTemplates();
338
+
339
+ expect(templates.index).not.toEqual(templates.leaf);
340
+ expect(templates.index).not.toEqual(templates.simple);
341
+ expect(templates.index).not.toEqual(templates.actions);
342
+ expect(templates.leaf).not.toEqual(templates.simple);
343
+ expect(templates.leaf).not.toEqual(templates.actions);
344
+ expect(templates.simple).not.toEqual(templates.actions);
345
+ });
346
+
347
+ it('should respect variant option', () => {
348
+ const variants: TemplateVariant[] = ['minimal', 'standard', 'full'];
349
+
350
+ for (const variant of variants) {
351
+ const templates = generateAllTemplates({ variant });
352
+ expect(templates.index.length).toBeGreaterThan(0);
353
+ expect(templates.leaf.length).toBeGreaterThan(0);
354
+ expect(templates.simple.length).toBeGreaterThan(0);
355
+ expect(templates.actions.length).toBeGreaterThan(0);
356
+ }
357
+ });
358
+
359
+ it('should generate different sizes for different variants', () => {
360
+ const minimal = generateAllTemplates({ variant: 'minimal' });
361
+ const standard = generateAllTemplates({ variant: 'standard' });
362
+ const full = generateAllTemplates({ variant: 'full' });
363
+
364
+ // Full should be larger than standard, standard larger than minimal
365
+ expect(full.index.length).toBeGreaterThan(standard.index.length);
366
+ expect(standard.index.length).toBeGreaterThan(minimal.index.length);
367
+
368
+ expect(full.leaf.length).toBeGreaterThan(standard.leaf.length);
369
+ expect(standard.leaf.length).toBeGreaterThan(minimal.leaf.length);
370
+
371
+ expect(full.simple.length).toBeGreaterThan(standard.simple.length);
372
+ expect(standard.simple.length).toBeGreaterThan(minimal.simple.length);
373
+
374
+ expect(full.actions.length).toBeGreaterThan(standard.actions.length);
375
+ expect(standard.actions.length).toBeGreaterThan(minimal.actions.length);
376
+ });
377
+ });
378
+ });