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.
- package/.claude-plugin/README.md +2 -2
- package/CLAUDE.md +269 -51
- package/README.md +33 -10
- package/dist/plugins/specweave-github/lib/ThreeLayerSyncManager.d.ts +1 -1
- package/dist/plugins/specweave-github/lib/ThreeLayerSyncManager.js +1 -1
- package/dist/plugins/specweave-github/lib/enhanced-github-sync.js +1 -1
- package/dist/plugins/specweave-github/lib/enhanced-github-sync.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-spec-content-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-spec-content-sync.js +4 -1
- package/dist/plugins/specweave-github/lib/github-spec-content-sync.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-spec-sync.d.ts +1 -1
- package/dist/plugins/specweave-github/lib/github-spec-sync.js +1 -1
- package/dist/plugins/specweave-github/lib/github-sync-bidirectional.d.ts +9 -0
- package/dist/plugins/specweave-github/lib/github-sync-bidirectional.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-sync-bidirectional.js +10 -1
- package/dist/plugins/specweave-github/lib/github-sync-bidirectional.js.map +1 -1
- package/dist/plugins/specweave-github/lib/progress-comment-builder.js +2 -2
- package/dist/plugins/specweave-github/lib/progress-comment-builder.js.map +1 -1
- package/dist/plugins/specweave-github/lib/types.d.ts +1 -1
- package/dist/src/cli/commands/init.d.ts.map +1 -1
- package/dist/src/cli/commands/init.js +313 -1
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/index.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/index.js +41 -24
- package/dist/src/cli/helpers/issue-tracker/index.js.map +1 -1
- package/dist/src/config/import-config.d.ts +69 -0
- package/dist/src/config/import-config.d.ts.map +1 -0
- package/dist/src/config/import-config.js +136 -0
- package/dist/src/config/import-config.js.map +1 -0
- package/dist/src/config/types.d.ts +10 -10
- package/dist/src/core/living-docs/living-docs-sync.d.ts +2 -0
- package/dist/src/core/living-docs/living-docs-sync.d.ts.map +1 -1
- package/dist/src/core/living-docs/living-docs-sync.js +10 -1
- package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
- package/dist/src/core/living-docs/task-project-specific-generator.d.ts +2 -2
- package/dist/src/core/living-docs/task-project-specific-generator.js +2 -2
- package/dist/src/core/repo-structure/prompt-consolidator.d.ts +2 -2
- package/dist/src/core/repo-structure/prompt-consolidator.d.ts.map +1 -1
- package/dist/src/core/repo-structure/prompt-consolidator.js +3 -15
- package/dist/src/core/repo-structure/prompt-consolidator.js.map +1 -1
- package/dist/src/core/repo-structure/repo-structure-manager.d.ts +1 -1
- package/dist/src/core/repo-structure/repo-structure-manager.d.ts.map +1 -1
- package/dist/src/core/repo-structure/repo-structure-manager.js +3 -6
- package/dist/src/core/repo-structure/repo-structure-manager.js.map +1 -1
- package/dist/src/core/spec-content-sync.d.ts +4 -1
- package/dist/src/core/spec-content-sync.d.ts.map +1 -1
- package/dist/src/core/spec-content-sync.js +139 -4
- package/dist/src/core/spec-content-sync.js.map +1 -1
- package/dist/src/core/spec-task-mapper.d.ts.map +1 -1
- package/dist/src/core/spec-task-mapper.js +9 -8
- package/dist/src/core/spec-task-mapper.js.map +1 -1
- package/dist/src/core/status-line-validator.d.ts +63 -0
- package/dist/src/core/status-line-validator.d.ts.map +1 -0
- package/dist/src/core/status-line-validator.js +253 -0
- package/dist/src/core/status-line-validator.js.map +1 -0
- package/dist/src/core/sync/bidirectional-engine.d.ts +10 -1
- package/dist/src/core/sync/bidirectional-engine.d.ts.map +1 -1
- package/dist/src/core/sync/bidirectional-engine.js +10 -1
- package/dist/src/core/sync/bidirectional-engine.js.map +1 -1
- package/dist/src/core/sync/profile-manager.d.ts.map +1 -1
- package/dist/src/core/sync/profile-manager.js +3 -0
- package/dist/src/core/sync/profile-manager.js.map +1 -1
- package/dist/src/core/sync/project-context.d.ts.map +1 -1
- package/dist/src/core/sync/project-context.js +3 -0
- package/dist/src/core/sync/project-context.js.map +1 -1
- package/dist/src/core/sync/status-sync-engine.d.ts +1 -1
- package/dist/src/core/sync/status-sync-engine.js +1 -1
- package/dist/src/core/types/origin-metadata.d.ts +153 -0
- package/dist/src/core/types/origin-metadata.d.ts.map +1 -0
- package/dist/src/core/types/origin-metadata.js +166 -0
- package/dist/src/core/types/origin-metadata.js.map +1 -0
- package/dist/src/core/types/sync-profile.d.ts +8 -2
- package/dist/src/core/types/sync-profile.d.ts.map +1 -1
- package/dist/src/core/types/sync-profile.js.map +1 -1
- package/dist/src/core/types/sync-settings.d.ts +73 -0
- package/dist/src/core/types/sync-settings.d.ts.map +1 -0
- package/dist/src/core/types/sync-settings.js +90 -0
- package/dist/src/core/types/sync-settings.js.map +1 -0
- package/dist/src/core/utils/permission-checker.d.ts +100 -0
- package/dist/src/core/utils/permission-checker.d.ts.map +1 -0
- package/dist/src/core/utils/permission-checker.js +166 -0
- package/dist/src/core/utils/permission-checker.js.map +1 -0
- package/dist/src/generators/spec/spec-parser.js +3 -3
- package/dist/src/generators/spec/spec-parser.js.map +1 -1
- package/dist/src/generators/spec/task-parser.js +4 -4
- package/dist/src/generators/spec/task-parser.js.map +1 -1
- package/dist/src/id-generators/task-id-generator.d.ts +96 -0
- package/dist/src/id-generators/task-id-generator.d.ts.map +1 -0
- package/dist/src/id-generators/task-id-generator.js +143 -0
- package/dist/src/id-generators/task-id-generator.js.map +1 -0
- package/dist/src/id-generators/us-id-generator.d.ts +96 -0
- package/dist/src/id-generators/us-id-generator.d.ts.map +1 -0
- package/dist/src/id-generators/us-id-generator.js +143 -0
- package/dist/src/id-generators/us-id-generator.js.map +1 -0
- package/dist/src/importers/ado-importer.d.ts +43 -0
- package/dist/src/importers/ado-importer.d.ts.map +1 -0
- package/dist/src/importers/ado-importer.js +234 -0
- package/dist/src/importers/ado-importer.js.map +1 -0
- package/dist/src/importers/external-importer.d.ts +96 -0
- package/dist/src/importers/external-importer.d.ts.map +1 -0
- package/dist/src/importers/external-importer.js +13 -0
- package/dist/src/importers/external-importer.js.map +1 -0
- package/dist/src/importers/github-importer.d.ts +37 -0
- package/dist/src/importers/github-importer.d.ts.map +1 -0
- package/dist/src/importers/github-importer.js +161 -0
- package/dist/src/importers/github-importer.js.map +1 -0
- package/dist/src/importers/import-coordinator.d.ts +90 -0
- package/dist/src/importers/import-coordinator.d.ts.map +1 -0
- package/dist/src/importers/import-coordinator.js +182 -0
- package/dist/src/importers/import-coordinator.js.map +1 -0
- package/dist/src/importers/item-converter.d.ts +91 -0
- package/dist/src/importers/item-converter.d.ts.map +1 -0
- package/dist/src/importers/item-converter.js +221 -0
- package/dist/src/importers/item-converter.js.map +1 -0
- package/dist/src/importers/jira-importer.d.ts +42 -0
- package/dist/src/importers/jira-importer.d.ts.map +1 -0
- package/dist/src/importers/jira-importer.js +221 -0
- package/dist/src/importers/jira-importer.js.map +1 -0
- package/dist/src/init/repo/types.d.ts +2 -2
- package/dist/src/integrations/jira/jira-mapper.d.ts +1 -1
- package/dist/src/integrations/jira/jira-mapper.js +1 -1
- package/dist/src/living-docs/fs-id-allocator.d.ts +149 -0
- package/dist/src/living-docs/fs-id-allocator.d.ts.map +1 -0
- package/dist/src/living-docs/fs-id-allocator.js +325 -0
- package/dist/src/living-docs/fs-id-allocator.js.map +1 -0
- package/dist/src/living-docs/id-registry.d.ts +124 -0
- package/dist/src/living-docs/id-registry.d.ts.map +1 -0
- package/dist/src/living-docs/id-registry.js +230 -0
- package/dist/src/living-docs/id-registry.js.map +1 -0
- package/dist/src/progress/us-progress-tracker.d.ts +68 -0
- package/dist/src/progress/us-progress-tracker.d.ts.map +1 -0
- package/dist/src/progress/us-progress-tracker.js +120 -0
- package/dist/src/progress/us-progress-tracker.js.map +1 -0
- package/package.json +2 -2
- package/plugins/specweave/.claude-plugin/plugin.json +16 -2
- package/plugins/specweave/agents/architect/AGENT.md +11 -2
- package/plugins/specweave/agents/test-aware-planner/AGENT.md +81 -25
- package/plugins/specweave/commands/specweave-import-docs.md +278 -88
- package/plugins/specweave/commands/specweave-progress.md +45 -97
- package/plugins/specweave/hooks/post-increment-completion.sh +168 -26
- package/plugins/specweave/hooks/post-increment-planning.sh +148 -4
- package/plugins/specweave/hooks/post-task-completion.sh +64 -4
- package/plugins/specweave/lib/hooks/sync-cache.js +294 -0
- package/plugins/specweave/lib/hooks/sync-living-docs.js +32 -1
- package/plugins/specweave/lib/hooks/sync-us-tasks.js +23 -13
- package/plugins/specweave-ado/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-ado/lib/conflict-resolver.ts +1 -1
- package/plugins/specweave-alternatives/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-backend/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-confluent/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-cost-optimizer/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-diagrams/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-docs/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-docs-preview/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-figma/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-frontend/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-github/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-github/hooks/post-task-completion.sh +37 -22
- package/plugins/specweave-github/lib/ThreeLayerSyncManager.ts +1 -1
- package/plugins/specweave-github/lib/enhanced-github-sync.js +1 -1
- package/plugins/specweave-github/lib/enhanced-github-sync.ts +1 -1
- package/plugins/specweave-github/lib/github-spec-content-sync.js +2 -1
- package/plugins/specweave-github/lib/github-spec-content-sync.ts +4 -1
- package/plugins/specweave-github/lib/github-spec-sync.js +1 -1
- package/plugins/specweave-github/lib/github-spec-sync.ts +1 -1
- package/plugins/specweave-github/lib/github-sync-bidirectional.js +1 -1
- package/plugins/specweave-github/lib/github-sync-bidirectional.ts +10 -1
- package/plugins/specweave-github/lib/progress-comment-builder.js +1 -1
- package/plugins/specweave-github/lib/progress-comment-builder.ts +2 -2
- package/plugins/specweave-github/lib/types.ts +1 -1
- package/plugins/specweave-infrastructure/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-jira/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-kafka/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-kafka-streams/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-kubernetes/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-ml/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-mobile/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-n8n/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-payments/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-release/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +252 -0
- package/plugins/specweave-testing/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-tooling/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-ui/.claude-plugin/plugin.json +1 -1
- package/src/templates/.env.example +5 -0
- package/src/templates/config-permissions-guide.md +413 -0
- package/src/templates/config.json.template +68 -0
- 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"}
|