@hstm-labs/forge-spec-parser 0.1.2 → 0.1.3

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/dist/index.d.ts CHANGED
@@ -2,7 +2,9 @@ export type { SpecFormat, ParsedSpecification, SpecMetadata, SpecSection, SpecRe
2
2
  export type { SpecParserPlugin } from './parser-plugin.js';
3
3
  export { ParserRegistry } from './registry.js';
4
4
  export { detectFormat } from './detect.js';
5
- export { loadSpecFile, parseSpec } from './loader.js';
5
+ export { loadSpecFile, parseSpec, parseSpecPath } from './loader.js';
6
+ export { resolveSpecFiles } from './resolve.js';
7
+ export { mergeSpecs } from './merge.js';
6
8
  export { MarkdownParserPlugin } from './parsers/markdown-parser.js';
7
9
  export { YamlParserPlugin } from './parsers/yaml-parser.js';
8
10
  export { JsonParserPlugin } from './parsers/json-parser.js';
@@ -14,5 +16,6 @@ export { checkCompleteness } from './validators/completeness-checker.js';
14
16
  export type { CompletenessResult } from './validators/completeness-checker.js';
15
17
  export { validateCrossReferences } from './validators/cross-reference-validator.js';
16
18
  export { validateSpec } from './validators/spec-validator.js';
17
- export { executeValidateStage } from './validate-stage.js';
19
+ export { executeValidateStage, executeValidateStageWithSpec } from './validate-stage.js';
20
+ export type { ValidateStageResult } from './validate-stage.js';
18
21
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,YAAY,EACV,UAAU,EACV,mBAAmB,EACnB,YAAY,EACZ,WAAW,EACX,eAAe,EACf,UAAU,EACV,WAAW,EACX,kBAAkB,EAClB,YAAY,EACZ,YAAY,GACb,MAAM,YAAY,CAAC;AAGpB,YAAY,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAG3D,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAG/C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAG5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAGnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAG9D,YAAY,EACV,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AACzE,YAAY,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAC/E,OAAO,EAAE,uBAAuB,EAAE,MAAM,2CAA2C,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAG9D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,YAAY,EACV,UAAU,EACV,mBAAmB,EACnB,YAAY,EACZ,WAAW,EACX,eAAe,EACf,UAAU,EACV,WAAW,EACX,kBAAkB,EAClB,YAAY,EACZ,YAAY,GACb,MAAM,YAAY,CAAC;AAGpB,YAAY,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAG3D,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAG/C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGhD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAGxC,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAG5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAGnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAG9D,YAAY,EACV,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AACzE,YAAY,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAC/E,OAAO,EAAE,uBAAuB,EAAE,MAAM,2CAA2C,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAG9D,OAAO,EAAE,oBAAoB,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AACzF,YAAY,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC"}
package/dist/index.js CHANGED
@@ -4,7 +4,11 @@ export { ParserRegistry } from './registry.js';
4
4
  // Format detection
5
5
  export { detectFormat } from './detect.js';
6
6
  // File loading and parse entry point
7
- export { loadSpecFile, parseSpec } from './loader.js';
7
+ export { loadSpecFile, parseSpec, parseSpecPath } from './loader.js';
8
+ // Spec path resolution (file or directory)
9
+ export { resolveSpecFiles } from './resolve.js';
10
+ // Multi-file merge
11
+ export { mergeSpecs } from './merge.js';
8
12
  // Parser plugins
9
13
  export { MarkdownParserPlugin } from './parsers/markdown-parser.js';
10
14
  export { YamlParserPlugin } from './parsers/yaml-parser.js';
@@ -19,5 +23,5 @@ export { checkCompleteness } from './validators/completeness-checker.js';
19
23
  export { validateCrossReferences } from './validators/cross-reference-validator.js';
20
24
  export { validateSpec } from './validators/spec-validator.js';
21
25
  // Validate stage integration
22
- export { executeValidateStage } from './validate-stage.js';
26
+ export { executeValidateStage, executeValidateStageWithSpec } from './validate-stage.js';
23
27
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAmB1C,kBAAkB;AAClB,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,mBAAmB;AACnB,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,qCAAqC;AACrC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAEtD,iBAAiB;AACjB,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAE5D,2BAA2B;AAC3B,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAEnE,2BAA2B;AAC3B,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAU9D,aAAa;AACb,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AAEzE,OAAO,EAAE,uBAAuB,EAAE,MAAM,2CAA2C,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAE9D,6BAA6B;AAC7B,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAmB1C,kBAAkB;AAClB,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,mBAAmB;AACnB,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,qCAAqC;AACrC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAErE,2CAA2C;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD,mBAAmB;AACnB,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,iBAAiB;AACjB,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAE5D,2BAA2B;AAC3B,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAEnE,2BAA2B;AAC3B,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAU9D,aAAa;AACb,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AAEzE,OAAO,EAAE,uBAAuB,EAAE,MAAM,2CAA2C,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAE9D,6BAA6B;AAC7B,OAAO,EAAE,oBAAoB,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC"}
package/dist/loader.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Specification file loader and primary parse entry point.
3
3
  *
4
- * Provides {@link loadSpecFile} for reading raw file content and
5
- * {@link parseSpec} as the main public API for parsing a specification
6
- * file into the canonical {@link ParsedSpecification} model.
4
+ * Provides {@link loadSpecFile} for reading raw file content,
5
+ * {@link parseSpec} for parsing a single file, and
6
+ * {@link parseSpecPath} as the high-level API that handles both
7
+ * file and directory `specPath` values.
7
8
  */
8
9
  import type { ParsedSpecification } from './types.js';
9
10
  import type { ParserRegistry } from './registry.js';
@@ -28,4 +29,18 @@ export declare function loadSpecFile(filePath: string): string;
28
29
  * @throws {@link ForgeError} with code `FORGE-SPEC-001` if format detection, file reading, or parsing fails
29
30
  */
30
31
  export declare function parseSpec(filePath: string, registry: ParserRegistry): ParsedSpecification;
32
+ /**
33
+ * Parse a specification path that may be a single file or a directory.
34
+ *
35
+ * When `specPath` is a directory, all supported spec files within it
36
+ * (recursively) are parsed and merged into a single
37
+ * {@link ParsedSpecification}.
38
+ *
39
+ * @param specPath - Path to a spec file or directory containing spec files
40
+ * @param registry - Parser registry with registered format plugins
41
+ * @returns Parsed (and possibly merged) specification
42
+ * @throws {@link ForgeError} `FORGE-SPEC-003` if path does not exist or directory is empty
43
+ * @throws {@link ForgeError} `FORGE-SPEC-001` if any file cannot be parsed
44
+ */
45
+ export declare function parseSpecPath(specPath: string, registry: ParserRegistry): ParsedSpecification;
31
46
  //# sourceMappingURL=loader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAWrD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CACvB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,cAAc,GACvB,mBAAmB,CAKrB"}
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAIpD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAWrD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CACvB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,cAAc,GACvB,mBAAmB,CAKrB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,cAAc,GACvB,mBAAmB,CASrB"}
package/dist/loader.js CHANGED
@@ -1,13 +1,16 @@
1
1
  /**
2
2
  * Specification file loader and primary parse entry point.
3
3
  *
4
- * Provides {@link loadSpecFile} for reading raw file content and
5
- * {@link parseSpec} as the main public API for parsing a specification
6
- * file into the canonical {@link ParsedSpecification} model.
4
+ * Provides {@link loadSpecFile} for reading raw file content,
5
+ * {@link parseSpec} for parsing a single file, and
6
+ * {@link parseSpecPath} as the high-level API that handles both
7
+ * file and directory `specPath` values.
7
8
  */
8
9
  import fs from 'node:fs';
9
10
  import { ForgeError, ErrorCodes } from '@hstm-labs/forge-common';
10
11
  import { detectFormat } from './detect.js';
12
+ import { resolveSpecFiles } from './resolve.js';
13
+ import { mergeSpecs } from './merge.js';
11
14
  /**
12
15
  * Read the content of a specification file from disk.
13
16
  *
@@ -42,4 +45,25 @@ export function parseSpec(filePath, registry) {
42
45
  const content = loadSpecFile(filePath);
43
46
  return plugin.parse(content, filePath);
44
47
  }
48
+ /**
49
+ * Parse a specification path that may be a single file or a directory.
50
+ *
51
+ * When `specPath` is a directory, all supported spec files within it
52
+ * (recursively) are parsed and merged into a single
53
+ * {@link ParsedSpecification}.
54
+ *
55
+ * @param specPath - Path to a spec file or directory containing spec files
56
+ * @param registry - Parser registry with registered format plugins
57
+ * @returns Parsed (and possibly merged) specification
58
+ * @throws {@link ForgeError} `FORGE-SPEC-003` if path does not exist or directory is empty
59
+ * @throws {@link ForgeError} `FORGE-SPEC-001` if any file cannot be parsed
60
+ */
61
+ export function parseSpecPath(specPath, registry) {
62
+ const files = resolveSpecFiles(specPath);
63
+ if (files.length === 1) {
64
+ return parseSpec(files[0], registry);
65
+ }
66
+ const specs = files.map((f) => parseSpec(f, registry));
67
+ return mergeSpecs(specs, specPath);
68
+ }
45
69
  //# sourceMappingURL=loader.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"loader.js","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAEjE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,QAAgB;IAC3C,IAAI,CAAC;QACH,OAAO,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC5C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,UAAU,CAClB,UAAU,CAAC,IAAI,CAAC,aAAa,EAC7B,sCAAsC,QAAQ,KAAK;YACjD,qDAAqD,EACvD,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAC/C,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,SAAS,CACvB,QAAgB,EAChB,QAAwB;IAExB,MAAM,MAAM,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IACtC,MAAM,MAAM,GAAG,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAC1C,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IACvC,OAAO,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AACzC,CAAC"}
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAEjE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,QAAgB;IAC3C,IAAI,CAAC;QACH,OAAO,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC5C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,UAAU,CAClB,UAAU,CAAC,IAAI,CAAC,aAAa,EAC7B,sCAAsC,QAAQ,KAAK;YACjD,qDAAqD,EACvD,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAC/C,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,SAAS,CACvB,QAAgB,EAChB,QAAwB;IAExB,MAAM,MAAM,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IACtC,MAAM,MAAM,GAAG,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAC1C,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IACvC,OAAO,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AACzC,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,aAAa,CAC3B,QAAgB,EAChB,QAAwB;IAExB,MAAM,KAAK,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAEzC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC,CAAE,EAAE,QAAQ,CAAC,CAAC;IACxC,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;IACvD,OAAO,UAAU,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;AACrC,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Multi-file specification merger.
3
+ *
4
+ * Combines multiple {@link ParsedSpecification} objects into a single
5
+ * unified specification. Used when `specPath` points to a directory
6
+ * containing multiple spec files.
7
+ */
8
+ import type { ParsedSpecification } from './types.js';
9
+ /**
10
+ * Merge multiple parsed specifications into one.
11
+ *
12
+ * - Metadata: titles are joined; `sourcePath` reflects the directory.
13
+ * - Sections, requirements, workflows: concatenated in order.
14
+ * - Entities: deduplicated by name (first occurrence wins).
15
+ * - API style: first non-undefined value wins.
16
+ *
17
+ * @param specs - Array of parsed specifications (must have at least one)
18
+ * @param sourcePath - Path to attribute as the merged spec's source
19
+ * @returns A single merged specification
20
+ * @throws {Error} if `specs` is empty
21
+ */
22
+ export declare function mergeSpecs(specs: ParsedSpecification[], sourcePath: string): ParsedSpecification;
23
+ //# sourceMappingURL=merge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge.d.ts","sourceRoot":"","sources":["../src/merge.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAgB,MAAM,YAAY,CAAC;AAEpE;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,mBAAmB,EAAE,EAC5B,UAAU,EAAE,MAAM,GACjB,mBAAmB,CAyCrB"}
package/dist/merge.js ADDED
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Multi-file specification merger.
3
+ *
4
+ * Combines multiple {@link ParsedSpecification} objects into a single
5
+ * unified specification. Used when `specPath` points to a directory
6
+ * containing multiple spec files.
7
+ */
8
+ /**
9
+ * Merge multiple parsed specifications into one.
10
+ *
11
+ * - Metadata: titles are joined; `sourcePath` reflects the directory.
12
+ * - Sections, requirements, workflows: concatenated in order.
13
+ * - Entities: deduplicated by name (first occurrence wins).
14
+ * - API style: first non-undefined value wins.
15
+ *
16
+ * @param specs - Array of parsed specifications (must have at least one)
17
+ * @param sourcePath - Path to attribute as the merged spec's source
18
+ * @returns A single merged specification
19
+ * @throws {Error} if `specs` is empty
20
+ */
21
+ export function mergeSpecs(specs, sourcePath) {
22
+ if (specs.length === 0) {
23
+ throw new Error('Cannot merge zero specifications.');
24
+ }
25
+ const first = specs[0];
26
+ if (specs.length === 1) {
27
+ return first;
28
+ }
29
+ const metadata = {
30
+ title: specs.map((s) => s.metadata.title).join(' + '),
31
+ sourcePath,
32
+ format: first.metadata.format,
33
+ description: specs
34
+ .map((s) => s.metadata.description)
35
+ .filter(Boolean)
36
+ .join('; ') || undefined,
37
+ };
38
+ const seenEntities = new Set();
39
+ const entities = specs.flatMap((s) => s.entities).filter((e) => {
40
+ if (seenEntities.has(e.name)) {
41
+ return false;
42
+ }
43
+ seenEntities.add(e.name);
44
+ return true;
45
+ });
46
+ const apiStyle = specs.find((s) => s.apiStyle !== undefined)?.apiStyle;
47
+ return {
48
+ metadata,
49
+ sections: specs.flatMap((s) => s.sections),
50
+ requirements: specs.flatMap((s) => s.requirements),
51
+ entities,
52
+ workflows: specs.flatMap((s) => s.workflows),
53
+ ...(apiStyle !== undefined ? { apiStyle } : {}),
54
+ };
55
+ }
56
+ //# sourceMappingURL=merge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge.js","sourceRoot":"","sources":["../src/merge.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,UAAU,CACxB,KAA4B,EAC5B,UAAkB;IAElB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACvD,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;IAExB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,QAAQ,GAAiB;QAC7B,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC;QACrD,UAAU;QACV,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,MAAM;QAC7B,WAAW,EACT,KAAK;aACF,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC;aAClC,MAAM,CAAC,OAAO,CAAC;aACf,IAAI,CAAC,IAAI,CAAC,IAAI,SAAS;KAC7B,CAAC;IAEF,MAAM,YAAY,GAAG,IAAI,GAAG,EAAU,CAAC;IACvC,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;QAC7D,IAAI,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7B,OAAO,KAAK,CAAC;QACf,CAAC;QACD,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACzB,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,EAAE,QAAQ,CAAC;IAEvE,OAAO;QACL,QAAQ;QACR,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;QAC1C,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC;QAClD,QAAQ;QACR,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QAC5C,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAChD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Spec path resolution — handles both file and directory `specPath` values.
3
+ *
4
+ * When `specPath` is a directory, recursively scans for files with supported
5
+ * extensions (`.md`, `.yaml`, `.yml`, `.json`) and returns them in sorted
6
+ * order for deterministic processing.
7
+ */
8
+ /**
9
+ * Resolve a `specPath` to one or more specification file paths.
10
+ *
11
+ * - If `specPath` is a file, returns it as a single-element array.
12
+ * - If `specPath` is a directory, recursively scans for files with
13
+ * supported extensions and returns them sorted alphabetically.
14
+ *
15
+ * @param specPath - Absolute or relative path to a spec file or directory
16
+ * @returns Sorted array of resolved spec file paths
17
+ * @throws {@link ForgeError} `FORGE-SPEC-003` if the path does not exist or
18
+ * the directory contains no supported spec files
19
+ */
20
+ export declare function resolveSpecFiles(specPath: string): string[];
21
+ //# sourceMappingURL=resolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAcH;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAgC3D"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Spec path resolution — handles both file and directory `specPath` values.
3
+ *
4
+ * When `specPath` is a directory, recursively scans for files with supported
5
+ * extensions (`.md`, `.yaml`, `.yml`, `.json`) and returns them in sorted
6
+ * order for deterministic processing.
7
+ */
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import { ForgeError, ErrorCodes } from '@hstm-labs/forge-common';
11
+ /** File extensions accepted as specification files. */
12
+ const SUPPORTED_EXTENSIONS = new Set([
13
+ '.md',
14
+ '.yaml',
15
+ '.yml',
16
+ '.json',
17
+ ]);
18
+ /**
19
+ * Resolve a `specPath` to one or more specification file paths.
20
+ *
21
+ * - If `specPath` is a file, returns it as a single-element array.
22
+ * - If `specPath` is a directory, recursively scans for files with
23
+ * supported extensions and returns them sorted alphabetically.
24
+ *
25
+ * @param specPath - Absolute or relative path to a spec file or directory
26
+ * @returns Sorted array of resolved spec file paths
27
+ * @throws {@link ForgeError} `FORGE-SPEC-003` if the path does not exist or
28
+ * the directory contains no supported spec files
29
+ */
30
+ export function resolveSpecFiles(specPath) {
31
+ const stat = fs.statSync(specPath, { throwIfNoEntry: false });
32
+ if (!stat) {
33
+ throw new ForgeError(ErrorCodes.SPEC.NOT_FOUND, `Specification path '${specPath}' does not exist. ` +
34
+ `Verify the specPath in forge.config.json and retry.`);
35
+ }
36
+ if (stat.isFile()) {
37
+ return [specPath];
38
+ }
39
+ if (stat.isDirectory()) {
40
+ const files = scanDirectory(specPath);
41
+ if (files.length === 0) {
42
+ throw new ForgeError(ErrorCodes.SPEC.NOT_FOUND, `No specification files found in directory '${specPath}'. ` +
43
+ `Add .md, .yaml, .yml, or .json files to the directory and retry.`);
44
+ }
45
+ return files.sort();
46
+ }
47
+ throw new ForgeError(ErrorCodes.SPEC.NOT_FOUND, `Specification path '${specPath}' is not a file or directory. ` +
48
+ `Verify the specPath in forge.config.json and retry.`);
49
+ }
50
+ /**
51
+ * Recursively scan a directory for files with supported spec extensions.
52
+ */
53
+ function scanDirectory(dirPath) {
54
+ const results = [];
55
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
56
+ for (const entry of entries) {
57
+ const fullPath = path.join(dirPath, entry.name);
58
+ if (entry.isDirectory()) {
59
+ results.push(...scanDirectory(fullPath));
60
+ }
61
+ else if (entry.isFile()) {
62
+ const ext = path.extname(entry.name).toLowerCase();
63
+ if (SUPPORTED_EXTENSIONS.has(ext)) {
64
+ results.push(fullPath);
65
+ }
66
+ }
67
+ }
68
+ return results;
69
+ }
70
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAEjE,uDAAuD;AACvD,MAAM,oBAAoB,GAAwB,IAAI,GAAG,CAAC;IACxD,KAAK;IACL,OAAO;IACP,MAAM;IACN,OAAO;CACR,CAAC,CAAC;AAEH;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,CAAC;IAE9D,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,UAAU,CAClB,UAAU,CAAC,IAAI,CAAC,SAAS,EACzB,uBAAuB,QAAQ,oBAAoB;YACjD,qDAAqD,CACxD,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;QAClB,OAAO,CAAC,QAAQ,CAAC,CAAC;IACpB,CAAC;IAED,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;QACvB,MAAM,KAAK,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;QACtC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,UAAU,CAClB,UAAU,CAAC,IAAI,CAAC,SAAS,EACzB,8CAA8C,QAAQ,KAAK;gBACzD,kEAAkE,CACrE,CAAC;QACJ,CAAC;QACD,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;IACtB,CAAC;IAED,MAAM,IAAI,UAAU,CAClB,UAAU,CAAC,IAAI,CAAC,SAAS,EACzB,uBAAuB,QAAQ,gCAAgC;QAC7D,qDAAqD,CACxD,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,SAAS,aAAa,CAAC,OAAe;IACpC,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAEjE,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAChD,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,OAAO,CAAC,IAAI,CAAC,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC3C,CAAC;aAAM,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;YAC1B,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;YACnD,IAAI,oBAAoB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBAClC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACzB,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -5,14 +5,34 @@
5
5
  * This function will be called by the pipeline runner when executing the
6
6
  * `validate` stage of a generation run.
7
7
  */
8
+ import type { ParsedSpecification } from './types.js';
8
9
  import type { ValidationReport } from './validation-types.js';
10
+ /** Result of the validate stage containing both the report and parsed spec. */
11
+ export interface ValidateStageResult {
12
+ /** Validation report with findings and completeness score. */
13
+ report: ValidationReport;
14
+ /** The parsed (and possibly merged) specification. */
15
+ parsedSpec: ParsedSpecification;
16
+ }
9
17
  /**
10
- * Execute the validate pipeline stage: parse a specification file and
11
- * validate the result.
18
+ * Execute the validate pipeline stage: parse a specification path
19
+ * (file or directory) and validate the result.
12
20
  *
13
- * @param specPath - Path to the specification file
21
+ * @param specPath - Path to a specification file or directory
14
22
  * @returns Validation report for the parsed specification
15
- * @throws {@link ForgeError} if the file cannot be read or parsed
23
+ * @throws {@link ForgeError} if the path cannot be resolved, read, or parsed
16
24
  */
17
25
  export declare function executeValidateStage(specPath: string): ValidationReport;
26
+ /**
27
+ * Execute the validate pipeline stage and return both the validation
28
+ * report and parsed specification.
29
+ *
30
+ * Avoids double-parsing when the caller also needs the parsed spec
31
+ * (e.g. the core pipeline `ValidateStage`).
32
+ *
33
+ * @param specPath - Path to a specification file or directory
34
+ * @returns Validation report and parsed specification
35
+ * @throws {@link ForgeError} if the path cannot be resolved, read, or parsed
36
+ */
37
+ export declare function executeValidateStageWithSpec(specPath: string): ValidateStageResult;
18
38
  //# sourceMappingURL=validate-stage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validate-stage.d.ts","sourceRoot":"","sources":["../src/validate-stage.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAK9D;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,GACf,gBAAgB,CAIlB"}
1
+ {"version":3,"file":"validate-stage.d.ts","sourceRoot":"","sources":["../src/validate-stage.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAK9D,+EAA+E;AAC/E,MAAM,WAAW,mBAAmB;IAClC,8DAA8D;IAC9D,MAAM,EAAE,gBAAgB,CAAC;IACzB,sDAAsD;IACtD,UAAU,EAAE,mBAAmB,CAAC;CACjC;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,GACf,gBAAgB,CAGlB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,MAAM,GACf,mBAAmB,CAKrB"}
@@ -6,19 +6,35 @@
6
6
  * `validate` stage of a generation run.
7
7
  */
8
8
  import { createDefaultRegistry } from './default-registry.js';
9
- import { parseSpec } from './loader.js';
9
+ import { parseSpecPath } from './loader.js';
10
10
  import { validateSpec } from './validators/spec-validator.js';
11
11
  /**
12
- * Execute the validate pipeline stage: parse a specification file and
13
- * validate the result.
12
+ * Execute the validate pipeline stage: parse a specification path
13
+ * (file or directory) and validate the result.
14
14
  *
15
- * @param specPath - Path to the specification file
15
+ * @param specPath - Path to a specification file or directory
16
16
  * @returns Validation report for the parsed specification
17
- * @throws {@link ForgeError} if the file cannot be read or parsed
17
+ * @throws {@link ForgeError} if the path cannot be resolved, read, or parsed
18
18
  */
19
19
  export function executeValidateStage(specPath) {
20
+ const { report } = executeValidateStageWithSpec(specPath);
21
+ return report;
22
+ }
23
+ /**
24
+ * Execute the validate pipeline stage and return both the validation
25
+ * report and parsed specification.
26
+ *
27
+ * Avoids double-parsing when the caller also needs the parsed spec
28
+ * (e.g. the core pipeline `ValidateStage`).
29
+ *
30
+ * @param specPath - Path to a specification file or directory
31
+ * @returns Validation report and parsed specification
32
+ * @throws {@link ForgeError} if the path cannot be resolved, read, or parsed
33
+ */
34
+ export function executeValidateStageWithSpec(specPath) {
20
35
  const registry = createDefaultRegistry();
21
- const spec = parseSpec(specPath, registry);
22
- return validateSpec(spec);
36
+ const parsedSpec = parseSpecPath(specPath, registry);
37
+ const report = validateSpec(parsedSpec);
38
+ return { report, parsedSpec };
23
39
  }
24
40
  //# sourceMappingURL=validate-stage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"validate-stage.js","sourceRoot":"","sources":["../src/validate-stage.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAE9D;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAClC,QAAgB;IAEhB,MAAM,QAAQ,GAAG,qBAAqB,EAAE,CAAC;IACzC,MAAM,IAAI,GAAG,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC3C,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC;AAC5B,CAAC"}
1
+ {"version":3,"file":"validate-stage.js","sourceRoot":"","sources":["../src/validate-stage.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAU9D;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAClC,QAAgB;IAEhB,MAAM,EAAE,MAAM,EAAE,GAAG,4BAA4B,CAAC,QAAQ,CAAC,CAAC;IAC1D,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,4BAA4B,CAC1C,QAAgB;IAEhB,MAAM,QAAQ,GAAG,qBAAqB,EAAE,CAAC;IACzC,MAAM,UAAU,GAAG,aAAa,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACrD,MAAM,MAAM,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC;IACxC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;AAChC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hstm-labs/forge-spec-parser",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -16,7 +16,7 @@
16
16
  "prepublishOnly": "npm run build"
17
17
  },
18
18
  "dependencies": {
19
- "@hstm-labs/forge-common": "0.1.2",
19
+ "@hstm-labs/forge-common": "0.1.3",
20
20
  "remark-frontmatter": "^5.0.0",
21
21
  "remark-gfm": "^4.0.1",
22
22
  "remark-parse": "^11.0.0",