specweave 0.22.12 → 0.22.14

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 (188) hide show
  1. package/.claude-plugin/README.md +2 -2
  2. package/CLAUDE.md +269 -51
  3. package/README.md +33 -10
  4. package/dist/plugins/specweave-github/lib/ThreeLayerSyncManager.d.ts +1 -1
  5. package/dist/plugins/specweave-github/lib/ThreeLayerSyncManager.js +1 -1
  6. package/dist/plugins/specweave-github/lib/enhanced-github-sync.js +1 -1
  7. package/dist/plugins/specweave-github/lib/enhanced-github-sync.js.map +1 -1
  8. package/dist/plugins/specweave-github/lib/github-spec-content-sync.d.ts.map +1 -1
  9. package/dist/plugins/specweave-github/lib/github-spec-content-sync.js +4 -1
  10. package/dist/plugins/specweave-github/lib/github-spec-content-sync.js.map +1 -1
  11. package/dist/plugins/specweave-github/lib/github-spec-sync.d.ts +1 -1
  12. package/dist/plugins/specweave-github/lib/github-spec-sync.js +1 -1
  13. package/dist/plugins/specweave-github/lib/github-sync-bidirectional.d.ts +9 -0
  14. package/dist/plugins/specweave-github/lib/github-sync-bidirectional.d.ts.map +1 -1
  15. package/dist/plugins/specweave-github/lib/github-sync-bidirectional.js +10 -1
  16. package/dist/plugins/specweave-github/lib/github-sync-bidirectional.js.map +1 -1
  17. package/dist/plugins/specweave-github/lib/progress-comment-builder.js +2 -2
  18. package/dist/plugins/specweave-github/lib/progress-comment-builder.js.map +1 -1
  19. package/dist/plugins/specweave-github/lib/types.d.ts +1 -1
  20. package/dist/src/cli/commands/init.d.ts.map +1 -1
  21. package/dist/src/cli/commands/init.js +313 -1
  22. package/dist/src/cli/commands/init.js.map +1 -1
  23. package/dist/src/cli/helpers/issue-tracker/index.d.ts.map +1 -1
  24. package/dist/src/cli/helpers/issue-tracker/index.js +41 -24
  25. package/dist/src/cli/helpers/issue-tracker/index.js.map +1 -1
  26. package/dist/src/config/import-config.d.ts +69 -0
  27. package/dist/src/config/import-config.d.ts.map +1 -0
  28. package/dist/src/config/import-config.js +136 -0
  29. package/dist/src/config/import-config.js.map +1 -0
  30. package/dist/src/config/types.d.ts +10 -10
  31. package/dist/src/core/living-docs/living-docs-sync.d.ts +2 -0
  32. package/dist/src/core/living-docs/living-docs-sync.d.ts.map +1 -1
  33. package/dist/src/core/living-docs/living-docs-sync.js +10 -1
  34. package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
  35. package/dist/src/core/living-docs/task-project-specific-generator.d.ts +2 -2
  36. package/dist/src/core/living-docs/task-project-specific-generator.js +2 -2
  37. package/dist/src/core/repo-structure/prompt-consolidator.d.ts +2 -2
  38. package/dist/src/core/repo-structure/prompt-consolidator.d.ts.map +1 -1
  39. package/dist/src/core/repo-structure/prompt-consolidator.js +3 -15
  40. package/dist/src/core/repo-structure/prompt-consolidator.js.map +1 -1
  41. package/dist/src/core/repo-structure/repo-structure-manager.d.ts +1 -1
  42. package/dist/src/core/repo-structure/repo-structure-manager.d.ts.map +1 -1
  43. package/dist/src/core/repo-structure/repo-structure-manager.js +3 -6
  44. package/dist/src/core/repo-structure/repo-structure-manager.js.map +1 -1
  45. package/dist/src/core/spec-content-sync.d.ts +4 -1
  46. package/dist/src/core/spec-content-sync.d.ts.map +1 -1
  47. package/dist/src/core/spec-content-sync.js +139 -4
  48. package/dist/src/core/spec-content-sync.js.map +1 -1
  49. package/dist/src/core/spec-task-mapper.d.ts.map +1 -1
  50. package/dist/src/core/spec-task-mapper.js +9 -8
  51. package/dist/src/core/spec-task-mapper.js.map +1 -1
  52. package/dist/src/core/status-line-validator.d.ts +63 -0
  53. package/dist/src/core/status-line-validator.d.ts.map +1 -0
  54. package/dist/src/core/status-line-validator.js +253 -0
  55. package/dist/src/core/status-line-validator.js.map +1 -0
  56. package/dist/src/core/sync/bidirectional-engine.d.ts +10 -1
  57. package/dist/src/core/sync/bidirectional-engine.d.ts.map +1 -1
  58. package/dist/src/core/sync/bidirectional-engine.js +10 -1
  59. package/dist/src/core/sync/bidirectional-engine.js.map +1 -1
  60. package/dist/src/core/sync/profile-manager.d.ts.map +1 -1
  61. package/dist/src/core/sync/profile-manager.js +3 -0
  62. package/dist/src/core/sync/profile-manager.js.map +1 -1
  63. package/dist/src/core/sync/project-context.d.ts.map +1 -1
  64. package/dist/src/core/sync/project-context.js +3 -0
  65. package/dist/src/core/sync/project-context.js.map +1 -1
  66. package/dist/src/core/sync/status-sync-engine.d.ts +1 -1
  67. package/dist/src/core/sync/status-sync-engine.js +1 -1
  68. package/dist/src/core/types/origin-metadata.d.ts +153 -0
  69. package/dist/src/core/types/origin-metadata.d.ts.map +1 -0
  70. package/dist/src/core/types/origin-metadata.js +166 -0
  71. package/dist/src/core/types/origin-metadata.js.map +1 -0
  72. package/dist/src/core/types/sync-profile.d.ts +8 -2
  73. package/dist/src/core/types/sync-profile.d.ts.map +1 -1
  74. package/dist/src/core/types/sync-profile.js.map +1 -1
  75. package/dist/src/core/types/sync-settings.d.ts +73 -0
  76. package/dist/src/core/types/sync-settings.d.ts.map +1 -0
  77. package/dist/src/core/types/sync-settings.js +90 -0
  78. package/dist/src/core/types/sync-settings.js.map +1 -0
  79. package/dist/src/core/utils/permission-checker.d.ts +100 -0
  80. package/dist/src/core/utils/permission-checker.d.ts.map +1 -0
  81. package/dist/src/core/utils/permission-checker.js +166 -0
  82. package/dist/src/core/utils/permission-checker.js.map +1 -0
  83. package/dist/src/generators/spec/spec-parser.js +3 -3
  84. package/dist/src/generators/spec/spec-parser.js.map +1 -1
  85. package/dist/src/generators/spec/task-parser.js +4 -4
  86. package/dist/src/generators/spec/task-parser.js.map +1 -1
  87. package/dist/src/id-generators/task-id-generator.d.ts +96 -0
  88. package/dist/src/id-generators/task-id-generator.d.ts.map +1 -0
  89. package/dist/src/id-generators/task-id-generator.js +143 -0
  90. package/dist/src/id-generators/task-id-generator.js.map +1 -0
  91. package/dist/src/id-generators/us-id-generator.d.ts +96 -0
  92. package/dist/src/id-generators/us-id-generator.d.ts.map +1 -0
  93. package/dist/src/id-generators/us-id-generator.js +143 -0
  94. package/dist/src/id-generators/us-id-generator.js.map +1 -0
  95. package/dist/src/importers/ado-importer.d.ts +43 -0
  96. package/dist/src/importers/ado-importer.d.ts.map +1 -0
  97. package/dist/src/importers/ado-importer.js +234 -0
  98. package/dist/src/importers/ado-importer.js.map +1 -0
  99. package/dist/src/importers/external-importer.d.ts +96 -0
  100. package/dist/src/importers/external-importer.d.ts.map +1 -0
  101. package/dist/src/importers/external-importer.js +13 -0
  102. package/dist/src/importers/external-importer.js.map +1 -0
  103. package/dist/src/importers/github-importer.d.ts +37 -0
  104. package/dist/src/importers/github-importer.d.ts.map +1 -0
  105. package/dist/src/importers/github-importer.js +161 -0
  106. package/dist/src/importers/github-importer.js.map +1 -0
  107. package/dist/src/importers/import-coordinator.d.ts +90 -0
  108. package/dist/src/importers/import-coordinator.d.ts.map +1 -0
  109. package/dist/src/importers/import-coordinator.js +182 -0
  110. package/dist/src/importers/import-coordinator.js.map +1 -0
  111. package/dist/src/importers/item-converter.d.ts +91 -0
  112. package/dist/src/importers/item-converter.d.ts.map +1 -0
  113. package/dist/src/importers/item-converter.js +221 -0
  114. package/dist/src/importers/item-converter.js.map +1 -0
  115. package/dist/src/importers/jira-importer.d.ts +42 -0
  116. package/dist/src/importers/jira-importer.d.ts.map +1 -0
  117. package/dist/src/importers/jira-importer.js +221 -0
  118. package/dist/src/importers/jira-importer.js.map +1 -0
  119. package/dist/src/init/repo/types.d.ts +2 -2
  120. package/dist/src/integrations/jira/jira-mapper.d.ts +1 -1
  121. package/dist/src/integrations/jira/jira-mapper.js +1 -1
  122. package/dist/src/living-docs/fs-id-allocator.d.ts +149 -0
  123. package/dist/src/living-docs/fs-id-allocator.d.ts.map +1 -0
  124. package/dist/src/living-docs/fs-id-allocator.js +325 -0
  125. package/dist/src/living-docs/fs-id-allocator.js.map +1 -0
  126. package/dist/src/living-docs/id-registry.d.ts +124 -0
  127. package/dist/src/living-docs/id-registry.d.ts.map +1 -0
  128. package/dist/src/living-docs/id-registry.js +230 -0
  129. package/dist/src/living-docs/id-registry.js.map +1 -0
  130. package/dist/src/progress/us-progress-tracker.d.ts +68 -0
  131. package/dist/src/progress/us-progress-tracker.d.ts.map +1 -0
  132. package/dist/src/progress/us-progress-tracker.js +120 -0
  133. package/dist/src/progress/us-progress-tracker.js.map +1 -0
  134. package/package.json +2 -2
  135. package/plugins/specweave/.claude-plugin/plugin.json +16 -2
  136. package/plugins/specweave/agents/architect/AGENT.md +11 -2
  137. package/plugins/specweave/agents/test-aware-planner/AGENT.md +81 -25
  138. package/plugins/specweave/commands/specweave-import-docs.md +278 -88
  139. package/plugins/specweave/commands/specweave-progress.md +45 -97
  140. package/plugins/specweave/hooks/post-increment-completion.sh +168 -26
  141. package/plugins/specweave/hooks/post-increment-planning.sh +148 -4
  142. package/plugins/specweave/hooks/post-task-completion.sh +64 -4
  143. package/plugins/specweave/lib/hooks/sync-cache.js +294 -0
  144. package/plugins/specweave/lib/hooks/sync-living-docs.js +32 -1
  145. package/plugins/specweave/lib/hooks/sync-us-tasks.js +23 -13
  146. package/plugins/specweave-ado/.claude-plugin/plugin.json +1 -1
  147. package/plugins/specweave-ado/lib/conflict-resolver.ts +1 -1
  148. package/plugins/specweave-alternatives/.claude-plugin/plugin.json +1 -1
  149. package/plugins/specweave-backend/.claude-plugin/plugin.json +1 -1
  150. package/plugins/specweave-confluent/.claude-plugin/plugin.json +1 -1
  151. package/plugins/specweave-cost-optimizer/.claude-plugin/plugin.json +1 -1
  152. package/plugins/specweave-diagrams/.claude-plugin/plugin.json +1 -1
  153. package/plugins/specweave-docs/.claude-plugin/plugin.json +1 -1
  154. package/plugins/specweave-docs-preview/.claude-plugin/plugin.json +1 -1
  155. package/plugins/specweave-figma/.claude-plugin/plugin.json +1 -1
  156. package/plugins/specweave-frontend/.claude-plugin/plugin.json +1 -1
  157. package/plugins/specweave-github/.claude-plugin/plugin.json +1 -1
  158. package/plugins/specweave-github/hooks/post-task-completion.sh +37 -22
  159. package/plugins/specweave-github/lib/ThreeLayerSyncManager.ts +1 -1
  160. package/plugins/specweave-github/lib/enhanced-github-sync.js +1 -1
  161. package/plugins/specweave-github/lib/enhanced-github-sync.ts +1 -1
  162. package/plugins/specweave-github/lib/github-spec-content-sync.js +2 -1
  163. package/plugins/specweave-github/lib/github-spec-content-sync.ts +4 -1
  164. package/plugins/specweave-github/lib/github-spec-sync.js +1 -1
  165. package/plugins/specweave-github/lib/github-spec-sync.ts +1 -1
  166. package/plugins/specweave-github/lib/github-sync-bidirectional.js +1 -1
  167. package/plugins/specweave-github/lib/github-sync-bidirectional.ts +10 -1
  168. package/plugins/specweave-github/lib/progress-comment-builder.js +1 -1
  169. package/plugins/specweave-github/lib/progress-comment-builder.ts +2 -2
  170. package/plugins/specweave-github/lib/types.ts +1 -1
  171. package/plugins/specweave-infrastructure/.claude-plugin/plugin.json +1 -1
  172. package/plugins/specweave-jira/.claude-plugin/plugin.json +1 -1
  173. package/plugins/specweave-kafka/.claude-plugin/plugin.json +1 -1
  174. package/plugins/specweave-kafka-streams/.claude-plugin/plugin.json +1 -1
  175. package/plugins/specweave-kubernetes/.claude-plugin/plugin.json +1 -1
  176. package/plugins/specweave-ml/.claude-plugin/plugin.json +1 -1
  177. package/plugins/specweave-mobile/.claude-plugin/plugin.json +1 -1
  178. package/plugins/specweave-n8n/.claude-plugin/plugin.json +1 -1
  179. package/plugins/specweave-payments/.claude-plugin/plugin.json +1 -1
  180. package/plugins/specweave-release/.claude-plugin/plugin.json +1 -1
  181. package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +252 -0
  182. package/plugins/specweave-testing/.claude-plugin/plugin.json +1 -1
  183. package/plugins/specweave-tooling/.claude-plugin/plugin.json +1 -1
  184. package/plugins/specweave-ui/.claude-plugin/plugin.json +1 -1
  185. package/src/templates/.env.example +5 -0
  186. package/src/templates/config-permissions-guide.md +413 -0
  187. package/src/templates/config.json.template +68 -0
  188. package/src/templates/tasks.md.template +180 -201
