@eddacraft/anvil-adapters 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.
- package/AGENTS.md +180 -0
- package/BMAD_ADAPTER_SPEC.md +489 -0
- package/LICENSE +14 -0
- package/README.md +500 -0
- package/dist/aps-markdown/adapter.d.ts +102 -0
- package/dist/aps-markdown/adapter.d.ts.map +1 -0
- package/dist/aps-markdown/adapter.js +351 -0
- package/dist/aps-markdown/index.d.ts +8 -0
- package/dist/aps-markdown/index.d.ts.map +1 -0
- package/dist/aps-markdown/index.js +7 -0
- package/dist/base/file-discovery.d.ts +63 -0
- package/dist/base/file-discovery.d.ts.map +1 -0
- package/dist/base/file-discovery.js +246 -0
- package/dist/base/index.d.ts +10 -0
- package/dist/base/index.d.ts.map +1 -0
- package/dist/base/index.js +9 -0
- package/dist/base/registry.d.ts +155 -0
- package/dist/base/registry.d.ts.map +1 -0
- package/dist/base/registry.js +227 -0
- package/dist/base/testing.d.ts +102 -0
- package/dist/base/testing.d.ts.map +1 -0
- package/dist/base/testing.js +221 -0
- package/dist/base/types.d.ts +255 -0
- package/dist/base/types.d.ts.map +1 -0
- package/dist/base/types.js +78 -0
- package/dist/base/utils.d.ts +127 -0
- package/dist/base/utils.d.ts.map +1 -0
- package/dist/base/utils.js +254 -0
- package/dist/bmad/format-adapter.d.ts +76 -0
- package/dist/bmad/format-adapter.d.ts.map +1 -0
- package/dist/bmad/format-adapter.js +186 -0
- package/dist/bmad/index.d.ts +12 -0
- package/dist/bmad/index.d.ts.map +1 -0
- package/dist/bmad/index.js +10 -0
- package/dist/bmad/parser.d.ts +12 -0
- package/dist/bmad/parser.d.ts.map +1 -0
- package/dist/bmad/parser.js +181 -0
- package/dist/bmad/serializer.d.ts +16 -0
- package/dist/bmad/serializer.d.ts.map +1 -0
- package/dist/bmad/serializer.js +170 -0
- package/dist/bmad/types.d.ts +127 -0
- package/dist/bmad/types.d.ts.map +1 -0
- package/dist/bmad/types.js +47 -0
- package/dist/bmad/utils.d.ts +120 -0
- package/dist/bmad/utils.d.ts.map +1 -0
- package/dist/bmad/utils.js +480 -0
- package/dist/common/index.d.ts +3 -0
- package/dist/common/index.d.ts.map +1 -0
- package/dist/common/index.js +2 -0
- package/dist/common/registry.d.ts +18 -0
- package/dist/common/registry.d.ts.map +1 -0
- package/dist/common/registry.js +58 -0
- package/dist/common/types.d.ts +68 -0
- package/dist/common/types.d.ts.map +1 -0
- package/dist/common/types.js +12 -0
- package/dist/generic/format-adapter.d.ts +64 -0
- package/dist/generic/format-adapter.d.ts.map +1 -0
- package/dist/generic/format-adapter.js +159 -0
- package/dist/generic/index.d.ts +10 -0
- package/dist/generic/index.d.ts.map +1 -0
- package/dist/generic/index.js +9 -0
- package/dist/generic/parser.d.ts +11 -0
- package/dist/generic/parser.d.ts.map +1 -0
- package/dist/generic/parser.js +106 -0
- package/dist/generic/serializer.d.ts +11 -0
- package/dist/generic/serializer.d.ts.map +1 -0
- package/dist/generic/serializer.js +118 -0
- package/dist/generic/types.d.ts +52 -0
- package/dist/generic/types.d.ts.map +1 -0
- package/dist/generic/types.js +6 -0
- package/dist/generic/utils.d.ts +51 -0
- package/dist/generic/utils.d.ts.map +1 -0
- package/dist/generic/utils.js +232 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/speckit/export.d.ts +22 -0
- package/dist/speckit/export.d.ts.map +1 -0
- package/dist/speckit/export.js +384 -0
- package/dist/speckit/format-adapter.d.ts +104 -0
- package/dist/speckit/format-adapter.d.ts.map +1 -0
- package/dist/speckit/format-adapter.js +488 -0
- package/dist/speckit/import-v2.d.ts +33 -0
- package/dist/speckit/import-v2.d.ts.map +1 -0
- package/dist/speckit/import-v2.js +361 -0
- package/dist/speckit/import.d.ts +16 -0
- package/dist/speckit/import.d.ts.map +1 -0
- package/dist/speckit/import.js +247 -0
- package/dist/speckit/index.d.ts +5 -0
- package/dist/speckit/index.d.ts.map +1 -0
- package/dist/speckit/index.js +4 -0
- package/dist/speckit/parser.d.ts +28 -0
- package/dist/speckit/parser.d.ts.map +1 -0
- package/dist/speckit/parser.js +283 -0
- package/dist/speckit/parsers/plan-parser.d.ts +71 -0
- package/dist/speckit/parsers/plan-parser.d.ts.map +1 -0
- package/dist/speckit/parsers/plan-parser.js +216 -0
- package/dist/speckit/parsers/spec-parser.d.ts +67 -0
- package/dist/speckit/parsers/spec-parser.d.ts.map +1 -0
- package/dist/speckit/parsers/spec-parser.js +255 -0
- package/dist/speckit/parsers/tasks-parser.d.ts +57 -0
- package/dist/speckit/parsers/tasks-parser.d.ts.map +1 -0
- package/dist/speckit/parsers/tasks-parser.js +157 -0
- package/package.json +23 -0
- package/project.json +29 -0
- package/src/__tests__/adapter-edge-cases.test.ts +937 -0
- package/src/__tests__/bmad-format-adapter.test.ts +1470 -0
- package/src/__tests__/fixtures/aps/expected-output.json +83 -0
- package/src/__tests__/fixtures/bmad/invalid-malformed-yaml.md +16 -0
- package/src/__tests__/fixtures/bmad/invalid-no-requirements.md +23 -0
- package/src/__tests__/fixtures/bmad/invalid-only-yaml.md +16 -0
- package/src/__tests__/fixtures/bmad/invalid-too-short.md +3 -0
- package/src/__tests__/fixtures/bmad/invalid-wrong-format.md +40 -0
- package/src/__tests__/fixtures/bmad/valid-agent.md +27 -0
- package/src/__tests__/fixtures/bmad/valid-architecture.md +116 -0
- package/src/__tests__/fixtures/bmad/valid-complex-prd.md +161 -0
- package/src/__tests__/fixtures/bmad/valid-epic.md +73 -0
- package/src/__tests__/fixtures/bmad/valid-minimal-prd.md +19 -0
- package/src/__tests__/fixtures/bmad/valid-prd.md +107 -0
- package/src/__tests__/fixtures/bmad/valid-story.md +107 -0
- package/src/__tests__/fixtures/bmad/valid-task.md +79 -0
- package/src/__tests__/fixtures/bmad/valid-v6-prd.md +35 -0
- package/src/__tests__/fixtures/generic/plan-detailed.md +39 -0
- package/src/__tests__/fixtures/generic/prd-simple.md +27 -0
- package/src/__tests__/fixtures/generic/rfc-example.md +26 -0
- package/src/__tests__/fixtures/generic/todo-list.md +23 -0
- package/src/__tests__/fixtures/speckit/sample-plan.md +63 -0
- package/src/__tests__/fixtures/speckit/sample-spec-namespaced.md +50 -0
- package/src/__tests__/fixtures/speckit/sample-spec.md +105 -0
- package/src/__tests__/fixtures/speckit/sample-tasks.md +87 -0
- package/src/__tests__/fixtures/speckit-official/auth-feature/plan.md +272 -0
- package/src/__tests__/fixtures/speckit-official/auth-feature/spec.md +149 -0
- package/src/__tests__/fixtures/speckit-official/auth-feature/tasks.md +169 -0
- package/src/__tests__/generic-format-adapter.test.ts +398 -0
- package/src/__tests__/speckit-export.test.ts +233 -0
- package/src/__tests__/speckit-format-adapter.test.ts +832 -0
- package/src/__tests__/speckit-import-v2.test.ts +253 -0
- package/src/__tests__/speckit-import.test.ts +209 -0
- package/src/__tests__/speckit-parser.test.ts +219 -0
- package/src/__tests__/speckit-spec-parser.test.ts +120 -0
- package/src/aps-markdown/__tests__/__fixtures__/simple-leaf.aps.md +17 -0
- package/src/aps-markdown/__tests__/adapter.test.ts +393 -0
- package/src/aps-markdown/adapter.ts +455 -0
- package/src/aps-markdown/index.ts +8 -0
- package/src/base/__tests__/registry.test.ts +515 -0
- package/src/base/file-discovery.ts +305 -0
- package/src/base/index.ts +10 -0
- package/src/base/registry.ts +263 -0
- package/src/base/testing.ts +334 -0
- package/src/base/types.ts +342 -0
- package/src/base/utils.ts +306 -0
- package/src/bmad/format-adapter.ts +227 -0
- package/src/bmad/index.ts +21 -0
- package/src/bmad/parser.ts +224 -0
- package/src/bmad/serializer.ts +206 -0
- package/src/bmad/types.ts +135 -0
- package/src/bmad/utils.ts +575 -0
- package/src/common/index.ts +2 -0
- package/src/common/registry.ts +72 -0
- package/src/common/types.ts +84 -0
- package/src/generic/__tests__/serializer.test.ts +167 -0
- package/src/generic/format-adapter.ts +200 -0
- package/src/generic/index.ts +11 -0
- package/src/generic/parser.ts +129 -0
- package/src/generic/serializer.ts +134 -0
- package/src/generic/types.ts +53 -0
- package/src/generic/utils.ts +270 -0
- package/src/index.ts +48 -0
- package/src/speckit/export.ts +489 -0
- package/src/speckit/format-adapter.ts +595 -0
- package/src/speckit/import-v2.ts +445 -0
- package/src/speckit/import.ts +305 -0
- package/src/speckit/index.ts +4 -0
- package/src/speckit/parser.ts +351 -0
- package/src/speckit/parsers/plan-parser.ts +342 -0
- package/src/speckit/parsers/spec-parser.ts +379 -0
- package/src/speckit/parsers/tasks-parser.ts +246 -0
- package/tsconfig.json +26 -0
- package/tsconfig.lib.json +21 -0
- package/tsconfig.lib.tsbuildinfo +1 -0
- package/tsconfig.spec.json +9 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vitest.config.ts +14 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { join, dirname } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { SpecParser } from '../speckit/parsers/spec-parser.js';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
9
|
+
const fixturesDir = join(__dirname, 'fixtures/speckit-official/auth-feature');
|
|
10
|
+
|
|
11
|
+
describe('SpecParser - Official Spec-Kit Format', () => {
|
|
12
|
+
let parser: SpecParser;
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
parser = new SpecParser();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should parse spec.md metadata', async () => {
|
|
19
|
+
const content = await readFile(join(fixturesDir, 'spec.md'), 'utf-8');
|
|
20
|
+
const parsed = parser.parseSpec(content);
|
|
21
|
+
|
|
22
|
+
expect(parsed.metadata.feature).toBe('User Authentication System');
|
|
23
|
+
expect(parsed.metadata.branch).toBe('feature/001-auth-system');
|
|
24
|
+
expect(parsed.metadata.date).toBe('2025-01-15');
|
|
25
|
+
expect(parsed.metadata.status).toBe('Draft');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should parse user scenarios with priorities', async () => {
|
|
29
|
+
const content = await readFile(join(fixturesDir, 'spec.md'), 'utf-8');
|
|
30
|
+
const parsed = parser.parseSpec(content);
|
|
31
|
+
|
|
32
|
+
expect(parsed.userScenarios).toHaveLength(4);
|
|
33
|
+
|
|
34
|
+
const registration = parsed.userScenarios[0];
|
|
35
|
+
expect(registration.priority).toBe('P1');
|
|
36
|
+
expect(registration.title).toBe('User Registration');
|
|
37
|
+
expect(registration.asA).toContain('new user');
|
|
38
|
+
expect(registration.iWantTo).toContain('create an account');
|
|
39
|
+
expect(registration.soThat).toContain('access the application securely');
|
|
40
|
+
expect(registration.acceptanceScenarios.length).toBeGreaterThan(0);
|
|
41
|
+
expect(registration.edgeCases.length).toBeGreaterThan(0);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should parse functional requirements', async () => {
|
|
45
|
+
const content = await readFile(join(fixturesDir, 'spec.md'), 'utf-8');
|
|
46
|
+
const parsed = parser.parseSpec(content);
|
|
47
|
+
|
|
48
|
+
expect(parsed.requirements.functional.length).toBeGreaterThan(0);
|
|
49
|
+
|
|
50
|
+
const fr001 = parsed.requirements.functional.find((r) => r.code === 'FR-001');
|
|
51
|
+
expect(fr001).toBeDefined();
|
|
52
|
+
expect(fr001?.description).toContain('RFC 5322');
|
|
53
|
+
expect(fr001?.needsClarification).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should identify requirements needing clarification', async () => {
|
|
57
|
+
const content = await readFile(join(fixturesDir, 'spec.md'), 'utf-8');
|
|
58
|
+
const parsed = parser.parseSpec(content);
|
|
59
|
+
|
|
60
|
+
const clarificationReqs = parsed.requirements.functional.filter((r) => r.needsClarification);
|
|
61
|
+
expect(clarificationReqs.length).toBeGreaterThan(0);
|
|
62
|
+
|
|
63
|
+
const fr008 = parsed.requirements.functional.find((r) => r.code === 'FR-008');
|
|
64
|
+
expect(fr008?.needsClarification).toBe(true);
|
|
65
|
+
expect(fr008?.clarificationQuestion).toContain('Token expiration time');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should parse key entities', async () => {
|
|
69
|
+
const content = await readFile(join(fixturesDir, 'spec.md'), 'utf-8');
|
|
70
|
+
const parsed = parser.parseSpec(content);
|
|
71
|
+
|
|
72
|
+
expect(parsed.requirements.entities.length).toBeGreaterThan(0);
|
|
73
|
+
|
|
74
|
+
const userEntity = parsed.requirements.entities.find((e) => e.name === 'User');
|
|
75
|
+
expect(userEntity).toBeDefined();
|
|
76
|
+
expect(userEntity?.represents).toContain('person with access');
|
|
77
|
+
expect(userEntity?.keyAttributes).toContain('email');
|
|
78
|
+
expect(userEntity?.relationships.length).toBeGreaterThan(0);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should parse success criteria', async () => {
|
|
82
|
+
const content = await readFile(join(fixturesDir, 'spec.md'), 'utf-8');
|
|
83
|
+
const parsed = parser.parseSpec(content);
|
|
84
|
+
|
|
85
|
+
expect(parsed.successCriteria.quantitative.length).toBeGreaterThan(0);
|
|
86
|
+
expect(parsed.successCriteria.qualitative.length).toBeGreaterThan(0);
|
|
87
|
+
expect(parsed.successCriteria.security).toBeDefined();
|
|
88
|
+
expect(parsed.successCriteria.security!.length).toBeGreaterThan(0);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should extract all clarification markers', async () => {
|
|
92
|
+
const content = await readFile(join(fixturesDir, 'spec.md'), 'utf-8');
|
|
93
|
+
const parsed = parser.parseSpec(content);
|
|
94
|
+
|
|
95
|
+
expect(parsed.clarifications.length).toBeGreaterThan(0);
|
|
96
|
+
expect(parsed.clarifications.some((c) => c.includes('remember me'))).toBe(true);
|
|
97
|
+
expect(parsed.clarifications.some((c) => c.includes('token expiration'))).toBe(true);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should handle edge cases in user scenarios', async () => {
|
|
101
|
+
const content = await readFile(join(fixturesDir, 'spec.md'), 'utf-8');
|
|
102
|
+
const parsed = parser.parseSpec(content);
|
|
103
|
+
|
|
104
|
+
const login = parsed.userScenarios.find((s) => s.title === 'User Login');
|
|
105
|
+
expect(login).toBeDefined();
|
|
106
|
+
expect(login?.edgeCases.length).toBeGreaterThan(0);
|
|
107
|
+
expect(login?.edgeCases.some((e) => e.includes('Invalid credentials'))).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should parse P2 and P3 scenarios', async () => {
|
|
111
|
+
const content = await readFile(join(fixturesDir, 'spec.md'), 'utf-8');
|
|
112
|
+
const parsed = parser.parseSpec(content);
|
|
113
|
+
|
|
114
|
+
const p2Scenarios = parsed.userScenarios.filter((s) => s.priority === 'P2');
|
|
115
|
+
const p3Scenarios = parsed.userScenarios.filter((s) => s.priority === 'P3');
|
|
116
|
+
|
|
117
|
+
expect(p2Scenarios.length).toBeGreaterThan(0);
|
|
118
|
+
expect(p3Scenarios.length).toBeGreaterThan(0);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Authentication Feature
|
|
2
|
+
|
|
3
|
+
**Scope:** AUTH **Owner:** @alice **Priority:** high
|
|
4
|
+
|
|
5
|
+
## Tasks
|
|
6
|
+
|
|
7
|
+
### AUTH-001: Implement login endpoint
|
|
8
|
+
|
|
9
|
+
**Intent:** Create POST /auth/login endpoint with JWT response **Expected
|
|
10
|
+
Outcome:** Returns JWT token on success, 401 on failure **Validation:**
|
|
11
|
+
`pnpm test -- --grep "login"` **Confidence:** high **Tags:** security, api
|
|
12
|
+
**Files:** src/auth/login.ts, src/auth/jwt.ts
|
|
13
|
+
|
|
14
|
+
### AUTH-002: Add password reset
|
|
15
|
+
|
|
16
|
+
**Intent:** Implement password reset flow with email verification
|
|
17
|
+
**Confidence:** medium **Dependencies:** AUTH-001
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* APSMarkdownAdapter Tests
|
|
3
|
+
* Tests for format detection and parsing of APS markdown documents
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFileSync } from 'node:fs';
|
|
7
|
+
import { join, dirname } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { describe, it, expect } from 'vitest';
|
|
10
|
+
import { APSMarkdownAdapter } from '../adapter.js';
|
|
11
|
+
|
|
12
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
|
|
14
|
+
describe('APSMarkdownAdapter', () => {
|
|
15
|
+
const adapter = new APSMarkdownAdapter();
|
|
16
|
+
|
|
17
|
+
describe('metadata', () => {
|
|
18
|
+
it('has correct metadata', () => {
|
|
19
|
+
expect(adapter.metadata.name).toBe('aps-markdown');
|
|
20
|
+
expect(adapter.metadata.extensions).toContain('.aps.md');
|
|
21
|
+
expect(adapter.metadata.formats).toContain('aps');
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('detect', () => {
|
|
26
|
+
it('detects .aps.md content with Tasks section', () => {
|
|
27
|
+
const content = `# Feature Plan
|
|
28
|
+
|
|
29
|
+
**Scope:** AUTH **Owner:** @alice
|
|
30
|
+
|
|
31
|
+
## Tasks
|
|
32
|
+
|
|
33
|
+
### AUTH-001: Implement login
|
|
34
|
+
|
|
35
|
+
**Intent:** Create login endpoint
|
|
36
|
+
`;
|
|
37
|
+
const result = adapter.detect(content);
|
|
38
|
+
expect(result.detected).toBe(true);
|
|
39
|
+
expect(result.confidence).toBeGreaterThanOrEqual(80);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('detects index file with Modules section', () => {
|
|
43
|
+
const content = `# Project Plan
|
|
44
|
+
|
|
45
|
+
## Modules
|
|
46
|
+
|
|
47
|
+
### auth
|
|
48
|
+
|
|
49
|
+
- **Path:** [./modules/auth.aps.md](./modules/auth.aps.md)
|
|
50
|
+
- **Scope:** AUTH
|
|
51
|
+
`;
|
|
52
|
+
const result = adapter.detect(content);
|
|
53
|
+
expect(result.detected).toBe(true);
|
|
54
|
+
// modules(20) + aps-link(25) + scope(10) = 55
|
|
55
|
+
expect(result.confidence).toBeGreaterThanOrEqual(55);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('does not detect regular markdown', () => {
|
|
59
|
+
const content = `# README
|
|
60
|
+
|
|
61
|
+
This is a regular readme file.
|
|
62
|
+
|
|
63
|
+
## Installation
|
|
64
|
+
|
|
65
|
+
Run npm install.
|
|
66
|
+
`;
|
|
67
|
+
const result = adapter.detect(content);
|
|
68
|
+
expect(result.detected).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('does not detect SpecKit format', () => {
|
|
72
|
+
const content = `# Feature: User Login
|
|
73
|
+
|
|
74
|
+
## User Story
|
|
75
|
+
As a user I want to login
|
|
76
|
+
|
|
77
|
+
## Acceptance Criteria
|
|
78
|
+
- Given valid credentials
|
|
79
|
+
- When I submit login form
|
|
80
|
+
- Then I am authenticated
|
|
81
|
+
`;
|
|
82
|
+
const result = adapter.detect(content);
|
|
83
|
+
expect(result.detected).toBe(false);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('canImport / canExport', () => {
|
|
88
|
+
it('should support importing aps format', () => {
|
|
89
|
+
expect(adapter.canImport('aps')).toBe(true);
|
|
90
|
+
expect(adapter.canImport('aps-markdown')).toBe(true);
|
|
91
|
+
expect(adapter.canImport('.aps.md')).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should support exporting to aps format', () => {
|
|
95
|
+
expect(adapter.canExport('aps')).toBe(true);
|
|
96
|
+
expect(adapter.canExport('.aps.md')).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should not support unknown formats', () => {
|
|
100
|
+
expect(adapter.canImport('speckit')).toBe(false);
|
|
101
|
+
expect(adapter.canImport('bmad')).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe('confidence scoring', () => {
|
|
106
|
+
it('has high confidence (90+) for leaf spec with SCOPE-NNN task and Intent', () => {
|
|
107
|
+
const content = `# Authentication Module
|
|
108
|
+
|
|
109
|
+
**Scope:** AUTH **Owner:** @alice **Priority:** high
|
|
110
|
+
|
|
111
|
+
> Handles user authentication and session management.
|
|
112
|
+
|
|
113
|
+
## Tasks
|
|
114
|
+
|
|
115
|
+
### AUTH-001: Implement login endpoint
|
|
116
|
+
|
|
117
|
+
**Intent:** Create POST /auth/login endpoint with JWT response
|
|
118
|
+
**Confidence:** high
|
|
119
|
+
**Expected Outcome:** Returns JWT token on success, 401 on failure
|
|
120
|
+
**Tags:** security, api
|
|
121
|
+
|
|
122
|
+
### AUTH-002: Add password reset
|
|
123
|
+
|
|
124
|
+
**Intent:** Implement password reset flow with email verification
|
|
125
|
+
**Confidence:** medium
|
|
126
|
+
**Dependencies:** AUTH-001
|
|
127
|
+
`;
|
|
128
|
+
const result = adapter.detect(content);
|
|
129
|
+
expect(result.detected).toBe(true);
|
|
130
|
+
expect(result.confidence).toBeGreaterThanOrEqual(90);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('has medium-high confidence (70+) for index with Modules and .aps.md paths', () => {
|
|
134
|
+
const content = `# Project Plan
|
|
135
|
+
|
|
136
|
+
> A multi-module project plan.
|
|
137
|
+
|
|
138
|
+
## Modules
|
|
139
|
+
|
|
140
|
+
### auth
|
|
141
|
+
|
|
142
|
+
- **Path:** [./modules/auth.aps.md](./modules/auth.aps.md)
|
|
143
|
+
- **Scope:** AUTH
|
|
144
|
+
- **Owner:** @alice
|
|
145
|
+
- **Priority:** high
|
|
146
|
+
|
|
147
|
+
### payments
|
|
148
|
+
|
|
149
|
+
- **Path:** [./modules/payments.aps.md](./modules/payments.aps.md)
|
|
150
|
+
- **Scope:** PAY
|
|
151
|
+
- **Owner:** @bob
|
|
152
|
+
- **Priority:** medium
|
|
153
|
+
- **Dependencies:** auth
|
|
154
|
+
`;
|
|
155
|
+
const result = adapter.detect(content);
|
|
156
|
+
expect(result.detected).toBe(true);
|
|
157
|
+
// modules(20) + aps-links(25+5) + scope(10) + owner(5) + priority(5) = 70
|
|
158
|
+
expect(result.confidence).toBeGreaterThanOrEqual(70);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('has low confidence for markdown without APS markers', () => {
|
|
162
|
+
const content = `# Some Document
|
|
163
|
+
|
|
164
|
+
This has a header but no APS-specific content.
|
|
165
|
+
|
|
166
|
+
## Section One
|
|
167
|
+
|
|
168
|
+
Just regular text here.
|
|
169
|
+
`;
|
|
170
|
+
const result = adapter.detect(content);
|
|
171
|
+
expect(result.confidence).toBeLessThan(50);
|
|
172
|
+
expect(result.detected).toBe(false);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('has partial confidence for partial APS markers', () => {
|
|
176
|
+
const content = `# Feature Plan
|
|
177
|
+
|
|
178
|
+
**Scope:** TEST
|
|
179
|
+
|
|
180
|
+
## Tasks
|
|
181
|
+
|
|
182
|
+
Some tasks without proper formatting.
|
|
183
|
+
`;
|
|
184
|
+
const result = adapter.detect(content);
|
|
185
|
+
// Has scope(10) and Tasks section(15) but no SCOPE-NNN pattern = 25
|
|
186
|
+
expect(result.confidence).toBeGreaterThanOrEqual(25);
|
|
187
|
+
expect(result.confidence).toBeLessThan(80);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe('detects ID and Packages fields', () => {
|
|
192
|
+
it('detects **ID:** field as APS indicator', () => {
|
|
193
|
+
const content = `# Feature Plan
|
|
194
|
+
|
|
195
|
+
**ID:** AUTH
|
|
196
|
+
|
|
197
|
+
## Tasks
|
|
198
|
+
|
|
199
|
+
### AUTH-001: Implement login
|
|
200
|
+
|
|
201
|
+
**Intent:** Create login endpoint
|
|
202
|
+
`;
|
|
203
|
+
const result = adapter.detect(content);
|
|
204
|
+
expect(result.detected).toBe(true);
|
|
205
|
+
expect(result.reason).toContain('scope-field');
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('detects **Packages:** field as APS indicator', () => {
|
|
209
|
+
const content = `# Feature Plan
|
|
210
|
+
|
|
211
|
+
**Scope:** AUTH **Packages:** @app/core, @app/utils
|
|
212
|
+
|
|
213
|
+
## Tasks
|
|
214
|
+
|
|
215
|
+
### AUTH-001: Implement login
|
|
216
|
+
|
|
217
|
+
**Intent:** Create login endpoint
|
|
218
|
+
`;
|
|
219
|
+
const result = adapter.detect(content);
|
|
220
|
+
expect(result.detected).toBe(true);
|
|
221
|
+
expect(result.reason).toContain('packages-field');
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
describe('detection reasons', () => {
|
|
226
|
+
it('provides reason for leaf spec detection', () => {
|
|
227
|
+
const content = `# Feature
|
|
228
|
+
|
|
229
|
+
**Scope:** AUTH
|
|
230
|
+
|
|
231
|
+
## Tasks
|
|
232
|
+
|
|
233
|
+
### AUTH-001: Task one
|
|
234
|
+
|
|
235
|
+
**Intent:** Do something
|
|
236
|
+
`;
|
|
237
|
+
const result = adapter.detect(content);
|
|
238
|
+
expect(result.reason).toBeDefined();
|
|
239
|
+
expect(result.reason).toContain('tasks-section');
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('provides reason for index detection', () => {
|
|
243
|
+
const content = `# Project
|
|
244
|
+
|
|
245
|
+
## Modules
|
|
246
|
+
|
|
247
|
+
### auth
|
|
248
|
+
|
|
249
|
+
- **Path:** [./auth.aps.md](./auth.aps.md)
|
|
250
|
+
`;
|
|
251
|
+
const result = adapter.detect(content);
|
|
252
|
+
expect(result.reason).toBeDefined();
|
|
253
|
+
expect(result.reason).toContain('modules-section');
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
describe('parse', () => {
|
|
258
|
+
it('parses a leaf spec to APSPlan', async () => {
|
|
259
|
+
const content = readFileSync(join(__dirname, '__fixtures__/simple-leaf.aps.md'), 'utf-8');
|
|
260
|
+
|
|
261
|
+
const result = await adapter.parse(content, {
|
|
262
|
+
repositoryPath: '/test/repo',
|
|
263
|
+
author: 'test-user',
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
expect(result.success).toBe(true);
|
|
267
|
+
expect(result.data).toBeDefined();
|
|
268
|
+
expect(result.data!.intent).toContain('Authentication Feature');
|
|
269
|
+
expect(result.data!.proposed_changes).toHaveLength(2);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('maps task fields to change metadata', async () => {
|
|
273
|
+
const content = readFileSync(join(__dirname, '__fixtures__/simple-leaf.aps.md'), 'utf-8');
|
|
274
|
+
|
|
275
|
+
const result = await adapter.parse(content);
|
|
276
|
+
|
|
277
|
+
expect(result.success).toBe(true);
|
|
278
|
+
const change = result.data!.proposed_changes[0];
|
|
279
|
+
expect(change.description).toContain('AUTH-001');
|
|
280
|
+
expect(change.metadata?.taskId).toBe('AUTH-001');
|
|
281
|
+
expect(change.metadata?.confidence).toBe('high');
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('preserves task dependencies', async () => {
|
|
285
|
+
const content = readFileSync(join(__dirname, '__fixtures__/simple-leaf.aps.md'), 'utf-8');
|
|
286
|
+
|
|
287
|
+
const result = await adapter.parse(content);
|
|
288
|
+
|
|
289
|
+
expect(result.success).toBe(true);
|
|
290
|
+
const change2 = result.data!.proposed_changes[1];
|
|
291
|
+
expect(change2.metadata?.dependencies).toEqual(['AUTH-001']);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('infers file_create type from create/add intent', async () => {
|
|
295
|
+
const content = `# Test Feature
|
|
296
|
+
|
|
297
|
+
**Scope:** TEST
|
|
298
|
+
|
|
299
|
+
## Tasks
|
|
300
|
+
|
|
301
|
+
### TEST-001: Create new file
|
|
302
|
+
|
|
303
|
+
**Intent:** Create a new configuration file
|
|
304
|
+
**Confidence:** high
|
|
305
|
+
`;
|
|
306
|
+
|
|
307
|
+
const result = await adapter.parse(content);
|
|
308
|
+
|
|
309
|
+
expect(result.success).toBe(true);
|
|
310
|
+
expect(result.data!.proposed_changes[0].type).toBe('file_create');
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('infers file_update type from update/modify intent', async () => {
|
|
314
|
+
const content = `# Test Feature
|
|
315
|
+
|
|
316
|
+
**Scope:** TEST
|
|
317
|
+
|
|
318
|
+
## Tasks
|
|
319
|
+
|
|
320
|
+
### TEST-001: Update existing code
|
|
321
|
+
|
|
322
|
+
**Intent:** Update the existing handler logic
|
|
323
|
+
**Confidence:** high
|
|
324
|
+
`;
|
|
325
|
+
|
|
326
|
+
const result = await adapter.parse(content);
|
|
327
|
+
|
|
328
|
+
expect(result.success).toBe(true);
|
|
329
|
+
expect(result.data!.proposed_changes[0].type).toBe('file_update');
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it('infers file_delete type from delete/remove intent', async () => {
|
|
333
|
+
const content = `# Test Feature
|
|
334
|
+
|
|
335
|
+
**Scope:** TEST
|
|
336
|
+
|
|
337
|
+
## Tasks
|
|
338
|
+
|
|
339
|
+
### TEST-001: Remove deprecated file
|
|
340
|
+
|
|
341
|
+
**Intent:** Delete the legacy module
|
|
342
|
+
**Confidence:** high
|
|
343
|
+
`;
|
|
344
|
+
|
|
345
|
+
const result = await adapter.parse(content);
|
|
346
|
+
|
|
347
|
+
expect(result.success).toBe(true);
|
|
348
|
+
expect(result.data!.proposed_changes[0].type).toBe('file_delete');
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it('generates valid plan ID and hash', async () => {
|
|
352
|
+
const content = readFileSync(join(__dirname, '__fixtures__/simple-leaf.aps.md'), 'utf-8');
|
|
353
|
+
|
|
354
|
+
const result = await adapter.parse(content);
|
|
355
|
+
|
|
356
|
+
expect(result.success).toBe(true);
|
|
357
|
+
expect(result.data!.id).toMatch(/^aps-[a-f0-9]{8,16}$/);
|
|
358
|
+
expect(result.data!.hash).toMatch(/^[a-f0-9]{64}$/);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('uses provided plan ID from context', async () => {
|
|
362
|
+
const content = readFileSync(join(__dirname, '__fixtures__/simple-leaf.aps.md'), 'utf-8');
|
|
363
|
+
|
|
364
|
+
const result = await adapter.parse(content, {
|
|
365
|
+
planId: 'aps-12345678',
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
expect(result.success).toBe(true);
|
|
369
|
+
expect(result.data!.id).toBe('aps-12345678');
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it('returns error for invalid content', async () => {
|
|
373
|
+
const content = 'Invalid content without H1 title';
|
|
374
|
+
|
|
375
|
+
const result = await adapter.parse(content);
|
|
376
|
+
|
|
377
|
+
expect(result.success).toBe(false);
|
|
378
|
+
expect(result.errors).toBeDefined();
|
|
379
|
+
expect(result.errors!.length).toBeGreaterThan(0);
|
|
380
|
+
expect(result.errors![0].code).toBe('PARSE_ERROR');
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it('uses first file from files array as change path', async () => {
|
|
384
|
+
const content = readFileSync(join(__dirname, '__fixtures__/simple-leaf.aps.md'), 'utf-8');
|
|
385
|
+
|
|
386
|
+
const result = await adapter.parse(content);
|
|
387
|
+
|
|
388
|
+
expect(result.success).toBe(true);
|
|
389
|
+
// AUTH-001 has Files: src/auth/login.ts, src/auth/jwt.ts
|
|
390
|
+
expect(result.data!.proposed_changes[0].path).toBe('src/auth/login.ts');
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
});
|