@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,84 @@
|
|
|
1
|
+
import type { APSPlan, ValidationResult } from '@eddacraft/anvil-core';
|
|
2
|
+
|
|
3
|
+
export interface SpecContext {
|
|
4
|
+
repositoryPath?: string;
|
|
5
|
+
branch?: string;
|
|
6
|
+
commit?: string;
|
|
7
|
+
author?: string;
|
|
8
|
+
timestamp?: string;
|
|
9
|
+
additionalContext?: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ExternalSpec {
|
|
13
|
+
format: string;
|
|
14
|
+
version: string;
|
|
15
|
+
content: unknown;
|
|
16
|
+
metadata?: Record<string, unknown>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ConversionResult<T = unknown> {
|
|
20
|
+
success: boolean;
|
|
21
|
+
data?: T;
|
|
22
|
+
errors?: ConversionError[];
|
|
23
|
+
warnings?: ConversionWarning[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ConversionError {
|
|
27
|
+
code: string;
|
|
28
|
+
message: string;
|
|
29
|
+
path?: string;
|
|
30
|
+
line?: number;
|
|
31
|
+
column?: number;
|
|
32
|
+
details?: unknown;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ConversionWarning {
|
|
36
|
+
code: string;
|
|
37
|
+
message: string;
|
|
38
|
+
path?: string;
|
|
39
|
+
line?: number;
|
|
40
|
+
column?: number;
|
|
41
|
+
details?: unknown;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface SpecToolAdapter {
|
|
45
|
+
readonly name: string;
|
|
46
|
+
readonly version: string;
|
|
47
|
+
readonly supportedFormats: readonly string[];
|
|
48
|
+
|
|
49
|
+
generateSpec(intent: string, context: SpecContext): Promise<APSPlan>;
|
|
50
|
+
validateSpec(spec: APSPlan): Promise<ValidationResult>;
|
|
51
|
+
convertToAPS(spec: ExternalSpec): Promise<ConversionResult<APSPlan>>;
|
|
52
|
+
convertFromAPS(spec: APSPlan): Promise<ConversionResult<ExternalSpec>>;
|
|
53
|
+
|
|
54
|
+
canImport(format: string): boolean;
|
|
55
|
+
canExport(format: string): boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface AdapterConfig {
|
|
59
|
+
preserveComments?: boolean;
|
|
60
|
+
preserveMetadata?: boolean;
|
|
61
|
+
strictMode?: boolean;
|
|
62
|
+
formatOptions?: Record<string, unknown>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export abstract class BaseAdapter implements SpecToolAdapter {
|
|
66
|
+
abstract readonly name: string;
|
|
67
|
+
abstract readonly version: string;
|
|
68
|
+
abstract readonly supportedFormats: readonly string[];
|
|
69
|
+
|
|
70
|
+
constructor(protected config: AdapterConfig = {}) {}
|
|
71
|
+
|
|
72
|
+
abstract generateSpec(intent: string, context: SpecContext): Promise<APSPlan>;
|
|
73
|
+
abstract validateSpec(spec: APSPlan): Promise<ValidationResult>;
|
|
74
|
+
abstract convertToAPS(spec: ExternalSpec): Promise<ConversionResult<APSPlan>>;
|
|
75
|
+
abstract convertFromAPS(spec: APSPlan): Promise<ConversionResult<ExternalSpec>>;
|
|
76
|
+
|
|
77
|
+
canImport(format: string): boolean {
|
|
78
|
+
return this.supportedFormats.includes(format);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
canExport(format: string): boolean {
|
|
82
|
+
return this.supportedFormats.includes(format);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic Serializer Tests
|
|
3
|
+
* Tests for type-safe serialization of APS plans
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect } from 'vitest';
|
|
7
|
+
import { serializeToGeneric } from '../serializer.js';
|
|
8
|
+
import type { APSPlan } from '@eddacraft/anvil-core';
|
|
9
|
+
|
|
10
|
+
describe('serializeToGeneric', () => {
|
|
11
|
+
const basePlan: APSPlan = {
|
|
12
|
+
id: 'aps-test123',
|
|
13
|
+
schema_version: '0.1.0',
|
|
14
|
+
hash: 'test-hash',
|
|
15
|
+
intent: 'Test plan',
|
|
16
|
+
proposed_changes: [],
|
|
17
|
+
provenance: {
|
|
18
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
19
|
+
author: 'test@example.com',
|
|
20
|
+
source: 'cli',
|
|
21
|
+
version: '1.0.0',
|
|
22
|
+
},
|
|
23
|
+
validations: {
|
|
24
|
+
required_checks: [],
|
|
25
|
+
skip_checks: [],
|
|
26
|
+
},
|
|
27
|
+
evidence: [],
|
|
28
|
+
executions: [],
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
it('should handle plan with no metadata', () => {
|
|
32
|
+
const result = serializeToGeneric(basePlan);
|
|
33
|
+
|
|
34
|
+
expect(result).toContain('# Test plan');
|
|
35
|
+
expect(result).toContain('## Purpose');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should handle plan with string title metadata', () => {
|
|
39
|
+
const plan = {
|
|
40
|
+
...basePlan,
|
|
41
|
+
metadata: {
|
|
42
|
+
title: 'Custom Title',
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const result = serializeToGeneric(plan);
|
|
47
|
+
|
|
48
|
+
expect(result).toContain('# Custom Title');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should handle plan with non-string title metadata gracefully', () => {
|
|
52
|
+
const plan = {
|
|
53
|
+
...basePlan,
|
|
54
|
+
metadata: {
|
|
55
|
+
title: 123 as unknown as string, // Invalid type
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const result = serializeToGeneric(plan);
|
|
60
|
+
|
|
61
|
+
// Should fall back to intent
|
|
62
|
+
expect(result).toContain('# Test plan');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should handle plan with string overview metadata', () => {
|
|
66
|
+
const plan = {
|
|
67
|
+
...basePlan,
|
|
68
|
+
metadata: {
|
|
69
|
+
overview: 'This is an overview',
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const result = serializeToGeneric(plan);
|
|
74
|
+
|
|
75
|
+
expect(result).toContain('## Overview');
|
|
76
|
+
expect(result).toContain('This is an overview');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should handle plan with non-string overview metadata gracefully', () => {
|
|
80
|
+
const plan = {
|
|
81
|
+
...basePlan,
|
|
82
|
+
metadata: {
|
|
83
|
+
overview: { text: 'overview' } as unknown as string, // Invalid type
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const result = serializeToGeneric(plan);
|
|
88
|
+
|
|
89
|
+
// Should fall back to purpose section
|
|
90
|
+
expect(result).toContain('## Purpose');
|
|
91
|
+
expect(result).not.toContain('## Overview');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should handle plan with string array goals', () => {
|
|
95
|
+
const plan = {
|
|
96
|
+
...basePlan,
|
|
97
|
+
metadata: {
|
|
98
|
+
goals: ['Goal 1', 'Goal 2', 'Goal 3'],
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const result = serializeToGeneric(plan);
|
|
103
|
+
|
|
104
|
+
expect(result).toContain('## Goals');
|
|
105
|
+
expect(result).toContain('- Goal 1');
|
|
106
|
+
expect(result).toContain('- Goal 2');
|
|
107
|
+
expect(result).toContain('- Goal 3');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should handle plan with non-string array goals gracefully', () => {
|
|
111
|
+
const plan = {
|
|
112
|
+
...basePlan,
|
|
113
|
+
metadata: {
|
|
114
|
+
goals: [1, 2, 3] as unknown as string[], // Invalid type
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const result = serializeToGeneric(plan);
|
|
119
|
+
|
|
120
|
+
// Should not include goals section
|
|
121
|
+
expect(result).not.toContain('## Goals');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should handle plan with mixed-type goals array gracefully', () => {
|
|
125
|
+
const plan = {
|
|
126
|
+
...basePlan,
|
|
127
|
+
metadata: {
|
|
128
|
+
goals: ['Goal 1', 123, 'Goal 2'] as unknown as string[], // Mixed types
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const result = serializeToGeneric(plan);
|
|
133
|
+
|
|
134
|
+
// Should not include goals section since not all elements are strings
|
|
135
|
+
expect(result).not.toContain('## Goals');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should handle plan with all metadata types', () => {
|
|
139
|
+
const plan = {
|
|
140
|
+
...basePlan,
|
|
141
|
+
metadata: {
|
|
142
|
+
title: 'Full Plan',
|
|
143
|
+
overview: 'Complete overview',
|
|
144
|
+
goals: ['Goal A', 'Goal B'],
|
|
145
|
+
},
|
|
146
|
+
proposed_changes: [
|
|
147
|
+
{
|
|
148
|
+
type: 'file_create' as const,
|
|
149
|
+
path: 'test.ts',
|
|
150
|
+
description: 'Create test file',
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const result = serializeToGeneric(plan);
|
|
156
|
+
|
|
157
|
+
expect(result).toContain('# Full Plan');
|
|
158
|
+
expect(result).toContain('## Overview');
|
|
159
|
+
expect(result).toContain('Complete overview');
|
|
160
|
+
expect(result).toContain('## Goals');
|
|
161
|
+
expect(result).toContain('- Goal A');
|
|
162
|
+
expect(result).toContain('- Goal B');
|
|
163
|
+
expect(result).toContain('## Changes');
|
|
164
|
+
expect(result).toContain('### Files to Create');
|
|
165
|
+
expect(result).toContain('test.ts');
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic Markdown Format Adapter
|
|
3
|
+
*
|
|
4
|
+
* FormatAdapter implementation for generic markdown planning documents.
|
|
5
|
+
* Serves as a fallback adapter for documents that don't match specific formats.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { generateHash, type APSPlan, type ValidationResult } from '@eddacraft/anvil-core';
|
|
9
|
+
import {
|
|
10
|
+
BaseFormatAdapter,
|
|
11
|
+
type AdapterMetadata,
|
|
12
|
+
type DetectionResult,
|
|
13
|
+
type ParseResult,
|
|
14
|
+
type SerializeResult,
|
|
15
|
+
type ParseContext,
|
|
16
|
+
type AdapterOptions,
|
|
17
|
+
} from '../base/types.js';
|
|
18
|
+
import { createDetection } from '../base/utils.js';
|
|
19
|
+
import { analyzeContent, calculateConfidenceScore, buildDetectionReason } from './utils.js';
|
|
20
|
+
import { parseGeneric } from './parser.js';
|
|
21
|
+
import { serializeToGeneric } from './serializer.js';
|
|
22
|
+
|
|
23
|
+
const MIN_CONTENT_LENGTH = 50;
|
|
24
|
+
const FALLBACK_DETECTION_THRESHOLD = 30;
|
|
25
|
+
const MAX_FALLBACK_CONFIDENCE = 45;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Generic Markdown FormatAdapter implementation
|
|
29
|
+
*
|
|
30
|
+
* Converts between generic markdown documents and APS plans.
|
|
31
|
+
* Designed to work as a fallback for documents that don't match
|
|
32
|
+
* specific formats like BMAD or SpecKit.
|
|
33
|
+
*/
|
|
34
|
+
export class GenericMarkdownAdapter extends BaseFormatAdapter {
|
|
35
|
+
readonly metadata: AdapterMetadata = {
|
|
36
|
+
name: 'generic-markdown',
|
|
37
|
+
version: '1.0.0',
|
|
38
|
+
displayName: 'Generic Markdown',
|
|
39
|
+
description: 'Generic markdown adapter for PRDs, plans, todos, and other planning documents',
|
|
40
|
+
formats: ['generic', 'markdown', 'prd', 'plan', 'todo', 'rfc', 'adr'],
|
|
41
|
+
extensions: ['.md', '.markdown'],
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Detect if content is generic markdown planning document
|
|
46
|
+
*
|
|
47
|
+
* Uses lower confidence threshold (30-40%) to serve as fallback.
|
|
48
|
+
* This adapter should be registered last so specific adapters
|
|
49
|
+
* (BMAD, SpecKit) take precedence.
|
|
50
|
+
*
|
|
51
|
+
* @param content - Document content to analyze
|
|
52
|
+
* @returns Detection result with confidence score
|
|
53
|
+
*/
|
|
54
|
+
detect(content: string): DetectionResult {
|
|
55
|
+
const indicators = analyzeContent(content);
|
|
56
|
+
const confidence = calculateConfidenceScore(indicators);
|
|
57
|
+
const reason = buildDetectionReason(indicators);
|
|
58
|
+
|
|
59
|
+
const cappedConfidence = Math.min(MAX_FALLBACK_CONFIDENCE, confidence);
|
|
60
|
+
|
|
61
|
+
return createDetection(
|
|
62
|
+
cappedConfidence >= FALLBACK_DETECTION_THRESHOLD,
|
|
63
|
+
cappedConfidence,
|
|
64
|
+
reason
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Parse generic markdown content to APS plan
|
|
70
|
+
*
|
|
71
|
+
* @param content - Markdown content
|
|
72
|
+
* @param context - Parse context for provenance
|
|
73
|
+
* @param options - Adapter options
|
|
74
|
+
* @returns Parse result with APS plan
|
|
75
|
+
*/
|
|
76
|
+
async parse(
|
|
77
|
+
content: string,
|
|
78
|
+
context?: ParseContext,
|
|
79
|
+
_options?: AdapterOptions
|
|
80
|
+
): Promise<ParseResult> {
|
|
81
|
+
try {
|
|
82
|
+
// Parse content to APS plan
|
|
83
|
+
const plan = parseGeneric(content, context);
|
|
84
|
+
|
|
85
|
+
// Generate hash for the plan
|
|
86
|
+
const planWithHash = {
|
|
87
|
+
...plan,
|
|
88
|
+
hash: generateHash(plan),
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return this.createParseSuccess(planWithHash);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
return this.createParseError([
|
|
94
|
+
{
|
|
95
|
+
code: 'PARSE_ERROR',
|
|
96
|
+
message:
|
|
97
|
+
error instanceof Error ? error.message : 'Failed to parse generic markdown content',
|
|
98
|
+
details: error,
|
|
99
|
+
},
|
|
100
|
+
]);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Serialize APS plan to generic markdown format
|
|
106
|
+
*
|
|
107
|
+
* @param plan - APS plan to serialize
|
|
108
|
+
* @param options - Adapter options
|
|
109
|
+
* @returns Serialize result with markdown content
|
|
110
|
+
*/
|
|
111
|
+
async serialize(plan: APSPlan, _options?: AdapterOptions): Promise<SerializeResult> {
|
|
112
|
+
try {
|
|
113
|
+
const content = serializeToGeneric(plan);
|
|
114
|
+
return this.createSerializeSuccess(content);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
return this.createSerializeError([
|
|
117
|
+
{
|
|
118
|
+
code: 'SERIALIZE_ERROR',
|
|
119
|
+
message:
|
|
120
|
+
error instanceof Error
|
|
121
|
+
? error.message
|
|
122
|
+
: 'Failed to serialize to generic markdown format',
|
|
123
|
+
details: error,
|
|
124
|
+
},
|
|
125
|
+
]);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Validate generic markdown content
|
|
131
|
+
*
|
|
132
|
+
* Checks for basic markdown structure without strict requirements.
|
|
133
|
+
*
|
|
134
|
+
* @param content - Content to validate
|
|
135
|
+
* @param options - Validation options
|
|
136
|
+
* @returns Validation result
|
|
137
|
+
*/
|
|
138
|
+
async validate(content: string, _options?: AdapterOptions): Promise<ValidationResult> {
|
|
139
|
+
const issues: Array<{
|
|
140
|
+
path: string;
|
|
141
|
+
message: string;
|
|
142
|
+
code: string;
|
|
143
|
+
severity: 'error' | 'warning';
|
|
144
|
+
}> = [];
|
|
145
|
+
|
|
146
|
+
if (content.trim().length < MIN_CONTENT_LENGTH) {
|
|
147
|
+
issues.push({
|
|
148
|
+
code: 'CONTENT_TOO_SHORT',
|
|
149
|
+
path: 'content',
|
|
150
|
+
message: 'Content is too short to be a valid planning document',
|
|
151
|
+
severity: 'error',
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const indicators = analyzeContent(content);
|
|
156
|
+
const confidence = calculateConfidenceScore(indicators);
|
|
157
|
+
|
|
158
|
+
if (confidence < FALLBACK_DETECTION_THRESHOLD) {
|
|
159
|
+
issues.push({
|
|
160
|
+
code: 'LOW_CONFIDENCE',
|
|
161
|
+
path: 'content',
|
|
162
|
+
message: `Content does not appear to be a planning document (confidence: ${confidence}%)`,
|
|
163
|
+
severity: 'error',
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Warn if missing common planning sections
|
|
168
|
+
if (
|
|
169
|
+
!indicators.hasRequirementsSection &&
|
|
170
|
+
!indicators.hasTasksSection &&
|
|
171
|
+
!indicators.hasFeaturesSection
|
|
172
|
+
) {
|
|
173
|
+
issues.push({
|
|
174
|
+
code: 'NO_PLANNING_SECTIONS',
|
|
175
|
+
path: 'content',
|
|
176
|
+
message: 'Document lacks common planning sections (requirements, tasks, or features)',
|
|
177
|
+
severity: 'warning',
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
valid: issues.filter((i) => i.severity === 'error').length === 0,
|
|
183
|
+
issues: issues.length > 0 ? issues : undefined,
|
|
184
|
+
summary:
|
|
185
|
+
issues.length === 0
|
|
186
|
+
? 'Generic markdown document is valid'
|
|
187
|
+
: `Found ${issues.length} validation issue${issues.length > 1 ? 's' : ''}`,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Create a new generic markdown adapter instance
|
|
194
|
+
*
|
|
195
|
+
* @param options - Adapter options
|
|
196
|
+
* @returns Generic markdown adapter instance
|
|
197
|
+
*/
|
|
198
|
+
export function createGenericMarkdownAdapter(options?: AdapterOptions): GenericMarkdownAdapter {
|
|
199
|
+
return new GenericMarkdownAdapter(options);
|
|
200
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic Markdown Adapter
|
|
3
|
+
*
|
|
4
|
+
* Exports for generic markdown format adapter.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { GenericMarkdownAdapter, createGenericMarkdownAdapter } from './format-adapter.js';
|
|
8
|
+
export type { GenericDocument, GenericIndicators } from './types.js';
|
|
9
|
+
export { parseGeneric } from './parser.js';
|
|
10
|
+
export { serializeToGeneric } from './serializer.js';
|
|
11
|
+
// Note: Utils are not exported to avoid conflicts with BMAD utils
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic Markdown Parser
|
|
3
|
+
*
|
|
4
|
+
* Parses generic markdown documents into APS plans.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { type APSPlan, type Change, validateRelativePath } from '@eddacraft/anvil-core';
|
|
8
|
+
import type { ParseContext } from '../base/types.js';
|
|
9
|
+
import type { GenericDocument } from './types.js';
|
|
10
|
+
import { generateDeterministicPlanId } from '../base/utils.js';
|
|
11
|
+
import { parseGenericDocument } from './utils.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Convert generic document item to APS change
|
|
15
|
+
*/
|
|
16
|
+
function itemToChange(item: string, type: 'requirement' | 'task' | 'feature'): Change {
|
|
17
|
+
// Determine change type based on item type
|
|
18
|
+
let changeType: Change['type'] = 'file_create';
|
|
19
|
+
let pathPrefix = 'src';
|
|
20
|
+
|
|
21
|
+
switch (type) {
|
|
22
|
+
case 'requirement':
|
|
23
|
+
changeType = 'file_create';
|
|
24
|
+
pathPrefix = 'src/features';
|
|
25
|
+
break;
|
|
26
|
+
case 'task':
|
|
27
|
+
changeType = 'file_update';
|
|
28
|
+
pathPrefix = 'src/tasks';
|
|
29
|
+
break;
|
|
30
|
+
case 'feature':
|
|
31
|
+
changeType = 'file_create';
|
|
32
|
+
pathPrefix = 'src/features';
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Generate a reasonable path from the item description
|
|
37
|
+
const slug = item
|
|
38
|
+
.toLowerCase()
|
|
39
|
+
.replace(/[^a-z0-9\s]/g, '')
|
|
40
|
+
.replace(/\s+/g, '-')
|
|
41
|
+
.substring(0, 50);
|
|
42
|
+
|
|
43
|
+
const rawPath = `${pathPrefix}/${slug}.ts`;
|
|
44
|
+
let safePath: string;
|
|
45
|
+
try {
|
|
46
|
+
safePath = validateRelativePath(rawPath);
|
|
47
|
+
} catch {
|
|
48
|
+
safePath = rawPath.replace(/\.{2,}/g, '');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
type: changeType,
|
|
53
|
+
path: safePath,
|
|
54
|
+
description: item,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function genericToAPS(
|
|
59
|
+
document: GenericDocument,
|
|
60
|
+
context?: ParseContext,
|
|
61
|
+
originalContent?: string
|
|
62
|
+
): APSPlan {
|
|
63
|
+
const planId =
|
|
64
|
+
context?.planId ??
|
|
65
|
+
(originalContent
|
|
66
|
+
? generateDeterministicPlanId(originalContent)
|
|
67
|
+
: `aps-${Date.now().toString(16).substring(0, 8)}`);
|
|
68
|
+
|
|
69
|
+
// Collect all changes from requirements, tasks, and features
|
|
70
|
+
const changes: Change[] = [];
|
|
71
|
+
|
|
72
|
+
// Add requirements as changes
|
|
73
|
+
if (document.requirements && document.requirements.length > 0) {
|
|
74
|
+
changes.push(...document.requirements.map((req) => itemToChange(req, 'requirement')));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Add tasks as changes
|
|
78
|
+
if (document.tasks && document.tasks.length > 0) {
|
|
79
|
+
changes.push(...document.tasks.map((task) => itemToChange(task, 'task')));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Add features as changes
|
|
83
|
+
if (document.features && document.features.length > 0) {
|
|
84
|
+
changes.push(...document.features.map((feat) => itemToChange(feat, 'feature')));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Build intent from document
|
|
88
|
+
const intent =
|
|
89
|
+
document.intent || document.title || document.overview?.substring(0, 200) || 'Generic plan';
|
|
90
|
+
|
|
91
|
+
// Build provenance
|
|
92
|
+
const timestamp = context?.timestamp || new Date().toISOString();
|
|
93
|
+
const author = context?.author || 'unknown';
|
|
94
|
+
|
|
95
|
+
// Create APS plan
|
|
96
|
+
const plan: APSPlan = {
|
|
97
|
+
id: planId,
|
|
98
|
+
schema_version: '0.1.0',
|
|
99
|
+
intent,
|
|
100
|
+
proposed_changes: changes,
|
|
101
|
+
provenance: {
|
|
102
|
+
timestamp,
|
|
103
|
+
author,
|
|
104
|
+
source: 'cli',
|
|
105
|
+
version: '1.0.0',
|
|
106
|
+
repository: context?.repositoryPath,
|
|
107
|
+
branch: context?.branch,
|
|
108
|
+
commit: context?.commit,
|
|
109
|
+
},
|
|
110
|
+
validations: {
|
|
111
|
+
required_checks: ['lint', 'test'],
|
|
112
|
+
skip_checks: [],
|
|
113
|
+
},
|
|
114
|
+
metadata: {
|
|
115
|
+
source_format: 'generic-markdown',
|
|
116
|
+
title: document.title,
|
|
117
|
+
overview: document.overview,
|
|
118
|
+
goals: document.goals,
|
|
119
|
+
},
|
|
120
|
+
hash: '0000000000000000000000000000000000000000000000000000000000000000',
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
return plan;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function parseGeneric(content: string, context?: ParseContext): APSPlan {
|
|
127
|
+
const document = parseGenericDocument(content);
|
|
128
|
+
return genericToAPS(document, context, content);
|
|
129
|
+
}
|