@@ -0,0 +1,149 @@
1
+ /**
2
+ * FS-ID Allocator - Intelligent Feature ID allocation with chronological placement
3
+ *
4
+ * Allocates FS-XXX IDs for external work items based on creation date,
5
+ * attempting chronological insertion while preventing collisions.
6
+ *
7
+ * Features:
8
+ * - Chronological ID allocation based on work item creation date
9
+ * - Gap filling (insert FS-011E between FS-010 and FS-020)
10
+ * - Append mode fallback (max ID + 1)
11
+ * - Collision detection (checks both FS-XXX and FS-XXXE)
12
+ * - Archive-aware (scans both active and archived features)
13
+ */
14
+ import { type ExternalItemMetadata } from '../core/types/origin-metadata.js';
15
+ /**
16
+ * Feature metadata extracted from living docs
17
+ */
18
+ export interface FeatureMetadata {
19
+ /** Feature ID (e.g., "FS-042", "FS-042E") */
20
+ id: string;
21
+ /** Feature creation timestamp (ISO 8601) */
22
+ createdAt: string;
23
+ /** Origin (internal | external) */
24
+ origin: 'internal' | 'external';
25
+ /** External ID if external origin */
26
+ externalId?: string;
27
+ /** Path to feature folder */
28
+ path: string;
29
+ }
30
+ /**
31
+ * External work item to be allocated an FS-ID
32
+ */
33
+ export interface ExternalWorkItem {
34
+ /** External ID (e.g., "GH-#638", "JIRA-SPEC-789") */
35
+ externalId: string;
36
+ /** Work item title */
37
+ title: string;
38
+ /** Creation timestamp (ISO 8601) */
39
+ createdAt: string;
40
+ /** External URL */
41
+ externalUrl: string;
42
+ }
43
+ /**
44
+ * ID allocation result
45
+ */
46
+ export interface AllocationResult {
47
+ /** Allocated FS-ID (e.g., "FS-042E") */
48
+ id: string;
49
+ /** Allocation strategy used */
50
+ strategy: 'chronological-insert' | 'append' | 'first';
51
+ /** Reason for allocation decision */
52
+ reason: string;
53
+ /** Numeric ID value */
54
+ number: number;
55
+ }
56
+ /**
57
+ * FS-ID Allocator
58
+ *
59
+ * Intelligently allocates Feature IDs with chronological placement
60
+ */
61
+ export declare class FSIdAllocator {
62
+ private projectRoot;
63
+ private specsPath;
64
+ private archivePath;
65
+ private existingFeatures;
66
+ private scanned;
67
+ constructor(projectRoot: string);
68
+ /**
69
+ * Scan existing FS-IDs (both active and archived)
70
+ *
71
+ * CRITICAL: Archives are scanned to prevent ID reuse
72
+ */
73
+ scanExistingIds(): Promise<void>;
74
+ /**
75
+ * Scan directory for FS-XXX folders
76
+ */
77
+ private scanDirectory;
78
+ /**
79
+ * Parse feature metadata from README.md frontmatter
80
+ */
81
+ private parseFeatureMetadata;
82
+ /**
83
+ * Allocate FS-ID for external work item with chronological placement
84
+ *
85
+ * Algorithm:
86
+ * 1. Sort existing IDs by creation date
87
+ * 2. Find chronological insertion point based on work item creation date
88
+ * 3. Check for ID gaps between consecutive features
89
+ * 4. If gap exists, allocate next ID in gap (e.g., FS-011E between FS-010 and FS-020)
90
+ * 5. If no gap, append to end (max ID + 1 with E suffix)
91
+ *
92
+ * @param workItem - External work item to allocate ID for
93
+ * @returns Allocation result with ID and strategy
94
+ */
95
+ allocateId(workItem: ExternalWorkItem): Promise<AllocationResult>;
96
+ /**
97
+ * Check if ID has collision (exact match or variant)
98
+ *
99
+ * @param id - FS-ID to check (e.g., "FS-042" or "FS-042E")
100
+ * @returns True if collision exists
101
+ *
102
+ * @example
103
+ * // Given existing IDs: FS-010, FS-011E, FS-020
104
+ * hasCollision('FS-011') // true (FS-011E exists)
105
+ * hasCollision('FS-011E') // true (exact match)
106
+ * hasCollision('FS-012') // false
107
+ */
108
+ hasCollision(id: string): boolean;
109
+ /**
110
+ * Get maximum numeric ID value (ignoring E suffix)
111
+ *
112
+ * @returns Maximum ID number
113
+ */
114
+ getMaxId(): number;
115
+ /**
116
+ * Extract numeric part from FS-ID
117
+ *
118
+ * @param id - FS-ID (e.g., "FS-042" or "FS-042E")
119
+ * @returns Numeric part (e.g., 42)
120
+ */
121
+ private extractNumber;
122
+ /**
123
+ * Get all existing feature IDs (sorted by creation date)
124
+ *
125
+ * @returns Array of feature metadata sorted chronologically
126
+ */
127
+ getExistingFeatures(): FeatureMetadata[];
128
+ /**
129
+ * Get allocation statistics
130
+ */
131
+ getStats(): {
132
+ total: number;
133
+ active: number;
134
+ archived: number;
135
+ internal: number;
136
+ external: number;
137
+ maxId: number;
138
+ };
139
+ /**
140
+ * Create feature folder with README.md and metadata
141
+ *
142
+ * @param fsId - Feature ID (e.g., "FS-042E")
143
+ * @param workItem - External work item metadata
144
+ * @param metadata - External item metadata for origin tracking
145
+ * @returns Path to created feature folder
146
+ */
147
+ createFeatureFolder(fsId: string, workItem: ExternalWorkItem, metadata: ExternalItemMetadata): Promise<string>;
148
+ }
149
+ //# sourceMappingURL=fs-id-allocator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs-id-allocator.d.ts","sourceRoot":"","sources":["../../../src/living-docs/fs-id-allocator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,EAAgB,KAAK,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AAE3F;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,6CAA6C;IAC7C,EAAE,EAAE,MAAM,CAAC;IAEX,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAC;IAElB,mCAAmC;IACnC,MAAM,EAAE,UAAU,GAAG,UAAU,CAAC;IAEhC,qCAAqC;IACrC,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,qDAAqD;IACrD,UAAU,EAAE,MAAM,CAAC;IAEnB,sBAAsB;IACtB,KAAK,EAAE,MAAM,CAAC;IAEd,oCAAoC;IACpC,SAAS,EAAE,MAAM,CAAC;IAElB,mBAAmB;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,wCAAwC;IACxC,EAAE,EAAE,MAAM,CAAC;IAEX,+BAA+B;IAC/B,QAAQ,EAAE,sBAAsB,GAAG,QAAQ,GAAG,OAAO,CAAC;IAEtD,qCAAqC;IACrC,MAAM,EAAE,MAAM,CAAC;IAEf,uBAAuB;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,gBAAgB,CAA2C;IACnE,OAAO,CAAC,OAAO,CAAkB;gBAErB,WAAW,EAAE,MAAM;IAM/B;;;;OAIG;IACG,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAgBtC;;OAEG;YACW,aAAa;IAqB3B;;OAEG;YACW,oBAAoB;IA4ClC;;;;;;;;;;;;OAYG;IACG,UAAU,CAAC,QAAQ,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAyFvE;;;;;;;;;;;OAWG;IACH,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAcjC;;;;OAIG;IACH,QAAQ,IAAI,MAAM;IASlB;;;;;OAKG;IACH,OAAO,CAAC,aAAa;IAQrB;;;;OAIG;IACH,mBAAmB,IAAI,eAAe,EAAE;IAMxC;;OAEG;IACH,QAAQ,IAAI;QACV,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;KACf;IAaD;;;;;;;OAOG;IACG,mBAAmB,CACvB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,gBAAgB,EAC1B,QAAQ,EAAE,oBAAoB,GAC7B,OAAO,CAAC,MAAM,CAAC;CAuDnB"}
@@ -0,0 +1,325 @@
1
+ /**
2
+ * FS-ID Allocator - Intelligent Feature ID allocation with chronological placement
3
+ *
4
+ * Allocates FS-XXX IDs for external work items based on creation date,
5
+ * attempting chronological insertion while preventing collisions.
6
+ *
7
+ * Features:
8
+ * - Chronological ID allocation based on work item creation date
9
+ * - Gap filling (insert FS-011E between FS-010 and FS-020)
10
+ * - Append mode fallback (max ID + 1)
11
+ * - Collision detection (checks both FS-XXX and FS-XXXE)
12
+ * - Archive-aware (scans both active and archived features)
13
+ */
14
+ import fs from 'fs-extra';
15
+ import path from 'path';
16
+ import matter from 'gray-matter';
17
+ import { formatOrigin } from '../core/types/origin-metadata.js';
18
+ /**
19
+ * FS-ID Allocator
20
+ *
21
+ * Intelligently allocates Feature IDs with chronological placement
22
+ */
23
+ export class FSIdAllocator {
24
+ constructor(projectRoot) {
25
+ this.existingFeatures = new Map();
26
+ this.scanned = false;
27
+ this.projectRoot = projectRoot;
28
+ this.specsPath = path.join(projectRoot, '.specweave/docs/internal/specs');
29
+ this.archivePath = path.join(projectRoot, '.specweave/docs/_archive/specs');
30
+ }
31
+ /**
32
+ * Scan existing FS-IDs (both active and archived)
33
+ *
34
+ * CRITICAL: Archives are scanned to prevent ID reuse
35
+ */
36
+ async scanExistingIds() {
37
+ this.existingFeatures.clear();
38
+ // Scan active features
39
+ if (await fs.pathExists(this.specsPath)) {
40
+ await this.scanDirectory(this.specsPath, 'active');
41
+ }
42
+ // Scan archived features (CRITICAL - prevents ID reuse)
43
+ if (await fs.pathExists(this.archivePath)) {
44
+ await this.scanDirectory(this.archivePath, 'archived');
45
+ }
46
+ this.scanned = true;
47
+ }
48
+ /**
49
+ * Scan directory for FS-XXX folders
50
+ */
51
+ async scanDirectory(dirPath, source) {
52
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
53
+ for (const entry of entries) {
54
+ if (!entry.isDirectory())
55
+ continue;
56
+ // Match FS-XXX or FS-XXXE pattern
57
+ const match = entry.name.match(/^(FS-\d{3}E?)$/);
58
+ if (!match)
59
+ continue;
60
+ const fsId = match[1];
61
+ const featurePath = path.join(dirPath, entry.name);
62
+ // Try to parse feature metadata from README.md
63
+ const metadata = await this.parseFeatureMetadata(featurePath, fsId);
64
+ if (metadata) {
65
+ this.existingFeatures.set(fsId, metadata);
66
+ }
67
+ }
68
+ }
69
+ /**
70
+ * Parse feature metadata from README.md frontmatter
71
+ */
72
+ async parseFeatureMetadata(featurePath, fsId) {
73
+ // Try FEATURE.md first, then README.md
74
+ const possibleFiles = ['FEATURE.md', 'README.md', 'feature.md', 'readme.md'];
75
+ for (const filename of possibleFiles) {
76
+ const filePath = path.join(featurePath, filename);
77
+ if (await fs.pathExists(filePath)) {
78
+ try {
79
+ const content = await fs.readFile(filePath, 'utf-8');
80
+ const parsed = matter(content);
81
+ const frontmatter = parsed.data;
82
+ // Normalize timestamp (remove milliseconds for consistency)
83
+ // gray-matter parses ISO 8601 timestamps as Date objects, not strings
84
+ const rawTimestamp = frontmatter.created || frontmatter.createdAt || new Date(0);
85
+ const timestamp = rawTimestamp instanceof Date ? rawTimestamp.toISOString() : rawTimestamp;
86
+ const normalizedTimestamp = timestamp.replace(/\.\d{3}Z$/, 'Z');
87
+ return {
88
+ id: fsId,
89
+ createdAt: normalizedTimestamp,
90
+ origin: fsId.endsWith('E') ? 'external' : 'internal',
91
+ externalId: frontmatter.external_id || frontmatter.externalId,
92
+ path: featurePath
93
+ };
94
+ }
95
+ catch (error) {
96
+ // Continue to next file
97
+ continue;
98
+ }
99
+ }
100
+ }
101
+ // Fallback: create minimal metadata if no file found
102
+ return {
103
+ id: fsId,
104
+ createdAt: new Date(0).toISOString(), // Epoch (will sort to beginning)
105
+ origin: fsId.endsWith('E') ? 'external' : 'internal',
106
+ path: featurePath
107
+ };
108
+ }
109
+ /**
110
+ * Allocate FS-ID for external work item with chronological placement
111
+ *
112
+ * Algorithm:
113
+ * 1. Sort existing IDs by creation date
114
+ * 2. Find chronological insertion point based on work item creation date
115
+ * 3. Check for ID gaps between consecutive features
116
+ * 4. If gap exists, allocate next ID in gap (e.g., FS-011E between FS-010 and FS-020)
117
+ * 5. If no gap, append to end (max ID + 1 with E suffix)
118
+ *
119
+ * @param workItem - External work item to allocate ID for
120
+ * @returns Allocation result with ID and strategy
121
+ */
122
+ async allocateId(workItem) {
123
+ if (!this.scanned) {
124
+ await this.scanExistingIds();
125
+ }
126
+ // Handle empty case (first feature)
127
+ if (this.existingFeatures.size === 0) {
128
+ return {
129
+ id: 'FS-001E',
130
+ strategy: 'first',
131
+ reason: 'First external feature in project',
132
+ number: 1
133
+ };
134
+ }
135
+ // Sort features by creation date
136
+ const sortedFeatures = Array.from(this.existingFeatures.values()).sort((a, b) => {
137
+ return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
138
+ });
139
+ // Find chronological insertion point
140
+ const workItemDate = new Date(workItem.createdAt).getTime();
141
+ let insertionIndex = sortedFeatures.findIndex(f => new Date(f.createdAt).getTime() > workItemDate);
142
+ // If work item is after all existing features, append
143
+ if (insertionIndex === -1) {
144
+ const maxId = this.getMaxId();
145
+ const nextNumber = maxId + 1;
146
+ const nextId = `FS-${String(nextNumber).padStart(3, '0')}E`;
147
+ // Check for collision
148
+ if (this.hasCollision(nextId)) {
149
+ throw new Error(`ID collision: ${nextId} already exists`);
150
+ }
151
+ return {
152
+ id: nextId,
153
+ strategy: 'append',
154
+ reason: `Work item created after all existing features (${new Date(workItem.createdAt).toISOString()})`,
155
+ number: nextNumber
156
+ };
157
+ }
158
+ // Try chronological insertion into gap
159
+ const beforeFeature = insertionIndex > 0 ? sortedFeatures[insertionIndex - 1] : null;
160
+ const afterFeature = sortedFeatures[insertionIndex];
161
+ if (beforeFeature && afterFeature) {
162
+ const beforeNumber = this.extractNumber(beforeFeature.id);
163
+ const afterNumber = this.extractNumber(afterFeature.id);
164
+ // Check if there's a gap (at least 2 numbers apart)
165
+ if (afterNumber - beforeNumber > 1) {
166
+ // Allocate next ID after beforeFeature
167
+ const nextNumber = beforeNumber + 1;
168
+ const nextId = `FS-${String(nextNumber).padStart(3, '0')}E`;
169
+ // Check for collision
170
+ if (!this.hasCollision(nextId)) {
171
+ return {
172
+ id: nextId,
173
+ strategy: 'chronological-insert',
174
+ reason: `Inserted chronologically between ${beforeFeature.id} (${beforeFeature.createdAt}) and ${afterFeature.id} (${afterFeature.createdAt})`,
175
+ number: nextNumber
176
+ };
177
+ }
178
+ }
179
+ }
180
+ // Fallback: append to end if gap insertion failed
181
+ const maxId = this.getMaxId();
182
+ const nextNumber = maxId + 1;
183
+ const nextId = `FS-${String(nextNumber).padStart(3, '0')}E`;
184
+ // Check for collision
185
+ if (this.hasCollision(nextId)) {
186
+ throw new Error(`ID collision: ${nextId} already exists`);
187
+ }
188
+ return {
189
+ id: nextId,
190
+ strategy: 'append',
191
+ reason: 'Gap insertion not possible, appended to end',
192
+ number: nextNumber
193
+ };
194
+ }
195
+ /**
196
+ * Check if ID has collision (exact match or variant)
197
+ *
198
+ * @param id - FS-ID to check (e.g., "FS-042" or "FS-042E")
199
+ * @returns True if collision exists
200
+ *
201
+ * @example
202
+ * // Given existing IDs: FS-010, FS-011E, FS-020
203
+ * hasCollision('FS-011') // true (FS-011E exists)
204
+ * hasCollision('FS-011E') // true (exact match)
205
+ * hasCollision('FS-012') // false
206
+ */
207
+ hasCollision(id) {
208
+ // Exact match
209
+ if (this.existingFeatures.has(id)) {
210
+ return true;
211
+ }
212
+ // Variant match (check both FS-XXX and FS-XXXE)
213
+ const number = this.extractNumber(id);
214
+ const internalId = `FS-${String(number).padStart(3, '0')}`;
215
+ const externalId = `FS-${String(number).padStart(3, '0')}E`;
216
+ return this.existingFeatures.has(internalId) || this.existingFeatures.has(externalId);
217
+ }
218
+ /**
219
+ * Get maximum numeric ID value (ignoring E suffix)
220
+ *
221
+ * @returns Maximum ID number
222
+ */
223
+ getMaxId() {
224
+ if (this.existingFeatures.size === 0) {
225
+ return 0;
226
+ }
227
+ const numbers = Array.from(this.existingFeatures.keys()).map(id => this.extractNumber(id));
228
+ return Math.max(...numbers);
229
+ }
230
+ /**
231
+ * Extract numeric part from FS-ID
232
+ *
233
+ * @param id - FS-ID (e.g., "FS-042" or "FS-042E")
234
+ * @returns Numeric part (e.g., 42)
235
+ */
236
+ extractNumber(id) {
237
+ const match = id.match(/^FS-(\d{3,})E?$/);
238
+ if (!match) {
239
+ throw new Error(`Invalid FS-ID format: ${id}`);
240
+ }
241
+ return parseInt(match[1], 10);
242
+ }
243
+ /**
244
+ * Get all existing feature IDs (sorted by creation date)
245
+ *
246
+ * @returns Array of feature metadata sorted chronologically
247
+ */
248
+ getExistingFeatures() {
249
+ return Array.from(this.existingFeatures.values()).sort((a, b) => {
250
+ return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
251
+ });
252
+ }
253
+ /**
254
+ * Get allocation statistics
255
+ */
256
+ getStats() {
257
+ const features = Array.from(this.existingFeatures.values());
258
+ return {
259
+ total: features.length,
260
+ active: features.filter(f => !f.path.includes('/_archive/')).length,
261
+ archived: features.filter(f => f.path.includes('/_archive/')).length,
262
+ internal: features.filter(f => f.origin === 'internal').length,
263
+ external: features.filter(f => f.origin === 'external').length,
264
+ maxId: this.getMaxId()
265
+ };
266
+ }
267
+ /**
268
+ * Create feature folder with README.md and metadata
269
+ *
270
+ * @param fsId - Feature ID (e.g., "FS-042E")
271
+ * @param workItem - External work item metadata
272
+ * @param metadata - External item metadata for origin tracking
273
+ * @returns Path to created feature folder
274
+ */
275
+ async createFeatureFolder(fsId, workItem, metadata) {
276
+ // Create folder path
277
+ const featurePath = path.join(this.specsPath, fsId);
278
+ await fs.ensureDir(featurePath);
279
+ // Generate README.md with frontmatter
280
+ const originBadge = formatOrigin(metadata);
281
+ // Build frontmatter lines (only include defined optional fields)
282
+ const frontmatterLines = [
283
+ `id: ${fsId}`,
284
+ `title: ${workItem.title}`,
285
+ `created: ${workItem.createdAt}`,
286
+ `origin: external`,
287
+ `source: ${metadata.source}`,
288
+ `external_id: ${metadata.external_id}`,
289
+ `external_url: ${metadata.external_url}`,
290
+ `imported_at: ${metadata.imported_at}`
291
+ ];
292
+ // Only add optional fields if explicitly defined
293
+ if (metadata.external_title !== undefined) {
294
+ frontmatterLines.push(`external_title: ${metadata.external_title}`);
295
+ }
296
+ if (metadata.format_preservation !== undefined) {
297
+ frontmatterLines.push(`format_preservation: ${metadata.format_preservation}`);
298
+ }
299
+ const readmeContent = `---
300
+ ${frontmatterLines.join('\n')}
301
+ ---
302
+
303
+ # ${workItem.title}
304
+
305
+ **Origin**: ${originBadge}
306
+
307
+ ## Description
308
+
309
+ Imported from external work item.
310
+
311
+ ## User Stories
312
+
313
+ User stories will be added here during import.
314
+
315
+ ## Status
316
+
317
+ - **Created**: ${workItem.createdAt}
318
+ - **Imported**: ${metadata.imported_at}
319
+ `;
320
+ const readmePath = path.join(featurePath, 'README.md');
321
+ await fs.writeFile(readmePath, readmeContent);
322
+ return featurePath;
323
+ }
324
+ }
325
+ //# sourceMappingURL=fs-id-allocator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs-id-allocator.js","sourceRoot":"","sources":["../../../src/living-docs/fs-id-allocator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * ID Registry - Atomic Feature ID registration with collision detection
3
+ *
4
+ * Provides thread-safe ID registration using file-based locking to prevent
5
+ * race conditions during concurrent imports.
6
+ *
7
+ * Features:
8
+ * - File-based locking for atomic updates
9
+ * - Collision detection (checks both FS-XXX and FS-XXXE)
10
+ * - JSON-based persistent storage
11
+ * - Automatic lock cleanup with timeout
12
+ */
13
+ /**
14
+ * Registry entry metadata
15
+ */
16
+ export interface RegistryEntry {
17
+ /** Feature ID (e.g., "FS-042E") */
18
+ id: string;
19
+ /** Registration timestamp (ISO 8601) */
20
+ registeredAt: string;
21
+ /** External ID if external origin */
22
+ externalId?: string;
23
+ /** External URL */
24
+ externalUrl?: string;
25
+ /** Origin type */
26
+ origin: 'internal' | 'external';
27
+ }
28
+ /**
29
+ * Registry data structure
30
+ */
31
+ export interface RegistryData {
32
+ /** Map of FS-ID to metadata */
33
+ entries: Record<string, RegistryEntry>;
34
+ /** Last updated timestamp */
35
+ lastUpdated: string;
36
+ /** Registry version */
37
+ version: number;
38
+ }
39
+ /**
40
+ * ID Registry with atomic file-based locking
41
+ */
42
+ export declare class IDRegistry {
43
+ private registryPath;
44
+ private lockPath;
45
+ private lockTimeout;
46
+ private retryDelay;
47
+ constructor(projectRoot: string);
48
+ /**
49
+ * Register a new Feature ID atomically
50
+ *
51
+ * @param fsId - Feature ID to register (e.g., "FS-042E")
52
+ * @param entry - Registry entry metadata
53
+ * @throws Error if collision detected or lock acquisition fails
54
+ */
55
+ registerID(fsId: string, entry: Omit<RegistryEntry, 'id'>): Promise<void>;
56
+ /**
57
+ * Check if ID exists in registry
58
+ *
59
+ * @param fsId - Feature ID to check
60
+ * @returns True if ID is registered
61
+ */
62
+ hasID(fsId: string): Promise<boolean>;
63
+ /**
64
+ * Get registry entry
65
+ *
66
+ * @param fsId - Feature ID to retrieve
67
+ * @returns Registry entry or null if not found
68
+ */
69
+ getEntry(fsId: string): Promise<RegistryEntry | null>;
70
+ /**
71
+ * Get all registered IDs
72
+ *
73
+ * @returns Array of all registered Feature IDs
74
+ */
75
+ getAllIDs(): Promise<string[]>;
76
+ /**
77
+ * Acquire lock file with timeout and retry
78
+ *
79
+ * @throws Error if lock cannot be acquired within timeout
80
+ */
81
+ private acquireLock;
82
+ /**
83
+ * Release lock file
84
+ */
85
+ private releaseLock;
86
+ /**
87
+ * Get lock file age in milliseconds
88
+ *
89
+ * @returns Lock age or Infinity if lock doesn't exist
90
+ */
91
+ private getLockAge;
92
+ /**
93
+ * Read registry from disk
94
+ *
95
+ * @returns Registry data
96
+ */
97
+ private readRegistry;
98
+ /**
99
+ * Write registry to disk atomically
100
+ *
101
+ * @param registry - Registry data to write
102
+ */
103
+ private writeRegistry;
104
+ /**
105
+ * Detect ID collision (exact match or variant)
106
+ *
107
+ * @param fsId - ID to check (e.g., "FS-042E")
108
+ * @param registry - Current registry
109
+ * @returns Conflicting ID or null if no collision
110
+ *
111
+ * @example
112
+ * // Given registry: {FS-042: {...}}
113
+ * detectCollision('FS-042E', registry) // Returns: 'FS-042'
114
+ * detectCollision('FS-043', registry) // Returns: null
115
+ */
116
+ private detectCollision;
117
+ /**
118
+ * Sleep for specified milliseconds
119
+ *
120
+ * @param ms - Milliseconds to sleep
121
+ */
122
+ private sleep;
123
+ }
124
+ //# sourceMappingURL=id-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"id-registry.d.ts","sourceRoot":"","sources":["../../../src/living-docs/id-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,mCAAmC;IACnC,EAAE,EAAE,MAAM,CAAC;IAEX,wCAAwC;IACxC,YAAY,EAAE,MAAM,CAAC;IAErB,qCAAqC;IACrC,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,mBAAmB;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,kBAAkB;IAClB,MAAM,EAAE,UAAU,GAAG,UAAU,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,+BAA+B;IAC/B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAEvC,6BAA6B;IAC7B,WAAW,EAAE,MAAM,CAAC;IAEpB,uBAAuB;IACvB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,WAAW,CAAiB;IACpC,OAAO,CAAC,UAAU,CAAe;gBAErB,WAAW,EAAE,MAAM;IAK/B;;;;;;OAMG;IACG,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IA8B/E;;;;;OAKG;IACG,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAK3C;;;;;OAKG;IACG,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAK3D;;;;OAIG;IACG,SAAS,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAKpC;;;;OAIG;YACW,WAAW;IAoCzB;;OAEG;YACW,WAAW;IAWzB;;;;OAIG;YACW,UAAU;IAUxB;;;;OAIG;YACW,YAAY;IAiB1B;;;;OAIG;YACW,aAAa;IAW3B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,eAAe;IA4BvB;;;;OAIG;IACH,OAAO,CAAC,KAAK;CAGd"}