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.
Files changed (155) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/README.md +12 -1
  3. package/bin/specweave.js +25 -7
  4. package/dist/plugins/specweave/lib/integrations/github/github-client-v2.js +2 -2
  5. package/dist/plugins/specweave/lib/integrations/github/github-client-v2.js.map +1 -1
  6. package/dist/src/cli/commands/auto-status.d.ts +0 -10
  7. package/dist/src/cli/commands/auto-status.d.ts.map +1 -1
  8. package/dist/src/cli/commands/auto-status.js +3 -2
  9. package/dist/src/cli/commands/auto-status.js.map +1 -1
  10. package/dist/src/cli/commands/auto.d.ts +0 -17
  11. package/dist/src/cli/commands/auto.d.ts.map +1 -1
  12. package/dist/src/cli/commands/auto.js +8 -14
  13. package/dist/src/cli/commands/auto.js.map +1 -1
  14. package/dist/src/cli/commands/get.d.ts +4 -0
  15. package/dist/src/cli/commands/get.d.ts.map +1 -1
  16. package/dist/src/cli/commands/get.js +15 -33
  17. package/dist/src/cli/commands/get.js.map +1 -1
  18. package/dist/src/cli/commands/qa.js +2 -2
  19. package/dist/src/cli/commands/qa.js.map +1 -1
  20. package/dist/src/cli/commands/save.js +1 -1
  21. package/dist/src/cli/commands/save.js.map +1 -1
  22. package/dist/src/cli/commands/sync-progress.d.ts +0 -10
  23. package/dist/src/cli/commands/sync-progress.d.ts.map +1 -1
  24. package/dist/src/cli/commands/sync-progress.js +21 -27
  25. package/dist/src/cli/commands/sync-progress.js.map +1 -1
  26. package/dist/src/cli/commands/task.js +1 -1
  27. package/dist/src/cli/commands/task.js.map +1 -1
  28. package/dist/src/cli/commands/update.js +1 -1
  29. package/dist/src/cli/commands/update.js.map +1 -1
  30. package/dist/src/cli/helpers/get/register-repo.d.ts +1 -11
  31. package/dist/src/cli/helpers/get/register-repo.d.ts.map +1 -1
  32. package/dist/src/cli/helpers/get/register-repo.js +6 -85
  33. package/dist/src/cli/helpers/get/register-repo.js.map +1 -1
  34. package/dist/src/cli/helpers/get/source-parser.d.ts +1 -1
  35. package/dist/src/cli/helpers/get/source-parser.d.ts.map +1 -1
  36. package/dist/src/cli/helpers/get/source-parser.js +4 -4
  37. package/dist/src/cli/helpers/get/source-parser.js.map +1 -1
  38. package/dist/src/cli/helpers/init/path-utils.d.ts +1 -1
  39. package/dist/src/cli/helpers/init/path-utils.d.ts.map +1 -1
  40. package/dist/src/cli/helpers/init/path-utils.js +44 -2
  41. package/dist/src/cli/helpers/init/path-utils.js.map +1 -1
  42. package/dist/src/cli/helpers/init/types.d.ts +1 -0
  43. package/dist/src/cli/helpers/init/types.d.ts.map +1 -1
  44. package/dist/src/core/ac-test-validator-cli.js +1 -1
  45. package/dist/src/core/ac-test-validator-cli.js.map +1 -1
  46. package/dist/src/core/config/types.d.ts +3 -1
  47. package/dist/src/core/config/types.d.ts.map +1 -1
  48. package/dist/src/core/config/types.js.map +1 -1
  49. package/dist/src/core/doctor/checkers/increments-checker.js +2 -2
  50. package/dist/src/core/doctor/checkers/increments-checker.js.map +1 -1
  51. package/dist/src/core/hooks/handlers/hook-router.d.ts.map +1 -1
  52. package/dist/src/core/hooks/handlers/hook-router.js +5 -7
  53. package/dist/src/core/hooks/handlers/hook-router.js.map +1 -1
  54. package/dist/src/core/hooks/handlers/stop.d.ts.map +1 -1
  55. package/dist/src/core/hooks/handlers/stop.js +17 -14
  56. package/dist/src/core/hooks/handlers/stop.js.map +1 -1
  57. package/dist/src/core/hooks/handlers/utils.d.ts.map +1 -1
  58. package/dist/src/core/hooks/handlers/utils.js +7 -15
  59. package/dist/src/core/hooks/handlers/utils.js.map +1 -1
  60. package/dist/src/core/increment/duplicate-detector.d.ts +0 -8
  61. package/dist/src/core/increment/duplicate-detector.d.ts.map +1 -1
  62. package/dist/src/core/increment/duplicate-detector.js +29 -13
  63. package/dist/src/core/increment/duplicate-detector.js.map +1 -1
  64. package/dist/src/core/increment/increment-archiver.d.ts +0 -6
  65. package/dist/src/core/increment/increment-archiver.d.ts.map +1 -1
  66. package/dist/src/core/increment/increment-archiver.js +12 -7
  67. package/dist/src/core/increment/increment-archiver.js.map +1 -1
  68. package/dist/src/core/increment/increment-utils.d.ts +6 -6
  69. package/dist/src/core/increment/increment-utils.d.ts.map +1 -1
  70. package/dist/src/core/increment/increment-utils.js +26 -27
  71. package/dist/src/core/increment/increment-utils.js.map +1 -1
  72. package/dist/src/core/increment/metadata-manager.d.ts.map +1 -1
  73. package/dist/src/core/increment/metadata-manager.js +20 -7
  74. package/dist/src/core/increment/metadata-manager.js.map +1 -1
  75. package/dist/src/core/increment/number-reservation.d.ts +16 -0
  76. package/dist/src/core/increment/number-reservation.d.ts.map +1 -0
  77. package/dist/src/core/increment/number-reservation.js +125 -0
  78. package/dist/src/core/increment/number-reservation.js.map +1 -0
  79. package/dist/src/core/increment/template-creator.d.ts.map +1 -1
  80. package/dist/src/core/increment/template-creator.js +11 -43
  81. package/dist/src/core/increment/template-creator.js.map +1 -1
  82. package/dist/src/core/living-docs/feature-archiver.js +3 -3
  83. package/dist/src/core/living-docs/feature-archiver.js.map +1 -1
  84. package/dist/src/core/living-docs/feature-consistency-validator.js +1 -1
  85. package/dist/src/core/living-docs/feature-consistency-validator.js.map +1 -1
  86. package/dist/src/core/living-docs/feature-id-manager.js +2 -2
  87. package/dist/src/core/living-docs/feature-id-manager.js.map +1 -1
  88. package/dist/src/core/living-docs/hierarchy-mapper.js +3 -3
  89. package/dist/src/core/living-docs/hierarchy-mapper.js.map +1 -1
  90. package/dist/src/core/living-docs/living-docs-sync.js +1 -1
  91. package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
  92. package/dist/src/core/qa/qa-runner.d.ts +0 -6
  93. package/dist/src/core/qa/qa-runner.d.ts.map +1 -1
  94. package/dist/src/core/qa/qa-runner.js +8 -10
  95. package/dist/src/core/qa/qa-runner.js.map +1 -1
  96. package/dist/src/core/session/handoff-all.d.ts +2 -2
  97. package/dist/src/core/session/handoff-all.d.ts.map +1 -1
  98. package/dist/src/core/session/handoff-all.js +5 -3
  99. package/dist/src/core/session/handoff-all.js.map +1 -1
  100. package/dist/src/core/session/handoff-list.d.ts +0 -19
  101. package/dist/src/core/session/handoff-list.d.ts.map +1 -1
  102. package/dist/src/core/session/handoff-list.js +7 -4
  103. package/dist/src/core/session/handoff-list.js.map +1 -1
  104. package/dist/src/core/session/handoff-remote.d.ts +0 -19
  105. package/dist/src/core/session/handoff-remote.d.ts.map +1 -1
  106. package/dist/src/core/session/handoff-remote.js +3 -3
  107. package/dist/src/core/session/handoff-remote.js.map +1 -1
  108. package/dist/src/core/session/nested-repos.d.ts.map +1 -1
  109. package/dist/src/core/session/nested-repos.js +7 -1
  110. package/dist/src/core/session/nested-repos.js.map +1 -1
  111. package/dist/src/core/specs/spec-metadata-manager.js +1 -1
  112. package/dist/src/core/specs/spec-metadata-manager.js.map +1 -1
  113. package/dist/src/core/specs/spec-parser.js +1 -1
  114. package/dist/src/core/specs/spec-parser.js.map +1 -1
  115. package/dist/src/core/tasks/resolve-increment.d.ts.map +1 -1
  116. package/dist/src/core/tasks/resolve-increment.js +3 -2
  117. package/dist/src/core/tasks/resolve-increment.js.map +1 -1
  118. package/dist/src/core/types/increment-metadata.d.ts +2 -0
  119. package/dist/src/core/types/increment-metadata.d.ts.map +1 -1
  120. package/dist/src/core/types/increment-metadata.js.map +1 -1
  121. package/dist/src/core/workspace/adoption.d.ts +21 -0
  122. package/dist/src/core/workspace/adoption.d.ts.map +1 -0
  123. package/dist/src/core/workspace/adoption.js +187 -0
  124. package/dist/src/core/workspace/adoption.js.map +1 -0
  125. package/dist/src/core/workspace/context.d.ts +21 -0
  126. package/dist/src/core/workspace/context.d.ts.map +1 -0
  127. package/dist/src/core/workspace/context.js +105 -0
  128. package/dist/src/core/workspace/context.js.map +1 -0
  129. package/dist/src/importers/item-converter.js +1 -1
  130. package/dist/src/importers/item-converter.js.map +1 -1
  131. package/dist/src/sync/github-reconciler.js +2 -2
  132. package/dist/src/sync/github-reconciler.js.map +1 -1
  133. package/dist/src/utils/external-tool-drift-detector.js +1 -1
  134. package/dist/src/utils/external-tool-drift-detector.js.map +1 -1
  135. package/dist/src/utils/feature-id-collision.js +1 -1
  136. package/dist/src/utils/feature-id-collision.js.map +1 -1
  137. package/dist/src/utils/find-project-root.d.ts.map +1 -1
  138. package/dist/src/utils/find-project-root.js +16 -36
  139. package/dist/src/utils/find-project-root.js.map +1 -1
  140. package/dist/src/utils/increment-id-validator.d.ts +27 -67
  141. package/dist/src/utils/increment-id-validator.d.ts.map +1 -1
  142. package/dist/src/utils/increment-id-validator.js +57 -106
  143. package/dist/src/utils/increment-id-validator.js.map +1 -1
  144. package/dist/src/utils/resolve-increment-id.d.ts +0 -22
  145. package/dist/src/utils/resolve-increment-id.d.ts.map +1 -1
  146. package/dist/src/utils/resolve-increment-id.js +17 -41
  147. package/dist/src/utils/resolve-increment-id.js.map +1 -1
  148. package/package.json +2 -1
  149. package/plugins/specweave/.claude-plugin/plugin.json +1 -1
  150. package/plugins/specweave/lib/integrations/github/github-client-v2.js +2 -2
  151. package/plugins/specweave/lib/integrations/github/github-client-v2.ts +2 -2
  152. package/src/core/schemas/specweave-config.schema.json +1130 -0
  153. package/src/core/schemas/sync.schema.json +687 -0
  154. package/src/core/schemas/workspace.schema.json +209 -0
  155. package/src/templates/git-hooks/pre-commit.template +4 -4
