specweave 3.0.15 → 3.0.17
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/.claude-plugin/marketplace.json +2 -2
- package/README.md +12 -1
- package/bin/specweave.js +25 -7
- package/dist/plugins/specweave/lib/integrations/github/github-client-v2.js +2 -2
- package/dist/plugins/specweave/lib/integrations/github/github-client-v2.js.map +1 -1
- package/dist/src/cli/commands/auto-status.d.ts +0 -10
- package/dist/src/cli/commands/auto-status.d.ts.map +1 -1
- package/dist/src/cli/commands/auto-status.js +3 -2
- package/dist/src/cli/commands/auto-status.js.map +1 -1
- package/dist/src/cli/commands/auto.d.ts +0 -17
- package/dist/src/cli/commands/auto.d.ts.map +1 -1
- package/dist/src/cli/commands/auto.js +8 -14
- package/dist/src/cli/commands/auto.js.map +1 -1
- package/dist/src/cli/commands/get.d.ts +4 -0
- package/dist/src/cli/commands/get.d.ts.map +1 -1
- package/dist/src/cli/commands/get.js +15 -33
- package/dist/src/cli/commands/get.js.map +1 -1
- package/dist/src/cli/commands/qa.js +2 -2
- package/dist/src/cli/commands/qa.js.map +1 -1
- package/dist/src/cli/commands/save.js +1 -1
- package/dist/src/cli/commands/save.js.map +1 -1
- package/dist/src/cli/commands/sync-progress.d.ts +0 -10
- package/dist/src/cli/commands/sync-progress.d.ts.map +1 -1
- package/dist/src/cli/commands/sync-progress.js +21 -27
- package/dist/src/cli/commands/sync-progress.js.map +1 -1
- package/dist/src/cli/commands/task.js +1 -1
- package/dist/src/cli/commands/task.js.map +1 -1
- package/dist/src/cli/commands/update.js +1 -1
- package/dist/src/cli/commands/update.js.map +1 -1
- package/dist/src/cli/helpers/get/register-repo.d.ts +1 -11
- package/dist/src/cli/helpers/get/register-repo.d.ts.map +1 -1
- package/dist/src/cli/helpers/get/register-repo.js +6 -85
- package/dist/src/cli/helpers/get/register-repo.js.map +1 -1
- package/dist/src/cli/helpers/get/source-parser.d.ts +1 -1
- package/dist/src/cli/helpers/get/source-parser.d.ts.map +1 -1
- package/dist/src/cli/helpers/get/source-parser.js +4 -4
- package/dist/src/cli/helpers/get/source-parser.js.map +1 -1
- package/dist/src/cli/helpers/init/path-utils.d.ts +1 -1
- package/dist/src/cli/helpers/init/path-utils.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/path-utils.js +44 -2
- package/dist/src/cli/helpers/init/path-utils.js.map +1 -1
- package/dist/src/cli/helpers/init/types.d.ts +1 -0
- package/dist/src/cli/helpers/init/types.d.ts.map +1 -1
- package/dist/src/core/ac-test-validator-cli.js +1 -1
- package/dist/src/core/ac-test-validator-cli.js.map +1 -1
- package/dist/src/core/config/types.d.ts +3 -1
- package/dist/src/core/config/types.d.ts.map +1 -1
- package/dist/src/core/config/types.js.map +1 -1
- package/dist/src/core/doctor/checkers/increments-checker.js +2 -2
- package/dist/src/core/doctor/checkers/increments-checker.js.map +1 -1
- package/dist/src/core/hooks/handlers/hook-router.d.ts.map +1 -1
- package/dist/src/core/hooks/handlers/hook-router.js +5 -7
- package/dist/src/core/hooks/handlers/hook-router.js.map +1 -1
- package/dist/src/core/hooks/handlers/stop.d.ts.map +1 -1
- package/dist/src/core/hooks/handlers/stop.js +17 -14
- package/dist/src/core/hooks/handlers/stop.js.map +1 -1
- package/dist/src/core/hooks/handlers/utils.d.ts.map +1 -1
- package/dist/src/core/hooks/handlers/utils.js +7 -15
- package/dist/src/core/hooks/handlers/utils.js.map +1 -1
- package/dist/src/core/increment/duplicate-detector.d.ts +0 -8
- package/dist/src/core/increment/duplicate-detector.d.ts.map +1 -1
- package/dist/src/core/increment/duplicate-detector.js +29 -13
- package/dist/src/core/increment/duplicate-detector.js.map +1 -1
- package/dist/src/core/increment/increment-archiver.d.ts +0 -6
- package/dist/src/core/increment/increment-archiver.d.ts.map +1 -1
- package/dist/src/core/increment/increment-archiver.js +12 -7
- package/dist/src/core/increment/increment-archiver.js.map +1 -1
- package/dist/src/core/increment/increment-utils.d.ts +6 -6
- package/dist/src/core/increment/increment-utils.d.ts.map +1 -1
- package/dist/src/core/increment/increment-utils.js +26 -27
- package/dist/src/core/increment/increment-utils.js.map +1 -1
- package/dist/src/core/increment/metadata-manager.d.ts.map +1 -1
- package/dist/src/core/increment/metadata-manager.js +20 -7
- package/dist/src/core/increment/metadata-manager.js.map +1 -1
- package/dist/src/core/increment/number-reservation.d.ts +16 -0
- package/dist/src/core/increment/number-reservation.d.ts.map +1 -0
- package/dist/src/core/increment/number-reservation.js +125 -0
- package/dist/src/core/increment/number-reservation.js.map +1 -0
- package/dist/src/core/increment/template-creator.d.ts.map +1 -1
- package/dist/src/core/increment/template-creator.js +11 -43
- package/dist/src/core/increment/template-creator.js.map +1 -1
- package/dist/src/core/living-docs/feature-archiver.js +3 -3
- package/dist/src/core/living-docs/feature-archiver.js.map +1 -1
- package/dist/src/core/living-docs/feature-consistency-validator.js +1 -1
- package/dist/src/core/living-docs/feature-consistency-validator.js.map +1 -1
- package/dist/src/core/living-docs/feature-id-manager.js +2 -2
- package/dist/src/core/living-docs/feature-id-manager.js.map +1 -1
- package/dist/src/core/living-docs/hierarchy-mapper.js +3 -3
- package/dist/src/core/living-docs/hierarchy-mapper.js.map +1 -1
- package/dist/src/core/living-docs/living-docs-sync.js +1 -1
- package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
- package/dist/src/core/qa/qa-runner.d.ts +0 -6
- package/dist/src/core/qa/qa-runner.d.ts.map +1 -1
- package/dist/src/core/qa/qa-runner.js +8 -10
- package/dist/src/core/qa/qa-runner.js.map +1 -1
- package/dist/src/core/session/handoff-all.d.ts +2 -2
- package/dist/src/core/session/handoff-all.d.ts.map +1 -1
- package/dist/src/core/session/handoff-all.js +5 -3
- package/dist/src/core/session/handoff-all.js.map +1 -1
- package/dist/src/core/session/handoff-list.d.ts +0 -19
- package/dist/src/core/session/handoff-list.d.ts.map +1 -1
- package/dist/src/core/session/handoff-list.js +7 -4
- package/dist/src/core/session/handoff-list.js.map +1 -1
- package/dist/src/core/session/handoff-remote.d.ts +0 -19
- package/dist/src/core/session/handoff-remote.d.ts.map +1 -1
- package/dist/src/core/session/handoff-remote.js +3 -3
- package/dist/src/core/session/handoff-remote.js.map +1 -1
- package/dist/src/core/session/nested-repos.d.ts.map +1 -1
- package/dist/src/core/session/nested-repos.js +7 -1
- package/dist/src/core/session/nested-repos.js.map +1 -1
- package/dist/src/core/specs/spec-metadata-manager.js +1 -1
- package/dist/src/core/specs/spec-metadata-manager.js.map +1 -1
- package/dist/src/core/specs/spec-parser.js +1 -1
- package/dist/src/core/specs/spec-parser.js.map +1 -1
- package/dist/src/core/tasks/resolve-increment.d.ts.map +1 -1
- package/dist/src/core/tasks/resolve-increment.js +3 -2
- package/dist/src/core/tasks/resolve-increment.js.map +1 -1
- package/dist/src/core/types/increment-metadata.d.ts +2 -0
- package/dist/src/core/types/increment-metadata.d.ts.map +1 -1
- package/dist/src/core/types/increment-metadata.js.map +1 -1
- package/dist/src/core/workspace/adoption.d.ts +21 -0
- package/dist/src/core/workspace/adoption.d.ts.map +1 -0
- package/dist/src/core/workspace/adoption.js +187 -0
- package/dist/src/core/workspace/adoption.js.map +1 -0
- package/dist/src/core/workspace/context.d.ts +21 -0
- package/dist/src/core/workspace/context.d.ts.map +1 -0
- package/dist/src/core/workspace/context.js +105 -0
- package/dist/src/core/workspace/context.js.map +1 -0
- package/dist/src/importers/item-converter.js +1 -1
- package/dist/src/importers/item-converter.js.map +1 -1
- package/dist/src/sync/github-reconciler.js +2 -2
- package/dist/src/sync/github-reconciler.js.map +1 -1
- package/dist/src/utils/external-tool-drift-detector.js +1 -1
- package/dist/src/utils/external-tool-drift-detector.js.map +1 -1
- package/dist/src/utils/feature-id-collision.js +1 -1
- package/dist/src/utils/feature-id-collision.js.map +1 -1
- package/dist/src/utils/find-project-root.d.ts.map +1 -1
- package/dist/src/utils/find-project-root.js +16 -36
- package/dist/src/utils/find-project-root.js.map +1 -1
- package/dist/src/utils/increment-id-validator.d.ts +27 -67
- package/dist/src/utils/increment-id-validator.d.ts.map +1 -1
- package/dist/src/utils/increment-id-validator.js +57 -106
- package/dist/src/utils/increment-id-validator.js.map +1 -1
- package/dist/src/utils/resolve-increment-id.d.ts +0 -22
- package/dist/src/utils/resolve-increment-id.d.ts.map +1 -1
- package/dist/src/utils/resolve-increment-id.js +17 -41
- package/dist/src/utils/resolve-increment-id.js.map +1 -1
- package/package.json +2 -1
- package/plugins/specweave/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave/lib/integrations/github/github-client-v2.js +2 -2
- package/plugins/specweave/lib/integrations/github/github-client-v2.ts +2 -2
- package/src/core/schemas/specweave-config.schema.json +1130 -0
- package/src/core/schemas/sync.schema.json +687 -0
- package/src/core/schemas/workspace.schema.json +209 -0
- package/src/templates/git-hooks/pre-commit.template +4 -4
|
@@ -1,71 +1,31 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Validates increment ID format to prevent path traversal attacks.
|
|
17
|
-
*
|
|
18
|
-
* @param id - Increment ID to validate
|
|
19
|
-
* @returns true if ID format is valid, false otherwise
|
|
20
|
-
*
|
|
21
|
-
* @example
|
|
22
|
-
* isValidIncrementId('0001-feature-name') // true
|
|
23
|
-
* isValidIncrementId('0111E-external') // true
|
|
24
|
-
* isValidIncrementId('0001A-hotfix') // true (alpha suffix)
|
|
25
|
-
* isValidIncrementId('../../etc') // false (path traversal)
|
|
26
|
-
* isValidIncrementId('') // false (empty)
|
|
27
|
-
* isValidIncrementId('001-short') // false (only 3 digits)
|
|
28
|
-
*/
|
|
1
|
+
/** Shared increment ID syntax. Four digits are minimum padding, never a ceiling. */
|
|
2
|
+
export interface ParsedIncrementId {
|
|
3
|
+
id: string;
|
|
4
|
+
number: number;
|
|
5
|
+
/** Canonical numeric alias, without an import suffix. */
|
|
6
|
+
numberText: string;
|
|
7
|
+
suffix: string;
|
|
8
|
+
name: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function formatIncrementNumber(number: number): string;
|
|
11
|
+
/** Readers accept old three-digit IDs and underscores without renaming them. */
|
|
12
|
+
export declare function parseIncrementId(id: string, options?: {
|
|
13
|
+
allowLegacy?: boolean;
|
|
14
|
+
}): ParsedIncrementId | null;
|
|
29
15
|
export declare function isValidIncrementId(id: string): boolean;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
* fail fast with a clear error message.
|
|
35
|
-
*
|
|
36
|
-
* @param id - Increment ID to validate
|
|
37
|
-
* @throws Error if ID format is invalid
|
|
38
|
-
*
|
|
39
|
-
* @example
|
|
40
|
-
* validateIncrementId('0001-feature'); // passes
|
|
41
|
-
* validateIncrementId('../../etc'); // throws Error
|
|
42
|
-
*/
|
|
43
|
-
export declare function validateIncrementId(id: string): void;
|
|
44
|
-
/**
|
|
45
|
-
* Sanitizes an increment ID by extracting just the number prefix.
|
|
46
|
-
* Useful for sorting or comparison operations.
|
|
47
|
-
*
|
|
48
|
-
* @param id - Increment ID to extract number from
|
|
49
|
-
* @returns Numeric prefix (e.g., 1 for "0001-feature") or null if invalid
|
|
50
|
-
*
|
|
51
|
-
* @example
|
|
52
|
-
* extractIncrementNumber('0053-feature') // 53
|
|
53
|
-
* extractIncrementNumber('0111E-external') // 111
|
|
54
|
-
* extractIncrementNumber('invalid') // null
|
|
55
|
-
*/
|
|
16
|
+
export declare function validateIncrementId(id: string, options?: {
|
|
17
|
+
allowLegacy?: boolean;
|
|
18
|
+
}): void;
|
|
19
|
+
/** Numeric extraction requires a boundary; it never truncates a longer number. */
|
|
56
20
|
export declare function extractIncrementNumber(id: string): number | null;
|
|
57
|
-
/**
|
|
58
|
-
* Checks if an increment ID has the external suffix (E).
|
|
59
|
-
*
|
|
60
|
-
* External increments are imported from external tools (GitHub, JIRA, ADO)
|
|
61
|
-
* and have read-only sync behavior.
|
|
62
|
-
*
|
|
63
|
-
* @param id - Increment ID to check
|
|
64
|
-
* @returns true if this is an external increment
|
|
65
|
-
*
|
|
66
|
-
* @example
|
|
67
|
-
* isExternalIncrement('0111E-jira-import') // true
|
|
68
|
-
* isExternalIncrement('0001-feature') // false
|
|
69
|
-
*/
|
|
70
21
|
export declare function isExternalIncrement(id: string): boolean;
|
|
22
|
+
export declare function compareIncrementIds(a: string, b: string): number;
|
|
23
|
+
/** Bare numeric aliases match the whole number, optionally an exact suffix. */
|
|
24
|
+
export declare function parseIncrementAlias(value: string): {
|
|
25
|
+
number: number;
|
|
26
|
+
suffix: string;
|
|
27
|
+
} | null;
|
|
28
|
+
export declare function matchesIncrementAlias(id: string, alias: string): boolean;
|
|
29
|
+
/** Shared selection policy for readers with different filesystem adapters. */
|
|
30
|
+
export declare function matchIncrementIds(ids: string[], query: string): string[];
|
|
71
31
|
//# sourceMappingURL=increment-id-validator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"increment-id-validator.d.ts","sourceRoot":"","sources":["../../../src/utils/increment-id-validator.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"increment-id-validator.d.ts","sourceRoot":"","sources":["../../../src/utils/increment-id-validator.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,yDAAyD;IACzD,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAK5D;AAED,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,iBAAiB,GAAG,IAAI,CAU9G;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,IAAI,CAI7F;AAED,kFAAkF;AAClF,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMhE;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAIhE;AAED,+EAA+E;AAC/E,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAK5F;AAED,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAIxE;AAED,8EAA8E;AAC9E,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAIxE"}
|
|
@@ -1,117 +1,68 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* SECURITY: Centralized validation for increment IDs to prevent path traversal attacks.
|
|
5
|
-
* Increment IDs are used in file paths - malicious IDs like "../../etc/passwd" could
|
|
6
|
-
* escape the .specweave/increments directory.
|
|
7
|
-
*
|
|
8
|
-
* Valid formats:
|
|
9
|
-
* - Standard: NNNN-name (e.g., "0001-feature-name")
|
|
10
|
-
* - External: NNNNE-name (e.g., "0111E-external-import")
|
|
11
|
-
* - Archived: Same as above, stored in _archive/
|
|
12
|
-
*
|
|
13
|
-
* @module utils/increment-id-validator
|
|
14
|
-
*/
|
|
15
|
-
/**
|
|
16
|
-
* Validates increment ID format to prevent path traversal attacks.
|
|
17
|
-
*
|
|
18
|
-
* @param id - Increment ID to validate
|
|
19
|
-
* @returns true if ID format is valid, false otherwise
|
|
20
|
-
*
|
|
21
|
-
* @example
|
|
22
|
-
* isValidIncrementId('0001-feature-name') // true
|
|
23
|
-
* isValidIncrementId('0111E-external') // true
|
|
24
|
-
* isValidIncrementId('0001A-hotfix') // true (alpha suffix)
|
|
25
|
-
* isValidIncrementId('../../etc') // false (path traversal)
|
|
26
|
-
* isValidIncrementId('') // false (empty)
|
|
27
|
-
* isValidIncrementId('001-short') // false (only 3 digits)
|
|
28
|
-
*/
|
|
29
|
-
export function isValidIncrementId(id) {
|
|
30
|
-
if (!id || typeof id !== 'string') {
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
// Expected format:
|
|
34
|
-
// - 4 digits (0000-9999)
|
|
35
|
-
// - Optional single letter suffix (A-Z, a-z, or E for external)
|
|
36
|
-
// - Hyphen
|
|
37
|
-
// - Kebab-case name (alphanumeric and hyphens, no leading/trailing hyphens)
|
|
38
|
-
//
|
|
39
|
-
// Regex breakdown:
|
|
40
|
-
// ^\d{4} - Must start with exactly 4 digits
|
|
41
|
-
// [A-Za-z]? - Optional single letter suffix
|
|
42
|
-
// - - Required hyphen separator
|
|
43
|
-
// [a-zA-Z0-9] - Name must start with alphanumeric
|
|
44
|
-
// [a-zA-Z0-9-]* - Rest of name can include hyphens
|
|
45
|
-
// [a-zA-Z0-9]$ - Name must end with alphanumeric (prevents trailing hyphen)
|
|
46
|
-
// OR
|
|
47
|
-
// [a-zA-Z0-9]$ - Single character name is also valid
|
|
48
|
-
//
|
|
49
|
-
// Note: We don't allow dots, slashes, or other path-sensitive characters
|
|
50
|
-
if (!/^\d{4}[A-Za-z]?-[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/.test(id)) {
|
|
51
|
-
return false;
|
|
1
|
+
export function formatIncrementNumber(number) {
|
|
2
|
+
if (!Number.isSafeInteger(number) || number < 1) {
|
|
3
|
+
throw new Error('Increment number must be a positive safe integer');
|
|
52
4
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
5
|
+
return String(number).padStart(4, '0');
|
|
6
|
+
}
|
|
7
|
+
/** Readers accept old three-digit IDs and underscores without renaming them. */
|
|
8
|
+
export function parseIncrementId(id, options = {}) {
|
|
9
|
+
if (typeof id !== 'string')
|
|
10
|
+
return null;
|
|
11
|
+
const pattern = options.allowLegacy
|
|
12
|
+
? /^(\d{3,})([A-Za-z]?)-([a-zA-Z0-9](?:[a-zA-Z0-9_-]*[a-zA-Z0-9])?)$/
|
|
13
|
+
: /^(\d{4,})([A-Za-z]?)-([a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?)$/;
|
|
14
|
+
const match = id.match(pattern);
|
|
15
|
+
if (!match)
|
|
16
|
+
return null;
|
|
17
|
+
const number = Number(match[1]);
|
|
18
|
+
if (!Number.isSafeInteger(number) || number < 1)
|
|
19
|
+
return null;
|
|
20
|
+
return { id, number, numberText: formatIncrementNumber(number), suffix: match[2], name: match[3] };
|
|
21
|
+
}
|
|
22
|
+
export function isValidIncrementId(id) {
|
|
23
|
+
return parseIncrementId(id) !== null;
|
|
56
24
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
* Use this in entry points (CLI commands, API handlers) where you want to
|
|
61
|
-
* fail fast with a clear error message.
|
|
62
|
-
*
|
|
63
|
-
* @param id - Increment ID to validate
|
|
64
|
-
* @throws Error if ID format is invalid
|
|
65
|
-
*
|
|
66
|
-
* @example
|
|
67
|
-
* validateIncrementId('0001-feature'); // passes
|
|
68
|
-
* validateIncrementId('../../etc'); // throws Error
|
|
69
|
-
*/
|
|
70
|
-
export function validateIncrementId(id) {
|
|
71
|
-
if (!isValidIncrementId(id)) {
|
|
72
|
-
throw new Error(`Invalid increment ID format: "${id}". ` +
|
|
73
|
-
`Expected format: NNNN-name (e.g., "0001-feature-name") or NNNNE-name for external imports.`);
|
|
25
|
+
export function validateIncrementId(id, options = {}) {
|
|
26
|
+
if (!parseIncrementId(id, options)) {
|
|
27
|
+
throw new Error(`Invalid increment ID format: "${id}". Expected format: NNNN-name (four or more digits), e.g. "0001-feature-name" or "10000G-import".`);
|
|
74
28
|
}
|
|
75
29
|
}
|
|
76
|
-
/**
|
|
77
|
-
* Sanitizes an increment ID by extracting just the number prefix.
|
|
78
|
-
* Useful for sorting or comparison operations.
|
|
79
|
-
*
|
|
80
|
-
* @param id - Increment ID to extract number from
|
|
81
|
-
* @returns Numeric prefix (e.g., 1 for "0001-feature") or null if invalid
|
|
82
|
-
*
|
|
83
|
-
* @example
|
|
84
|
-
* extractIncrementNumber('0053-feature') // 53
|
|
85
|
-
* extractIncrementNumber('0111E-external') // 111
|
|
86
|
-
* extractIncrementNumber('invalid') // null
|
|
87
|
-
*/
|
|
30
|
+
/** Numeric extraction requires a boundary; it never truncates a longer number. */
|
|
88
31
|
export function extractIncrementNumber(id) {
|
|
89
|
-
if (
|
|
32
|
+
if (typeof id !== 'string')
|
|
90
33
|
return null;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (!match) {
|
|
34
|
+
const match = id.match(/^(\d{4,})(?:[A-Za-z])?(?=-|$)/);
|
|
35
|
+
if (!match)
|
|
94
36
|
return null;
|
|
95
|
-
|
|
96
|
-
return
|
|
37
|
+
const number = Number(match[1]);
|
|
38
|
+
return Number.isSafeInteger(number) ? number : null;
|
|
97
39
|
}
|
|
98
|
-
/**
|
|
99
|
-
* Checks if an increment ID has the external suffix (E).
|
|
100
|
-
*
|
|
101
|
-
* External increments are imported from external tools (GitHub, JIRA, ADO)
|
|
102
|
-
* and have read-only sync behavior.
|
|
103
|
-
*
|
|
104
|
-
* @param id - Increment ID to check
|
|
105
|
-
* @returns true if this is an external increment
|
|
106
|
-
*
|
|
107
|
-
* @example
|
|
108
|
-
* isExternalIncrement('0111E-jira-import') // true
|
|
109
|
-
* isExternalIncrement('0001-feature') // false
|
|
110
|
-
*/
|
|
111
40
|
export function isExternalIncrement(id) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
41
|
+
return parseIncrementId(id)?.suffix === 'E';
|
|
42
|
+
}
|
|
43
|
+
export function compareIncrementIds(a, b) {
|
|
44
|
+
const x = parseIncrementId(a, { allowLegacy: true });
|
|
45
|
+
const y = parseIncrementId(b, { allowLegacy: true });
|
|
46
|
+
return x && y ? x.number - y.number || a.localeCompare(b) : a.localeCompare(b);
|
|
47
|
+
}
|
|
48
|
+
/** Bare numeric aliases match the whole number, optionally an exact suffix. */
|
|
49
|
+
export function parseIncrementAlias(value) {
|
|
50
|
+
if (typeof value !== 'string')
|
|
51
|
+
return null;
|
|
52
|
+
const match = value.match(/^(\d+)([A-Za-z]?)$/);
|
|
53
|
+
const number = match ? Number(match[1]) : 0;
|
|
54
|
+
return match && Number.isSafeInteger(number) && number > 0 ? { number, suffix: match[2].toUpperCase() } : null;
|
|
55
|
+
}
|
|
56
|
+
export function matchesIncrementAlias(id, alias) {
|
|
57
|
+
const parsed = parseIncrementId(id, { allowLegacy: true });
|
|
58
|
+
const query = parseIncrementAlias(alias);
|
|
59
|
+
return !!parsed && !!query && parsed.number === query.number && (!query.suffix || parsed.suffix.toUpperCase() === query.suffix);
|
|
60
|
+
}
|
|
61
|
+
/** Shared selection policy for readers with different filesystem adapters. */
|
|
62
|
+
export function matchIncrementIds(ids, query) {
|
|
63
|
+
const valid = ids.filter(id => parseIncrementId(id, { allowLegacy: true }));
|
|
64
|
+
if (valid.includes(query))
|
|
65
|
+
return [query];
|
|
66
|
+
return valid.filter(id => matchesIncrementAlias(id, query)).sort(compareIncrementIds);
|
|
116
67
|
}
|
|
117
68
|
//# sourceMappingURL=increment-id-validator.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"increment-id-validator.js","sourceRoot":"","sources":["../../../src/utils/increment-id-validator.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"increment-id-validator.js","sourceRoot":"","sources":["../../../src/utils/increment-id-validator.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,24 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resolve Increment ID Utility
|
|
3
|
-
*
|
|
4
|
-
* Resolves a potentially short increment ID (e.g., "0589") to the full
|
|
5
|
-
* directory name (e.g., "0589-cli-complete-improvements") by matching
|
|
6
|
-
* against .specweave/increments/ entries.
|
|
7
|
-
*
|
|
8
|
-
* Supports:
|
|
9
|
-
* - Exact match: "0589-cli-complete-improvements" → same
|
|
10
|
-
* - Prefix match: "0589" → "0589-cli-complete-improvements"
|
|
11
|
-
* - Ambiguous match: "0573" → ["0573-fix-a", "0573-fix-b"]
|
|
12
|
-
* - No match: "9999" → null
|
|
13
|
-
*
|
|
14
|
-
* @module utils/resolve-increment-id
|
|
15
|
-
*/
|
|
16
|
-
/**
|
|
17
|
-
* Resolve a potentially short increment ID to the full directory name.
|
|
18
|
-
*
|
|
19
|
-
* @param incrementId - Short or full increment ID
|
|
20
|
-
* @param projectRoot - Project root directory containing .specweave/
|
|
21
|
-
* @returns Full increment directory name (string), array of matches (string[]) if ambiguous, or null if not found
|
|
22
|
-
*/
|
|
23
1
|
export declare function resolveIncrementId(incrementId: string, projectRoot: string): string | string[] | null;
|
|
24
2
|
//# sourceMappingURL=resolve-increment-id.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-increment-id.d.ts","sourceRoot":"","sources":["../../../src/utils/resolve-increment-id.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"resolve-increment-id.d.ts","sourceRoot":"","sources":["../../../src/utils/resolve-increment-id.ts"],"names":[],"mappings":"AAKA,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAgBrG"}
|
|
@@ -1,53 +1,29 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resolve Increment ID Utility
|
|
3
|
-
*
|
|
4
|
-
* Resolves a potentially short increment ID (e.g., "0589") to the full
|
|
5
|
-
* directory name (e.g., "0589-cli-complete-improvements") by matching
|
|
6
|
-
* against .specweave/increments/ entries.
|
|
7
|
-
*
|
|
8
|
-
* Supports:
|
|
9
|
-
* - Exact match: "0589-cli-complete-improvements" → same
|
|
10
|
-
* - Prefix match: "0589" → "0589-cli-complete-improvements"
|
|
11
|
-
* - Ambiguous match: "0573" → ["0573-fix-a", "0573-fix-b"]
|
|
12
|
-
* - No match: "9999" → null
|
|
13
|
-
*
|
|
14
|
-
* @module utils/resolve-increment-id
|
|
15
|
-
*/
|
|
1
|
+
/** Resolve a full ID, exact numeric alias, or immutable UUID without prefix guessing. */
|
|
16
2
|
import * as fs from 'fs';
|
|
17
3
|
import * as path from 'path';
|
|
18
|
-
|
|
19
|
-
* Resolve a potentially short increment ID to the full directory name.
|
|
20
|
-
*
|
|
21
|
-
* @param incrementId - Short or full increment ID
|
|
22
|
-
* @param projectRoot - Project root directory containing .specweave/
|
|
23
|
-
* @returns Full increment directory name (string), array of matches (string[]) if ambiguous, or null if not found
|
|
24
|
-
*/
|
|
4
|
+
import { compareIncrementIds, matchIncrementIds, parseIncrementId } from './increment-id-validator.js';
|
|
25
5
|
export function resolveIncrementId(incrementId, projectRoot) {
|
|
26
6
|
const incrementsDir = path.join(projectRoot, '.specweave', 'increments');
|
|
27
|
-
if (!fs.existsSync(incrementsDir)) {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
// Check for exact match first
|
|
31
|
-
const exactPath = path.join(incrementsDir, incrementId);
|
|
32
|
-
if (fs.existsSync(exactPath)) {
|
|
33
|
-
return incrementId;
|
|
34
|
-
}
|
|
35
|
-
// Try prefix match
|
|
36
7
|
let entries;
|
|
37
8
|
try {
|
|
38
|
-
entries = fs.readdirSync(incrementsDir)
|
|
9
|
+
entries = fs.readdirSync(incrementsDir)
|
|
10
|
+
.filter((entry) => parseIncrementId(entry, { allowLegacy: true }) && fs.statSync(path.join(incrementsDir, entry)).isDirectory());
|
|
39
11
|
}
|
|
40
12
|
catch {
|
|
41
13
|
return null;
|
|
42
14
|
}
|
|
43
|
-
const
|
|
44
|
-
if (
|
|
45
|
-
return
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
15
|
+
const exact = entries.find((entry) => entry === incrementId);
|
|
16
|
+
if (exact)
|
|
17
|
+
return exact;
|
|
18
|
+
const uuid = /^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i.test(incrementId);
|
|
19
|
+
const matches = !uuid ? matchIncrementIds(entries, incrementId) : entries.filter((entry) => {
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(fs.readFileSync(path.join(incrementsDir, entry, 'metadata.json'), 'utf8')).uuid?.toLowerCase() === incrementId.toLowerCase();
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}).sort(compareIncrementIds);
|
|
27
|
+
return matches.length === 0 ? null : matches.length === 1 ? matches[0] : matches;
|
|
52
28
|
}
|
|
53
29
|
//# sourceMappingURL=resolve-increment-id.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-increment-id.js","sourceRoot":"","sources":["../../../src/utils/resolve-increment-id.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"resolve-increment-id.js","sourceRoot":"","sources":["../../../src/utils/resolve-increment-id.ts"],"names":[],"mappings":"AAAA,yFAAyF;AACzF,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAEvG,MAAM,UAAU,kBAAkB,CAAC,WAAmB,EAAE,WAAmB;IACzE,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,YAAY,EAAE,YAAY,CAAC,CAAC;IACzE,IAAI,OAAiB,CAAC;IACtB,IAAI,CAAC;QACH,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,aAAa,CAAC;aACpC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,gBAAgB,CAAC,KAAK,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;IACrI,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,IAAI,CAAC;IAAC,CAAC;IACxB,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,WAAW,CAAC,CAAC;IAC7D,IAAI,KAAK;QAAE,OAAO,KAAK,CAAC;IACxB,MAAM,IAAI,GAAG,gDAAgD,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAChF,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,iBAAiB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;QACzF,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,KAAK,EAAE,eAAe,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,WAAW,CAAC,WAAW,EAAE,CAAC;QACjJ,CAAC;QAAC,MAAM,CAAC;YAAC,OAAO,KAAK,CAAC;QAAC,CAAC;IAC3B,CAAC,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IAC7B,OAAO,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;AACnF,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specweave",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.17",
|
|
4
4
|
"description": "Keep intent, specifications, and verified progress portable across AI coding tools. A local live work board, append-only task evidence, explicit handoffs, and optional GitHub/Jira/Azure DevOps integrations.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -94,6 +94,7 @@
|
|
|
94
94
|
".claude-plugin",
|
|
95
95
|
"plugins",
|
|
96
96
|
"src/templates",
|
|
97
|
+
"src/core/schemas",
|
|
97
98
|
"README.md",
|
|
98
99
|
"CLAUDE.md",
|
|
99
100
|
"INSTALL.md",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sw",
|
|
3
3
|
"description": "Portable intent and verified progress across coding tools. Eleven focused workflow skills, two default lifecycle hooks, and a deterministic CLI and live local dashboard.",
|
|
4
|
-
"version": "3.0.
|
|
4
|
+
"version": "3.0.17",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "SpecWeave Team",
|
|
7
7
|
"url": "https://spec-weave.com"
|
|
@@ -228,9 +228,9 @@ WHY: Correct data flow is: Increment \u2192 Living Docs \u2192 GitHub
|
|
|
228
228
|
FIX: Run 'specweave docs sync <id>' to generate living docs, then sync to GitHub.`
|
|
229
229
|
);
|
|
230
230
|
}
|
|
231
|
-
const plainIncrementPattern = /^\[\d{3,
|
|
231
|
+
const plainIncrementPattern = /^\[\d{3,}[A-Za-z]?\]\s/;
|
|
232
232
|
if (plainIncrementPattern.test(title)) {
|
|
233
|
-
const match = title.match(/^\[(\d{3,
|
|
233
|
+
const match = title.match(/^\[(\d{3,}[A-Za-z]?)\]/);
|
|
234
234
|
const incrementId = match ? match[1] : "XXXX";
|
|
235
235
|
const num = parseInt(incrementId.replace("E", ""), 10);
|
|
236
236
|
const isExternal = incrementId.endsWith("E");
|
|
@@ -307,9 +307,9 @@ export class GitHubClientV2 {
|
|
|
307
307
|
|
|
308
308
|
// Check for plain increment ID format [0001] without FS- prefix (CRITICAL FIX v1.0.31)
|
|
309
309
|
// This catches issues like "[0001] Basic layout MVP" that should be "[FS-001][US-001] Basic layout MVP"
|
|
310
|
-
const plainIncrementPattern = /^\[\d{3,
|
|
310
|
+
const plainIncrementPattern = /^\[\d{3,}[A-Za-z]?\]\s/;
|
|
311
311
|
if (plainIncrementPattern.test(title)) {
|
|
312
|
-
const match = title.match(/^\[(\d{3,
|
|
312
|
+
const match = title.match(/^\[(\d{3,}[A-Za-z]?)\]/);
|
|
313
313
|
const incrementId = match ? match[1] : 'XXXX';
|
|
314
314
|
// Derive proper FS-ID from the increment number
|
|
315
315
|
const num = parseInt(incrementId.replace('E', ''), 10);
|