@ido4/tech-spec-format 0.8.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/dist/cli.d.ts +19 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +169 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/spec-parser.d.ts +9 -0
- package/dist/spec-parser.d.ts.map +1 -0
- package/dist/spec-parser.js +373 -0
- package/dist/spec-parser.js.map +1 -0
- package/dist/tech-spec-validator.bundle.js +18 -0
- package/dist/types.d.ts +73 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +24 -0
- package/dist/types.js.map +1 -0
- package/package.json +48 -0
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ido4-tech-spec-format CLI — Structural validation for technical spec artifacts.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* ido4-tech-spec-format <file.md>
|
|
7
|
+
* ido4-tech-spec-format --version
|
|
8
|
+
* ido4-tech-spec-format --help
|
|
9
|
+
*
|
|
10
|
+
* Reads a technical spec markdown file, parses it, and outputs a rich JSON
|
|
11
|
+
* result to stdout. Exit code 0 = valid, 1 = structural errors found,
|
|
12
|
+
* 2 = usage/IO error.
|
|
13
|
+
*
|
|
14
|
+
* Designed for consumption by ido4specs agents — the JSON output provides full
|
|
15
|
+
* parsed structure, computed metrics, and separated errors/warnings so the
|
|
16
|
+
* agent can interpret results intelligently and suggest specific fixes.
|
|
17
|
+
*/
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;GAeG"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* ido4-tech-spec-format CLI — Structural validation for technical spec artifacts.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* ido4-tech-spec-format <file.md>
|
|
8
|
+
* ido4-tech-spec-format --version
|
|
9
|
+
* ido4-tech-spec-format --help
|
|
10
|
+
*
|
|
11
|
+
* Reads a technical spec markdown file, parses it, and outputs a rich JSON
|
|
12
|
+
* result to stdout. Exit code 0 = valid, 1 = structural errors found,
|
|
13
|
+
* 2 = usage/IO error.
|
|
14
|
+
*
|
|
15
|
+
* Designed for consumption by ido4specs agents — the JSON output provides full
|
|
16
|
+
* parsed structure, computed metrics, and separated errors/warnings so the
|
|
17
|
+
* agent can interpret results intelligently and suggest specific fixes.
|
|
18
|
+
*/
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
const node_fs_1 = require("node:fs");
|
|
21
|
+
const node_path_1 = require("node:path");
|
|
22
|
+
const spec_parser_js_1 = require("./spec-parser.js");
|
|
23
|
+
const types_js_1 = require("./types.js");
|
|
24
|
+
function getVersion() {
|
|
25
|
+
if (typeof __TECH_SPEC_FORMAT_VERSION__ === 'string') {
|
|
26
|
+
return __TECH_SPEC_FORMAT_VERSION__;
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const pkgJson = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(__dirname, '../package.json'), 'utf-8'));
|
|
30
|
+
return pkgJson.version;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return 'unknown';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const version = getVersion();
|
|
37
|
+
function main() {
|
|
38
|
+
const arg = process.argv[2];
|
|
39
|
+
if (!arg || arg === '--help' || arg === '-h') {
|
|
40
|
+
const usage = [
|
|
41
|
+
'Usage: ido4-tech-spec-format <file.md>',
|
|
42
|
+
' ido4-tech-spec-format --version',
|
|
43
|
+
'',
|
|
44
|
+
'Parses a technical spec and outputs structured JSON to stdout.',
|
|
45
|
+
'Exit codes: 0 = valid, 1 = structural errors, 2 = usage/IO error.',
|
|
46
|
+
'',
|
|
47
|
+
`Supported format versions: ${types_js_1.SUPPORTED_FORMAT_VERSIONS.join(', ')}`,
|
|
48
|
+
];
|
|
49
|
+
process.stderr.write(usage.join('\n') + '\n');
|
|
50
|
+
process.exit(arg ? 0 : 2);
|
|
51
|
+
}
|
|
52
|
+
if (arg === '--version' || arg === '-v') {
|
|
53
|
+
process.stdout.write(`${version}\n`);
|
|
54
|
+
process.exit(0);
|
|
55
|
+
}
|
|
56
|
+
const absolutePath = (0, node_path_1.resolve)(arg);
|
|
57
|
+
let content;
|
|
58
|
+
try {
|
|
59
|
+
content = (0, node_fs_1.readFileSync)(absolutePath, 'utf-8');
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
63
|
+
process.stderr.write(`Error reading file: ${message}\n`);
|
|
64
|
+
process.exit(2);
|
|
65
|
+
}
|
|
66
|
+
const startMs = Date.now();
|
|
67
|
+
const result = (0, spec_parser_js_1.parseSpec)(content);
|
|
68
|
+
const durationMs = Date.now() - startMs;
|
|
69
|
+
const errors = result.errors.filter(e => e.severity === 'error');
|
|
70
|
+
const warnings = result.errors.filter(e => e.severity === 'warning');
|
|
71
|
+
const allTasks = [
|
|
72
|
+
...result.groups.flatMap(g => g.tasks),
|
|
73
|
+
...result.orphanTasks,
|
|
74
|
+
];
|
|
75
|
+
const output = {
|
|
76
|
+
valid: errors.length === 0,
|
|
77
|
+
meta: {
|
|
78
|
+
file: absolutePath,
|
|
79
|
+
parseDurationMs: durationMs,
|
|
80
|
+
parserVersion: version,
|
|
81
|
+
supportedFormatVersions: types_js_1.SUPPORTED_FORMAT_VERSIONS,
|
|
82
|
+
// schemaVersion describes the shape of THIS JSON output, not the
|
|
83
|
+
// technical-spec format version. Consumers can key their JSON-parsing
|
|
84
|
+
// logic off this field so breaking changes to the CLI output shape
|
|
85
|
+
// become explicit signals rather than silent drift.
|
|
86
|
+
schemaVersion: '1.0',
|
|
87
|
+
},
|
|
88
|
+
metrics: {
|
|
89
|
+
groupCount: result.groups.length,
|
|
90
|
+
taskCount: allTasks.length,
|
|
91
|
+
orphanTaskCount: result.orphanTasks.length,
|
|
92
|
+
dependencyEdgeCount: allTasks.reduce((sum, t) => sum + t.dependsOn.length, 0),
|
|
93
|
+
maxDependencyDepth: computeMaxDepth(allTasks),
|
|
94
|
+
successConditionCount: allTasks.reduce((sum, t) => sum + t.successConditions.length, 0),
|
|
95
|
+
errorCount: errors.length,
|
|
96
|
+
warningCount: warnings.length,
|
|
97
|
+
},
|
|
98
|
+
project: result.project,
|
|
99
|
+
groups: result.groups.map(g => ({
|
|
100
|
+
name: g.name,
|
|
101
|
+
prefix: g.prefix,
|
|
102
|
+
size: g.size,
|
|
103
|
+
risk: g.risk,
|
|
104
|
+
description: g.description,
|
|
105
|
+
taskCount: g.tasks.length,
|
|
106
|
+
tasks: g.tasks.map(t => ({
|
|
107
|
+
ref: t.ref,
|
|
108
|
+
title: t.title,
|
|
109
|
+
effort: t.effort,
|
|
110
|
+
risk: t.risk,
|
|
111
|
+
taskType: t.taskType,
|
|
112
|
+
aiSuitability: t.aiSuitability,
|
|
113
|
+
dependsOn: t.dependsOn,
|
|
114
|
+
successConditions: t.successConditions,
|
|
115
|
+
groupName: t.groupName,
|
|
116
|
+
})),
|
|
117
|
+
})),
|
|
118
|
+
orphanTasks: result.orphanTasks.map(t => ({
|
|
119
|
+
ref: t.ref,
|
|
120
|
+
title: t.title,
|
|
121
|
+
effort: t.effort,
|
|
122
|
+
risk: t.risk,
|
|
123
|
+
taskType: t.taskType,
|
|
124
|
+
aiSuitability: t.aiSuitability,
|
|
125
|
+
dependsOn: t.dependsOn,
|
|
126
|
+
successConditions: t.successConditions,
|
|
127
|
+
})),
|
|
128
|
+
dependencyGraph: buildDependencyGraph(allTasks),
|
|
129
|
+
errors: errors.map(e => ({ line: e.line, message: e.message })),
|
|
130
|
+
warnings: warnings.map(e => ({ line: e.line, message: e.message })),
|
|
131
|
+
};
|
|
132
|
+
process.stdout.write(JSON.stringify(output, null, 2) + '\n');
|
|
133
|
+
process.exit(errors.length > 0 ? 1 : 0);
|
|
134
|
+
}
|
|
135
|
+
function buildDependencyGraph(tasks) {
|
|
136
|
+
const graph = {};
|
|
137
|
+
for (const task of tasks) {
|
|
138
|
+
if (task.dependsOn.length > 0) {
|
|
139
|
+
graph[task.ref] = task.dependsOn;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return graph;
|
|
143
|
+
}
|
|
144
|
+
function computeMaxDepth(tasks) {
|
|
145
|
+
const depMap = new Map();
|
|
146
|
+
for (const task of tasks) {
|
|
147
|
+
depMap.set(task.ref, task.dependsOn);
|
|
148
|
+
}
|
|
149
|
+
const cache = new Map();
|
|
150
|
+
function depth(ref, visited) {
|
|
151
|
+
if (cache.has(ref))
|
|
152
|
+
return cache.get(ref);
|
|
153
|
+
if (visited.has(ref))
|
|
154
|
+
return 0;
|
|
155
|
+
visited.add(ref);
|
|
156
|
+
const deps = depMap.get(ref) ?? [];
|
|
157
|
+
const maxChildDepth = deps.reduce((max, dep) => Math.max(max, depMap.has(dep) ? depth(dep, visited) : 0), 0);
|
|
158
|
+
const result = deps.length > 0 ? maxChildDepth + 1 : 0;
|
|
159
|
+
cache.set(ref, result);
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
let maxDepth = 0;
|
|
163
|
+
for (const task of tasks) {
|
|
164
|
+
maxDepth = Math.max(maxDepth, depth(task.ref, new Set()));
|
|
165
|
+
}
|
|
166
|
+
return maxDepth;
|
|
167
|
+
}
|
|
168
|
+
main();
|
|
169
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ido4/tech-spec-format — Technical spec format parser.
|
|
3
|
+
*
|
|
4
|
+
* Zero-runtime-dependency package (aside from the peer parser utilities shared
|
|
5
|
+
* with @ido4/spec-format) for parsing and validating technical spec artifacts
|
|
6
|
+
* produced by ido4specs. Consumed by @ido4/core for MCP-side ingestion and by
|
|
7
|
+
* ido4specs directly via the bundled CLI for deterministic structural validation.
|
|
8
|
+
*/
|
|
9
|
+
export { parseSpec } from './spec-parser.js';
|
|
10
|
+
export type { ParsedSpec, ParsedProjectHeader, ParsedGroup, ParsedTask, ParseError, SupportedFormatVersion, } from './types.js';
|
|
11
|
+
export { SUPPORTED_FORMAT_VERSIONS } from './types.js';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAG7C,YAAY,EACV,UAAU,EACV,mBAAmB,EACnB,WAAW,EACX,UAAU,EACV,UAAU,EACV,sBAAsB,GACvB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @ido4/tech-spec-format — Technical spec format parser.
|
|
4
|
+
*
|
|
5
|
+
* Zero-runtime-dependency package (aside from the peer parser utilities shared
|
|
6
|
+
* with @ido4/spec-format) for parsing and validating technical spec artifacts
|
|
7
|
+
* produced by ido4specs. Consumed by @ido4/core for MCP-side ingestion and by
|
|
8
|
+
* ido4specs directly via the bundled CLI for deterministic structural validation.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.SUPPORTED_FORMAT_VERSIONS = exports.parseSpec = void 0;
|
|
12
|
+
// Parser
|
|
13
|
+
var spec_parser_js_1 = require("./spec-parser.js");
|
|
14
|
+
Object.defineProperty(exports, "parseSpec", { enumerable: true, get: function () { return spec_parser_js_1.parseSpec; } });
|
|
15
|
+
// Version contract
|
|
16
|
+
var types_js_1 = require("./types.js");
|
|
17
|
+
Object.defineProperty(exports, "SUPPORTED_FORMAT_VERSIONS", { enumerable: true, get: function () { return types_js_1.SUPPORTED_FORMAT_VERSIONS; } });
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AAEH,SAAS;AACT,mDAA6C;AAApC,2GAAA,SAAS,OAAA;AAYlB,mBAAmB;AACnB,uCAAuD;AAA9C,qHAAA,yBAAyB,OAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spec Parser — Parses spec artifact markdown into a structured ParsedSpec AST.
|
|
3
|
+
*
|
|
4
|
+
* Pure function, no side effects, zero profile awareness.
|
|
5
|
+
* Uses a line-by-line state machine: INIT → PROJECT → GROUP → TASK.
|
|
6
|
+
*/
|
|
7
|
+
import type { ParsedSpec } from './types.js';
|
|
8
|
+
export declare function parseSpec(markdown: string): ParsedSpec;
|
|
9
|
+
//# sourceMappingURL=spec-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spec-parser.d.ts","sourceRoot":"","sources":["../src/spec-parser.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,UAAU,EAKX,MAAM,YAAY,CAAC;AA2BpB,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CA0TtD"}
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Spec Parser — Parses spec artifact markdown into a structured ParsedSpec AST.
|
|
4
|
+
*
|
|
5
|
+
* Pure function, no side effects, zero profile awareness.
|
|
6
|
+
* Uses a line-by-line state machine: INIT → PROJECT → GROUP → TASK.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.parseSpec = parseSpec;
|
|
10
|
+
const types_js_1 = require("./types.js");
|
|
11
|
+
const spec_format_1 = require("@ido4/spec-format");
|
|
12
|
+
const PROJECT_HEADING = /^# (.+)$/;
|
|
13
|
+
const GROUP_HEADING = /^## Capability:\s*(.+)$/;
|
|
14
|
+
const TASK_HEADING = /^### ([A-Z]{2,5}-\d{2,3}[A-Z]?):\s*(.+)$/;
|
|
15
|
+
const BLOCKQUOTE = /^>\s?(.*)$/;
|
|
16
|
+
const BULLET_ITEM = /^- (.+)$/;
|
|
17
|
+
const SECTION_HEADER = /^\*\*(.+?):\*\*\s*$/;
|
|
18
|
+
const SEPARATOR = /^---\s*$/;
|
|
19
|
+
const FORMAT_MARKER = /^>\s*format:\s*(.+?)\s*\|\s*version:\s*(.+?)\s*$/;
|
|
20
|
+
// Pre-compute supported major versions for fast lookup.
|
|
21
|
+
const SUPPORTED_MAJORS = new Set(types_js_1.SUPPORTED_FORMAT_VERSIONS.map(v => parseInt(v.split('.')[0], 10)));
|
|
22
|
+
const KNOWN_TASK_METADATA_KEYS = new Set([
|
|
23
|
+
'effort', 'risk', 'type', 'ai', 'depends_on',
|
|
24
|
+
]);
|
|
25
|
+
const KNOWN_GROUP_METADATA_KEYS = new Set([
|
|
26
|
+
'size', 'risk',
|
|
27
|
+
]);
|
|
28
|
+
function parseSpec(markdown) {
|
|
29
|
+
// Normalize line endings so CRLF (Windows) and CR-only (classic Mac) inputs
|
|
30
|
+
// parse identically to LF (Unix). Without this, regex anchors like `$` trip
|
|
31
|
+
// over trailing `\r` and fields like the format marker fail to detect.
|
|
32
|
+
const normalized = markdown.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
33
|
+
const lines = normalized.split('\n');
|
|
34
|
+
const errors = [];
|
|
35
|
+
let state = 'INIT';
|
|
36
|
+
const project = {
|
|
37
|
+
name: '',
|
|
38
|
+
description: '',
|
|
39
|
+
constraints: [],
|
|
40
|
+
nonGoals: [],
|
|
41
|
+
openQuestions: [],
|
|
42
|
+
};
|
|
43
|
+
const groups = [];
|
|
44
|
+
const orphanTasks = [];
|
|
45
|
+
const seenRefs = new Set();
|
|
46
|
+
let currentGroup = null;
|
|
47
|
+
let currentTask = null;
|
|
48
|
+
// Track metadata parsing state
|
|
49
|
+
let expectingMetadata = false;
|
|
50
|
+
let metadataTarget = 'project';
|
|
51
|
+
let projectDescriptionDone = false;
|
|
52
|
+
let currentSection = null;
|
|
53
|
+
// Body accumulation for tasks
|
|
54
|
+
let bodyLines = [];
|
|
55
|
+
function flushTask() {
|
|
56
|
+
if (!currentTask)
|
|
57
|
+
return;
|
|
58
|
+
currentTask.body = bodyLines.join('\n').trim();
|
|
59
|
+
bodyLines = [];
|
|
60
|
+
currentSection = null;
|
|
61
|
+
currentTask = null;
|
|
62
|
+
}
|
|
63
|
+
function flushGroupDescription() {
|
|
64
|
+
if (!currentGroup)
|
|
65
|
+
return;
|
|
66
|
+
currentGroup.description = bodyLines.join('\n').trim();
|
|
67
|
+
bodyLines = [];
|
|
68
|
+
currentSection = null;
|
|
69
|
+
}
|
|
70
|
+
for (let i = 0; i < lines.length; i++) {
|
|
71
|
+
const line = lines[i];
|
|
72
|
+
const lineNum = i + 1;
|
|
73
|
+
// --- Check for headings (highest priority transitions) ---
|
|
74
|
+
const projectMatch = PROJECT_HEADING.exec(line);
|
|
75
|
+
if (projectMatch && state === 'INIT' && projectMatch[1]) {
|
|
76
|
+
project.name = projectMatch[1].trim();
|
|
77
|
+
state = 'PROJECT';
|
|
78
|
+
expectingMetadata = true;
|
|
79
|
+
metadataTarget = 'project';
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const groupMatch = GROUP_HEADING.exec(line);
|
|
83
|
+
if (groupMatch && groupMatch[1]) {
|
|
84
|
+
// Flush previous task or group description
|
|
85
|
+
if (state === 'TASK')
|
|
86
|
+
flushTask();
|
|
87
|
+
if (state === 'GROUP' && currentGroup && currentGroup.tasks.length === 0) {
|
|
88
|
+
flushGroupDescription();
|
|
89
|
+
}
|
|
90
|
+
const groupName = groupMatch[1].trim();
|
|
91
|
+
currentGroup = {
|
|
92
|
+
name: groupName,
|
|
93
|
+
prefix: (0, spec_format_1.derivePrefix)(groupName),
|
|
94
|
+
description: '',
|
|
95
|
+
tasks: [],
|
|
96
|
+
};
|
|
97
|
+
groups.push(currentGroup);
|
|
98
|
+
state = 'GROUP';
|
|
99
|
+
expectingMetadata = true;
|
|
100
|
+
metadataTarget = 'group';
|
|
101
|
+
bodyLines = [];
|
|
102
|
+
currentSection = null;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const taskMatch = TASK_HEADING.exec(line);
|
|
106
|
+
if (taskMatch && taskMatch[1] && taskMatch[2]) {
|
|
107
|
+
// Flush previous task or group description
|
|
108
|
+
if (state === 'TASK')
|
|
109
|
+
flushTask();
|
|
110
|
+
if (state === 'GROUP' && currentGroup && currentGroup.tasks.length === 0) {
|
|
111
|
+
flushGroupDescription();
|
|
112
|
+
}
|
|
113
|
+
const ref = taskMatch[1];
|
|
114
|
+
const title = taskMatch[2].trim();
|
|
115
|
+
// Check for duplicate refs
|
|
116
|
+
if (seenRefs.has(ref)) {
|
|
117
|
+
errors.push({
|
|
118
|
+
line: lineNum,
|
|
119
|
+
message: `Duplicate task ref: ${ref}`,
|
|
120
|
+
severity: 'error',
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
seenRefs.add(ref);
|
|
124
|
+
currentTask = {
|
|
125
|
+
ref,
|
|
126
|
+
title,
|
|
127
|
+
body: '',
|
|
128
|
+
dependsOn: [],
|
|
129
|
+
successConditions: [],
|
|
130
|
+
groupName: currentGroup?.name ?? null,
|
|
131
|
+
};
|
|
132
|
+
if (currentGroup) {
|
|
133
|
+
currentGroup.tasks.push(currentTask);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
orphanTasks.push(currentTask);
|
|
137
|
+
}
|
|
138
|
+
state = 'TASK';
|
|
139
|
+
expectingMetadata = true;
|
|
140
|
+
metadataTarget = 'task';
|
|
141
|
+
bodyLines = [];
|
|
142
|
+
currentSection = null;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
// --- Separator ---
|
|
146
|
+
if (SEPARATOR.test(line)) {
|
|
147
|
+
if (state === 'TASK')
|
|
148
|
+
flushTask();
|
|
149
|
+
if (state === 'GROUP' && currentGroup && currentGroup.tasks.length === 0) {
|
|
150
|
+
flushGroupDescription();
|
|
151
|
+
}
|
|
152
|
+
currentSection = null;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
// --- Blockquote (metadata or project description) ---
|
|
156
|
+
const bqMatch = BLOCKQUOTE.exec(line);
|
|
157
|
+
if (bqMatch && expectingMetadata) {
|
|
158
|
+
const content = (bqMatch[1] ?? '').trim();
|
|
159
|
+
if (metadataTarget === 'project') {
|
|
160
|
+
// Check for format marker BEFORE treating as description.
|
|
161
|
+
// The format marker is strict-when-present, lenient-when-absent:
|
|
162
|
+
// - If present, parse and validate against SUPPORTED_FORMAT_VERSIONS.
|
|
163
|
+
// Major mismatch → error. Newer minor → warning. Matching → silent.
|
|
164
|
+
// - If absent, the parser proceeds without the marker metadata
|
|
165
|
+
// (backward compatibility with pre-versioned artifacts).
|
|
166
|
+
if (project.format === undefined) {
|
|
167
|
+
const markerMatch = FORMAT_MARKER.exec(line);
|
|
168
|
+
if (markerMatch && markerMatch[1] && markerMatch[2]) {
|
|
169
|
+
project.format = markerMatch[1].trim();
|
|
170
|
+
project.version = markerMatch[2].trim();
|
|
171
|
+
validateFormatVersion(project.format, project.version, lineNum, errors);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (!projectDescriptionDone) {
|
|
176
|
+
if (project.description)
|
|
177
|
+
project.description += ' ';
|
|
178
|
+
project.description += content;
|
|
179
|
+
}
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (metadataTarget === 'group' && currentGroup) {
|
|
183
|
+
const meta = (0, spec_format_1.parseMetadataLine)(content);
|
|
184
|
+
if (Object.keys(meta).length > 0) {
|
|
185
|
+
for (const key of Object.keys(meta)) {
|
|
186
|
+
if (!KNOWN_GROUP_METADATA_KEYS.has(key)) {
|
|
187
|
+
errors.push({
|
|
188
|
+
line: lineNum,
|
|
189
|
+
message: `Unknown group metadata key: ${key}`,
|
|
190
|
+
severity: 'warning',
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (meta['size'])
|
|
195
|
+
currentGroup.size = meta['size'];
|
|
196
|
+
if (meta['risk'])
|
|
197
|
+
currentGroup.risk = meta['risk'];
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (metadataTarget === 'task' && currentTask) {
|
|
202
|
+
const meta = (0, spec_format_1.parseMetadataLine)(content);
|
|
203
|
+
if (Object.keys(meta).length > 0) {
|
|
204
|
+
for (const key of Object.keys(meta)) {
|
|
205
|
+
if (!KNOWN_TASK_METADATA_KEYS.has(key)) {
|
|
206
|
+
errors.push({
|
|
207
|
+
line: lineNum,
|
|
208
|
+
message: `Unknown task metadata key: ${key}`,
|
|
209
|
+
severity: 'warning',
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (meta['effort'])
|
|
214
|
+
currentTask.effort = meta['effort'];
|
|
215
|
+
if (meta['risk'])
|
|
216
|
+
currentTask.risk = meta['risk'];
|
|
217
|
+
if (meta['type'])
|
|
218
|
+
currentTask.taskType = meta['type'];
|
|
219
|
+
if (meta['ai'])
|
|
220
|
+
currentTask.aiSuitability = meta['ai'];
|
|
221
|
+
if (meta['depends_on']) {
|
|
222
|
+
const raw = meta['depends_on'].trim();
|
|
223
|
+
if (raw !== '-') {
|
|
224
|
+
currentTask.dependsOn = raw.split(',').map(s => s.trim()).filter(Boolean);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// Not a metadata line — stop expecting metadata
|
|
231
|
+
expectingMetadata = false;
|
|
232
|
+
}
|
|
233
|
+
// After first non-blockquote, non-empty line in PROJECT state, description is done
|
|
234
|
+
if (state === 'PROJECT' && metadataTarget === 'project' && !bqMatch && line.trim()) {
|
|
235
|
+
projectDescriptionDone = true;
|
|
236
|
+
expectingMetadata = false;
|
|
237
|
+
}
|
|
238
|
+
// --- Section headers in project or task ---
|
|
239
|
+
const sectionMatch = SECTION_HEADER.exec(line);
|
|
240
|
+
if (sectionMatch && sectionMatch[1]) {
|
|
241
|
+
const sectionName = sectionMatch[1].toLowerCase();
|
|
242
|
+
if (state === 'PROJECT') {
|
|
243
|
+
if (sectionName === 'constraints') {
|
|
244
|
+
currentSection = 'constraints';
|
|
245
|
+
}
|
|
246
|
+
else if (sectionName === 'non-goals') {
|
|
247
|
+
currentSection = 'nonGoals';
|
|
248
|
+
}
|
|
249
|
+
else if (sectionName === 'open questions') {
|
|
250
|
+
currentSection = 'openQuestions';
|
|
251
|
+
}
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (state === 'TASK' && currentTask) {
|
|
255
|
+
expectingMetadata = false;
|
|
256
|
+
if (sectionName === 'success conditions') {
|
|
257
|
+
currentSection = 'successConditions';
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
// Other sections (Technical notes, Open questions) become part of body
|
|
261
|
+
currentSection = 'body';
|
|
262
|
+
bodyLines.push(line);
|
|
263
|
+
}
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// --- Bullet items ---
|
|
268
|
+
const bulletMatch = BULLET_ITEM.exec(line);
|
|
269
|
+
if (bulletMatch && bulletMatch[1]) {
|
|
270
|
+
if (state === 'PROJECT' && currentSection) {
|
|
271
|
+
const item = bulletMatch[1].trim();
|
|
272
|
+
if (currentSection === 'constraints')
|
|
273
|
+
project.constraints.push(item);
|
|
274
|
+
if (currentSection === 'nonGoals')
|
|
275
|
+
project.nonGoals.push(item);
|
|
276
|
+
if (currentSection === 'openQuestions')
|
|
277
|
+
project.openQuestions.push(item);
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
if (state === 'TASK' && currentTask && currentSection === 'successConditions') {
|
|
281
|
+
currentTask.successConditions.push(bulletMatch[1].trim());
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// --- Body accumulation ---
|
|
286
|
+
if (state === 'TASK') {
|
|
287
|
+
expectingMetadata = false;
|
|
288
|
+
bodyLines.push(line);
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
if (state === 'GROUP' && currentGroup && currentGroup.tasks.length === 0) {
|
|
292
|
+
expectingMetadata = false;
|
|
293
|
+
bodyLines.push(line);
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
// Flush last task/group
|
|
298
|
+
if (state === 'TASK')
|
|
299
|
+
flushTask();
|
|
300
|
+
if (state === 'GROUP' && currentGroup && currentGroup.tasks.length === 0) {
|
|
301
|
+
flushGroupDescription();
|
|
302
|
+
}
|
|
303
|
+
// --- Post-parse validation ---
|
|
304
|
+
// Groups with no tasks
|
|
305
|
+
for (const group of groups) {
|
|
306
|
+
if (group.tasks.length === 0) {
|
|
307
|
+
errors.push({
|
|
308
|
+
line: 0,
|
|
309
|
+
message: `Group "${group.name}" has no tasks`,
|
|
310
|
+
severity: 'warning',
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
// Empty spec
|
|
315
|
+
const allTasks = [...groups.flatMap(g => g.tasks), ...orphanTasks];
|
|
316
|
+
if (allTasks.length === 0) {
|
|
317
|
+
errors.push({
|
|
318
|
+
line: 0,
|
|
319
|
+
message: 'Spec contains no tasks',
|
|
320
|
+
severity: 'warning',
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
return { project, groups, orphanTasks, errors };
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Validates a format marker against SUPPORTED_FORMAT_VERSIONS.
|
|
327
|
+
*
|
|
328
|
+
* - Wrong format identifier (not "tech-spec") → error
|
|
329
|
+
* - Major-version mismatch (e.g., file says 2.0, parser supports 1.x) → error
|
|
330
|
+
* - Newer-minor mismatch (e.g., file says 1.1, parser supports 1.0) → warning
|
|
331
|
+
* - Matching major + current-or-older minor → silent
|
|
332
|
+
*/
|
|
333
|
+
function validateFormatVersion(format, version, line, errors) {
|
|
334
|
+
if (format !== 'tech-spec') {
|
|
335
|
+
errors.push({
|
|
336
|
+
line,
|
|
337
|
+
message: `Unexpected format identifier "${format}" — this parser expects "tech-spec". If this file is a strategic spec, use @ido4/spec-format instead.`,
|
|
338
|
+
severity: 'error',
|
|
339
|
+
});
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
const versionMatch = /^(\d+)\.(\d+)$/.exec(version);
|
|
343
|
+
if (!versionMatch || !versionMatch[1] || !versionMatch[2]) {
|
|
344
|
+
errors.push({
|
|
345
|
+
line,
|
|
346
|
+
message: `Invalid format version "${version}" — expected MAJOR.MINOR format (e.g., "1.0"). Supported: ${types_js_1.SUPPORTED_FORMAT_VERSIONS.join(', ')}.`,
|
|
347
|
+
severity: 'error',
|
|
348
|
+
});
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
const fileMajor = parseInt(versionMatch[1], 10);
|
|
352
|
+
const fileMinor = parseInt(versionMatch[2], 10);
|
|
353
|
+
if (!SUPPORTED_MAJORS.has(fileMajor)) {
|
|
354
|
+
errors.push({
|
|
355
|
+
line,
|
|
356
|
+
message: `Unsupported format version ${fileMajor}.${fileMinor} — this parser supports ${types_js_1.SUPPORTED_FORMAT_VERSIONS.join(', ')}. Upgrade @ido4/tech-spec-format to parse newer specs.`,
|
|
357
|
+
severity: 'error',
|
|
358
|
+
});
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
// Same major — check if the file's minor is newer than anything we support.
|
|
362
|
+
const maxSupportedMinor = Math.max(...types_js_1.SUPPORTED_FORMAT_VERSIONS
|
|
363
|
+
.filter(v => parseInt(v.split('.')[0], 10) === fileMajor)
|
|
364
|
+
.map(v => parseInt(v.split('.')[1], 10)));
|
|
365
|
+
if (fileMinor > maxSupportedMinor) {
|
|
366
|
+
errors.push({
|
|
367
|
+
line,
|
|
368
|
+
message: `Format version ${fileMajor}.${fileMinor} is newer than this parser's maximum supported minor (${fileMajor}.${maxSupportedMinor}). Unknown optional fields may be ignored. Upgrade @ido4/tech-spec-format for full support.`,
|
|
369
|
+
severity: 'warning',
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
//# sourceMappingURL=spec-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spec-parser.js","sourceRoot":"","sources":["../src/spec-parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ido4/tech-spec-format v0.8.0 | bundled 2026-04-15
|
|
3
|
+
// Source: https://github.com/ido4-dev/ido4/tree/main/packages/tech-spec-format | DO NOT EDIT
|
|
4
|
+
"use strict";var se=Object.create;var V=Object.defineProperty;var ne=Object.getOwnPropertyDescriptor;var ie=Object.getOwnPropertyNames;var re=Object.getPrototypeOf,oe=Object.prototype.hasOwnProperty;var D=(o,n)=>()=>(n||o((n={exports:{}}).exports,n),n.exports);var ae=(o,n,r,s)=>{if(n&&typeof n=="object"||typeof n=="function")for(let t of ie(n))!oe.call(o,t)&&t!==r&&V(o,t,{get:()=>n[t],enumerable:!(s=ne(n,t))||s.enumerable});return o};var ce=(o,n,r)=>(r=o!=null?se(re(o)):{},ae(n||!o||!o.__esModule?V(r,"default",{value:o,enumerable:!0}):r,o));var B=D(N=>{"use strict";Object.defineProperty(N,"__esModule",{value:!0});N.STRATEGIC_RISKS=N.STRATEGIC_PRIORITIES=void 0;N.STRATEGIC_PRIORITIES=["must-have","should-have","nice-to-have"];N.STRATEGIC_RISKS=["low","medium","high"]});var J=D(L=>{"use strict";Object.defineProperty(L,"__esModule",{value:!0});L.parseMetadataLine=pe;L.derivePrefix=ue;function pe(o){let n={};for(let r of o.split("|")){let s=r.trim().match(/^(\w+):\s*(.+)$/);s&&s[1]&&s[2]&&(n[s[1]]=s[2].trim())}return n}function ue(o){let n=o.split(/\s+/).filter(Boolean);return n.length===1?n[0].substring(0,3).toUpperCase():n.map(r=>r[0]).join("").toUpperCase()}});var q=D(Y=>{"use strict";Object.defineProperty(Y,"__esModule",{value:!0});Y.parseStrategicSpec=_e;var $=B(),H=J(),fe=/^# (.+)$/,le=/^>\s*format:\s*(.+?)\s*\|\s*version:\s*(.+)$/,de=/^## Group:\s*(.+)$/,ge=/^## Cross-Cutting Concerns\s*$/i,me=/^### (.+)$/,he=/^### ([A-Z]{2,5}-\d{2,3}[A-Z]?):\s*(.+)$/,Te=/^>\s?(.*)$/,Se=/^(?:[-*+]|\d+\.)\s+(.+)$/,Q=/^\*\*(.+?):\*\*\s*$/,Oe=/^---\s*$/,Ie=new Set(["priority"]),ye=new Set(["priority","risk","depends_on"]);function _e(o){let n=o.split(`
|
|
5
|
+
`),r=[],s="INIT",t={name:"",format:"",version:"",description:"",stakeholders:[],constraints:[],nonGoals:[],openQuestions:[]},c=[],a=[],g=[],S=new Set,i=null,e=null,p=null,y=!1,k="project",O=!1,d=null,I=[];function E(){e&&(e.body=I.join(`
|
|
6
|
+
`).trim(),I=[],d=null,e=null)}function w(){i&&(i.description=I.join(`
|
|
7
|
+
`).trim(),I=[],d=null)}function C(){p&&(p.content=I.join(`
|
|
8
|
+
`).trim(),I=[],p=null)}for(let f=0;f<n.length;f++){let m=n[f],b=f+1,j=fe.exec(m);if(j&&s==="INIT"&&j[1]){t.name=j[1].trim(),s="PROJECT",y=!0,k="project";continue}if(ge.test(m)){s==="CAPABILITY"&&E(),s==="GROUP"&&i&&i.capabilities.length===0&&w(),s="CROSS_CUTTING",I=[],d=null;continue}let x=de.exec(m);if(x&&x[1]){s==="CAPABILITY"&&E(),s==="GROUP"&&i&&i.capabilities.length===0&&w(),s==="CROSS_CUTTING"&&C();let l=x[1].trim();i={name:l,prefix:(0,H.derivePrefix)(l),description:"",capabilities:[]},a.push(i),s="GROUP",y=!0,k="group",I=[],d=null;continue}let A=he.exec(m);if(A&&A[1]&&A[2]){s==="CAPABILITY"&&E(),s==="GROUP"&&i&&i.capabilities.length===0&&w(),s==="CROSS_CUTTING"&&C();let l=A[1],T=A[2].trim();S.has(l)&&r.push({line:b,message:`Duplicate capability ref: ${l}`,severity:"error"}),S.add(l),e={ref:l,title:T,body:"",dependsOn:[],successConditions:[],groupName:i?.name??null},i?i.capabilities.push(e):g.push(e),s="CAPABILITY",y=!0,k="capability",I=[],d=null;continue}if(s==="CROSS_CUTTING"){let l=me.exec(m);if(l&&l[1]){C(),p={name:l[1].trim(),content:""},c.push(p),I=[];continue}}if(Oe.test(m)){s==="CAPABILITY"&&E(),s==="GROUP"&&i&&i.capabilities.length===0&&w(),s==="CROSS_CUTTING"&&C(),d=null;continue}let h=Te.exec(m);if(h){let l=(h[1]??"").trim();if(s==="PROJECT"&&k==="project"&&!O){let T=le.exec(m);if(T&&T[1]&&T[2]){t.format=T[1].trim(),t.version=T[2].trim();continue}l&&(t.description&&(t.description+=" "),t.description+=l);continue}if(y&&k==="group"&&i){let T=(0,H.parseMetadataLine)(l);if(Object.keys(T).length>0){for(let v of Object.keys(T))Ie.has(v)||r.push({line:b,message:`Unknown group metadata key: ${v}`,severity:"warning"});T.priority&&(i.priority=T.priority);continue}}if(y&&k==="capability"&&e){let T=(0,H.parseMetadataLine)(l);if(Object.keys(T).length>0){for(let v of Object.keys(T))ye.has(v)||r.push({line:b,message:`Unknown capability metadata key: ${v}`,severity:"warning"});if(T.priority&&(e.priority=T.priority),T.risk&&(e.risk=T.risk),T.depends_on){let v=T.depends_on.trim();v!=="-"&&(e.dependsOn=v.split(",").map(te=>te.trim()).filter(Boolean))}continue}}y=!1}s==="PROJECT"&&k==="project"&&!h&&m.trim()&&(Q.test(m)?(O=!0,y=!1):O||(t.description&&(t.description+=" "),t.description+=m.trim()));let u=Q.exec(m);if(u&&u[1]){let l=u[1].toLowerCase();if(s==="PROJECT"){l==="stakeholders"?d="stakeholders":l==="constraints"?d="constraints":l==="non-goals"?d="nonGoals":l==="open questions"&&(d="openQuestions");continue}if(s==="CAPABILITY"&&e){y=!1,l==="success conditions"?d="successConditions":(d="body",I.push(m));continue}}let R=Se.exec(m);if(R&&R[1]){if(s==="PROJECT"&&d){let l=R[1].trim();if(d==="stakeholders"){let T=Ce(l);T&&t.stakeholders.push(T)}else d==="constraints"?t.constraints.push(l):d==="nonGoals"?t.nonGoals.push(l):d==="openQuestions"&&t.openQuestions.push(l);continue}if(s==="CAPABILITY"&&e&&d==="successConditions"){e.successConditions.push(R[1].trim());continue}}if(s==="CAPABILITY"){y=!1,I.push(m);continue}if(s==="GROUP"&&i&&i.capabilities.length===0){y=!1,I.push(m);continue}if(s==="CROSS_CUTTING"&&p){I.push(m);continue}}s==="CAPABILITY"&&E(),s==="GROUP"&&i&&i.capabilities.length===0&&w(),s==="CROSS_CUTTING"&&C(),t.format!=="strategic-spec"&&r.push({line:0,message:'Missing or invalid format marker: expected "format: strategic-spec | version: 1.0"',severity:"error"});for(let f of a)f.capabilities.length===0&&r.push({line:0,message:`Group "${f.name}" has no capabilities`,severity:"warning"});let _=[...a.flatMap(f=>f.capabilities),...g];_.length===0&&r.push({line:0,message:"Strategic spec contains no capabilities",severity:"warning"});for(let f of a)f.priority&&!$.STRATEGIC_PRIORITIES.includes(f.priority)&&r.push({line:0,message:`Group "${f.name}" has invalid priority: "${f.priority}" (expected: ${$.STRATEGIC_PRIORITIES.join(", ")})`,severity:"warning"});for(let f of _)f.priority&&!$.STRATEGIC_PRIORITIES.includes(f.priority)&&r.push({line:0,message:`Capability ${f.ref} has invalid priority: "${f.priority}" (expected: ${$.STRATEGIC_PRIORITIES.join(", ")})`,severity:"warning"}),f.risk&&!$.STRATEGIC_RISKS.includes(f.risk)&&r.push({line:0,message:`Capability ${f.ref} has invalid risk: "${f.risk}" (expected: ${$.STRATEGIC_RISKS.join(", ")})`,severity:"warning"});for(let f of _)for(let m of f.dependsOn)S.has(m)||r.push({line:0,message:`Capability ${f.ref} depends on "${m}" which does not exist`,severity:"error"});let M=Re(_);return M&&r.push({line:0,message:`Circular dependency detected: ${M.join(" \u2192 ")}`,severity:"error"}),{project:t,crossCuttingConcerns:c,groups:a,orphanCapabilities:g,errors:r}}function Ce(o){let n=o.match(/^(.+?):\s+(.+)$/);return n&&n[1]&&n[2]?{name:n[1].trim(),perspective:n[2].trim()}:null}function Re(o){let n=new Set(o.map(a=>a.ref)),r=new Map,s=new Map;for(let a of n)r.set(a,[]),s.set(a,0);for(let a of o)for(let g of a.dependsOn)n.has(g)&&(r.get(g).push(a.ref),s.set(a.ref,(s.get(a.ref)??0)+1));let t=[];for(let[a,g]of s)g===0&&t.push(a);let c=0;for(;t.length>0;){let a=t.shift();c++;for(let g of r.get(a)??[]){let S=(s.get(g)??1)-1;s.set(g,S),S===0&&t.push(g)}}return c===n.size?null:[...s.entries()].filter(([,a])=>a>0).map(([a])=>a)}});var Z=D(P=>{"use strict";Object.defineProperty(P,"__esModule",{value:!0});P.derivePrefix=P.parseMetadataLine=P.STRATEGIC_RISKS=P.STRATEGIC_PRIORITIES=P.parseStrategicSpec=void 0;var Pe=q();Object.defineProperty(P,"parseStrategicSpec",{enumerable:!0,get:function(){return Pe.parseStrategicSpec}});var z=B();Object.defineProperty(P,"STRATEGIC_PRIORITIES",{enumerable:!0,get:function(){return z.STRATEGIC_PRIORITIES}});Object.defineProperty(P,"STRATEGIC_RISKS",{enumerable:!0,get:function(){return z.STRATEGIC_RISKS}});var W=J();Object.defineProperty(P,"parseMetadataLine",{enumerable:!0,get:function(){return W.parseMetadataLine}});Object.defineProperty(P,"derivePrefix",{enumerable:!0,get:function(){return W.derivePrefix}})});var F=require("node:fs"),K=require("node:path");var G=["1.0"];var U=ce(Z()),ke=/^# (.+)$/,Ee=/^## Capability:\s*(.+)$/,Ae=/^### ([A-Z]{2,5}-\d{2,3}[A-Z]?):\s*(.+)$/,be=/^>\s?(.*)$/,ve=/^- (.+)$/,Ge=/^\*\*(.+?):\*\*\s*$/,Me=/^---\s*$/,je=/^>\s*format:\s*(.+?)\s*\|\s*version:\s*(.+?)\s*$/,xe=new Set(G.map(o=>parseInt(o.split(".")[0],10))),we=new Set(["effort","risk","type","ai","depends_on"]),Ne=new Set(["size","risk"]);function X(o){let r=o.replace(/\r\n/g,`
|
|
9
|
+
`).replace(/\r/g,`
|
|
10
|
+
`).split(`
|
|
11
|
+
`),s=[],t="INIT",c={name:"",description:"",constraints:[],nonGoals:[],openQuestions:[]},a=[],g=[],S=new Set,i=null,e=null,p=!1,y="project",k=!1,O=null,d=[];function I(){e&&(e.body=d.join(`
|
|
12
|
+
`).trim(),d=[],O=null,e=null)}function E(){i&&(i.description=d.join(`
|
|
13
|
+
`).trim(),d=[],O=null)}for(let C=0;C<r.length;C++){let _=r[C],M=C+1,f=ke.exec(_);if(f&&t==="INIT"&&f[1]){c.name=f[1].trim(),t="PROJECT",p=!0,y="project";continue}let m=Ee.exec(_);if(m&&m[1]){t==="TASK"&&I(),t==="GROUP"&&i&&i.tasks.length===0&&E();let h=m[1].trim();i={name:h,prefix:(0,U.derivePrefix)(h),description:"",tasks:[]},a.push(i),t="GROUP",p=!0,y="group",d=[],O=null;continue}let b=Ae.exec(_);if(b&&b[1]&&b[2]){t==="TASK"&&I(),t==="GROUP"&&i&&i.tasks.length===0&&E();let h=b[1],u=b[2].trim();S.has(h)&&s.push({line:M,message:`Duplicate task ref: ${h}`,severity:"error"}),S.add(h),e={ref:h,title:u,body:"",dependsOn:[],successConditions:[],groupName:i?.name??null},i?i.tasks.push(e):g.push(e),t="TASK",p=!0,y="task",d=[],O=null;continue}if(Me.test(_)){t==="TASK"&&I(),t==="GROUP"&&i&&i.tasks.length===0&&E(),O=null;continue}let j=be.exec(_);if(j&&p){let h=(j[1]??"").trim();if(y==="project"){if(c.format===void 0){let u=je.exec(_);if(u&&u[1]&&u[2]){c.format=u[1].trim(),c.version=u[2].trim(),$e(c.format,c.version,M,s);continue}}k||(c.description&&(c.description+=" "),c.description+=h);continue}if(y==="group"&&i){let u=(0,U.parseMetadataLine)(h);if(Object.keys(u).length>0){for(let R of Object.keys(u))Ne.has(R)||s.push({line:M,message:`Unknown group metadata key: ${R}`,severity:"warning"});u.size&&(i.size=u.size),u.risk&&(i.risk=u.risk);continue}}if(y==="task"&&e){let u=(0,U.parseMetadataLine)(h);if(Object.keys(u).length>0){for(let R of Object.keys(u))we.has(R)||s.push({line:M,message:`Unknown task metadata key: ${R}`,severity:"warning"});if(u.effort&&(e.effort=u.effort),u.risk&&(e.risk=u.risk),u.type&&(e.taskType=u.type),u.ai&&(e.aiSuitability=u.ai),u.depends_on){let R=u.depends_on.trim();R!=="-"&&(e.dependsOn=R.split(",").map(l=>l.trim()).filter(Boolean))}continue}}p=!1}t==="PROJECT"&&y==="project"&&!j&&_.trim()&&(k=!0,p=!1);let x=Ge.exec(_);if(x&&x[1]){let h=x[1].toLowerCase();if(t==="PROJECT"){h==="constraints"?O="constraints":h==="non-goals"?O="nonGoals":h==="open questions"&&(O="openQuestions");continue}if(t==="TASK"&&e){p=!1,h==="success conditions"?O="successConditions":(O="body",d.push(_));continue}}let A=ve.exec(_);if(A&&A[1]){if(t==="PROJECT"&&O){let h=A[1].trim();O==="constraints"&&c.constraints.push(h),O==="nonGoals"&&c.nonGoals.push(h),O==="openQuestions"&&c.openQuestions.push(h);continue}if(t==="TASK"&&e&&O==="successConditions"){e.successConditions.push(A[1].trim());continue}}if(t==="TASK"){p=!1,d.push(_);continue}if(t==="GROUP"&&i&&i.tasks.length===0){p=!1,d.push(_);continue}}t==="TASK"&&I(),t==="GROUP"&&i&&i.tasks.length===0&&E();for(let C of a)C.tasks.length===0&&s.push({line:0,message:`Group "${C.name}" has no tasks`,severity:"warning"});return[...a.flatMap(C=>C.tasks),...g].length===0&&s.push({line:0,message:"Spec contains no tasks",severity:"warning"}),{project:c,groups:a,orphanTasks:g,errors:s}}function $e(o,n,r,s){if(o!=="tech-spec"){s.push({line:r,message:`Unexpected format identifier "${o}" \u2014 this parser expects "tech-spec". If this file is a strategic spec, use @ido4/spec-format instead.`,severity:"error"});return}let t=/^(\d+)\.(\d+)$/.exec(n);if(!t||!t[1]||!t[2]){s.push({line:r,message:`Invalid format version "${n}" \u2014 expected MAJOR.MINOR format (e.g., "1.0"). Supported: ${G.join(", ")}.`,severity:"error"});return}let c=parseInt(t[1],10),a=parseInt(t[2],10);if(!xe.has(c)){s.push({line:r,message:`Unsupported format version ${c}.${a} \u2014 this parser supports ${G.join(", ")}. Upgrade @ido4/tech-spec-format to parse newer specs.`,severity:"error"});return}let g=Math.max(...G.filter(S=>parseInt(S.split(".")[0],10)===c).map(S=>parseInt(S.split(".")[1],10)));a>g&&s.push({line:r,message:`Format version ${c}.${a} is newer than this parser's maximum supported minor (${c}.${g}). Unknown optional fields may be ignored. Upgrade @ido4/tech-spec-format for full support.`,severity:"warning"})}function Ue(){return"0.8.0";try{return JSON.parse((0,F.readFileSync)((0,K.join)(__dirname,"../package.json"),"utf-8")).version}catch{return"unknown"}}var ee=Ue();function De(){let o=process.argv[2];if(!o||o==="--help"||o==="-h"){let e=["Usage: ido4-tech-spec-format <file.md>"," ido4-tech-spec-format --version","","Parses a technical spec and outputs structured JSON to stdout.","Exit codes: 0 = valid, 1 = structural errors, 2 = usage/IO error.","",`Supported format versions: ${G.join(", ")}`];process.stderr.write(e.join(`
|
|
14
|
+
`)+`
|
|
15
|
+
`),process.exit(o?0:2)}(o==="--version"||o==="-v")&&(process.stdout.write(`${ee}
|
|
16
|
+
`),process.exit(0));let n=(0,K.resolve)(o),r;try{r=(0,F.readFileSync)(n,"utf-8")}catch(e){let p=e instanceof Error?e.message:String(e);process.stderr.write(`Error reading file: ${p}
|
|
17
|
+
`),process.exit(2)}let s=Date.now(),t=X(r),c=Date.now()-s,a=t.errors.filter(e=>e.severity==="error"),g=t.errors.filter(e=>e.severity==="warning"),S=[...t.groups.flatMap(e=>e.tasks),...t.orphanTasks],i={valid:a.length===0,meta:{file:n,parseDurationMs:c,parserVersion:ee,supportedFormatVersions:G,schemaVersion:"1.0"},metrics:{groupCount:t.groups.length,taskCount:S.length,orphanTaskCount:t.orphanTasks.length,dependencyEdgeCount:S.reduce((e,p)=>e+p.dependsOn.length,0),maxDependencyDepth:Ke(S),successConditionCount:S.reduce((e,p)=>e+p.successConditions.length,0),errorCount:a.length,warningCount:g.length},project:t.project,groups:t.groups.map(e=>({name:e.name,prefix:e.prefix,size:e.size,risk:e.risk,description:e.description,taskCount:e.tasks.length,tasks:e.tasks.map(p=>({ref:p.ref,title:p.title,effort:p.effort,risk:p.risk,taskType:p.taskType,aiSuitability:p.aiSuitability,dependsOn:p.dependsOn,successConditions:p.successConditions,groupName:p.groupName}))})),orphanTasks:t.orphanTasks.map(e=>({ref:e.ref,title:e.title,effort:e.effort,risk:e.risk,taskType:e.taskType,aiSuitability:e.aiSuitability,dependsOn:e.dependsOn,successConditions:e.successConditions})),dependencyGraph:Le(S),errors:a.map(e=>({line:e.line,message:e.message})),warnings:g.map(e=>({line:e.line,message:e.message}))};process.stdout.write(JSON.stringify(i,null,2)+`
|
|
18
|
+
`),process.exit(a.length>0?1:0)}function Le(o){let n={};for(let r of o)r.dependsOn.length>0&&(n[r.ref]=r.dependsOn);return n}function Ke(o){let n=new Map;for(let c of o)n.set(c.ref,c.dependsOn);let r=new Map;function s(c,a){if(r.has(c))return r.get(c);if(a.has(c))return 0;a.add(c);let g=n.get(c)??[],S=g.reduce((e,p)=>Math.max(e,n.has(p)?s(p,a):0),0),i=g.length>0?S+1:0;return r.set(c,i),i}let t=0;for(let c of o)t=Math.max(t,s(c.ref,new Set));return t}De();
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for the technical spec parser.
|
|
3
|
+
*
|
|
4
|
+
* These types describe the output of parseSpec — the AST extracted from a
|
|
5
|
+
* technical spec markdown file. Profile-agnostic, zero mapper/service coupling.
|
|
6
|
+
* The mapper and ingestion-service types live in @ido4/core/domains/ingestion,
|
|
7
|
+
* because those layers need the MethodologyProfile and container infrastructure.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Format versions this parser supports. Used by consumers to check compatibility
|
|
11
|
+
* before handing a spec to the parser. Major-version mismatches should fail fast
|
|
12
|
+
* with a clear remediation message. Minor mismatches may produce warnings on
|
|
13
|
+
* unknown optional fields but still parse successfully within a major version.
|
|
14
|
+
*
|
|
15
|
+
* This is the version contract between ido4specs (producer) and @ido4/core's
|
|
16
|
+
* ingestion pipeline (consumer). See:
|
|
17
|
+
* ido4specs/docs/extraction-plan.md (section 5)
|
|
18
|
+
* ido4specs/references/technical-spec-format.md (once extraction lands)
|
|
19
|
+
*/
|
|
20
|
+
export declare const SUPPORTED_FORMAT_VERSIONS: readonly ["1.0"];
|
|
21
|
+
export type SupportedFormatVersion = typeof SUPPORTED_FORMAT_VERSIONS[number];
|
|
22
|
+
export interface ParsedSpec {
|
|
23
|
+
project: ParsedProjectHeader;
|
|
24
|
+
groups: ParsedGroup[];
|
|
25
|
+
orphanTasks: ParsedTask[];
|
|
26
|
+
errors: ParseError[];
|
|
27
|
+
}
|
|
28
|
+
export interface ParsedProjectHeader {
|
|
29
|
+
name: string;
|
|
30
|
+
description: string;
|
|
31
|
+
constraints: string[];
|
|
32
|
+
nonGoals: string[];
|
|
33
|
+
openQuestions: string[];
|
|
34
|
+
/**
|
|
35
|
+
* Format identifier declared in the spec (e.g., "tech-spec"). Set when the
|
|
36
|
+
* parser finds a `> format: tech-spec | version: X.Y` marker line. Undefined
|
|
37
|
+
* if the spec omits the marker (parser still accepts the spec for backward
|
|
38
|
+
* compatibility with pre-versioned artifacts).
|
|
39
|
+
*/
|
|
40
|
+
format?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Format version declared in the spec (e.g., "1.0"). Set when the format
|
|
43
|
+
* marker is present. Used by the parser to fail fast on major-version
|
|
44
|
+
* mismatches with SUPPORTED_FORMAT_VERSIONS.
|
|
45
|
+
*/
|
|
46
|
+
version?: string;
|
|
47
|
+
}
|
|
48
|
+
export interface ParsedGroup {
|
|
49
|
+
name: string;
|
|
50
|
+
prefix: string;
|
|
51
|
+
size?: string;
|
|
52
|
+
risk?: string;
|
|
53
|
+
description: string;
|
|
54
|
+
tasks: ParsedTask[];
|
|
55
|
+
}
|
|
56
|
+
export interface ParsedTask {
|
|
57
|
+
ref: string;
|
|
58
|
+
title: string;
|
|
59
|
+
body: string;
|
|
60
|
+
effort?: string;
|
|
61
|
+
risk?: string;
|
|
62
|
+
taskType?: string;
|
|
63
|
+
aiSuitability?: string;
|
|
64
|
+
dependsOn: string[];
|
|
65
|
+
successConditions: string[];
|
|
66
|
+
groupName: string | null;
|
|
67
|
+
}
|
|
68
|
+
export interface ParseError {
|
|
69
|
+
line: number;
|
|
70
|
+
message: string;
|
|
71
|
+
severity: 'warning' | 'error';
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB,kBAAmB,CAAC;AAC1D,MAAM,MAAM,sBAAsB,GAAG,OAAO,yBAAyB,CAAC,MAAM,CAAC,CAAC;AAI9E,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,mBAAmB,CAAC;IAC7B,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,SAAS,GAAG,OAAO,CAAC;CAC/B"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Types for the technical spec parser.
|
|
4
|
+
*
|
|
5
|
+
* These types describe the output of parseSpec — the AST extracted from a
|
|
6
|
+
* technical spec markdown file. Profile-agnostic, zero mapper/service coupling.
|
|
7
|
+
* The mapper and ingestion-service types live in @ido4/core/domains/ingestion,
|
|
8
|
+
* because those layers need the MethodologyProfile and container infrastructure.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.SUPPORTED_FORMAT_VERSIONS = void 0;
|
|
12
|
+
/**
|
|
13
|
+
* Format versions this parser supports. Used by consumers to check compatibility
|
|
14
|
+
* before handing a spec to the parser. Major-version mismatches should fail fast
|
|
15
|
+
* with a clear remediation message. Minor mismatches may produce warnings on
|
|
16
|
+
* unknown optional fields but still parse successfully within a major version.
|
|
17
|
+
*
|
|
18
|
+
* This is the version contract between ido4specs (producer) and @ido4/core's
|
|
19
|
+
* ingestion pipeline (consumer). See:
|
|
20
|
+
* ido4specs/docs/extraction-plan.md (section 5)
|
|
21
|
+
* ido4specs/references/technical-spec-format.md (once extraction lands)
|
|
22
|
+
*/
|
|
23
|
+
exports.SUPPORTED_FORMAT_VERSIONS = ['1.0'];
|
|
24
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AAEH;;;;;;;;;;GAUG;AACU,QAAA,yBAAyB,GAAG,CAAC,KAAK,CAAU,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ido4/tech-spec-format",
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"description": "Technical spec format parser — deterministic structural validation for ido4specs artifacts",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"ido4-tech-spec-format": "dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"build:bundle": "node esbuild.bundle.mjs",
|
|
13
|
+
"test": "vitest run",
|
|
14
|
+
"clean": "rm -rf dist"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@ido4/spec-format": "~0.8.0"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/node": "^20.0.0",
|
|
21
|
+
"esbuild": "^0.21.5",
|
|
22
|
+
"typescript": "^5.5.0",
|
|
23
|
+
"vitest": "^2.1.0"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist"
|
|
27
|
+
],
|
|
28
|
+
"keywords": [
|
|
29
|
+
"tech-spec-format",
|
|
30
|
+
"technical-spec",
|
|
31
|
+
"ido4",
|
|
32
|
+
"ido4specs",
|
|
33
|
+
"parser",
|
|
34
|
+
"markdown",
|
|
35
|
+
"validation"
|
|
36
|
+
],
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "https://github.com/ido4-dev/ido4.git",
|
|
40
|
+
"directory": "packages/tech-spec-format"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://github.com/ido4-dev/ido4/tree/main/packages/tech-spec-format",
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/ido4-dev/ido4/issues"
|
|
45
|
+
},
|
|
46
|
+
"author": "Bogdan Ionut Coman",
|
|
47
|
+
"license": "MIT"
|
|
48
|
+
}
|