@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,455 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* APS Markdown Format Adapter
|
|
3
|
+
*
|
|
4
|
+
* FormatAdapter implementation for Anvil Plan Spec (APS) markdown format.
|
|
5
|
+
* Handles both leaf specs (.aps.md with Tasks section) and index files
|
|
6
|
+
* (.aps.md with Modules section).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
type APSPlan,
|
|
11
|
+
type ValidationResult,
|
|
12
|
+
type Change,
|
|
13
|
+
generatePlanId,
|
|
14
|
+
generateHash,
|
|
15
|
+
APS_SCHEMA_VERSION,
|
|
16
|
+
} from '@eddacraft/anvil-core';
|
|
17
|
+
import { parseDocument, type ParsedDocument, type Task } from '@eddacraft/anvil-aps';
|
|
18
|
+
import {
|
|
19
|
+
BaseFormatAdapter,
|
|
20
|
+
type AdapterMetadata,
|
|
21
|
+
type DetectionResult,
|
|
22
|
+
type ParseResult,
|
|
23
|
+
type SerializeResult,
|
|
24
|
+
type ParseContext,
|
|
25
|
+
type AdapterOptions,
|
|
26
|
+
} from '../base/types.js';
|
|
27
|
+
import { createDetection } from '../base/utils.js';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Detection indicators for APS markdown format
|
|
31
|
+
*/
|
|
32
|
+
interface APSMarkdownIndicators {
|
|
33
|
+
/** Has H1 title */
|
|
34
|
+
hasH1Title: boolean;
|
|
35
|
+
/** Has **Scope:** or **ID:** field */
|
|
36
|
+
hasScopeField: boolean;
|
|
37
|
+
/** Has ## Tasks section */
|
|
38
|
+
hasTasksSection: boolean;
|
|
39
|
+
/** Has ## Modules section */
|
|
40
|
+
hasModulesSection: boolean;
|
|
41
|
+
/** Has SCOPE-NNN task ID pattern (e.g., AUTH-001) */
|
|
42
|
+
hasScopeTaskPattern: boolean;
|
|
43
|
+
/** Has **Intent:** field in tasks */
|
|
44
|
+
hasIntentField: boolean;
|
|
45
|
+
/** Has **Path:** with .aps.md links */
|
|
46
|
+
hasAPSModuleLinks: boolean;
|
|
47
|
+
/** Has **Confidence:** field */
|
|
48
|
+
hasConfidenceField: boolean;
|
|
49
|
+
/** Has **Owner:** field */
|
|
50
|
+
hasOwnerField: boolean;
|
|
51
|
+
/** Has **Priority:** field */
|
|
52
|
+
hasPriorityField: boolean;
|
|
53
|
+
/** Has **Packages:** field (monorepo support) */
|
|
54
|
+
hasPackagesField: boolean;
|
|
55
|
+
/** Count of SCOPE-NNN patterns found */
|
|
56
|
+
taskPatternCount: number;
|
|
57
|
+
/** Count of .aps.md links found */
|
|
58
|
+
apsLinkCount: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* APS Markdown FormatAdapter implementation
|
|
63
|
+
*
|
|
64
|
+
* Converts between APS markdown documents and APS plans.
|
|
65
|
+
*/
|
|
66
|
+
export class APSMarkdownAdapter extends BaseFormatAdapter {
|
|
67
|
+
readonly metadata: AdapterMetadata = {
|
|
68
|
+
name: 'aps-markdown',
|
|
69
|
+
version: '1.0.0',
|
|
70
|
+
displayName: 'Anvil Plan Spec Markdown',
|
|
71
|
+
description: 'APS markdown format adapter for plan specs (.aps.md)',
|
|
72
|
+
formats: ['aps', 'aps-markdown', 'aps.md'],
|
|
73
|
+
extensions: ['.aps.md'],
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Detect if content is APS markdown format
|
|
78
|
+
*
|
|
79
|
+
* Uses confidence scoring based on multiple indicators:
|
|
80
|
+
* - Tasks section with SCOPE-NNN headings (30 points)
|
|
81
|
+
* - **Intent:** field in tasks (20 points)
|
|
82
|
+
* - Modules section (25 points)
|
|
83
|
+
* - .aps.md path links (20 points)
|
|
84
|
+
* - **Scope:** field (10 points)
|
|
85
|
+
* - **Confidence:** field (10 points)
|
|
86
|
+
* - **Owner:** field (5 points)
|
|
87
|
+
* - **Priority:** field (5 points)
|
|
88
|
+
*
|
|
89
|
+
* @param content - Document content to analyze
|
|
90
|
+
* @returns Detection result with confidence score
|
|
91
|
+
*/
|
|
92
|
+
detect(content: string): DetectionResult {
|
|
93
|
+
const indicators = this.analyzeContent(content);
|
|
94
|
+
const confidence = this.calculateConfidence(indicators);
|
|
95
|
+
const reason = this.buildDetectionReason(indicators);
|
|
96
|
+
|
|
97
|
+
// Detection threshold: 50% confidence
|
|
98
|
+
return createDetection(confidence >= 50, confidence, reason);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Parse APS markdown content to APS plan
|
|
103
|
+
*
|
|
104
|
+
* Converts an APS markdown document (leaf spec) to an APSPlan execution schema.
|
|
105
|
+
* Each task in the document becomes a proposed change in the plan.
|
|
106
|
+
*
|
|
107
|
+
* @param content - APS markdown content
|
|
108
|
+
* @param context - Parse context for provenance
|
|
109
|
+
* @param _options - Adapter options
|
|
110
|
+
* @returns Parse result with APS plan
|
|
111
|
+
*/
|
|
112
|
+
async parse(
|
|
113
|
+
content: string,
|
|
114
|
+
context?: ParseContext,
|
|
115
|
+
_options?: AdapterOptions
|
|
116
|
+
): Promise<ParseResult> {
|
|
117
|
+
try {
|
|
118
|
+
const doc = await parseDocument(content, context?.repositoryPath);
|
|
119
|
+
const plan = this.convertToAPSPlan(doc, context);
|
|
120
|
+
return this.createParseSuccess(plan);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
return this.createParseError([
|
|
123
|
+
{
|
|
124
|
+
code: 'PARSE_ERROR',
|
|
125
|
+
message: error instanceof Error ? error.message : String(error),
|
|
126
|
+
},
|
|
127
|
+
]);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Convert a parsed document to an APSPlan
|
|
133
|
+
*/
|
|
134
|
+
private convertToAPSPlan(doc: ParsedDocument, context?: ParseContext): APSPlan {
|
|
135
|
+
const planId = context?.planId ?? generatePlanId();
|
|
136
|
+
const timestamp = context?.timestamp ?? new Date().toISOString();
|
|
137
|
+
|
|
138
|
+
const intent = doc.metadata?.scope ? `${doc.title} (Scope: ${doc.metadata.scope})` : doc.title;
|
|
139
|
+
|
|
140
|
+
const proposed_changes = doc.tasks.map((task) => this.taskToChange(task));
|
|
141
|
+
|
|
142
|
+
const planWithoutHash = {
|
|
143
|
+
schema_version: APS_SCHEMA_VERSION,
|
|
144
|
+
id: planId,
|
|
145
|
+
intent,
|
|
146
|
+
proposed_changes,
|
|
147
|
+
provenance: {
|
|
148
|
+
timestamp,
|
|
149
|
+
author: context?.author ?? process.env['USER'] ?? 'unknown',
|
|
150
|
+
source: 'cli' as const,
|
|
151
|
+
version: this.metadata.version,
|
|
152
|
+
repository: context?.repositoryPath ?? process.cwd(),
|
|
153
|
+
branch: context?.branch ?? 'main',
|
|
154
|
+
commit: context?.commit ?? '',
|
|
155
|
+
},
|
|
156
|
+
validations: {
|
|
157
|
+
required_checks: ['lint', 'test'],
|
|
158
|
+
skip_checks: [],
|
|
159
|
+
},
|
|
160
|
+
evidence: [],
|
|
161
|
+
executions: [],
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const hash = generateHash(planWithoutHash);
|
|
165
|
+
return { ...planWithoutHash, hash } as APSPlan;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Convert a task to a change object
|
|
170
|
+
*/
|
|
171
|
+
private taskToChange(task: Task): Change {
|
|
172
|
+
const changeType = this.inferChangeType(task);
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
type: changeType,
|
|
176
|
+
path: task.files?.[0] ?? `task/${task.id}`,
|
|
177
|
+
description: `${task.id}: ${task.title}\n\n${task.intent}`,
|
|
178
|
+
metadata: {
|
|
179
|
+
taskId: task.id,
|
|
180
|
+
confidence: task.confidence,
|
|
181
|
+
validation: task.validation,
|
|
182
|
+
expectedOutcome: task.expectedOutcome,
|
|
183
|
+
tags: task.tags,
|
|
184
|
+
files: task.files,
|
|
185
|
+
scopes: task.scopes,
|
|
186
|
+
dependencies: task.dependencies,
|
|
187
|
+
risks: task.risks,
|
|
188
|
+
packages: task.packages,
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Infer the change type from task intent
|
|
195
|
+
*/
|
|
196
|
+
private inferChangeType(task: Task): Change['type'] {
|
|
197
|
+
const intent = task.intent.toLowerCase();
|
|
198
|
+
const title = task.title.toLowerCase();
|
|
199
|
+
|
|
200
|
+
if (
|
|
201
|
+
intent.includes('create') ||
|
|
202
|
+
intent.includes('add') ||
|
|
203
|
+
title.includes('implement') ||
|
|
204
|
+
title.includes('create')
|
|
205
|
+
) {
|
|
206
|
+
return 'file_create';
|
|
207
|
+
}
|
|
208
|
+
if (
|
|
209
|
+
intent.includes('update') ||
|
|
210
|
+
intent.includes('modify') ||
|
|
211
|
+
intent.includes('fix') ||
|
|
212
|
+
title.includes('update') ||
|
|
213
|
+
title.includes('fix')
|
|
214
|
+
) {
|
|
215
|
+
return 'file_update';
|
|
216
|
+
}
|
|
217
|
+
if (
|
|
218
|
+
intent.includes('delete') ||
|
|
219
|
+
intent.includes('remove') ||
|
|
220
|
+
title.includes('delete') ||
|
|
221
|
+
title.includes('remove')
|
|
222
|
+
) {
|
|
223
|
+
return 'file_delete';
|
|
224
|
+
}
|
|
225
|
+
if (intent.includes('config') || intent.includes('setting')) {
|
|
226
|
+
return 'config_update';
|
|
227
|
+
}
|
|
228
|
+
return 'script_execute';
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Serialize APS plan to APS markdown format
|
|
233
|
+
*
|
|
234
|
+
* NOTE: Not yet implemented - returns NOT_IMPLEMENTED error.
|
|
235
|
+
*
|
|
236
|
+
* @param _plan - APS plan to serialize
|
|
237
|
+
* @param _options - Adapter options
|
|
238
|
+
* @returns Serialize result with APS markdown
|
|
239
|
+
*/
|
|
240
|
+
async serialize(_plan: APSPlan, _options?: AdapterOptions): Promise<SerializeResult> {
|
|
241
|
+
return this.createSerializeError([
|
|
242
|
+
{
|
|
243
|
+
code: 'NOT_IMPLEMENTED',
|
|
244
|
+
message: 'APSMarkdownAdapter.serialize() is not yet implemented',
|
|
245
|
+
},
|
|
246
|
+
]);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Validate APS markdown content
|
|
251
|
+
*
|
|
252
|
+
* NOTE: Not yet implemented - returns NOT_IMPLEMENTED error.
|
|
253
|
+
*
|
|
254
|
+
* @param _content - APS markdown content to validate
|
|
255
|
+
* @param _options - Validation options
|
|
256
|
+
* @returns Validation result
|
|
257
|
+
*/
|
|
258
|
+
async validate(_content: string, _options?: AdapterOptions): Promise<ValidationResult> {
|
|
259
|
+
return {
|
|
260
|
+
valid: false,
|
|
261
|
+
issues: [
|
|
262
|
+
{
|
|
263
|
+
code: 'NOT_IMPLEMENTED',
|
|
264
|
+
path: '',
|
|
265
|
+
message: 'APSMarkdownAdapter.validate() is not yet implemented',
|
|
266
|
+
severity: 'error',
|
|
267
|
+
},
|
|
268
|
+
],
|
|
269
|
+
summary: 'Validation not yet implemented',
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Override canImport to handle the compound extension .aps.md
|
|
275
|
+
*/
|
|
276
|
+
override canImport(format: string): boolean {
|
|
277
|
+
const normalized = format.toLowerCase().replace(/^\./, '');
|
|
278
|
+
return (
|
|
279
|
+
this.metadata.formats.includes(normalized) ||
|
|
280
|
+
this.metadata.extensions.some((ext) => ext.replace(/^\./, '') === normalized) ||
|
|
281
|
+
normalized === 'aps.md'
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Analyze content for APS markdown indicators
|
|
287
|
+
*/
|
|
288
|
+
private analyzeContent(content: string): APSMarkdownIndicators {
|
|
289
|
+
// Check for H1 title
|
|
290
|
+
const hasH1Title = /^#\s+.+$/m.test(content);
|
|
291
|
+
|
|
292
|
+
// Check for **Scope:** or **ID:** field (ID is the current spec, Scope is legacy)
|
|
293
|
+
const hasScopeField =
|
|
294
|
+
/\*\*Scope:\*\*\s*\S+/i.test(content) || /\*\*ID:\*\*\s*\S+/i.test(content);
|
|
295
|
+
|
|
296
|
+
// Check for ## Tasks section
|
|
297
|
+
const hasTasksSection = /^##\s+Tasks\s*$/im.test(content);
|
|
298
|
+
|
|
299
|
+
// Check for ## Modules section
|
|
300
|
+
const hasModulesSection = /^##\s+Modules\s*$/im.test(content);
|
|
301
|
+
|
|
302
|
+
// Check for SCOPE-NNN pattern in ### headings (e.g., ### AUTH-001: Description)
|
|
303
|
+
const scopeTaskPattern = /^###\s+[A-Z]{2,10}-\d{3}:/gm;
|
|
304
|
+
const taskPatternMatches = content.match(scopeTaskPattern) || [];
|
|
305
|
+
const hasScopeTaskPattern = taskPatternMatches.length > 0;
|
|
306
|
+
const taskPatternCount = taskPatternMatches.length;
|
|
307
|
+
|
|
308
|
+
// Check for **Intent:** field
|
|
309
|
+
const hasIntentField = /\*\*Intent:\*\*/i.test(content);
|
|
310
|
+
|
|
311
|
+
// Check for **Path:** with .aps.md links
|
|
312
|
+
const apsLinkPattern = /\*\*Path:\*\*\s*\[.*?\]\([^)]*\.aps\.md\)/gi;
|
|
313
|
+
const apsLinkMatches = content.match(apsLinkPattern) || [];
|
|
314
|
+
const hasAPSModuleLinks = apsLinkMatches.length > 0;
|
|
315
|
+
const apsLinkCount = apsLinkMatches.length;
|
|
316
|
+
|
|
317
|
+
// Check for **Confidence:** field
|
|
318
|
+
const hasConfidenceField = /\*\*Confidence:\*\*/i.test(content);
|
|
319
|
+
|
|
320
|
+
// Check for **Owner:** field
|
|
321
|
+
const hasOwnerField = /\*\*Owner:\*\*/i.test(content);
|
|
322
|
+
|
|
323
|
+
// Check for **Priority:** field
|
|
324
|
+
const hasPriorityField = /\*\*Priority:\*\*/i.test(content);
|
|
325
|
+
|
|
326
|
+
// Check for **Packages:** field (monorepo support)
|
|
327
|
+
const hasPackagesField = /\*\*Packages:\*\*/i.test(content);
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
hasH1Title,
|
|
331
|
+
hasScopeField,
|
|
332
|
+
hasTasksSection,
|
|
333
|
+
hasModulesSection,
|
|
334
|
+
hasScopeTaskPattern,
|
|
335
|
+
hasIntentField,
|
|
336
|
+
hasAPSModuleLinks,
|
|
337
|
+
hasConfidenceField,
|
|
338
|
+
hasOwnerField,
|
|
339
|
+
hasPriorityField,
|
|
340
|
+
hasPackagesField,
|
|
341
|
+
taskPatternCount,
|
|
342
|
+
apsLinkCount,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Calculate confidence score based on indicators
|
|
348
|
+
*/
|
|
349
|
+
private calculateConfidence(indicators: APSMarkdownIndicators): number {
|
|
350
|
+
let score = 0;
|
|
351
|
+
|
|
352
|
+
// Leaf spec indicators (Tasks section path)
|
|
353
|
+
if (indicators.hasTasksSection) {
|
|
354
|
+
score += 15; // Has ## Tasks section
|
|
355
|
+
|
|
356
|
+
if (indicators.hasScopeTaskPattern) {
|
|
357
|
+
score += 30; // Has SCOPE-NNN task IDs
|
|
358
|
+
// Bonus for multiple tasks
|
|
359
|
+
if (indicators.taskPatternCount > 1) {
|
|
360
|
+
score += 5;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (indicators.hasIntentField) {
|
|
365
|
+
score += 20; // Has **Intent:** in tasks
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Index file indicators (Modules section path)
|
|
370
|
+
if (indicators.hasModulesSection) {
|
|
371
|
+
score += 20; // Has ## Modules section
|
|
372
|
+
|
|
373
|
+
if (indicators.hasAPSModuleLinks) {
|
|
374
|
+
score += 25; // Has .aps.md path links
|
|
375
|
+
// Bonus for multiple modules
|
|
376
|
+
if (indicators.apsLinkCount > 1) {
|
|
377
|
+
score += 5;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Common APS metadata fields
|
|
383
|
+
if (indicators.hasScopeField) {
|
|
384
|
+
score += 10; // Has **Scope:** field
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (indicators.hasConfidenceField) {
|
|
388
|
+
score += 10; // Has **Confidence:** field
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (indicators.hasOwnerField) {
|
|
392
|
+
score += 5; // Has **Owner:** field
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (indicators.hasPriorityField) {
|
|
396
|
+
score += 5; // Has **Priority:** field
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (indicators.hasPackagesField) {
|
|
400
|
+
score += 5; // Has **Packages:** field (monorepo support)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return Math.min(100, score);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Build detection reason message
|
|
408
|
+
*/
|
|
409
|
+
private buildDetectionReason(indicators: APSMarkdownIndicators): string {
|
|
410
|
+
const reasons: string[] = [];
|
|
411
|
+
|
|
412
|
+
if (indicators.hasTasksSection) {
|
|
413
|
+
reasons.push('tasks-section');
|
|
414
|
+
}
|
|
415
|
+
if (indicators.hasScopeTaskPattern) {
|
|
416
|
+
reasons.push(`scope-tasks(${indicators.taskPatternCount})`);
|
|
417
|
+
}
|
|
418
|
+
if (indicators.hasIntentField) {
|
|
419
|
+
reasons.push('intent-field');
|
|
420
|
+
}
|
|
421
|
+
if (indicators.hasModulesSection) {
|
|
422
|
+
reasons.push('modules-section');
|
|
423
|
+
}
|
|
424
|
+
if (indicators.hasAPSModuleLinks) {
|
|
425
|
+
reasons.push(`aps-links(${indicators.apsLinkCount})`);
|
|
426
|
+
}
|
|
427
|
+
if (indicators.hasScopeField) {
|
|
428
|
+
reasons.push('scope-field');
|
|
429
|
+
}
|
|
430
|
+
if (indicators.hasConfidenceField) {
|
|
431
|
+
reasons.push('confidence-field');
|
|
432
|
+
}
|
|
433
|
+
if (indicators.hasOwnerField) {
|
|
434
|
+
reasons.push('owner-field');
|
|
435
|
+
}
|
|
436
|
+
if (indicators.hasPriorityField) {
|
|
437
|
+
reasons.push('priority-field');
|
|
438
|
+
}
|
|
439
|
+
if (indicators.hasPackagesField) {
|
|
440
|
+
reasons.push('packages-field');
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return reasons.length > 0 ? reasons.join(', ') : 'no APS indicators';
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Create a new APS markdown adapter instance
|
|
449
|
+
*
|
|
450
|
+
* @param options - Adapter options
|
|
451
|
+
* @returns APS markdown adapter instance
|
|
452
|
+
*/
|
|
453
|
+
export function createAPSMarkdownAdapter(options?: AdapterOptions): APSMarkdownAdapter {
|
|
454
|
+
return new APSMarkdownAdapter(options);
|
|
455
|
+
}
|