@seanmars/tospec 0.12.0 → 0.14.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/CHANGELOG.md +233 -0
- package/README.md +20 -7
- package/assets/dashboard/app.js +6 -0
- package/assets/dashboard/style.css +42 -5
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +39 -16
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +21 -16
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/dashboard.d.ts +95 -8
- package/dist/commands/dashboard.d.ts.map +1 -1
- package/dist/commands/dashboard.js +256 -31
- package/dist/commands/dashboard.js.map +1 -1
- package/dist/commands/decision.d.ts.map +1 -1
- package/dist/commands/decision.js +1 -13
- package/dist/commands/decision.js.map +1 -1
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +80 -44
- package/dist/commands/validate.js.map +1 -1
- package/dist/commands/workflow/instructions.d.ts +0 -2
- package/dist/commands/workflow/instructions.d.ts.map +1 -1
- package/dist/commands/workflow/instructions.js +3 -38
- package/dist/commands/workflow/instructions.js.map +1 -1
- package/dist/commands/workflow/shared.d.ts +0 -3
- package/dist/commands/workflow/shared.d.ts.map +1 -1
- package/dist/commands/workflow/shared.js.map +1 -1
- package/dist/core/archive.d.ts +11 -2
- package/dist/core/archive.d.ts.map +1 -1
- package/dist/core/archive.js +110 -111
- package/dist/core/archive.js.map +1 -1
- package/dist/core/artifact-graph/instruction-loader.d.ts +0 -5
- package/dist/core/artifact-graph/instruction-loader.d.ts.map +1 -1
- package/dist/core/artifact-graph/instruction-loader.js +37 -40
- package/dist/core/artifact-graph/instruction-loader.js.map +1 -1
- package/dist/core/artifact-graph/resolver.d.ts +27 -1
- package/dist/core/artifact-graph/resolver.d.ts.map +1 -1
- package/dist/core/artifact-graph/resolver.js +114 -127
- package/dist/core/artifact-graph/resolver.js.map +1 -1
- package/dist/core/artifact-graph/schema.d.ts.map +1 -1
- package/dist/core/artifact-graph/schema.js +22 -0
- package/dist/core/artifact-graph/schema.js.map +1 -1
- package/dist/core/change-metadata/schema.d.ts +1 -0
- package/dist/core/change-metadata/schema.d.ts.map +1 -1
- package/dist/core/change-metadata/schema.js +7 -0
- package/dist/core/change-metadata/schema.js.map +1 -1
- package/dist/core/change-presenter.d.ts +0 -1
- package/dist/core/change-presenter.d.ts.map +1 -1
- package/dist/core/change-presenter.js +4 -21
- package/dist/core/change-presenter.js.map +1 -1
- package/dist/core/command-generation/adapters/index.d.ts +9 -1
- package/dist/core/command-generation/adapters/index.d.ts.map +1 -1
- package/dist/core/command-generation/adapters/index.js +10 -1
- package/dist/core/command-generation/adapters/index.js.map +1 -1
- package/dist/core/command-generation/index.d.ts +2 -2
- package/dist/core/command-generation/index.d.ts.map +1 -1
- package/dist/core/command-generation/index.js +2 -2
- package/dist/core/command-generation/index.js.map +1 -1
- package/dist/core/config.d.ts +0 -4
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +0 -4
- package/dist/core/config.js.map +1 -1
- package/dist/core/converters/json-converter.d.ts +0 -1
- package/dist/core/converters/json-converter.d.ts.map +1 -1
- package/dist/core/converters/json-converter.js +3 -17
- package/dist/core/converters/json-converter.js.map +1 -1
- package/dist/core/dashboard-activity.d.ts.map +1 -1
- package/dist/core/dashboard-activity.js +2 -8
- package/dist/core/dashboard-activity.js.map +1 -1
- package/dist/core/dashboard-data.d.ts +12 -6
- package/dist/core/dashboard-data.d.ts.map +1 -1
- package/dist/core/dashboard-data.js +13 -44
- package/dist/core/dashboard-data.js.map +1 -1
- package/dist/core/global-config.d.ts +59 -16
- package/dist/core/global-config.d.ts.map +1 -1
- package/dist/core/global-config.js +99 -41
- package/dist/core/global-config.js.map +1 -1
- package/dist/core/id.d.ts +0 -8
- package/dist/core/id.d.ts.map +1 -1
- package/dist/core/id.js +0 -19
- package/dist/core/id.js.map +1 -1
- package/dist/core/init.d.ts +0 -1
- package/dist/core/init.d.ts.map +1 -1
- package/dist/core/init.js +13 -23
- package/dist/core/init.js.map +1 -1
- package/dist/core/list.d.ts.map +1 -1
- package/dist/core/list.js +3 -34
- package/dist/core/list.js.map +1 -1
- package/dist/core/migrate.js +9 -15
- package/dist/core/migrate.js.map +1 -1
- package/dist/core/parsers/markdown-parser.d.ts +11 -1
- package/dist/core/parsers/markdown-parser.d.ts.map +1 -1
- package/dist/core/parsers/markdown-parser.js +25 -13
- package/dist/core/parsers/markdown-parser.js.map +1 -1
- package/dist/core/planning-home.d.ts +6 -0
- package/dist/core/planning-home.d.ts.map +1 -1
- package/dist/core/planning-home.js +6 -1
- package/dist/core/planning-home.js.map +1 -1
- package/dist/core/project-config.d.ts +17 -26
- package/dist/core/project-config.d.ts.map +1 -1
- package/dist/core/project-config.js +24 -138
- package/dist/core/project-config.js.map +1 -1
- package/dist/core/root-selection.d.ts.map +1 -1
- package/dist/core/root-selection.js +2 -15
- package/dist/core/root-selection.js.map +1 -1
- package/dist/core/shared/index.d.ts +1 -1
- package/dist/core/shared/index.d.ts.map +1 -1
- package/dist/core/shared/index.js +1 -1
- package/dist/core/shared/index.js.map +1 -1
- package/dist/core/shared/skill-generation.js +3 -3
- package/dist/core/shared/skill-generation.js.map +1 -1
- package/dist/core/shared/tool-detection.d.ts +9 -6
- package/dist/core/shared/tool-detection.d.ts.map +1 -1
- package/dist/core/shared/tool-detection.js +9 -9
- package/dist/core/shared/tool-detection.js.map +1 -1
- package/dist/core/spec-presenter.d.ts.map +1 -1
- package/dist/core/spec-presenter.js.map +1 -1
- package/dist/core/specs-apply.d.ts +0 -31
- package/dist/core/specs-apply.d.ts.map +1 -1
- package/dist/core/specs-apply.js +0 -102
- package/dist/core/specs-apply.js.map +1 -1
- package/dist/core/templates/fragments/verify.d.ts +12 -11
- package/dist/core/templates/fragments/verify.d.ts.map +1 -1
- package/dist/core/templates/fragments/verify.js +22 -15
- package/dist/core/templates/fragments/verify.js.map +1 -1
- package/dist/core/templates/workflows/apply.d.ts +2 -2
- package/dist/core/templates/workflows/apply.js +10 -10
- package/dist/core/templates/workflows/apply.js.map +1 -1
- package/dist/core/templates/workflows/archive.d.ts.map +1 -1
- package/dist/core/templates/workflows/archive.js +30 -20
- package/dist/core/templates/workflows/archive.js.map +1 -1
- package/dist/core/templates/workflows/propose.js +2 -2
- package/dist/core/templates/workflows/sync.d.ts +4 -3
- package/dist/core/templates/workflows/sync.d.ts.map +1 -1
- package/dist/core/templates/workflows/sync.js +30 -13
- package/dist/core/templates/workflows/sync.js.map +1 -1
- package/dist/core/templates/workflows/update.js +1 -1
- package/dist/core/update.d.ts.map +1 -1
- package/dist/core/update.js +10 -15
- package/dist/core/update.js.map +1 -1
- package/dist/core/user-state-migration.d.ts +58 -0
- package/dist/core/user-state-migration.d.ts.map +1 -0
- package/dist/core/user-state-migration.js +107 -0
- package/dist/core/user-state-migration.js.map +1 -0
- package/dist/core/validation/section-validator.d.ts.map +1 -1
- package/dist/core/validation/section-validator.js +3 -16
- package/dist/core/validation/section-validator.js.map +1 -1
- package/dist/core/validation/validator.d.ts +0 -9
- package/dist/core/validation/validator.d.ts.map +1 -1
- package/dist/core/validation/validator.js +86 -115
- package/dist/core/validation/validator.js.map +1 -1
- package/dist/utils/change-metadata.d.ts +18 -0
- package/dist/utils/change-metadata.d.ts.map +1 -1
- package/dist/utils/change-metadata.js +39 -0
- package/dist/utils/change-metadata.js.map +1 -1
- package/dist/utils/change-utils.d.ts.map +1 -1
- package/dist/utils/change-utils.js +3 -6
- package/dist/utils/change-utils.js.map +1 -1
- package/dist/utils/file-system.d.ts +17 -19
- package/dist/utils/file-system.d.ts.map +1 -1
- package/dist/utils/file-system.js +41 -189
- package/dist/utils/file-system.js.map +1 -1
- package/dist/utils/frontmatter.d.ts +22 -0
- package/dist/utils/frontmatter.d.ts.map +1 -0
- package/dist/utils/frontmatter.js +39 -0
- package/dist/utils/frontmatter.js.map +1 -0
- package/dist/utils/item-discovery.d.ts +7 -0
- package/dist/utils/item-discovery.d.ts.map +1 -1
- package/dist/utils/item-discovery.js +13 -23
- package/dist/utils/item-discovery.js.map +1 -1
- package/dist/utils/link.d.ts +3 -1
- package/dist/utils/link.d.ts.map +1 -1
- package/dist/utils/link.js +4 -14
- package/dist/utils/link.js.map +1 -1
- package/dist/utils/spec-files.d.ts +19 -0
- package/dist/utils/spec-files.d.ts.map +1 -0
- package/dist/utils/spec-files.js +44 -0
- package/dist/utils/spec-files.js.map +1 -0
- package/dist/utils/task-progress.d.ts +0 -1
- package/dist/utils/task-progress.d.ts.map +1 -1
- package/dist/utils/task-progress.js +5 -6
- package/dist/utils/task-progress.js.map +1 -1
- package/package.json +11 -2
- package/schemas/issue/schema.yaml +6 -3
- package/schemas/sdd/schema.yaml +15 -2
- package/dist/core/references.d.ts +0 -40
- package/dist/core/references.d.ts.map +0 -1
- package/dist/core/references.js +0 -11
- package/dist/core/references.js.map +0 -1
- package/dist/utils/command-references.d.ts +0 -18
- package/dist/utils/command-references.d.ts.map +0 -1
- package/dist/utils/command-references.js +0 -20
- package/dist/utils/command-references.js.map +0 -1
- package/dist/utils/index.d.ts +0 -6
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -9
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/shell-detection.d.ts +0 -20
- package/dist/utils/shell-detection.d.ts.map +0 -1
- package/dist/utils/shell-detection.js +0 -41
- package/dist/utils/shell-detection.js.map +0 -1
package/dist/core/update.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update.js","sourceRoot":"","sources":["../../src/core/update.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,aAAa,
|
|
1
|
+
{"version":3,"file":"update.js","sourceRoot":"","sources":["../../src/core/update.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every path the migration acts on, resolved by the caller. Taking them rather
|
|
3
|
+
* than reading the environment is what keeps this testable without a platform
|
|
4
|
+
* whose legacy paths differ from its current ones — and it keeps the one policy
|
|
5
|
+
* decision (does XDG_DATA_HOME name the live schema directory?) in one place.
|
|
6
|
+
*
|
|
7
|
+
* `schemasDir: null` means "do not migrate schemas at all": XDG_DATA_HOME names
|
|
8
|
+
* a directory the user chose and that is still honoured, so the platform-default
|
|
9
|
+
* legacy directory it superseded is left where it is.
|
|
10
|
+
*/
|
|
11
|
+
export interface MigrationPaths {
|
|
12
|
+
configDir: string;
|
|
13
|
+
legacyConfigDir: string;
|
|
14
|
+
schemasDir: string | null;
|
|
15
|
+
legacySchemasDir: string;
|
|
16
|
+
/**
|
|
17
|
+
* How to move things. Swapped out in tests only: the copy fallback's
|
|
18
|
+
* mid-flight failure — the one that can leave a half-written destination —
|
|
19
|
+
* cannot be provoked portably through the filesystem, and it is the failure
|
|
20
|
+
* whose handling matters most.
|
|
21
|
+
*/
|
|
22
|
+
move?: MoveOperations;
|
|
23
|
+
}
|
|
24
|
+
export interface MoveOperations {
|
|
25
|
+
file: (src: string, dest: string) => Promise<void>;
|
|
26
|
+
directory: (src: string, dest: string) => Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
export interface MigrationResult {
|
|
29
|
+
/** Destinations that now hold what the legacy location used to. */
|
|
30
|
+
moved: string[];
|
|
31
|
+
/** Legacy paths kept because the destination was already occupied. */
|
|
32
|
+
leftBehind: string[];
|
|
33
|
+
/** Moves that could not complete; the legacy copy is still authoritative. */
|
|
34
|
+
failed: Array<{
|
|
35
|
+
source: string;
|
|
36
|
+
message: string;
|
|
37
|
+
}>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Moves user-level state from its pre-`~/.config/tospec` locations exactly once.
|
|
41
|
+
*
|
|
42
|
+
* Config file and schema directory are judged independently, so an install that
|
|
43
|
+
* already migrated one still migrates the other. Nothing is ever overwritten or
|
|
44
|
+
* deleted: a destination that already exists means the legacy copy is left on
|
|
45
|
+
* disk and reported, for the user to delete when they are ready.
|
|
46
|
+
*/
|
|
47
|
+
export declare function migrateLegacyUserState(paths: MigrationPaths): Promise<MigrationResult>;
|
|
48
|
+
/**
|
|
49
|
+
* The migration as `init` and `update` call it: resolve today's and yesterday's
|
|
50
|
+
* locations from the environment, run it, and report in one line per outcome.
|
|
51
|
+
* Failures are warnings — a migration that cannot happen must never stop the
|
|
52
|
+
* command that triggered it.
|
|
53
|
+
*
|
|
54
|
+
* Needs no once-per-process guard: a second run finds the sources gone and does
|
|
55
|
+
* nothing, which is also exactly what makes it safe to call from two commands.
|
|
56
|
+
*/
|
|
57
|
+
export declare function runLegacyUserStateMigration(): Promise<void>;
|
|
58
|
+
//# sourceMappingURL=user-state-migration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-state-migration.d.ts","sourceRoot":"","sources":["../../src/core/user-state-migration.ts"],"names":[],"mappings":"AAUA;;;;;;;;;GASG;AACH,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,cAAc,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACzD;AAID,MAAM,WAAW,eAAe;IAC9B,mEAAmE;IACnE,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,sEAAsE;IACtE,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,6EAA6E;IAC7E,MAAM,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpD;AAED;;;;;;;GAOG;AACH,wBAAsB,sBAAsB,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC,CAgB5F;AA8DD;;;;;;;;GAQG;AACH,wBAAsB,2BAA2B,IAAI,OAAO,CAAC,IAAI,CAAC,CAsBjE"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { moveDirectory, moveFile } from './archive.js';
|
|
4
|
+
import { GLOBAL_CONFIG_FILE_NAME, getGlobalConfigDir, getLegacyConfigDir, getLegacyDataDir, } from './global-config.js';
|
|
5
|
+
const REAL_MOVES = { file: moveFile, directory: moveDirectory };
|
|
6
|
+
/**
|
|
7
|
+
* Moves user-level state from its pre-`~/.config/tospec` locations exactly once.
|
|
8
|
+
*
|
|
9
|
+
* Config file and schema directory are judged independently, so an install that
|
|
10
|
+
* already migrated one still migrates the other. Nothing is ever overwritten or
|
|
11
|
+
* deleted: a destination that already exists means the legacy copy is left on
|
|
12
|
+
* disk and reported, for the user to delete when they are ready.
|
|
13
|
+
*/
|
|
14
|
+
export async function migrateLegacyUserState(paths) {
|
|
15
|
+
const result = { moved: [], leftBehind: [], failed: [] };
|
|
16
|
+
const move = paths.move ?? REAL_MOVES;
|
|
17
|
+
await migrateItem(result, path.join(paths.legacyConfigDir, GLOBAL_CONFIG_FILE_NAME), path.join(paths.configDir, GLOBAL_CONFIG_FILE_NAME), move.file);
|
|
18
|
+
if (paths.schemasDir !== null) {
|
|
19
|
+
await migrateItem(result, paths.legacySchemasDir, paths.schemasDir, move.directory);
|
|
20
|
+
}
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
async function migrateItem(result, source, destination, move) {
|
|
24
|
+
// A missing source is the common case (nothing to migrate) and is also how a
|
|
25
|
+
// concurrent run looks to whichever process loses the race — both mean
|
|
26
|
+
// "already migrated", and neither is worth a word to the user.
|
|
27
|
+
if (!(await exists(source)))
|
|
28
|
+
return;
|
|
29
|
+
// The same directory reached by two names: identical paths on macOS/Linux,
|
|
30
|
+
// where config never moved, and the junction someone may have created by hand
|
|
31
|
+
// as a workaround. Moving either into itself would destroy the data.
|
|
32
|
+
if (await sameRealPath(source, destination))
|
|
33
|
+
return;
|
|
34
|
+
if (await exists(destination)) {
|
|
35
|
+
result.leftBehind.push(source);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
// Inside the try: creating the destination's parent is as capable of
|
|
40
|
+
// failing as the move itself (a file sitting where the directory belongs,
|
|
41
|
+
// a read-only tree), and a migration that cannot happen must never take
|
|
42
|
+
// down the command that triggered it.
|
|
43
|
+
await fs.mkdir(path.dirname(destination), { recursive: true });
|
|
44
|
+
await move(source, destination);
|
|
45
|
+
result.moved.push(destination);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
// The move helpers fall back to copy-then-remove, which is not atomic: a
|
|
49
|
+
// copy that dies partway leaves a destination that later reads as "already
|
|
50
|
+
// migrated" and would silently truncate the user's state. Remove it so the
|
|
51
|
+
// only two outcomes stay "migrated" and "not migrated".
|
|
52
|
+
await fs.rm(destination, { recursive: true, force: true }).catch(() => { });
|
|
53
|
+
result.failed.push({
|
|
54
|
+
source,
|
|
55
|
+
message: error instanceof Error ? error.message : String(error),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function exists(target) {
|
|
60
|
+
try {
|
|
61
|
+
await fs.lstat(target);
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async function sameRealPath(source, destination) {
|
|
69
|
+
try {
|
|
70
|
+
return (await fs.realpath(source)) === (await fs.realpath(destination));
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// An unresolvable destination simply does not exist yet.
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The migration as `init` and `update` call it: resolve today's and yesterday's
|
|
79
|
+
* locations from the environment, run it, and report in one line per outcome.
|
|
80
|
+
* Failures are warnings — a migration that cannot happen must never stop the
|
|
81
|
+
* command that triggered it.
|
|
82
|
+
*
|
|
83
|
+
* Needs no once-per-process guard: a second run finds the sources gone and does
|
|
84
|
+
* nothing, which is also exactly what makes it safe to call from two commands.
|
|
85
|
+
*/
|
|
86
|
+
export async function runLegacyUserStateMigration() {
|
|
87
|
+
const configDir = getGlobalConfigDir();
|
|
88
|
+
// XDG_DATA_HOME still names a live schema override, so when it is set there is
|
|
89
|
+
// no legacy schema directory to reclaim.
|
|
90
|
+
const schemasDir = process.env.XDG_DATA_HOME ? null : path.join(configDir, 'schemas');
|
|
91
|
+
const result = await migrateLegacyUserState({
|
|
92
|
+
configDir,
|
|
93
|
+
legacyConfigDir: getLegacyConfigDir(),
|
|
94
|
+
schemasDir,
|
|
95
|
+
legacySchemasDir: path.join(getLegacyDataDir(), 'schemas'),
|
|
96
|
+
});
|
|
97
|
+
for (const destination of result.moved) {
|
|
98
|
+
console.log(`Moved tospec user state to ${destination}`);
|
|
99
|
+
}
|
|
100
|
+
for (const source of result.leftBehind) {
|
|
101
|
+
console.log(`Left ${source} in place — ${configDir} already has a copy.`);
|
|
102
|
+
}
|
|
103
|
+
for (const failure of result.failed) {
|
|
104
|
+
console.error(`Warning: could not move ${failure.source} — ${failure.message}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=user-state-migration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-state-migration.js","sourceRoot":"","sources":["../../src/core/user-state-migration.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"section-validator.d.ts","sourceRoot":"","sources":["../../../src/core/validation/section-validator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,WAAW,sBAAsB;IACrC,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,wBAAwB;IACvC,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;
|
|
1
|
+
{"version":3,"file":"section-validator.d.ts","sourceRoot":"","sources":["../../../src/core/validation/section-validator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,WAAW,sBAAsB;IACrC,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,wBAAwB;IACvC,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,sBAAsB,EAC7B,OAAO,EAAE,wBAAwB,GAChC,eAAe,EAAE,CAwCnB"}
|
|
@@ -1,17 +1,4 @@
|
|
|
1
|
-
import { MarkdownParser } from '../parsers/markdown-parser.js';
|
|
2
|
-
function findSectionByTitle(sections, title) {
|
|
3
|
-
const target = title.trim().toLowerCase();
|
|
4
|
-
for (const section of sections) {
|
|
5
|
-
if (section.title.trim().toLowerCase() === target) {
|
|
6
|
-
return section;
|
|
7
|
-
}
|
|
8
|
-
const found = findSectionByTitle(section.children, title);
|
|
9
|
-
if (found) {
|
|
10
|
-
return found;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
return undefined;
|
|
14
|
-
}
|
|
1
|
+
import { MarkdownParser, findSection } from '../parsers/markdown-parser.js';
|
|
15
2
|
/**
|
|
16
3
|
* Validates a document's headings against schema-declared rules, for
|
|
17
4
|
* artifacts (ticket.md, design.md, task.md, ...) that have no Requirement
|
|
@@ -22,7 +9,7 @@ export function validateSections(content, rules, context) {
|
|
|
22
9
|
const issues = [];
|
|
23
10
|
const sections = new MarkdownParser(content).getSections();
|
|
24
11
|
for (const requiredTitle of rules.requiredSections) {
|
|
25
|
-
const section =
|
|
12
|
+
const section = findSection(sections, requiredTitle);
|
|
26
13
|
if (!section) {
|
|
27
14
|
const hint = context.templateRef ? ` (see template: ${context.templateRef})` : '';
|
|
28
15
|
issues.push({
|
|
@@ -41,7 +28,7 @@ export function validateSections(content, rules, context) {
|
|
|
41
28
|
}
|
|
42
29
|
}
|
|
43
30
|
for (const [sectionTitle, minLength] of Object.entries(rules.minSectionLength)) {
|
|
44
|
-
const section =
|
|
31
|
+
const section = findSection(sections, sectionTitle);
|
|
45
32
|
if (!section) {
|
|
46
33
|
continue; // absence is already reported above when also required
|
|
47
34
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"section-validator.js","sourceRoot":"","sources":["../../../src/core/validation/section-validator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,
|
|
1
|
+
{"version":3,"file":"section-validator.js","sourceRoot":"","sources":["../../../src/core/validation/section-validator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAe5E;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAC9B,OAAe,EACf,KAA6B,EAC7B,OAAiC;IAEjC,MAAM,MAAM,GAAsB,EAAE,CAAC;IACrC,MAAM,QAAQ,GAAG,IAAI,cAAc,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;IAE3D,KAAK,MAAM,aAAa,IAAI,KAAK,CAAC,gBAAgB,EAAE,CAAC;QACnD,MAAM,OAAO,GAAG,WAAW,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;QACrD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,mBAAmB,OAAO,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAClF,MAAM,CAAC,IAAI,CAAC;gBACV,KAAK,EAAE,OAAO;gBACd,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,OAAO,EAAE,6BAA6B,aAAa,IAAI,IAAI,EAAE;aAC9D,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;YAC5B,MAAM,CAAC,IAAI,CAAC;gBACV,KAAK,EAAE,SAAS;gBAChB,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,OAAO,EAAE,YAAY,aAAa,YAAY;aAC/C,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,KAAK,MAAM,CAAC,YAAY,EAAE,SAAS,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,gBAAgB,CAAC,EAAE,CAAC;QAC/E,MAAM,OAAO,GAAG,WAAW,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;QACpD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,SAAS,CAAC,uDAAuD;QACnE,CAAC;QACD,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC;QAC7C,IAAI,MAAM,GAAG,CAAC,IAAI,MAAM,GAAG,SAAS,EAAE,CAAC;YACrC,MAAM,CAAC,IAAI,CAAC;gBACV,KAAK,EAAE,SAAS;gBAChB,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,OAAO,EAAE,YAAY,YAAY,qCAAqC,SAAS,sBAAsB,MAAM,GAAG;aAC/G,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -18,20 +18,11 @@ export declare class Validator {
|
|
|
18
18
|
* - No duplicates within sections; no cross-section conflicts per spec
|
|
19
19
|
*/
|
|
20
20
|
validateChangeDeltaSpecs(changeDir: string): Promise<ValidationReport>;
|
|
21
|
-
/**
|
|
22
|
-
* Recursively collect every delta `spec.md` under a change's specs directory,
|
|
23
|
-
* so both the one-level (specs/<capability>/spec.md) and nested multi-area
|
|
24
|
-
* (specs/<area>/<capability>/spec.md) layouts are discovered (#1182b).
|
|
25
|
-
* Returns absolute paths, sorted for deterministic issue ordering.
|
|
26
|
-
*/
|
|
27
|
-
private findDeltaSpecFiles;
|
|
28
21
|
private convertZodErrors;
|
|
29
22
|
private applySpecRules;
|
|
30
23
|
private applyChangeRules;
|
|
31
24
|
private enrichTopLevelError;
|
|
32
|
-
private extractNameFromPath;
|
|
33
25
|
private createReport;
|
|
34
|
-
isValid(report: ValidationReport): boolean;
|
|
35
26
|
private extractRequirementText;
|
|
36
27
|
private containsShallOrMust;
|
|
37
28
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../../src/core/validation/validator.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,gBAAgB,EAAoC,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../../src/core/validation/validator.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,gBAAgB,EAAoC,MAAM,YAAY,CAAC;AAkBhF,qBAAa,SAAS;IACpB,OAAO,CAAC,UAAU,CAAU;gBAEhB,UAAU,GAAE,OAAe;IAIjC,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAiB/D;;OAEG;IACG,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAkBjF,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA+BjE;;;;;;;;OAQG;IACG,wBAAwB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAyP5E,OAAO,CAAC,gBAAgB;IAcxB,OAAO,CAAC,cAAc;IAqEtB,OAAO,CAAC,gBAAgB;IA2BxB,OAAO,CAAC,mBAAmB;IAc3B,OAAO,CAAC,YAAY;IAoBpB,OAAO,CAAC,sBAAsB;IAU9B,OAAO,CAAC,mBAAmB;IAI3B;;;;;;;;;OASG;IACH,OAAO,CAAC,8BAA8B;IAQtC;;;;;OAKG;IACH,OAAO,CAAC,qBAAqB;IA2C7B,OAAO,CAAC,iBAAiB;CAO1B"}
|
|
@@ -7,35 +7,29 @@ import { MIN_PURPOSE_LENGTH, MAX_REQUIREMENT_TEXT_LENGTH, VALIDATION_MESSAGES }
|
|
|
7
7
|
import { parseDeltaSpec, normalizeRequirementName, extractRequirementsSection } from '../parsers/requirement-blocks.js';
|
|
8
8
|
import { extractRequirementBody as extractRequirementBodyShared, containsShallOrMust as containsShallOrMustShared, analyzeScenarios, scanCodeFences, } from '../parsers/requirement-text.js';
|
|
9
9
|
import { findMainSpecStructureIssues } from '../parsers/spec-structure.js';
|
|
10
|
-
import { FileSystemUtils } from '../../utils/file-system.js';
|
|
10
|
+
import { FileSystemUtils, extractNameFromPath } from '../../utils/file-system.js';
|
|
11
|
+
import { findSpecFiles } from '../../utils/spec-files.js';
|
|
11
12
|
export class Validator {
|
|
12
13
|
strictMode;
|
|
13
14
|
constructor(strictMode = false) {
|
|
14
15
|
this.strictMode = strictMode;
|
|
15
16
|
}
|
|
16
17
|
async validateSpec(filePath) {
|
|
17
|
-
const
|
|
18
|
-
|
|
18
|
+
const specName = extractNameFromPath(filePath);
|
|
19
|
+
let content;
|
|
19
20
|
try {
|
|
20
|
-
|
|
21
|
-
const parser = new MarkdownParser(content);
|
|
22
|
-
const spec = parser.parseSpec(specName);
|
|
23
|
-
const result = SpecSchema.safeParse(spec);
|
|
24
|
-
if (!result.success) {
|
|
25
|
-
issues.push(...this.convertZodErrors(result.error));
|
|
26
|
-
}
|
|
27
|
-
issues.push(...this.applySpecRules(spec, content));
|
|
21
|
+
content = readFileSync(filePath, 'utf-8');
|
|
28
22
|
}
|
|
29
23
|
catch (error) {
|
|
30
24
|
const baseMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
path: 'file',
|
|
35
|
-
message: enriched,
|
|
36
|
-
});
|
|
25
|
+
return this.createReport([
|
|
26
|
+
{ level: 'ERROR', path: 'file', message: this.enrichTopLevelError(specName, baseMessage) },
|
|
27
|
+
]);
|
|
37
28
|
}
|
|
38
|
-
|
|
29
|
+
// Everything past the read is identical to the content path — keeping one
|
|
30
|
+
// body is what stops `validate` and `archive` grading the same spec
|
|
31
|
+
// differently.
|
|
32
|
+
return this.validateSpecContent(specName, content);
|
|
39
33
|
}
|
|
40
34
|
/**
|
|
41
35
|
* Validate spec content from a string (used for pre-write validation of rebuilt specs)
|
|
@@ -60,7 +54,7 @@ export class Validator {
|
|
|
60
54
|
}
|
|
61
55
|
async validateChange(filePath) {
|
|
62
56
|
const issues = [];
|
|
63
|
-
const changeName =
|
|
57
|
+
const changeName = extractNameFromPath(filePath);
|
|
64
58
|
try {
|
|
65
59
|
const content = readFileSync(filePath, 'utf-8');
|
|
66
60
|
const changeDir = path.dirname(filePath);
|
|
@@ -96,14 +90,18 @@ export class Validator {
|
|
|
96
90
|
const issues = [];
|
|
97
91
|
const specsDir = path.join(changeDir, 'specs');
|
|
98
92
|
let totalDeltas = 0;
|
|
99
|
-
let
|
|
93
|
+
let hasMisplacedSpec = false;
|
|
100
94
|
const missingHeaderSpecs = [];
|
|
101
95
|
const emptySectionSpecs = [];
|
|
102
96
|
try {
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
97
|
+
// The walk below is recursive, but its purpose is to *reject* anything
|
|
98
|
+
// outside specs/<capability>/spec.md, not to support it. The layout is
|
|
99
|
+
// fixed at exactly one directory level; see
|
|
100
|
+
// tospec/decisions/20260730_014309-delta-spec-layout-one-level.md, which
|
|
101
|
+
// reverses the nested multi-area support added in #1182b. Only validation
|
|
102
|
+
// ever went recursive — the merge path (findSpecUpdates) never did — so a
|
|
103
|
+
// misplaced file that validates clean is a file that archives into
|
|
104
|
+
// nothing. Validation is the only place that can catch it.
|
|
107
105
|
const rootSpecPath = path.join(specsDir, 'spec.md');
|
|
108
106
|
// A spec.md directly at the specs/ root has no capability folder, so the
|
|
109
107
|
// apply/archive merge path (findSpecUpdates) drops it: without this error
|
|
@@ -111,17 +109,50 @@ export class Validator {
|
|
|
111
109
|
// reach the main specs. Only a regular file counts — a *directory* named
|
|
112
110
|
// spec.md is a capability folder like any other.
|
|
113
111
|
const rootSpecStat = await fs.stat(rootSpecPath).catch(() => null);
|
|
114
|
-
|
|
115
|
-
|
|
112
|
+
if (rootSpecStat?.isFile() === true) {
|
|
113
|
+
hasMisplacedSpec = true;
|
|
116
114
|
issues.push({
|
|
117
115
|
level: 'ERROR',
|
|
118
116
|
path: 'spec.md',
|
|
119
117
|
message: 'Delta spec found at specs/spec.md. Delta specs must live in a capability folder (e.g. specs/<capability>/spec.md) — a file at the specs/ root is ignored when the change is applied or archived.',
|
|
120
118
|
});
|
|
121
119
|
}
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
|
|
120
|
+
// Report each misplaced file once, here, and drop it from the delta pass
|
|
121
|
+
// below — validating it as if it were a real delta would contradict the
|
|
122
|
+
// error that just said it will never be applied.
|
|
123
|
+
const specFiles = [];
|
|
124
|
+
for (const specFile of await findSpecFiles(specsDir)) {
|
|
125
|
+
if (specFile === rootSpecPath)
|
|
126
|
+
continue; // already reported above
|
|
127
|
+
const entryPath = FileSystemUtils.toPosixPath(path.relative(specsDir, specFile));
|
|
128
|
+
const segments = entryPath.split('/');
|
|
129
|
+
if (segments.length > 2) {
|
|
130
|
+
hasMisplacedSpec = true;
|
|
131
|
+
// The fix is mechanical, so name the flattened path outright:
|
|
132
|
+
// specs/platform/session/spec.md -> specs/platform-session/spec.md.
|
|
133
|
+
const flattened = segments.slice(0, -1).join('-');
|
|
134
|
+
issues.push({
|
|
135
|
+
level: 'ERROR',
|
|
136
|
+
path: entryPath,
|
|
137
|
+
message: `Delta spec found at specs/${entryPath}. Delta specs live in exactly one capability folder (specs/<capability>/spec.md) — a nested path is ignored when the change is applied or archived. Flatten it to specs/${flattened}/spec.md.`,
|
|
138
|
+
});
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
// fast-glob (which computes artifact status from the schema's
|
|
142
|
+
// specs/*/spec.md) skips dot-directories, while the merge path's
|
|
143
|
+
// readdir does not. A dot capability would archive without ever
|
|
144
|
+
// showing as done — the same class of silent divergence.
|
|
145
|
+
if (segments[0].startsWith('.')) {
|
|
146
|
+
hasMisplacedSpec = true;
|
|
147
|
+
issues.push({
|
|
148
|
+
level: 'ERROR',
|
|
149
|
+
path: entryPath,
|
|
150
|
+
message: `Delta spec found at specs/${entryPath}. A capability folder must not start with a dot — dot-directories are invisible when artifact status is computed, so the change would archive without ever reporting its specs as done. Rename the folder.`,
|
|
151
|
+
});
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
specFiles.push(specFile);
|
|
155
|
+
}
|
|
125
156
|
for (const specFile of specFiles) {
|
|
126
157
|
let content;
|
|
127
158
|
try {
|
|
@@ -165,43 +196,31 @@ export class Validator {
|
|
|
165
196
|
const removedNames = new Set();
|
|
166
197
|
const renamedFrom = new Set();
|
|
167
198
|
const renamedTo = new Set();
|
|
168
|
-
//
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
issues.push({ level: 'ERROR', path: entryPath, message: `Duplicate requirement in MODIFIED: "${block.name}"` });
|
|
193
|
-
}
|
|
194
|
-
else {
|
|
195
|
-
modifiedNames.add(key);
|
|
196
|
-
}
|
|
197
|
-
const requirementText = this.extractRequirementText(block.raw);
|
|
198
|
-
if (!requirementText) {
|
|
199
|
-
issues.push({ level: 'ERROR', path: entryPath, message: `MODIFIED "${block.name}" is missing requirement text` });
|
|
200
|
-
}
|
|
201
|
-
else if (!this.containsShallOrMust(requirementText)) {
|
|
202
|
-
issues.push({ level: 'ERROR', path: entryPath, message: this.buildMissingShallOrMustMessage(`MODIFIED "${block.name}"`, block.name) });
|
|
199
|
+
// ADDED and MODIFIED carry identical per-requirement rules. One body
|
|
200
|
+
// means a new rule cannot land on only one of the two sections; the
|
|
201
|
+
// outer loop preserves the all-ADDED-then-all-MODIFIED issue order.
|
|
202
|
+
for (const [section, blocks, seen] of [
|
|
203
|
+
['ADDED', plan.added, addedNames],
|
|
204
|
+
['MODIFIED', plan.modified, modifiedNames],
|
|
205
|
+
]) {
|
|
206
|
+
for (const block of blocks) {
|
|
207
|
+
const key = normalizeRequirementName(block.name);
|
|
208
|
+
totalDeltas++;
|
|
209
|
+
if (seen.has(key)) {
|
|
210
|
+
issues.push({ level: 'ERROR', path: entryPath, message: `Duplicate requirement in ${section}: "${block.name}"` });
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
seen.add(key);
|
|
214
|
+
}
|
|
215
|
+
const requirementText = this.extractRequirementText(block.raw);
|
|
216
|
+
if (!requirementText) {
|
|
217
|
+
issues.push({ level: 'ERROR', path: entryPath, message: `${section} "${block.name}" is missing requirement text` });
|
|
218
|
+
}
|
|
219
|
+
else if (!this.containsShallOrMust(requirementText)) {
|
|
220
|
+
issues.push({ level: 'ERROR', path: entryPath, message: this.buildMissingShallOrMustMessage(`${section} "${block.name}"`, block.name) });
|
|
221
|
+
}
|
|
222
|
+
issues.push(...this.collectScenarioIssues(section, block, entryPath));
|
|
203
223
|
}
|
|
204
|
-
issues.push(...this.collectScenarioIssues('MODIFIED', block, entryPath));
|
|
205
224
|
}
|
|
206
225
|
// Validate REMOVED — the template requires Reason and Migration
|
|
207
226
|
// fields on every entry (report 2.4).
|
|
@@ -304,43 +323,14 @@ export class Validator {
|
|
|
304
323
|
message: 'No delta sections found. Add headers such as "## ADDED Requirements" or move non-delta notes outside specs/.',
|
|
305
324
|
});
|
|
306
325
|
}
|
|
307
|
-
//
|
|
326
|
+
// A misplaced-spec error already names the file and the fix; adding "No
|
|
308
327
|
// deltas found" on top would contradict it, since the deltas are sitting in
|
|
309
328
|
// the file just reported.
|
|
310
|
-
if (totalDeltas === 0 && !
|
|
329
|
+
if (totalDeltas === 0 && !hasMisplacedSpec) {
|
|
311
330
|
issues.push({ level: 'ERROR', path: 'file', message: this.enrichTopLevelError('change', VALIDATION_MESSAGES.CHANGE_NO_DELTAS) });
|
|
312
331
|
}
|
|
313
332
|
return this.createReport(issues);
|
|
314
333
|
}
|
|
315
|
-
/**
|
|
316
|
-
* Recursively collect every delta `spec.md` under a change's specs directory,
|
|
317
|
-
* so both the one-level (specs/<capability>/spec.md) and nested multi-area
|
|
318
|
-
* (specs/<area>/<capability>/spec.md) layouts are discovered (#1182b).
|
|
319
|
-
* Returns absolute paths, sorted for deterministic issue ordering.
|
|
320
|
-
*/
|
|
321
|
-
async findDeltaSpecFiles(specsDir) {
|
|
322
|
-
const results = [];
|
|
323
|
-
const walk = async (dir) => {
|
|
324
|
-
let entries;
|
|
325
|
-
try {
|
|
326
|
-
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
327
|
-
}
|
|
328
|
-
catch {
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
for (const entry of entries) {
|
|
332
|
-
const full = path.join(dir, entry.name);
|
|
333
|
-
if (entry.isDirectory()) {
|
|
334
|
-
await walk(full);
|
|
335
|
-
}
|
|
336
|
-
else if (entry.isFile() && entry.name === 'spec.md') {
|
|
337
|
-
results.push(full);
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
};
|
|
341
|
-
await walk(specsDir);
|
|
342
|
-
return results.sort();
|
|
343
|
-
}
|
|
344
334
|
convertZodErrors(error) {
|
|
345
335
|
return error.issues.map(err => {
|
|
346
336
|
let message = err.message;
|
|
@@ -450,22 +440,6 @@ export class Validator {
|
|
|
450
440
|
}
|
|
451
441
|
return msg;
|
|
452
442
|
}
|
|
453
|
-
extractNameFromPath(filePath) {
|
|
454
|
-
const normalizedPath = FileSystemUtils.toPosixPath(filePath);
|
|
455
|
-
const parts = normalizedPath.split('/');
|
|
456
|
-
// Look for the directory name after 'specs' or 'changes'
|
|
457
|
-
for (let i = parts.length - 1; i >= 0; i--) {
|
|
458
|
-
if (parts[i] === 'specs' || parts[i] === 'changes') {
|
|
459
|
-
if (i < parts.length - 1) {
|
|
460
|
-
return parts[i + 1];
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
// Fallback to filename without extension if not in expected structure
|
|
465
|
-
const fileName = parts[parts.length - 1] ?? '';
|
|
466
|
-
const dotIndex = fileName.lastIndexOf('.');
|
|
467
|
-
return dotIndex > 0 ? fileName.slice(0, dotIndex) : fileName;
|
|
468
|
-
}
|
|
469
443
|
createReport(issues) {
|
|
470
444
|
const errors = issues.filter(i => i.level === 'ERROR').length;
|
|
471
445
|
const warnings = issues.filter(i => i.level === 'WARNING').length;
|
|
@@ -483,9 +457,6 @@ export class Validator {
|
|
|
483
457
|
},
|
|
484
458
|
};
|
|
485
459
|
}
|
|
486
|
-
isValid(report) {
|
|
487
|
-
return report.valid;
|
|
488
|
-
}
|
|
489
460
|
extractRequirementText(blockRaw) {
|
|
490
461
|
// Delegate to the shared, fence-/metadata-/multi-line-aware body reader.
|
|
491
462
|
// Validation intentionally does not use the parser/display header-title
|