@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,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SpecKit Format Adapter
|
|
3
|
+
*
|
|
4
|
+
* FormatAdapter implementation for GitHub spec-kit format.
|
|
5
|
+
* Handles simple SpecKit specification documents with Intent, Overview, Goals,
|
|
6
|
+
* Requirements, and Changes sections.
|
|
7
|
+
*
|
|
8
|
+
* Uses SpecKitParser to parse markdown with ## sections and convert to/from APS format.
|
|
9
|
+
*/
|
|
10
|
+
import { type APSPlan, type ValidationResult } from '@eddacraft/anvil-core';
|
|
11
|
+
import { BaseFormatAdapter, type AdapterMetadata, type DetectionResult, type ParseResult, type SerializeResult, type ParseContext, type AdapterOptions, type PathDetectionHint } from '../base/types.js';
|
|
12
|
+
/**
|
|
13
|
+
* SpecKit FormatAdapter implementation
|
|
14
|
+
*
|
|
15
|
+
* Converts between SpecKit format documents and APS plans.
|
|
16
|
+
*/
|
|
17
|
+
export declare class SpecKitFormatAdapter extends BaseFormatAdapter {
|
|
18
|
+
readonly metadata: AdapterMetadata;
|
|
19
|
+
private parser;
|
|
20
|
+
constructor(options?: AdapterOptions);
|
|
21
|
+
/**
|
|
22
|
+
* Detect if content is SpecKit format
|
|
23
|
+
*
|
|
24
|
+
* Uses confidence scoring based on multiple indicators:
|
|
25
|
+
* - Specification header (20 points)
|
|
26
|
+
* - Intent section (15 points)
|
|
27
|
+
* - Overview section (10 points)
|
|
28
|
+
* - Goals section (10 points)
|
|
29
|
+
* - Requirements section (10 points)
|
|
30
|
+
* - Changes section (20 points)
|
|
31
|
+
* - Files to Create/Update sections (10 points)
|
|
32
|
+
* - Code blocks (5 points)
|
|
33
|
+
*
|
|
34
|
+
* @param content - Document content to analyze
|
|
35
|
+
* @returns Detection result with confidence score
|
|
36
|
+
*/
|
|
37
|
+
detect(content: string): DetectionResult;
|
|
38
|
+
/**
|
|
39
|
+
* Detect with file path hints for improved accuracy
|
|
40
|
+
*
|
|
41
|
+
* Uses sibling file information (e.g., AGENTS.md) and content
|
|
42
|
+
* namespace patterns (e.g., `speckit.*`) to boost detection.
|
|
43
|
+
*
|
|
44
|
+
* @param content - Document content to analyze
|
|
45
|
+
* @param hint - Path and directory information
|
|
46
|
+
* @returns Detection result with confidence score
|
|
47
|
+
*/
|
|
48
|
+
detectWithPath(content: string, hint: PathDetectionHint): DetectionResult;
|
|
49
|
+
/**
|
|
50
|
+
* Parse SpecKit content to APS plan
|
|
51
|
+
*
|
|
52
|
+
* @param content - SpecKit markdown content
|
|
53
|
+
* @param context - Parse context for provenance
|
|
54
|
+
* @param options - Adapter options
|
|
55
|
+
* @returns Parse result with APS plan
|
|
56
|
+
*/
|
|
57
|
+
parse(content: string, context?: ParseContext, _options?: AdapterOptions): Promise<ParseResult>;
|
|
58
|
+
/**
|
|
59
|
+
* Infer APS change type from SpecKit change type
|
|
60
|
+
*/
|
|
61
|
+
private inferChangeType;
|
|
62
|
+
/**
|
|
63
|
+
* Infer file path from change description
|
|
64
|
+
*/
|
|
65
|
+
private inferPathFromDescription;
|
|
66
|
+
/**
|
|
67
|
+
* Serialize APS plan to SpecKit format
|
|
68
|
+
*
|
|
69
|
+
* @param plan - APS plan to serialize
|
|
70
|
+
* @param options - Adapter options
|
|
71
|
+
* @returns Serialize result with SpecKit markdown
|
|
72
|
+
*/
|
|
73
|
+
serialize(plan: APSPlan, _options?: AdapterOptions): Promise<SerializeResult>;
|
|
74
|
+
/**
|
|
75
|
+
* Validate SpecKit content
|
|
76
|
+
*
|
|
77
|
+
* Checks for required SpecKit elements without full conversion.
|
|
78
|
+
*
|
|
79
|
+
* @param content - SpecKit content to validate
|
|
80
|
+
* @param options - Validation options
|
|
81
|
+
* @returns Validation result
|
|
82
|
+
*/
|
|
83
|
+
validate(content: string, _options?: AdapterOptions): Promise<ValidationResult>;
|
|
84
|
+
/**
|
|
85
|
+
* Analyze content for SpecKit indicators
|
|
86
|
+
*/
|
|
87
|
+
private analyzeContent;
|
|
88
|
+
/**
|
|
89
|
+
* Calculate confidence score
|
|
90
|
+
*/
|
|
91
|
+
private calculateConfidence;
|
|
92
|
+
/**
|
|
93
|
+
* Build detection reason message
|
|
94
|
+
*/
|
|
95
|
+
private buildDetectionReason;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Create a new SpecKit format adapter instance
|
|
99
|
+
*
|
|
100
|
+
* @param options - Adapter options
|
|
101
|
+
* @returns SpecKit adapter instance
|
|
102
|
+
*/
|
|
103
|
+
export declare function createSpecKitAdapter(options?: AdapterOptions): SpecKitFormatAdapter;
|
|
104
|
+
//# sourceMappingURL=format-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-adapter.d.ts","sourceRoot":"","sources":["../../src/speckit/format-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAEL,KAAK,OAAO,EACZ,KAAK,gBAAgB,EAItB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,iBAAiB,EACjB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACvB,MAAM,kBAAkB,CAAC;AAwB1B;;;;GAIG;AACH,qBAAa,oBAAqB,SAAQ,iBAAiB;IACzD,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAOhC;IAEF,OAAO,CAAC,MAAM,CAAgB;gBAElB,OAAO,CAAC,EAAE,cAAc;IAKpC;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe;IAUxC;;;;;;;;;OASG;IACH,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,eAAe;IAQzE;;;;;;;OAOG;IACG,KAAK,CACT,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,YAAY,EACtB,QAAQ,CAAC,EAAE,cAAc,GACxB,OAAO,CAAC,WAAW,CAAC;IA+EvB;;OAEG;IACH,OAAO,CAAC,eAAe;IAQvB;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAehC;;;;;;OAMG;IACG,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;IAsInF;;;;;;;;OAQG;IACG,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA8DrF;;OAEG;IACH,OAAO,CAAC,cAAc;IA4BtB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IA8D3B;;OAEG;IACH,OAAO,CAAC,oBAAoB;CAoC7B;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,CAAC,EAAE,cAAc,GAAG,oBAAoB,CAEnF"}
|
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SpecKit Format Adapter
|
|
3
|
+
*
|
|
4
|
+
* FormatAdapter implementation for GitHub spec-kit format.
|
|
5
|
+
* Handles simple SpecKit specification documents with Intent, Overview, Goals,
|
|
6
|
+
* Requirements, and Changes sections.
|
|
7
|
+
*
|
|
8
|
+
* Uses SpecKitParser to parse markdown with ## sections and convert to/from APS format.
|
|
9
|
+
*/
|
|
10
|
+
import { generateHash, createPlan, validateRelativePath, } from '@eddacraft/anvil-core';
|
|
11
|
+
import { BaseFormatAdapter, } from '../base/types.js';
|
|
12
|
+
import { createDetection, generateDeterministicPlanId } from '../base/utils.js';
|
|
13
|
+
import { SpecKitParser } from './parser.js';
|
|
14
|
+
/**
|
|
15
|
+
* SpecKit FormatAdapter implementation
|
|
16
|
+
*
|
|
17
|
+
* Converts between SpecKit format documents and APS plans.
|
|
18
|
+
*/
|
|
19
|
+
export class SpecKitFormatAdapter extends BaseFormatAdapter {
|
|
20
|
+
metadata = {
|
|
21
|
+
name: 'speckit',
|
|
22
|
+
version: '2.0.0',
|
|
23
|
+
displayName: 'GitHub SpecKit',
|
|
24
|
+
description: 'GitHub spec-kit format adapter (spec.md, plan.md, tasks.md)',
|
|
25
|
+
formats: ['speckit', 'spec-kit', 'spec.md', 'plan.md', 'tasks.md'],
|
|
26
|
+
extensions: ['.md'],
|
|
27
|
+
};
|
|
28
|
+
parser;
|
|
29
|
+
constructor(options) {
|
|
30
|
+
super(options);
|
|
31
|
+
this.parser = new SpecKitParser();
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Detect if content is SpecKit format
|
|
35
|
+
*
|
|
36
|
+
* Uses confidence scoring based on multiple indicators:
|
|
37
|
+
* - Specification header (20 points)
|
|
38
|
+
* - Intent section (15 points)
|
|
39
|
+
* - Overview section (10 points)
|
|
40
|
+
* - Goals section (10 points)
|
|
41
|
+
* - Requirements section (10 points)
|
|
42
|
+
* - Changes section (20 points)
|
|
43
|
+
* - Files to Create/Update sections (10 points)
|
|
44
|
+
* - Code blocks (5 points)
|
|
45
|
+
*
|
|
46
|
+
* @param content - Document content to analyze
|
|
47
|
+
* @returns Detection result with confidence score
|
|
48
|
+
*/
|
|
49
|
+
detect(content) {
|
|
50
|
+
const indicators = this.analyzeContent(content);
|
|
51
|
+
const confidence = this.calculateConfidence(indicators);
|
|
52
|
+
const reason = this.buildDetectionReason(indicators);
|
|
53
|
+
// Detection threshold: 50% confidence
|
|
54
|
+
// Lower threshold than BMAD to accommodate minimal SpecKit documents
|
|
55
|
+
return createDetection(confidence >= 50, confidence, reason);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Detect with file path hints for improved accuracy
|
|
59
|
+
*
|
|
60
|
+
* Uses sibling file information (e.g., AGENTS.md) and content
|
|
61
|
+
* namespace patterns (e.g., `speckit.*`) to boost detection.
|
|
62
|
+
*
|
|
63
|
+
* @param content - Document content to analyze
|
|
64
|
+
* @param hint - Path and directory information
|
|
65
|
+
* @returns Detection result with confidence score
|
|
66
|
+
*/
|
|
67
|
+
detectWithPath(content, hint) {
|
|
68
|
+
const indicators = this.analyzeContent(content, hint);
|
|
69
|
+
const confidence = this.calculateConfidence(indicators);
|
|
70
|
+
const reason = this.buildDetectionReason(indicators);
|
|
71
|
+
return createDetection(confidence >= 50, confidence, reason);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Parse SpecKit content to APS plan
|
|
75
|
+
*
|
|
76
|
+
* @param content - SpecKit markdown content
|
|
77
|
+
* @param context - Parse context for provenance
|
|
78
|
+
* @param options - Adapter options
|
|
79
|
+
* @returns Parse result with APS plan
|
|
80
|
+
*/
|
|
81
|
+
async parse(content, context, _options) {
|
|
82
|
+
try {
|
|
83
|
+
// Parse SpecKit markdown using SpecKitParser
|
|
84
|
+
const parsed = this.parser.parseSpecMarkdown(content);
|
|
85
|
+
// Build intent from parsed content
|
|
86
|
+
const intent = parsed.intent || 'Implement Feature';
|
|
87
|
+
// Build proposed changes from parsed changes
|
|
88
|
+
const changes = [];
|
|
89
|
+
if (parsed.changes && parsed.changes.length > 0) {
|
|
90
|
+
for (const change of parsed.changes) {
|
|
91
|
+
const changeType = this.inferChangeType(change.type);
|
|
92
|
+
const rawPath = change.path || this.inferPathFromDescription(change.description);
|
|
93
|
+
let safePath;
|
|
94
|
+
try {
|
|
95
|
+
safePath = validateRelativePath(rawPath);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
changes.push({
|
|
101
|
+
type: changeType,
|
|
102
|
+
path: safePath,
|
|
103
|
+
description: change.description,
|
|
104
|
+
content: change.content,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Build provenance
|
|
109
|
+
const provenance = {
|
|
110
|
+
timestamp: context?.timestamp || new Date().toISOString(),
|
|
111
|
+
source: 'cli',
|
|
112
|
+
version: this.metadata.version,
|
|
113
|
+
author: context?.author,
|
|
114
|
+
repository: context?.repositoryPath,
|
|
115
|
+
branch: context?.branch,
|
|
116
|
+
commit: context?.commit,
|
|
117
|
+
};
|
|
118
|
+
const planId = context?.planId ?? generateDeterministicPlanId(content);
|
|
119
|
+
// Create APS plan
|
|
120
|
+
const plan = {
|
|
121
|
+
...createPlan({
|
|
122
|
+
id: planId,
|
|
123
|
+
intent,
|
|
124
|
+
provenance,
|
|
125
|
+
changes,
|
|
126
|
+
}),
|
|
127
|
+
schema_version: '0.1.0',
|
|
128
|
+
hash: '0'.repeat(64), // Temporary, will be replaced
|
|
129
|
+
metadata: {
|
|
130
|
+
source_format: 'speckit',
|
|
131
|
+
overview: parsed.overview,
|
|
132
|
+
goals: parsed.goals,
|
|
133
|
+
requirements: parsed.requirements,
|
|
134
|
+
...parsed.metadata,
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
// Generate hash for the plan
|
|
138
|
+
const planWithHash = {
|
|
139
|
+
...plan,
|
|
140
|
+
hash: generateHash(plan),
|
|
141
|
+
};
|
|
142
|
+
return this.createParseSuccess(planWithHash);
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
return this.createParseError([
|
|
146
|
+
{
|
|
147
|
+
code: 'PARSE_ERROR',
|
|
148
|
+
message: error instanceof Error ? error.message : 'Failed to parse SpecKit content',
|
|
149
|
+
details: error,
|
|
150
|
+
},
|
|
151
|
+
]);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Infer APS change type from SpecKit change type
|
|
156
|
+
*/
|
|
157
|
+
inferChangeType(type) {
|
|
158
|
+
const typeLower = type.toLowerCase();
|
|
159
|
+
if (typeLower.includes('create'))
|
|
160
|
+
return 'file_create';
|
|
161
|
+
if (typeLower.includes('update') || typeLower.includes('modify'))
|
|
162
|
+
return 'file_update';
|
|
163
|
+
if (typeLower.includes('delete') || typeLower.includes('remove'))
|
|
164
|
+
return 'file_delete';
|
|
165
|
+
return 'file_update'; // Default to update
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Infer file path from change description
|
|
169
|
+
*/
|
|
170
|
+
inferPathFromDescription(description) {
|
|
171
|
+
// Try to extract path from common patterns like "at path/to/file" or "`path/to/file`"
|
|
172
|
+
const pathMatch = description.match(/at\s+`([^`]+)`/) ||
|
|
173
|
+
description.match(/at\s+(\S+\.\w+)/) ||
|
|
174
|
+
description.match(/`([^`]+\.\w+)`/);
|
|
175
|
+
if (pathMatch) {
|
|
176
|
+
return pathMatch[1];
|
|
177
|
+
}
|
|
178
|
+
// Fallback: generate a generic path
|
|
179
|
+
return 'src/generated-file.ts';
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Serialize APS plan to SpecKit format
|
|
183
|
+
*
|
|
184
|
+
* @param plan - APS plan to serialize
|
|
185
|
+
* @param options - Adapter options
|
|
186
|
+
* @returns Serialize result with SpecKit markdown
|
|
187
|
+
*/
|
|
188
|
+
async serialize(plan, _options) {
|
|
189
|
+
try {
|
|
190
|
+
const sections = [];
|
|
191
|
+
// Header
|
|
192
|
+
sections.push('# Specification');
|
|
193
|
+
sections.push('');
|
|
194
|
+
// Intent section
|
|
195
|
+
sections.push('## Intent');
|
|
196
|
+
sections.push('');
|
|
197
|
+
sections.push(plan.intent);
|
|
198
|
+
sections.push('');
|
|
199
|
+
// Overview section (if available in metadata)
|
|
200
|
+
const overview = plan.metadata?.['overview'];
|
|
201
|
+
if (overview) {
|
|
202
|
+
sections.push('## Overview');
|
|
203
|
+
sections.push('');
|
|
204
|
+
sections.push(overview);
|
|
205
|
+
sections.push('');
|
|
206
|
+
}
|
|
207
|
+
// Goals section (if available in metadata)
|
|
208
|
+
const goals = plan.metadata?.['goals'];
|
|
209
|
+
if (goals && Array.isArray(goals)) {
|
|
210
|
+
sections.push('## Goals');
|
|
211
|
+
sections.push('');
|
|
212
|
+
for (const goal of goals) {
|
|
213
|
+
sections.push(`- ${goal}`);
|
|
214
|
+
}
|
|
215
|
+
sections.push('');
|
|
216
|
+
}
|
|
217
|
+
// Requirements section (if available in metadata)
|
|
218
|
+
const requirements = plan.metadata?.['requirements'];
|
|
219
|
+
if (requirements && Array.isArray(requirements)) {
|
|
220
|
+
sections.push('## Requirements');
|
|
221
|
+
sections.push('');
|
|
222
|
+
for (const req of requirements) {
|
|
223
|
+
sections.push(`- ${req}`);
|
|
224
|
+
}
|
|
225
|
+
sections.push('');
|
|
226
|
+
}
|
|
227
|
+
// Changes section
|
|
228
|
+
if (plan.proposed_changes.length > 0) {
|
|
229
|
+
sections.push('## Changes');
|
|
230
|
+
sections.push('');
|
|
231
|
+
// Group changes by type
|
|
232
|
+
const fileCreates = plan.proposed_changes.filter((c) => c.type === 'file_create');
|
|
233
|
+
const fileUpdates = plan.proposed_changes.filter((c) => c.type === 'file_update');
|
|
234
|
+
const fileDeletes = plan.proposed_changes.filter((c) => c.type === 'file_delete');
|
|
235
|
+
// Files to Create
|
|
236
|
+
if (fileCreates.length > 0) {
|
|
237
|
+
sections.push('### Files to Create');
|
|
238
|
+
sections.push('');
|
|
239
|
+
for (const change of fileCreates) {
|
|
240
|
+
sections.push(`#### Create ${change.path}`);
|
|
241
|
+
sections.push('');
|
|
242
|
+
sections.push(change.description || 'No description provided');
|
|
243
|
+
sections.push('');
|
|
244
|
+
if (change.content) {
|
|
245
|
+
sections.push('```typescript');
|
|
246
|
+
sections.push(change.content);
|
|
247
|
+
sections.push('```');
|
|
248
|
+
sections.push('');
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// Files to Update
|
|
253
|
+
if (fileUpdates.length > 0) {
|
|
254
|
+
sections.push('### Files to Update');
|
|
255
|
+
sections.push('');
|
|
256
|
+
for (const change of fileUpdates) {
|
|
257
|
+
sections.push(`#### Update ${change.path}`);
|
|
258
|
+
sections.push('');
|
|
259
|
+
sections.push(change.description || 'No description provided');
|
|
260
|
+
sections.push('');
|
|
261
|
+
if (change.content) {
|
|
262
|
+
sections.push('```typescript');
|
|
263
|
+
sections.push(change.content);
|
|
264
|
+
sections.push('```');
|
|
265
|
+
sections.push('');
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// Files to Delete
|
|
270
|
+
if (fileDeletes.length > 0) {
|
|
271
|
+
sections.push('### Files to Delete');
|
|
272
|
+
sections.push('');
|
|
273
|
+
for (const change of fileDeletes) {
|
|
274
|
+
sections.push(`#### Delete ${change.path}`);
|
|
275
|
+
sections.push('');
|
|
276
|
+
sections.push(change.description || 'No description provided');
|
|
277
|
+
sections.push('');
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// Metadata section (if additional metadata exists)
|
|
282
|
+
const metadataKeys = Object.keys(plan.metadata || {}).filter((k) => !['source_format', 'overview', 'goals', 'requirements'].includes(k));
|
|
283
|
+
if (metadataKeys.length > 0) {
|
|
284
|
+
sections.push('## Metadata');
|
|
285
|
+
sections.push('');
|
|
286
|
+
sections.push('```json');
|
|
287
|
+
const filteredMetadata = {};
|
|
288
|
+
for (const key of metadataKeys) {
|
|
289
|
+
filteredMetadata[key] = plan.metadata?.[key];
|
|
290
|
+
}
|
|
291
|
+
sections.push(JSON.stringify(filteredMetadata, null, 2));
|
|
292
|
+
sections.push('```');
|
|
293
|
+
sections.push('');
|
|
294
|
+
}
|
|
295
|
+
const content = sections.join('\n');
|
|
296
|
+
return this.createSerializeSuccess(content);
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
return this.createSerializeError([
|
|
300
|
+
{
|
|
301
|
+
code: 'SERIALIZE_ERROR',
|
|
302
|
+
message: error instanceof Error ? error.message : 'Failed to serialize to SpecKit format',
|
|
303
|
+
details: error,
|
|
304
|
+
},
|
|
305
|
+
]);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Validate SpecKit content
|
|
310
|
+
*
|
|
311
|
+
* Checks for required SpecKit elements without full conversion.
|
|
312
|
+
*
|
|
313
|
+
* @param content - SpecKit content to validate
|
|
314
|
+
* @param options - Validation options
|
|
315
|
+
* @returns Validation result
|
|
316
|
+
*/
|
|
317
|
+
async validate(content, _options) {
|
|
318
|
+
const issues = [];
|
|
319
|
+
// Check for minimum content length
|
|
320
|
+
if (content.trim().length < 100) {
|
|
321
|
+
issues.push({
|
|
322
|
+
code: 'CONTENT_TOO_SHORT',
|
|
323
|
+
path: 'content',
|
|
324
|
+
message: 'Content is too short to be a valid SpecKit document',
|
|
325
|
+
severity: 'error',
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
// Analyze content for SpecKit indicators
|
|
329
|
+
const indicators = this.analyzeContent(content);
|
|
330
|
+
const confidence = this.calculateConfidence(indicators);
|
|
331
|
+
// Low confidence suggests invalid SpecKit format
|
|
332
|
+
if (confidence < 50) {
|
|
333
|
+
issues.push({
|
|
334
|
+
code: 'LOW_CONFIDENCE',
|
|
335
|
+
path: 'content',
|
|
336
|
+
message: `Content does not appear to be a valid SpecKit document (confidence: ${confidence}%)`,
|
|
337
|
+
severity: 'error',
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
// Check for required sections
|
|
341
|
+
if (!indicators.hasSpecificationHeader && !indicators.hasChangesSection) {
|
|
342
|
+
issues.push({
|
|
343
|
+
code: 'MISSING_REQUIRED_SECTIONS',
|
|
344
|
+
path: 'content',
|
|
345
|
+
message: 'Missing required sections (Specification header or Changes section)',
|
|
346
|
+
severity: 'error',
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
// Warn if missing recommended sections
|
|
350
|
+
if (!indicators.hasIntentSection) {
|
|
351
|
+
issues.push({
|
|
352
|
+
code: 'MISSING_INTENT',
|
|
353
|
+
path: 'content',
|
|
354
|
+
message: 'Missing recommended Intent section',
|
|
355
|
+
severity: 'warning',
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
return {
|
|
359
|
+
valid: issues.filter((i) => i.severity === 'error').length === 0,
|
|
360
|
+
issues: issues.length > 0 ? issues : undefined,
|
|
361
|
+
summary: issues.length === 0
|
|
362
|
+
? 'SpecKit document is valid'
|
|
363
|
+
: `Found ${issues.length} validation issue${issues.length > 1 ? 's' : ''}`,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Analyze content for SpecKit indicators
|
|
368
|
+
*/
|
|
369
|
+
analyzeContent(content, hint) {
|
|
370
|
+
const lowerContent = content.toLowerCase();
|
|
371
|
+
// Detect speckit.* namespace commands (e.g., /speckit.clarify, /speckit.analyze)
|
|
372
|
+
const hasSpeckitNamespace = /\b\/?speckit\.\w+\b/i.test(content);
|
|
373
|
+
// Check for AGENTS.md sibling
|
|
374
|
+
const hasAgentsMdSibling = hint?.siblingFiles?.some((f) => f.toLowerCase() === 'agents.md') ?? false;
|
|
375
|
+
return {
|
|
376
|
+
hasSpecificationHeader: /^#\s+(specification|spec)\s*$/im.test(content),
|
|
377
|
+
hasIntentSection: /^##\s+intent\s*$/im.test(content),
|
|
378
|
+
hasOverviewSection: /^##\s+overview\s*$/im.test(content),
|
|
379
|
+
hasGoalsSection: /^##\s+goals?\s*$/im.test(content),
|
|
380
|
+
hasRequirementsSection: /^##\s+requirements?\s*$/im.test(content),
|
|
381
|
+
hasChangesSection: /^##\s+changes?\s*$/im.test(content),
|
|
382
|
+
hasFilesToCreateSection: lowerContent.includes('files to create') || lowerContent.includes('create file'),
|
|
383
|
+
hasFilesToUpdateSection: lowerContent.includes('files to update') || lowerContent.includes('update file'),
|
|
384
|
+
hasCodeBlocks: /```[\s\S]*?```/.test(content),
|
|
385
|
+
sectionCount: (content.match(/^##\s+/gim) || []).length,
|
|
386
|
+
hasSpeckitNamespace,
|
|
387
|
+
hasAgentsMdSibling,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Calculate confidence score
|
|
392
|
+
*/
|
|
393
|
+
calculateConfidence(indicators) {
|
|
394
|
+
let score = 0;
|
|
395
|
+
// Specification header (20 points)
|
|
396
|
+
if (indicators.hasSpecificationHeader) {
|
|
397
|
+
score += 20;
|
|
398
|
+
}
|
|
399
|
+
// Intent section (15 points)
|
|
400
|
+
if (indicators.hasIntentSection) {
|
|
401
|
+
score += 15;
|
|
402
|
+
}
|
|
403
|
+
// Overview section (10 points)
|
|
404
|
+
if (indicators.hasOverviewSection) {
|
|
405
|
+
score += 10;
|
|
406
|
+
}
|
|
407
|
+
// Goals section (10 points)
|
|
408
|
+
if (indicators.hasGoalsSection) {
|
|
409
|
+
score += 10;
|
|
410
|
+
}
|
|
411
|
+
// Requirements section (10 points)
|
|
412
|
+
if (indicators.hasRequirementsSection) {
|
|
413
|
+
score += 10;
|
|
414
|
+
}
|
|
415
|
+
// Changes section (20 points)
|
|
416
|
+
if (indicators.hasChangesSection) {
|
|
417
|
+
score += 20;
|
|
418
|
+
}
|
|
419
|
+
// Files to Create/Update sections (10 points)
|
|
420
|
+
if (indicators.hasFilesToCreateSection || indicators.hasFilesToUpdateSection) {
|
|
421
|
+
score += 10;
|
|
422
|
+
}
|
|
423
|
+
// Code blocks (5 points)
|
|
424
|
+
if (indicators.hasCodeBlocks) {
|
|
425
|
+
score += 5;
|
|
426
|
+
}
|
|
427
|
+
// speckit.* namespace commands (10 points)
|
|
428
|
+
if (indicators.hasSpeckitNamespace) {
|
|
429
|
+
score += 10;
|
|
430
|
+
}
|
|
431
|
+
// AGENTS.md sibling (15 points)
|
|
432
|
+
if (indicators.hasAgentsMdSibling) {
|
|
433
|
+
score += 15;
|
|
434
|
+
}
|
|
435
|
+
// Bonus: If has both Specification header AND Intent section, ensure at least 50% confidence
|
|
436
|
+
// This accommodates minimal but valid SpecKit documents
|
|
437
|
+
if (indicators.hasSpecificationHeader && indicators.hasIntentSection && score < 50) {
|
|
438
|
+
score = 50;
|
|
439
|
+
}
|
|
440
|
+
return Math.min(100, score);
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Build detection reason message
|
|
444
|
+
*/
|
|
445
|
+
buildDetectionReason(indicators) {
|
|
446
|
+
const reasons = [];
|
|
447
|
+
if (indicators.hasSpecificationHeader) {
|
|
448
|
+
reasons.push('specification-header');
|
|
449
|
+
}
|
|
450
|
+
if (indicators.hasIntentSection) {
|
|
451
|
+
reasons.push('intent-section');
|
|
452
|
+
}
|
|
453
|
+
if (indicators.hasGoalsSection) {
|
|
454
|
+
reasons.push('goals-section');
|
|
455
|
+
}
|
|
456
|
+
if (indicators.hasRequirementsSection) {
|
|
457
|
+
reasons.push('requirements-section');
|
|
458
|
+
}
|
|
459
|
+
if (indicators.hasChangesSection) {
|
|
460
|
+
reasons.push('changes-section');
|
|
461
|
+
}
|
|
462
|
+
if (indicators.hasFilesToCreateSection || indicators.hasFilesToUpdateSection) {
|
|
463
|
+
reasons.push('file-changes');
|
|
464
|
+
}
|
|
465
|
+
if (indicators.hasCodeBlocks) {
|
|
466
|
+
reasons.push('code-blocks');
|
|
467
|
+
}
|
|
468
|
+
if (indicators.hasSpeckitNamespace) {
|
|
469
|
+
reasons.push('speckit-namespace');
|
|
470
|
+
}
|
|
471
|
+
if (indicators.hasAgentsMdSibling) {
|
|
472
|
+
reasons.push('agents-md');
|
|
473
|
+
}
|
|
474
|
+
if (indicators.sectionCount >= 3) {
|
|
475
|
+
reasons.push(`${indicators.sectionCount} sections`);
|
|
476
|
+
}
|
|
477
|
+
return reasons.length > 0 ? reasons.join(', ') : 'no strong indicators';
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Create a new SpecKit format adapter instance
|
|
482
|
+
*
|
|
483
|
+
* @param options - Adapter options
|
|
484
|
+
* @returns SpecKit adapter instance
|
|
485
|
+
*/
|
|
486
|
+
export function createSpecKitAdapter(options) {
|
|
487
|
+
return new SpecKitFormatAdapter(options);
|
|
488
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SpecKit Import Adapter v2 - Official Format
|
|
3
|
+
*
|
|
4
|
+
* Supports official GitHub spec-kit format:
|
|
5
|
+
* - spec.md: Requirements and user scenarios (WHAT and WHY)
|
|
6
|
+
* - plan.md: Technical implementation details (HOW)
|
|
7
|
+
* - tasks.md: Executable task breakdown
|
|
8
|
+
*
|
|
9
|
+
* Can import individual files or complete feature directories
|
|
10
|
+
*/
|
|
11
|
+
import { type APSPlan } from '@eddacraft/anvil-core';
|
|
12
|
+
import type { AdapterConfig, ConversionResult, ExternalSpec, SpecContext } from '../common/types.js';
|
|
13
|
+
import { BaseAdapter } from '../common/types.js';
|
|
14
|
+
export declare class SpecKitImportAdapterV2 extends BaseAdapter {
|
|
15
|
+
readonly name = "speckit-import-v2";
|
|
16
|
+
readonly version = "2.0.0";
|
|
17
|
+
readonly supportedFormats: readonly ["speckit", "spec-kit", "spec.md", "plan.md", "tasks.md"];
|
|
18
|
+
private specParser;
|
|
19
|
+
private planParser;
|
|
20
|
+
private tasksParser;
|
|
21
|
+
constructor(config?: AdapterConfig);
|
|
22
|
+
generateSpec(intent: string, context: SpecContext): Promise<APSPlan>;
|
|
23
|
+
validateSpec(spec: APSPlan): Promise<import('@eddacraft/anvil-core').ValidationResult>;
|
|
24
|
+
convertToAPS(spec: ExternalSpec): Promise<ConversionResult<APSPlan>>;
|
|
25
|
+
convertFromAPS(_spec: APSPlan): Promise<ConversionResult<ExternalSpec>>;
|
|
26
|
+
private buildAPSFromDocs;
|
|
27
|
+
private buildIntent;
|
|
28
|
+
private buildProposedChanges;
|
|
29
|
+
private inferPathFromScenario;
|
|
30
|
+
private inferAPIPath;
|
|
31
|
+
private generateSpecTemplate;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=import-v2.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import-v2.d.ts","sourceRoot":"","sources":["../../src/speckit/import-v2.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAc,KAAK,OAAO,EAAgC,MAAM,uBAAuB,CAAC;AAC/F,OAAO,KAAK,EACV,aAAa,EAEb,gBAAgB,EAEhB,YAAY,EACZ,WAAW,EACZ,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAqBjD,qBAAa,sBAAuB,SAAQ,WAAW;IACrD,QAAQ,CAAC,IAAI,uBAAuB;IACpC,QAAQ,CAAC,OAAO,WAAW;IAC3B,QAAQ,CAAC,gBAAgB,qEAAsE;IAE/F,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,WAAW,CAAc;gBAErB,MAAM,GAAE,aAAkB;IAOhC,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IAmCpE,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,uBAAuB,EAAE,gBAAgB,CAAC;IAuCtF,YAAY,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAgEpE,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,gBAAgB,CAAC,YAAY,CAAC,CAAC;IAY7E,OAAO,CAAC,gBAAgB;IA8DxB,OAAO,CAAC,WAAW;IAgBnB,OAAO,CAAC,oBAAoB;IAqF5B,OAAO,CAAC,qBAAqB;IAiB7B,OAAO,CAAC,YAAY;IAUpB,OAAO,CAAC,oBAAoB;CA+C7B"}
|