@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,351 @@
|
|
|
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
|
+
import { generatePlanId, generateHash, APS_SCHEMA_VERSION, } from '@eddacraft/anvil-core';
|
|
9
|
+
import { parseDocument } from '@eddacraft/anvil-aps';
|
|
10
|
+
import { BaseFormatAdapter, } from '../base/types.js';
|
|
11
|
+
import { createDetection } from '../base/utils.js';
|
|
12
|
+
/**
|
|
13
|
+
* APS Markdown FormatAdapter implementation
|
|
14
|
+
*
|
|
15
|
+
* Converts between APS markdown documents and APS plans.
|
|
16
|
+
*/
|
|
17
|
+
export class APSMarkdownAdapter extends BaseFormatAdapter {
|
|
18
|
+
metadata = {
|
|
19
|
+
name: 'aps-markdown',
|
|
20
|
+
version: '1.0.0',
|
|
21
|
+
displayName: 'Anvil Plan Spec Markdown',
|
|
22
|
+
description: 'APS markdown format adapter for plan specs (.aps.md)',
|
|
23
|
+
formats: ['aps', 'aps-markdown', 'aps.md'],
|
|
24
|
+
extensions: ['.aps.md'],
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Detect if content is APS markdown format
|
|
28
|
+
*
|
|
29
|
+
* Uses confidence scoring based on multiple indicators:
|
|
30
|
+
* - Tasks section with SCOPE-NNN headings (30 points)
|
|
31
|
+
* - **Intent:** field in tasks (20 points)
|
|
32
|
+
* - Modules section (25 points)
|
|
33
|
+
* - .aps.md path links (20 points)
|
|
34
|
+
* - **Scope:** field (10 points)
|
|
35
|
+
* - **Confidence:** field (10 points)
|
|
36
|
+
* - **Owner:** field (5 points)
|
|
37
|
+
* - **Priority:** field (5 points)
|
|
38
|
+
*
|
|
39
|
+
* @param content - Document content to analyze
|
|
40
|
+
* @returns Detection result with confidence score
|
|
41
|
+
*/
|
|
42
|
+
detect(content) {
|
|
43
|
+
const indicators = this.analyzeContent(content);
|
|
44
|
+
const confidence = this.calculateConfidence(indicators);
|
|
45
|
+
const reason = this.buildDetectionReason(indicators);
|
|
46
|
+
// Detection threshold: 50% confidence
|
|
47
|
+
return createDetection(confidence >= 50, confidence, reason);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Parse APS markdown content to APS plan
|
|
51
|
+
*
|
|
52
|
+
* Converts an APS markdown document (leaf spec) to an APSPlan execution schema.
|
|
53
|
+
* Each task in the document becomes a proposed change in the plan.
|
|
54
|
+
*
|
|
55
|
+
* @param content - APS markdown content
|
|
56
|
+
* @param context - Parse context for provenance
|
|
57
|
+
* @param _options - Adapter options
|
|
58
|
+
* @returns Parse result with APS plan
|
|
59
|
+
*/
|
|
60
|
+
async parse(content, context, _options) {
|
|
61
|
+
try {
|
|
62
|
+
const doc = await parseDocument(content, context?.repositoryPath);
|
|
63
|
+
const plan = this.convertToAPSPlan(doc, context);
|
|
64
|
+
return this.createParseSuccess(plan);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
return this.createParseError([
|
|
68
|
+
{
|
|
69
|
+
code: 'PARSE_ERROR',
|
|
70
|
+
message: error instanceof Error ? error.message : String(error),
|
|
71
|
+
},
|
|
72
|
+
]);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Convert a parsed document to an APSPlan
|
|
77
|
+
*/
|
|
78
|
+
convertToAPSPlan(doc, context) {
|
|
79
|
+
const planId = context?.planId ?? generatePlanId();
|
|
80
|
+
const timestamp = context?.timestamp ?? new Date().toISOString();
|
|
81
|
+
const intent = doc.metadata?.scope ? `${doc.title} (Scope: ${doc.metadata.scope})` : doc.title;
|
|
82
|
+
const proposed_changes = doc.tasks.map((task) => this.taskToChange(task));
|
|
83
|
+
const planWithoutHash = {
|
|
84
|
+
schema_version: APS_SCHEMA_VERSION,
|
|
85
|
+
id: planId,
|
|
86
|
+
intent,
|
|
87
|
+
proposed_changes,
|
|
88
|
+
provenance: {
|
|
89
|
+
timestamp,
|
|
90
|
+
author: context?.author ?? process.env['USER'] ?? 'unknown',
|
|
91
|
+
source: 'cli',
|
|
92
|
+
version: this.metadata.version,
|
|
93
|
+
repository: context?.repositoryPath ?? process.cwd(),
|
|
94
|
+
branch: context?.branch ?? 'main',
|
|
95
|
+
commit: context?.commit ?? '',
|
|
96
|
+
},
|
|
97
|
+
validations: {
|
|
98
|
+
required_checks: ['lint', 'test'],
|
|
99
|
+
skip_checks: [],
|
|
100
|
+
},
|
|
101
|
+
evidence: [],
|
|
102
|
+
executions: [],
|
|
103
|
+
};
|
|
104
|
+
const hash = generateHash(planWithoutHash);
|
|
105
|
+
return { ...planWithoutHash, hash };
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Convert a task to a change object
|
|
109
|
+
*/
|
|
110
|
+
taskToChange(task) {
|
|
111
|
+
const changeType = this.inferChangeType(task);
|
|
112
|
+
return {
|
|
113
|
+
type: changeType,
|
|
114
|
+
path: task.files?.[0] ?? `task/${task.id}`,
|
|
115
|
+
description: `${task.id}: ${task.title}\n\n${task.intent}`,
|
|
116
|
+
metadata: {
|
|
117
|
+
taskId: task.id,
|
|
118
|
+
confidence: task.confidence,
|
|
119
|
+
validation: task.validation,
|
|
120
|
+
expectedOutcome: task.expectedOutcome,
|
|
121
|
+
tags: task.tags,
|
|
122
|
+
files: task.files,
|
|
123
|
+
scopes: task.scopes,
|
|
124
|
+
dependencies: task.dependencies,
|
|
125
|
+
risks: task.risks,
|
|
126
|
+
packages: task.packages,
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Infer the change type from task intent
|
|
132
|
+
*/
|
|
133
|
+
inferChangeType(task) {
|
|
134
|
+
const intent = task.intent.toLowerCase();
|
|
135
|
+
const title = task.title.toLowerCase();
|
|
136
|
+
if (intent.includes('create') ||
|
|
137
|
+
intent.includes('add') ||
|
|
138
|
+
title.includes('implement') ||
|
|
139
|
+
title.includes('create')) {
|
|
140
|
+
return 'file_create';
|
|
141
|
+
}
|
|
142
|
+
if (intent.includes('update') ||
|
|
143
|
+
intent.includes('modify') ||
|
|
144
|
+
intent.includes('fix') ||
|
|
145
|
+
title.includes('update') ||
|
|
146
|
+
title.includes('fix')) {
|
|
147
|
+
return 'file_update';
|
|
148
|
+
}
|
|
149
|
+
if (intent.includes('delete') ||
|
|
150
|
+
intent.includes('remove') ||
|
|
151
|
+
title.includes('delete') ||
|
|
152
|
+
title.includes('remove')) {
|
|
153
|
+
return 'file_delete';
|
|
154
|
+
}
|
|
155
|
+
if (intent.includes('config') || intent.includes('setting')) {
|
|
156
|
+
return 'config_update';
|
|
157
|
+
}
|
|
158
|
+
return 'script_execute';
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Serialize APS plan to APS markdown format
|
|
162
|
+
*
|
|
163
|
+
* NOTE: Not yet implemented - returns NOT_IMPLEMENTED error.
|
|
164
|
+
*
|
|
165
|
+
* @param _plan - APS plan to serialize
|
|
166
|
+
* @param _options - Adapter options
|
|
167
|
+
* @returns Serialize result with APS markdown
|
|
168
|
+
*/
|
|
169
|
+
async serialize(_plan, _options) {
|
|
170
|
+
return this.createSerializeError([
|
|
171
|
+
{
|
|
172
|
+
code: 'NOT_IMPLEMENTED',
|
|
173
|
+
message: 'APSMarkdownAdapter.serialize() is not yet implemented',
|
|
174
|
+
},
|
|
175
|
+
]);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Validate APS markdown content
|
|
179
|
+
*
|
|
180
|
+
* NOTE: Not yet implemented - returns NOT_IMPLEMENTED error.
|
|
181
|
+
*
|
|
182
|
+
* @param _content - APS markdown content to validate
|
|
183
|
+
* @param _options - Validation options
|
|
184
|
+
* @returns Validation result
|
|
185
|
+
*/
|
|
186
|
+
async validate(_content, _options) {
|
|
187
|
+
return {
|
|
188
|
+
valid: false,
|
|
189
|
+
issues: [
|
|
190
|
+
{
|
|
191
|
+
code: 'NOT_IMPLEMENTED',
|
|
192
|
+
path: '',
|
|
193
|
+
message: 'APSMarkdownAdapter.validate() is not yet implemented',
|
|
194
|
+
severity: 'error',
|
|
195
|
+
},
|
|
196
|
+
],
|
|
197
|
+
summary: 'Validation not yet implemented',
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Override canImport to handle the compound extension .aps.md
|
|
202
|
+
*/
|
|
203
|
+
canImport(format) {
|
|
204
|
+
const normalized = format.toLowerCase().replace(/^\./, '');
|
|
205
|
+
return (this.metadata.formats.includes(normalized) ||
|
|
206
|
+
this.metadata.extensions.some((ext) => ext.replace(/^\./, '') === normalized) ||
|
|
207
|
+
normalized === 'aps.md');
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Analyze content for APS markdown indicators
|
|
211
|
+
*/
|
|
212
|
+
analyzeContent(content) {
|
|
213
|
+
// Check for H1 title
|
|
214
|
+
const hasH1Title = /^#\s+.+$/m.test(content);
|
|
215
|
+
// Check for **Scope:** or **ID:** field (ID is the current spec, Scope is legacy)
|
|
216
|
+
const hasScopeField = /\*\*Scope:\*\*\s*\S+/i.test(content) || /\*\*ID:\*\*\s*\S+/i.test(content);
|
|
217
|
+
// Check for ## Tasks section
|
|
218
|
+
const hasTasksSection = /^##\s+Tasks\s*$/im.test(content);
|
|
219
|
+
// Check for ## Modules section
|
|
220
|
+
const hasModulesSection = /^##\s+Modules\s*$/im.test(content);
|
|
221
|
+
// Check for SCOPE-NNN pattern in ### headings (e.g., ### AUTH-001: Description)
|
|
222
|
+
const scopeTaskPattern = /^###\s+[A-Z]{2,10}-\d{3}:/gm;
|
|
223
|
+
const taskPatternMatches = content.match(scopeTaskPattern) || [];
|
|
224
|
+
const hasScopeTaskPattern = taskPatternMatches.length > 0;
|
|
225
|
+
const taskPatternCount = taskPatternMatches.length;
|
|
226
|
+
// Check for **Intent:** field
|
|
227
|
+
const hasIntentField = /\*\*Intent:\*\*/i.test(content);
|
|
228
|
+
// Check for **Path:** with .aps.md links
|
|
229
|
+
const apsLinkPattern = /\*\*Path:\*\*\s*\[.*?\]\([^)]*\.aps\.md\)/gi;
|
|
230
|
+
const apsLinkMatches = content.match(apsLinkPattern) || [];
|
|
231
|
+
const hasAPSModuleLinks = apsLinkMatches.length > 0;
|
|
232
|
+
const apsLinkCount = apsLinkMatches.length;
|
|
233
|
+
// Check for **Confidence:** field
|
|
234
|
+
const hasConfidenceField = /\*\*Confidence:\*\*/i.test(content);
|
|
235
|
+
// Check for **Owner:** field
|
|
236
|
+
const hasOwnerField = /\*\*Owner:\*\*/i.test(content);
|
|
237
|
+
// Check for **Priority:** field
|
|
238
|
+
const hasPriorityField = /\*\*Priority:\*\*/i.test(content);
|
|
239
|
+
// Check for **Packages:** field (monorepo support)
|
|
240
|
+
const hasPackagesField = /\*\*Packages:\*\*/i.test(content);
|
|
241
|
+
return {
|
|
242
|
+
hasH1Title,
|
|
243
|
+
hasScopeField,
|
|
244
|
+
hasTasksSection,
|
|
245
|
+
hasModulesSection,
|
|
246
|
+
hasScopeTaskPattern,
|
|
247
|
+
hasIntentField,
|
|
248
|
+
hasAPSModuleLinks,
|
|
249
|
+
hasConfidenceField,
|
|
250
|
+
hasOwnerField,
|
|
251
|
+
hasPriorityField,
|
|
252
|
+
hasPackagesField,
|
|
253
|
+
taskPatternCount,
|
|
254
|
+
apsLinkCount,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Calculate confidence score based on indicators
|
|
259
|
+
*/
|
|
260
|
+
calculateConfidence(indicators) {
|
|
261
|
+
let score = 0;
|
|
262
|
+
// Leaf spec indicators (Tasks section path)
|
|
263
|
+
if (indicators.hasTasksSection) {
|
|
264
|
+
score += 15; // Has ## Tasks section
|
|
265
|
+
if (indicators.hasScopeTaskPattern) {
|
|
266
|
+
score += 30; // Has SCOPE-NNN task IDs
|
|
267
|
+
// Bonus for multiple tasks
|
|
268
|
+
if (indicators.taskPatternCount > 1) {
|
|
269
|
+
score += 5;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (indicators.hasIntentField) {
|
|
273
|
+
score += 20; // Has **Intent:** in tasks
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// Index file indicators (Modules section path)
|
|
277
|
+
if (indicators.hasModulesSection) {
|
|
278
|
+
score += 20; // Has ## Modules section
|
|
279
|
+
if (indicators.hasAPSModuleLinks) {
|
|
280
|
+
score += 25; // Has .aps.md path links
|
|
281
|
+
// Bonus for multiple modules
|
|
282
|
+
if (indicators.apsLinkCount > 1) {
|
|
283
|
+
score += 5;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
// Common APS metadata fields
|
|
288
|
+
if (indicators.hasScopeField) {
|
|
289
|
+
score += 10; // Has **Scope:** field
|
|
290
|
+
}
|
|
291
|
+
if (indicators.hasConfidenceField) {
|
|
292
|
+
score += 10; // Has **Confidence:** field
|
|
293
|
+
}
|
|
294
|
+
if (indicators.hasOwnerField) {
|
|
295
|
+
score += 5; // Has **Owner:** field
|
|
296
|
+
}
|
|
297
|
+
if (indicators.hasPriorityField) {
|
|
298
|
+
score += 5; // Has **Priority:** field
|
|
299
|
+
}
|
|
300
|
+
if (indicators.hasPackagesField) {
|
|
301
|
+
score += 5; // Has **Packages:** field (monorepo support)
|
|
302
|
+
}
|
|
303
|
+
return Math.min(100, score);
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Build detection reason message
|
|
307
|
+
*/
|
|
308
|
+
buildDetectionReason(indicators) {
|
|
309
|
+
const reasons = [];
|
|
310
|
+
if (indicators.hasTasksSection) {
|
|
311
|
+
reasons.push('tasks-section');
|
|
312
|
+
}
|
|
313
|
+
if (indicators.hasScopeTaskPattern) {
|
|
314
|
+
reasons.push(`scope-tasks(${indicators.taskPatternCount})`);
|
|
315
|
+
}
|
|
316
|
+
if (indicators.hasIntentField) {
|
|
317
|
+
reasons.push('intent-field');
|
|
318
|
+
}
|
|
319
|
+
if (indicators.hasModulesSection) {
|
|
320
|
+
reasons.push('modules-section');
|
|
321
|
+
}
|
|
322
|
+
if (indicators.hasAPSModuleLinks) {
|
|
323
|
+
reasons.push(`aps-links(${indicators.apsLinkCount})`);
|
|
324
|
+
}
|
|
325
|
+
if (indicators.hasScopeField) {
|
|
326
|
+
reasons.push('scope-field');
|
|
327
|
+
}
|
|
328
|
+
if (indicators.hasConfidenceField) {
|
|
329
|
+
reasons.push('confidence-field');
|
|
330
|
+
}
|
|
331
|
+
if (indicators.hasOwnerField) {
|
|
332
|
+
reasons.push('owner-field');
|
|
333
|
+
}
|
|
334
|
+
if (indicators.hasPriorityField) {
|
|
335
|
+
reasons.push('priority-field');
|
|
336
|
+
}
|
|
337
|
+
if (indicators.hasPackagesField) {
|
|
338
|
+
reasons.push('packages-field');
|
|
339
|
+
}
|
|
340
|
+
return reasons.length > 0 ? reasons.join(', ') : 'no APS indicators';
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Create a new APS markdown adapter instance
|
|
345
|
+
*
|
|
346
|
+
* @param options - Adapter options
|
|
347
|
+
* @returns APS markdown adapter instance
|
|
348
|
+
*/
|
|
349
|
+
export function createAPSMarkdownAdapter(options) {
|
|
350
|
+
return new APSMarkdownAdapter(options);
|
|
351
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/aps-markdown/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Discovery Utility
|
|
3
|
+
*
|
|
4
|
+
* Discovers planning documents in repositories by searching for common patterns.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Discovered planning file
|
|
8
|
+
*/
|
|
9
|
+
export interface DiscoveredFile {
|
|
10
|
+
/** Full path to file */
|
|
11
|
+
path: string;
|
|
12
|
+
/** File name */
|
|
13
|
+
name: string;
|
|
14
|
+
/** File size in bytes */
|
|
15
|
+
size: number;
|
|
16
|
+
/** Last modified timestamp */
|
|
17
|
+
modified: Date;
|
|
18
|
+
/** Confidence score that this is a planning document (0-100) */
|
|
19
|
+
confidence: number;
|
|
20
|
+
/** Reason for detection */
|
|
21
|
+
reason: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Search options
|
|
25
|
+
*/
|
|
26
|
+
export interface SearchOptions {
|
|
27
|
+
/** Root directory to search from */
|
|
28
|
+
rootPath: string;
|
|
29
|
+
/** Maximum depth to search */
|
|
30
|
+
maxDepth?: number;
|
|
31
|
+
/** Directories to exclude */
|
|
32
|
+
excludeDirs?: string[];
|
|
33
|
+
/** File patterns to search for */
|
|
34
|
+
patterns?: string[];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Discover planning documents in a directory
|
|
38
|
+
*
|
|
39
|
+
* Searches for common planning document patterns like prd.md, plan.md, todo.md, etc.
|
|
40
|
+
*
|
|
41
|
+
* @param options - Search options
|
|
42
|
+
* @returns Array of discovered files, sorted by confidence
|
|
43
|
+
*/
|
|
44
|
+
export declare function discoverPlanningFiles(options: SearchOptions): Promise<DiscoveredFile[]>;
|
|
45
|
+
/**
|
|
46
|
+
* Find the most likely planning document
|
|
47
|
+
*
|
|
48
|
+
* Returns the single most likely planning document based on confidence and recency.
|
|
49
|
+
*
|
|
50
|
+
* @param options - Search options
|
|
51
|
+
* @returns The most likely planning document, or undefined if none found
|
|
52
|
+
*/
|
|
53
|
+
export declare function findBestPlanningFile(options: SearchOptions): Promise<DiscoveredFile | undefined>;
|
|
54
|
+
/**
|
|
55
|
+
* Group discovered files by name pattern
|
|
56
|
+
*
|
|
57
|
+
* Groups files like "prd.md", "plan.md" etc. for easier selection.
|
|
58
|
+
*
|
|
59
|
+
* @param files - Discovered files
|
|
60
|
+
* @returns Map of pattern to files
|
|
61
|
+
*/
|
|
62
|
+
export declare function groupFilesByPattern(files: DiscoveredFile[]): Map<string, DiscoveredFile[]>;
|
|
63
|
+
//# sourceMappingURL=file-discovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-discovery.d.ts","sourceRoot":"","sources":["../../src/base/file-discovery.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,8BAA8B;IAC9B,QAAQ,EAAE,IAAI,CAAC;IACf,gEAAgE;IAChE,UAAU,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,8BAA8B;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6BAA6B;IAC7B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,kCAAkC;IAClC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAiMD;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAiB7F;AAED;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAGrC;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,cAAc,EAAE,CAAC,CAsB1F"}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Discovery Utility
|
|
3
|
+
*
|
|
4
|
+
* Discovers planning documents in repositories by searching for common patterns.
|
|
5
|
+
*/
|
|
6
|
+
import { readdir, stat } from 'node:fs/promises';
|
|
7
|
+
import { join, basename } from 'node:path';
|
|
8
|
+
/**
|
|
9
|
+
* Maximum file size to consider (2MB)
|
|
10
|
+
*/
|
|
11
|
+
const MAX_FILE_SIZE_BYTES = 2 * 1024 * 1024;
|
|
12
|
+
/**
|
|
13
|
+
* Maximum directory depth for recursive search
|
|
14
|
+
*/
|
|
15
|
+
const MAX_DEPTH = 20;
|
|
16
|
+
/**
|
|
17
|
+
* Default directories to exclude
|
|
18
|
+
*/
|
|
19
|
+
const DEFAULT_EXCLUDE_DIRS = [
|
|
20
|
+
'node_modules',
|
|
21
|
+
'.git',
|
|
22
|
+
'dist',
|
|
23
|
+
'build',
|
|
24
|
+
'coverage',
|
|
25
|
+
'.next',
|
|
26
|
+
'.nuxt',
|
|
27
|
+
'out',
|
|
28
|
+
'target',
|
|
29
|
+
'vendor',
|
|
30
|
+
];
|
|
31
|
+
/**
|
|
32
|
+
* Default file patterns for planning documents
|
|
33
|
+
*/
|
|
34
|
+
const DEFAULT_PATTERNS = [
|
|
35
|
+
'prd',
|
|
36
|
+
'plan',
|
|
37
|
+
'todo',
|
|
38
|
+
'tasks',
|
|
39
|
+
'spec',
|
|
40
|
+
'requirements',
|
|
41
|
+
'rfc',
|
|
42
|
+
'adr',
|
|
43
|
+
'design',
|
|
44
|
+
'proposal',
|
|
45
|
+
'roadmap',
|
|
46
|
+
];
|
|
47
|
+
/**
|
|
48
|
+
* Calculate confidence score for a file name
|
|
49
|
+
*/
|
|
50
|
+
function calculateFileConfidence(filename) {
|
|
51
|
+
const lower = filename.toLowerCase();
|
|
52
|
+
let confidence = 0;
|
|
53
|
+
const reasons = [];
|
|
54
|
+
// Exact matches (high confidence)
|
|
55
|
+
const exactMatches = ['prd.md', 'plan.md', 'todo.md', 'tasks.md', 'spec.md', 'requirements.md'];
|
|
56
|
+
if (exactMatches.some((pattern) => lower === pattern)) {
|
|
57
|
+
confidence += 80;
|
|
58
|
+
reasons.push('exact-match');
|
|
59
|
+
}
|
|
60
|
+
// Pattern matches (medium-high confidence)
|
|
61
|
+
if (lower.includes('prd')) {
|
|
62
|
+
confidence += 60;
|
|
63
|
+
reasons.push('prd-pattern');
|
|
64
|
+
}
|
|
65
|
+
if (lower.includes('plan')) {
|
|
66
|
+
confidence += 55;
|
|
67
|
+
reasons.push('plan-pattern');
|
|
68
|
+
}
|
|
69
|
+
if (lower.includes('todo')) {
|
|
70
|
+
confidence += 50;
|
|
71
|
+
reasons.push('todo-pattern');
|
|
72
|
+
}
|
|
73
|
+
if (lower.includes('spec')) {
|
|
74
|
+
confidence += 50;
|
|
75
|
+
reasons.push('spec-pattern');
|
|
76
|
+
}
|
|
77
|
+
if (lower.includes('requirements')) {
|
|
78
|
+
confidence += 55;
|
|
79
|
+
reasons.push('requirements-pattern');
|
|
80
|
+
}
|
|
81
|
+
if (lower.includes('task')) {
|
|
82
|
+
confidence += 45;
|
|
83
|
+
reasons.push('task-pattern');
|
|
84
|
+
}
|
|
85
|
+
if (lower.includes('rfc')) {
|
|
86
|
+
confidence += 50;
|
|
87
|
+
reasons.push('rfc-pattern');
|
|
88
|
+
}
|
|
89
|
+
if (lower.includes('adr')) {
|
|
90
|
+
confidence += 50;
|
|
91
|
+
reasons.push('adr-pattern');
|
|
92
|
+
}
|
|
93
|
+
if (lower.includes('design')) {
|
|
94
|
+
confidence += 40;
|
|
95
|
+
reasons.push('design-pattern');
|
|
96
|
+
}
|
|
97
|
+
if (lower.includes('proposal')) {
|
|
98
|
+
confidence += 45;
|
|
99
|
+
reasons.push('proposal-pattern');
|
|
100
|
+
}
|
|
101
|
+
// Check for common planning directories
|
|
102
|
+
if (lower.includes('docs/') || lower.includes('/docs/')) {
|
|
103
|
+
confidence += 10;
|
|
104
|
+
reasons.push('in-docs-dir');
|
|
105
|
+
}
|
|
106
|
+
if (lower.includes('.anvil/') || lower.includes('/.anvil/')) {
|
|
107
|
+
confidence += 15;
|
|
108
|
+
reasons.push('in-anvil-dir');
|
|
109
|
+
}
|
|
110
|
+
// Markdown extension
|
|
111
|
+
if (lower.endsWith('.md') || lower.endsWith('.markdown')) {
|
|
112
|
+
confidence += 5;
|
|
113
|
+
reasons.push('markdown');
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
confidence: Math.min(100, confidence),
|
|
117
|
+
reason: reasons.join(', ') || 'no-match',
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Search directory recursively for planning files
|
|
122
|
+
*/
|
|
123
|
+
async function searchDirectory(dirPath, options, currentDepth = 0, results = []) {
|
|
124
|
+
// Stop if max depth reached (clamped to safety limit)
|
|
125
|
+
const effectiveMaxDepth = Math.min(options.maxDepth, MAX_DEPTH);
|
|
126
|
+
if (currentDepth > effectiveMaxDepth) {
|
|
127
|
+
return results;
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
const entries = await readdir(dirPath, { withFileTypes: true });
|
|
131
|
+
for (const entry of entries) {
|
|
132
|
+
const fullPath = join(dirPath, entry.name);
|
|
133
|
+
if (entry.isDirectory()) {
|
|
134
|
+
// Skip excluded directories
|
|
135
|
+
if (options.excludeDirs.includes(entry.name)) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
// Recurse into directory
|
|
139
|
+
await searchDirectory(fullPath, options, currentDepth + 1, results);
|
|
140
|
+
}
|
|
141
|
+
else if (entry.isFile()) {
|
|
142
|
+
// Check if file matches patterns
|
|
143
|
+
const lower = entry.name.toLowerCase();
|
|
144
|
+
const matchesPattern = options.patterns.some((pattern) => lower.includes(pattern));
|
|
145
|
+
if (matchesPattern && (lower.endsWith('.md') || lower.endsWith('.markdown'))) {
|
|
146
|
+
try {
|
|
147
|
+
const stats = await stat(fullPath);
|
|
148
|
+
// Skip files exceeding size limit
|
|
149
|
+
if (stats.size > MAX_FILE_SIZE_BYTES) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const { confidence, reason } = calculateFileConfidence(fullPath);
|
|
153
|
+
if (confidence >= 40) {
|
|
154
|
+
// Threshold for inclusion
|
|
155
|
+
results.push({
|
|
156
|
+
path: fullPath,
|
|
157
|
+
name: entry.name,
|
|
158
|
+
size: stats.size,
|
|
159
|
+
modified: stats.mtime,
|
|
160
|
+
confidence,
|
|
161
|
+
reason,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch (statError) {
|
|
166
|
+
console.error(`[FileDiscovery] Failed to stat file ${fullPath}:`, statError);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch (readError) {
|
|
173
|
+
console.error(`[FileDiscovery] Failed to read directory ${dirPath}:`, readError);
|
|
174
|
+
}
|
|
175
|
+
return results;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Discover planning documents in a directory
|
|
179
|
+
*
|
|
180
|
+
* Searches for common planning document patterns like prd.md, plan.md, todo.md, etc.
|
|
181
|
+
*
|
|
182
|
+
* @param options - Search options
|
|
183
|
+
* @returns Array of discovered files, sorted by confidence
|
|
184
|
+
*/
|
|
185
|
+
export async function discoverPlanningFiles(options) {
|
|
186
|
+
const searchOptions = {
|
|
187
|
+
rootPath: options.rootPath,
|
|
188
|
+
maxDepth: options.maxDepth ?? 5,
|
|
189
|
+
excludeDirs: options.excludeDirs ?? DEFAULT_EXCLUDE_DIRS,
|
|
190
|
+
patterns: options.patterns ?? DEFAULT_PATTERNS,
|
|
191
|
+
};
|
|
192
|
+
const results = await searchDirectory(searchOptions.rootPath, searchOptions);
|
|
193
|
+
// Sort by confidence (descending), then by modified date (newest first)
|
|
194
|
+
return results.sort((a, b) => {
|
|
195
|
+
if (a.confidence !== b.confidence) {
|
|
196
|
+
return b.confidence - a.confidence;
|
|
197
|
+
}
|
|
198
|
+
return b.modified.getTime() - a.modified.getTime();
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Find the most likely planning document
|
|
203
|
+
*
|
|
204
|
+
* Returns the single most likely planning document based on confidence and recency.
|
|
205
|
+
*
|
|
206
|
+
* @param options - Search options
|
|
207
|
+
* @returns The most likely planning document, or undefined if none found
|
|
208
|
+
*/
|
|
209
|
+
export async function findBestPlanningFile(options) {
|
|
210
|
+
const files = await discoverPlanningFiles(options);
|
|
211
|
+
return files[0];
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Group discovered files by name pattern
|
|
215
|
+
*
|
|
216
|
+
* Groups files like "prd.md", "plan.md" etc. for easier selection.
|
|
217
|
+
*
|
|
218
|
+
* @param files - Discovered files
|
|
219
|
+
* @returns Map of pattern to files
|
|
220
|
+
*/
|
|
221
|
+
export function groupFilesByPattern(files) {
|
|
222
|
+
const groups = new Map();
|
|
223
|
+
for (const file of files) {
|
|
224
|
+
const lower = basename(file.name).toLowerCase();
|
|
225
|
+
let pattern = 'other';
|
|
226
|
+
if (lower.includes('prd'))
|
|
227
|
+
pattern = 'prd';
|
|
228
|
+
else if (lower.includes('plan'))
|
|
229
|
+
pattern = 'plan';
|
|
230
|
+
else if (lower.includes('todo'))
|
|
231
|
+
pattern = 'todo';
|
|
232
|
+
else if (lower.includes('spec'))
|
|
233
|
+
pattern = 'spec';
|
|
234
|
+
else if (lower.includes('requirements'))
|
|
235
|
+
pattern = 'requirements';
|
|
236
|
+
else if (lower.includes('rfc'))
|
|
237
|
+
pattern = 'rfc';
|
|
238
|
+
else if (lower.includes('adr'))
|
|
239
|
+
pattern = 'adr';
|
|
240
|
+
if (!groups.has(pattern)) {
|
|
241
|
+
groups.set(pattern, []);
|
|
242
|
+
}
|
|
243
|
+
groups.get(pattern).push(file);
|
|
244
|
+
}
|
|
245
|
+
return groups;
|
|
246
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base Adapter Framework
|
|
3
|
+
*
|
|
4
|
+
* Core types, interfaces, and registry for format adapters.
|
|
5
|
+
*/
|
|
6
|
+
export * from './types.js';
|
|
7
|
+
export * from './registry.js';
|
|
8
|
+
export * from './utils.js';
|
|
9
|
+
export * from './file-discovery.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|