@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,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic Markdown Serializer
|
|
3
|
+
*
|
|
4
|
+
* Serializes APS plans to generic markdown format.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { APSPlan } from '@eddacraft/anvil-core';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Type guard to check if value is a string
|
|
11
|
+
*/
|
|
12
|
+
function isString(value: unknown): value is string {
|
|
13
|
+
return typeof value === 'string';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Type guard to check if value is a string array
|
|
18
|
+
*/
|
|
19
|
+
function isStringArray(value: unknown): value is string[] {
|
|
20
|
+
return Array.isArray(value) && value.every((item) => typeof item === 'string');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Serialize APS plan to generic markdown format
|
|
25
|
+
*/
|
|
26
|
+
export function serializeToGeneric(plan: APSPlan): string {
|
|
27
|
+
const lines: string[] = [];
|
|
28
|
+
|
|
29
|
+
// Title (from intent or metadata) - safely check if title is a non-empty string
|
|
30
|
+
const metadataTitle = plan.metadata?.['title'];
|
|
31
|
+
const title = isString(metadataTitle) && metadataTitle ? metadataTitle : plan.intent;
|
|
32
|
+
lines.push(`# ${title}`);
|
|
33
|
+
lines.push('');
|
|
34
|
+
|
|
35
|
+
// Overview section - safely check if overview is a string
|
|
36
|
+
const metadataOverview = plan.metadata?.['overview'];
|
|
37
|
+
if (metadataOverview && isString(metadataOverview)) {
|
|
38
|
+
lines.push('## Overview');
|
|
39
|
+
lines.push('');
|
|
40
|
+
lines.push(metadataOverview);
|
|
41
|
+
lines.push('');
|
|
42
|
+
} else if (plan.intent) {
|
|
43
|
+
lines.push('## Purpose');
|
|
44
|
+
lines.push('');
|
|
45
|
+
lines.push(plan.intent);
|
|
46
|
+
lines.push('');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Goals section - safely check if goals is a string array
|
|
50
|
+
const metadataGoals = plan.metadata?.['goals'];
|
|
51
|
+
if (metadataGoals && isStringArray(metadataGoals)) {
|
|
52
|
+
lines.push('## Goals');
|
|
53
|
+
lines.push('');
|
|
54
|
+
for (const goal of metadataGoals) {
|
|
55
|
+
lines.push(`- ${goal}`);
|
|
56
|
+
}
|
|
57
|
+
lines.push('');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Changes section
|
|
61
|
+
if (plan.proposed_changes.length > 0) {
|
|
62
|
+
lines.push('## Changes');
|
|
63
|
+
lines.push('');
|
|
64
|
+
|
|
65
|
+
// Group changes by type
|
|
66
|
+
const creates = plan.proposed_changes.filter((c) => c.type === 'file_create');
|
|
67
|
+
const updates = plan.proposed_changes.filter((c) => c.type === 'file_update');
|
|
68
|
+
const deletes = plan.proposed_changes.filter((c) => c.type === 'file_delete');
|
|
69
|
+
const configs = plan.proposed_changes.filter((c) => c.type === 'config_update');
|
|
70
|
+
|
|
71
|
+
if (creates.length > 0) {
|
|
72
|
+
lines.push('### Files to Create');
|
|
73
|
+
lines.push('');
|
|
74
|
+
for (const change of creates) {
|
|
75
|
+
lines.push(`- **${change.path}**: ${change.description}`);
|
|
76
|
+
}
|
|
77
|
+
lines.push('');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (updates.length > 0) {
|
|
81
|
+
lines.push('### Files to Update');
|
|
82
|
+
lines.push('');
|
|
83
|
+
for (const change of updates) {
|
|
84
|
+
lines.push(`- **${change.path}**: ${change.description}`);
|
|
85
|
+
}
|
|
86
|
+
lines.push('');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (configs.length > 0) {
|
|
90
|
+
lines.push('### Configuration Changes');
|
|
91
|
+
lines.push('');
|
|
92
|
+
for (const change of configs) {
|
|
93
|
+
lines.push(`- **${change.path}**: ${change.description}`);
|
|
94
|
+
}
|
|
95
|
+
lines.push('');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (deletes.length > 0) {
|
|
99
|
+
lines.push('### Files to Delete');
|
|
100
|
+
lines.push('');
|
|
101
|
+
for (const change of deletes) {
|
|
102
|
+
lines.push(`- **${change.path}**: ${change.description}`);
|
|
103
|
+
}
|
|
104
|
+
lines.push('');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Validation requirements
|
|
109
|
+
if (plan.validations.required_checks.length > 0) {
|
|
110
|
+
lines.push('## Validation Requirements');
|
|
111
|
+
lines.push('');
|
|
112
|
+
for (const check of plan.validations.required_checks) {
|
|
113
|
+
lines.push(`- ${check}`);
|
|
114
|
+
}
|
|
115
|
+
lines.push('');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Metadata section
|
|
119
|
+
if (plan.provenance) {
|
|
120
|
+
lines.push('## Metadata');
|
|
121
|
+
lines.push('');
|
|
122
|
+
lines.push(`- **Author**: ${plan.provenance.author || 'Unknown'}`);
|
|
123
|
+
lines.push(`- **Created**: ${plan.provenance.timestamp}`);
|
|
124
|
+
if (plan.provenance.repository) {
|
|
125
|
+
lines.push(`- **Repository**: ${plan.provenance.repository}`);
|
|
126
|
+
}
|
|
127
|
+
if (plan.provenance.branch) {
|
|
128
|
+
lines.push(`- **Branch**: ${plan.provenance.branch}`);
|
|
129
|
+
}
|
|
130
|
+
lines.push('');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return lines.join('\n');
|
|
134
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic Adapter Types
|
|
3
|
+
*
|
|
4
|
+
* Types for the generic markdown adapter that handles common planning document formats.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Parsed generic document structure
|
|
9
|
+
*/
|
|
10
|
+
export interface GenericDocument {
|
|
11
|
+
/** Document title (from first # heading) */
|
|
12
|
+
title?: string;
|
|
13
|
+
/** Intent/purpose extracted from intro or objective sections */
|
|
14
|
+
intent?: string;
|
|
15
|
+
/** Overview/description */
|
|
16
|
+
overview?: string;
|
|
17
|
+
/** Goals or objectives */
|
|
18
|
+
goals?: string[];
|
|
19
|
+
/** Requirements extracted from various sections */
|
|
20
|
+
requirements?: string[];
|
|
21
|
+
/** Tasks or action items */
|
|
22
|
+
tasks?: string[];
|
|
23
|
+
/** Features or capabilities */
|
|
24
|
+
features?: string[];
|
|
25
|
+
/** Metadata from document */
|
|
26
|
+
metadata?: Record<string, unknown>;
|
|
27
|
+
/** Raw content */
|
|
28
|
+
raw: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Detection indicators for generic markdown
|
|
33
|
+
*/
|
|
34
|
+
export interface GenericIndicators {
|
|
35
|
+
/** Has markdown headings */
|
|
36
|
+
hasHeadings: boolean;
|
|
37
|
+
/** Has lists (bullet or numbered) */
|
|
38
|
+
hasLists: boolean;
|
|
39
|
+
/** Has requirements-like content */
|
|
40
|
+
hasRequirementsSection: boolean;
|
|
41
|
+
/** Has goals/objectives section */
|
|
42
|
+
hasGoalsSection: boolean;
|
|
43
|
+
/** Has tasks/action items */
|
|
44
|
+
hasTasksSection: boolean;
|
|
45
|
+
/** Has features section */
|
|
46
|
+
hasFeaturesSection: boolean;
|
|
47
|
+
/** Has overview/description */
|
|
48
|
+
hasOverviewSection: boolean;
|
|
49
|
+
/** Document length */
|
|
50
|
+
wordCount: number;
|
|
51
|
+
/** Number of list items */
|
|
52
|
+
listItemCount: number;
|
|
53
|
+
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic Adapter Utilities
|
|
3
|
+
*
|
|
4
|
+
* Helper functions for parsing generic markdown documents.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { GenericDocument, GenericIndicators } from './types.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Extract document title from first heading
|
|
11
|
+
*/
|
|
12
|
+
export function extractTitle(content: string): string | undefined {
|
|
13
|
+
const match = content.match(/^#\s+(.+)$/m);
|
|
14
|
+
return match ? match[1].trim() : undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Extract intent from common sections
|
|
19
|
+
*/
|
|
20
|
+
export function extractIntent(content: string): string | undefined {
|
|
21
|
+
// Look for sections like: Purpose, Intent, Objective, Goal, Summary
|
|
22
|
+
const patterns = [
|
|
23
|
+
/##\s+(?:Purpose|Intent|Objective|Goal)\s*\n+([^\n#]+)/i,
|
|
24
|
+
/##\s+(?:Executive\s+)?Summary\s*\n+([^\n#]+)/i,
|
|
25
|
+
/^([^#\n]+?)(?=\n##|\n#|$)/m, // First paragraph as fallback
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
for (const pattern of patterns) {
|
|
29
|
+
const match = content.match(pattern);
|
|
30
|
+
if (match && match[1]) {
|
|
31
|
+
return match[1].trim().substring(0, 500); // Limit length
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Extract overview/description
|
|
40
|
+
*/
|
|
41
|
+
export function extractOverview(content: string): string | undefined {
|
|
42
|
+
const patterns = [/##\s+(?:Overview|Description|Background|Context)\s*\n+([\s\S]+?)(?=\n##|$)/i];
|
|
43
|
+
|
|
44
|
+
for (const pattern of patterns) {
|
|
45
|
+
const match = content.match(pattern);
|
|
46
|
+
if (match && match[1]) {
|
|
47
|
+
return match[1].trim();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Extract goals from lists under Goals/Objectives sections
|
|
56
|
+
*/
|
|
57
|
+
export function extractGoals(content: string): string[] {
|
|
58
|
+
const goals: string[] = [];
|
|
59
|
+
|
|
60
|
+
// Match Goals/Objectives section
|
|
61
|
+
const sectionMatch = content.match(/##\s+(?:Goals?|Objectives?)\s*\n+([\s\S]+?)(?=\n##|$)/i);
|
|
62
|
+
|
|
63
|
+
if (sectionMatch) {
|
|
64
|
+
const section = sectionMatch[1];
|
|
65
|
+
// Extract list items
|
|
66
|
+
const listItems = section.match(/^[-*]\s+(.+)$/gm);
|
|
67
|
+
if (listItems) {
|
|
68
|
+
goals.push(...listItems.map((item) => item.replace(/^[-*]\s+/, '').trim()));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return goals;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Extract requirements from various sections
|
|
77
|
+
*/
|
|
78
|
+
export function extractRequirements(content: string): string[] {
|
|
79
|
+
const requirements: string[] = [];
|
|
80
|
+
|
|
81
|
+
// Match Requirements/Needs/Must Have sections
|
|
82
|
+
const patterns = [
|
|
83
|
+
/##\s+(?:Requirements?|Needs?|Must\s+Have)\s*\n+([\s\S]+?)(?=\n##|$)/i,
|
|
84
|
+
/##\s+(?:Functional\s+)?Requirements?\s*\n+([\s\S]+?)(?=\n##|$)/i,
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
for (const pattern of patterns) {
|
|
88
|
+
const match = content.match(pattern);
|
|
89
|
+
if (match) {
|
|
90
|
+
const section = match[1];
|
|
91
|
+
// Extract list items
|
|
92
|
+
const listItems = section.match(/^[-*]\s+(.+)$/gm);
|
|
93
|
+
if (listItems) {
|
|
94
|
+
requirements.push(...listItems.map((item) => item.replace(/^[-*]\s+/, '').trim()));
|
|
95
|
+
}
|
|
96
|
+
// Extract numbered items
|
|
97
|
+
const numberedItems = section.match(/^\d+\.\s+(.+)$/gm);
|
|
98
|
+
if (numberedItems) {
|
|
99
|
+
requirements.push(...numberedItems.map((item) => item.replace(/^\d+\.\s+/, '').trim()));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return requirements;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Extract tasks from Tasks/Action Items sections
|
|
109
|
+
*/
|
|
110
|
+
export function extractTasks(content: string): string[] {
|
|
111
|
+
const tasks: string[] = [];
|
|
112
|
+
|
|
113
|
+
const patterns = [/##\s+(?:Tasks?|Action\s+Items?|To\s+Do|TODO)\s*\n+([\s\S]+?)(?=\n##|$)/i];
|
|
114
|
+
|
|
115
|
+
for (const pattern of patterns) {
|
|
116
|
+
const match = content.match(pattern);
|
|
117
|
+
if (match) {
|
|
118
|
+
const section = match[1];
|
|
119
|
+
// Extract list items (including checkboxes)
|
|
120
|
+
const listItems = section.match(/^[-*]\s+(?:\[[ x]\]\s+)?(.+)$/gm);
|
|
121
|
+
if (listItems) {
|
|
122
|
+
tasks.push(...listItems.map((item) => item.replace(/^[-*]\s+(?:\[[ x]\]\s+)?/, '').trim()));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return tasks;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Extract features from Features/Capabilities sections
|
|
132
|
+
*/
|
|
133
|
+
export function extractFeatures(content: string): string[] {
|
|
134
|
+
const features: string[] = [];
|
|
135
|
+
|
|
136
|
+
const patterns = [/##\s+(?:Features?|Capabilities?|Functionality)\s*\n+([\s\S]+?)(?=\n##|$)/i];
|
|
137
|
+
|
|
138
|
+
for (const pattern of patterns) {
|
|
139
|
+
const match = content.match(pattern);
|
|
140
|
+
if (match) {
|
|
141
|
+
const section = match[1];
|
|
142
|
+
const listItems = section.match(/^[-*]\s+(.+)$/gm);
|
|
143
|
+
if (listItems) {
|
|
144
|
+
features.push(...listItems.map((item) => item.replace(/^[-*]\s+/, '').trim()));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return features;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Analyze content for generic markdown indicators
|
|
154
|
+
*/
|
|
155
|
+
export function analyzeContent(content: string): GenericIndicators {
|
|
156
|
+
const headings = content.match(/^#{1,6}\s+.+$/gm) || [];
|
|
157
|
+
const listItems = content.match(/^[-*]\s+.+$/gm) || [];
|
|
158
|
+
const words = content.split(/\s+/).filter((w) => w.length > 0);
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
hasHeadings: headings.length > 0,
|
|
162
|
+
hasLists: listItems.length > 0,
|
|
163
|
+
hasRequirementsSection: /##\s+(?:Requirements?|Needs?)/i.test(content),
|
|
164
|
+
hasGoalsSection: /##\s+(?:Goals?|Objectives?)/i.test(content),
|
|
165
|
+
hasTasksSection: /##\s+(?:Tasks?|Action\s+Items?|To\s+Do)/i.test(content),
|
|
166
|
+
hasFeaturesSection: /##\s+(?:Features?|Capabilities?)/i.test(content),
|
|
167
|
+
hasOverviewSection: /##\s+(?:Overview|Description|Background)/i.test(content),
|
|
168
|
+
wordCount: words.length,
|
|
169
|
+
listItemCount: listItems.length,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Calculate confidence score for generic markdown detection
|
|
175
|
+
*/
|
|
176
|
+
export function calculateConfidenceScore(indicators: GenericIndicators): number {
|
|
177
|
+
let score = 0;
|
|
178
|
+
|
|
179
|
+
// Basic markdown structure (10 points)
|
|
180
|
+
if (indicators.hasHeadings) {
|
|
181
|
+
score += 10;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Has lists (10 points)
|
|
185
|
+
if (indicators.hasLists) {
|
|
186
|
+
score += 10;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Planning-related sections (5 points each)
|
|
190
|
+
if (indicators.hasRequirementsSection) {
|
|
191
|
+
score += 5;
|
|
192
|
+
}
|
|
193
|
+
if (indicators.hasGoalsSection) {
|
|
194
|
+
score += 5;
|
|
195
|
+
}
|
|
196
|
+
if (indicators.hasTasksSection) {
|
|
197
|
+
score += 5;
|
|
198
|
+
}
|
|
199
|
+
if (indicators.hasFeaturesSection) {
|
|
200
|
+
score += 5;
|
|
201
|
+
}
|
|
202
|
+
if (indicators.hasOverviewSection) {
|
|
203
|
+
score += 5;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Content density (up to 10 points)
|
|
207
|
+
if (indicators.wordCount >= 100) {
|
|
208
|
+
score += 5;
|
|
209
|
+
}
|
|
210
|
+
if (indicators.wordCount >= 500) {
|
|
211
|
+
score += 5;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// List items (up to 5 points)
|
|
215
|
+
if (indicators.listItemCount >= 3) {
|
|
216
|
+
score += 3;
|
|
217
|
+
}
|
|
218
|
+
if (indicators.listItemCount >= 10) {
|
|
219
|
+
score += 2;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return Math.min(100, score);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Build detection reason string
|
|
227
|
+
*/
|
|
228
|
+
export function buildDetectionReason(indicators: GenericIndicators): string {
|
|
229
|
+
const reasons: string[] = [];
|
|
230
|
+
|
|
231
|
+
if (indicators.hasHeadings) {
|
|
232
|
+
reasons.push('markdown-headings');
|
|
233
|
+
}
|
|
234
|
+
if (indicators.hasLists) {
|
|
235
|
+
reasons.push(`${indicators.listItemCount} list-items`);
|
|
236
|
+
}
|
|
237
|
+
if (indicators.hasRequirementsSection) {
|
|
238
|
+
reasons.push('requirements-section');
|
|
239
|
+
}
|
|
240
|
+
if (indicators.hasGoalsSection) {
|
|
241
|
+
reasons.push('goals-section');
|
|
242
|
+
}
|
|
243
|
+
if (indicators.hasTasksSection) {
|
|
244
|
+
reasons.push('tasks-section');
|
|
245
|
+
}
|
|
246
|
+
if (indicators.hasFeaturesSection) {
|
|
247
|
+
reasons.push('features-section');
|
|
248
|
+
}
|
|
249
|
+
if (indicators.hasOverviewSection) {
|
|
250
|
+
reasons.push('overview-section');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return reasons.length > 0 ? reasons.join(', ') : 'generic-markdown';
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Parse generic markdown document
|
|
258
|
+
*/
|
|
259
|
+
export function parseGenericDocument(content: string): GenericDocument {
|
|
260
|
+
return {
|
|
261
|
+
title: extractTitle(content),
|
|
262
|
+
intent: extractIntent(content),
|
|
263
|
+
overview: extractOverview(content),
|
|
264
|
+
goals: extractGoals(content),
|
|
265
|
+
requirements: extractRequirements(content),
|
|
266
|
+
tasks: extractTasks(content),
|
|
267
|
+
features: extractFeatures(content),
|
|
268
|
+
raw: content,
|
|
269
|
+
};
|
|
270
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anvil Adapters Package
|
|
3
|
+
*
|
|
4
|
+
* Provides format adapters for converting between external planning formats
|
|
5
|
+
* (SpecKit, BMAD, etc.) and the Anvil Plan Spec (APS).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Export base framework (types, registry, utils)
|
|
9
|
+
export * from './base/index.js';
|
|
10
|
+
|
|
11
|
+
// Export APS Markdown adapter (native format)
|
|
12
|
+
export * from './aps-markdown/index.js';
|
|
13
|
+
|
|
14
|
+
// Export SpecKit adapter
|
|
15
|
+
export * from './speckit/index.js';
|
|
16
|
+
|
|
17
|
+
// Export BMAD adapter
|
|
18
|
+
export * from './bmad/index.js';
|
|
19
|
+
|
|
20
|
+
// Export Generic adapter
|
|
21
|
+
export * from './generic/index.js';
|
|
22
|
+
|
|
23
|
+
// Export common types for backward compatibility
|
|
24
|
+
export type {
|
|
25
|
+
SpecContext,
|
|
26
|
+
ExternalSpec,
|
|
27
|
+
ConversionResult,
|
|
28
|
+
ConversionError,
|
|
29
|
+
ConversionWarning,
|
|
30
|
+
} from './common/types.js';
|
|
31
|
+
|
|
32
|
+
// Auto-register adapters when module is imported
|
|
33
|
+
import { registry as baseRegistry } from './base/index.js';
|
|
34
|
+
import { APSMarkdownAdapter } from './aps-markdown/index.js';
|
|
35
|
+
import { BMADFormatAdapter } from './bmad/index.js';
|
|
36
|
+
import { SpecKitFormatAdapter } from './speckit/index.js';
|
|
37
|
+
import { GenericMarkdownAdapter } from './generic/index.js';
|
|
38
|
+
|
|
39
|
+
// Register adapters in priority order
|
|
40
|
+
// APS adapter first (native format), then specific external formats
|
|
41
|
+
// Generic adapter is registered last as fallback
|
|
42
|
+
baseRegistry.register(new APSMarkdownAdapter());
|
|
43
|
+
baseRegistry.register(new BMADFormatAdapter());
|
|
44
|
+
baseRegistry.register(new SpecKitFormatAdapter());
|
|
45
|
+
baseRegistry.register(new GenericMarkdownAdapter());
|
|
46
|
+
|
|
47
|
+
// Export the registry instance as default export
|
|
48
|
+
export { baseRegistry as registry };
|