@@ -1,71 +1,31 @@
1
- /**
2
- * Increment ID Validator
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
- */
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
- * Validates increment ID and throws an error if invalid.
32
- *
33
- * Use this in entry points (CLI commands, API handlers) where you want to
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;;;;;;;;;;;;;GAaG;AAEH;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CA6BtD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAOpD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAWhE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAKvD"}
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
- * Increment ID Validator
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
- // Reject 0000 — increment numbers must be 0001+
54
- const num = parseInt(id.slice(0, 4), 10);
55
- return num > 0;
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
- * Validates increment ID and throws an error if invalid.
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 (!id || typeof id !== 'string') {
32
+ if (typeof id !== 'string')
90
33
  return null;
91
- }
92
- const match = id.match(/^(\d{4})/);
93
- if (!match) {
34
+ const match = id.match(/^(\d{4,})(?:[A-Za-z])?(?=-|$)/);
35
+ if (!match)
94
36
  return null;
95
- }
96
- return parseInt(match[1], 10);
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
- if (!isValidIncrementId(id)) {
113
- return false;
114
- }
115
- return /^\d{4}E-/.test(id);
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":"AAAA;;;;;;;;;;;;;GAaG;AAEH;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,kBAAkB,CAAC,EAAU;IAC3C,IAAI,CAAC,EAAE,IAAI,OAAO,EAAE,KAAK,QAAQ,EAAE,CAAC;QAClC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,mBAAmB;IACnB,yBAAyB;IACzB,gEAAgE;IAChE,WAAW;IACX,4EAA4E;IAC5E,EAAE;IACF,mBAAmB;IACnB,gDAAgD;IAChD,6CAA6C;IAC7C,yCAAyC;IACzC,kDAAkD;IAClD,mDAAmD;IACnD,4EAA4E;IAC5E,KAAK;IACL,qDAAqD;IACrD,EAAE;IACF,yEAAyE;IACzE,IAAI,CAAC,yDAAyD,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;QACxE,OAAO,KAAK,CAAC;IACf,CAAC;IAED,gDAAgD;IAChD,MAAM,GAAG,GAAG,QAAQ,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACzC,OAAO,GAAG,GAAG,CAAC,CAAC;AACjB,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,mBAAmB,CAAC,EAAU;IAC5C,IAAI,CAAC,kBAAkB,CAAC,EAAE,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CACb,iCAAiC,EAAE,KAAK;YACxC,4FAA4F,CAC7F,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,sBAAsB,CAAC,EAAU;IAC/C,IAAI,CAAC,EAAE,IAAI,OAAO,EAAE,KAAK,QAAQ,EAAE,CAAC;QAClC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IACnC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAChC,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,mBAAmB,CAAC,EAAU;IAC5C,IAAI,CAAC,kBAAkB,CAAC,EAAE,CAAC,EAAE,CAAC;QAC5B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC7B,CAAC"}
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":"AAAA;;;;;;;;;;;;;;GAcG;AAKH;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAiC1B"}
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 matches = entries.filter((e) => e.startsWith(incrementId));
44
- if (matches.length === 0) {
45
- return null;
46
- }
47
- if (matches.length === 1) {
48
- return matches[0];
49
- }
50
- // Ambiguous — multiple matches
51
- return matches.sort();
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;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAE7B;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAChC,WAAmB,EACnB,WAAmB;IAEnB,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,YAAY,EAAE,YAAY,CAAC,CAAC;IAEzE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QAClC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,8BAA8B;IAC9B,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;IACxD,IAAI,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC7B,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,mBAAmB;IACnB,IAAI,OAAiB,CAAC;IACtB,IAAI,CAAC;QACH,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC;IAEjE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,+BAA+B;IAC/B,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC;AACxB,CAAC"}
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.15",
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.15",
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,4}E?\]\s/;
231
+ const plainIncrementPattern = /^\[\d{3,}[A-Za-z]?\]\s/;
232
232
  if (plainIncrementPattern.test(title)) {
233
- const match = title.match(/^\[(\d{3,4}E?)\]/);
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,4}E?\]\s/;
310
+ const plainIncrementPattern = /^\[\d{3,}[A-Za-z]?\]\s/;
311
311
  if (plainIncrementPattern.test(title)) {
312
- const match = title.match(/^\[(\d{3,4}E?)\]/);
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);