specweave 0.23.2 → 0.23.5
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.md +367 -0
- package/dist/plugins/specweave/lib/utils/fs-native.d.ts +133 -0
- package/dist/plugins/specweave/lib/utils/fs-native.d.ts.map +1 -0
- package/dist/plugins/specweave/lib/utils/fs-native.js +224 -0
- package/dist/plugins/specweave/lib/utils/fs-native.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-client-v2.js +1 -1
- package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync.js +52 -20
- package/dist/plugins/specweave-github/lib/github-feature-sync.js.map +1 -1
- package/dist/plugins/specweave-github/lib/user-story-issue-builder.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/user-story-issue-builder.js +24 -0
- package/dist/plugins/specweave-github/lib/user-story-issue-builder.js.map +1 -1
- package/dist/src/cli/helpers/init/initial-increment-generator.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/initial-increment-generator.js +2 -1
- package/dist/src/cli/helpers/init/initial-increment-generator.js.map +1 -1
- package/dist/src/core/ac-test-validator-cli.d.ts +16 -0
- package/dist/src/core/ac-test-validator-cli.d.ts.map +1 -0
- package/dist/src/core/ac-test-validator-cli.js +118 -0
- package/dist/src/core/ac-test-validator-cli.js.map +1 -0
- package/dist/src/core/ac-test-validator.d.ts +111 -0
- package/dist/src/core/ac-test-validator.d.ts.map +1 -0
- package/dist/src/core/ac-test-validator.js +292 -0
- package/dist/src/core/ac-test-validator.js.map +1 -0
- package/dist/src/core/increment/desync-detector.d.ts +142 -0
- package/dist/src/core/increment/desync-detector.d.ts.map +1 -0
- package/dist/src/core/increment/desync-detector.js +270 -0
- package/dist/src/core/increment/desync-detector.js.map +1 -0
- package/dist/src/core/increment/metadata-manager.d.ts +8 -4
- package/dist/src/core/increment/metadata-manager.d.ts.map +1 -1
- package/dist/src/core/increment/metadata-manager.js +45 -21
- package/dist/src/core/increment/metadata-manager.js.map +1 -1
- package/dist/src/core/qa/qa-runner.js +9 -2
- package/dist/src/core/qa/qa-runner.js.map +1 -1
- package/dist/src/sync/sync-coordinator.d.ts +1 -1
- package/dist/src/sync/sync-coordinator.d.ts.map +1 -1
- package/dist/src/sync/sync-coordinator.js +40 -2
- package/dist/src/sync/sync-coordinator.js.map +1 -1
- package/dist/src/utils/fs-native.d.ts +133 -0
- package/dist/src/utils/fs-native.d.ts.map +1 -0
- package/dist/src/utils/fs-native.js +224 -0
- package/dist/src/utils/fs-native.js.map +1 -0
- package/package.json +1 -1
- package/plugins/specweave/.claude-plugin/plugin.json +12 -0
- package/plugins/specweave/agents/AGENTS-INDEX.md +216 -0
- package/plugins/specweave/agents/architect/AGENT.md +17 -0
- package/plugins/specweave/agents/code-standards-detective/AGENT.md +16 -0
- package/plugins/specweave/agents/docs-writer/AGENT.md +16 -0
- package/plugins/specweave/agents/increment-quality-judge-v2/AGENT.md +704 -0
- package/plugins/specweave/agents/infrastructure/AGENT.md +16 -0
- package/plugins/specweave/agents/performance/AGENT.md +16 -0
- package/plugins/specweave/agents/pm/AGENT.md +17 -0
- package/plugins/specweave/agents/qa-lead/AGENT.md +15 -0
- package/plugins/specweave/agents/reflective-reviewer/AGENT.md +16 -0
- package/plugins/specweave/agents/security/AGENT.md +16 -0
- package/plugins/specweave/agents/tdd-orchestrator/AGENT.md +16 -0
- package/plugins/specweave/agents/tech-lead/AGENT.md +16 -0
- package/plugins/specweave/agents/test-aware-planner/AGENT.md +16 -0
- package/plugins/specweave/agents/translator/AGENT.md +13 -0
- package/plugins/specweave/commands/specweave-done.md +14 -0
- package/plugins/specweave/commands/specweave-qa.md +11 -1
- package/plugins/specweave/commands/specweave-sync-status.md +356 -0
- package/plugins/specweave/commands/specweave-validate.md +10 -1
- package/plugins/specweave/hooks/pre-task-completion.sh +196 -0
- package/plugins/specweave/lib/hooks/git-diff-analyzer.js +3 -3
- package/plugins/specweave/lib/hooks/git-diff-analyzer.ts +3 -3
- package/plugins/specweave/lib/hooks/invoke-translator-skill.js +3 -2
- package/plugins/specweave/lib/hooks/invoke-translator-skill.ts +3 -2
- package/plugins/specweave/lib/hooks/prepare-reflection-context.js +3 -3
- package/plugins/specweave/lib/hooks/prepare-reflection-context.ts +3 -3
- package/plugins/specweave/lib/hooks/reflection-config-loader.js +4 -4
- package/plugins/specweave/lib/hooks/reflection-config-loader.ts +4 -4
- package/plugins/specweave/lib/hooks/reflection-storage.js +9 -9
- package/plugins/specweave/lib/hooks/reflection-storage.ts +9 -9
- package/plugins/specweave/lib/hooks/sync-cache.js +9 -8
- package/plugins/specweave/lib/hooks/sync-living-docs.js +57 -6
- package/plugins/specweave/lib/hooks/sync-us-tasks.js +6 -6
- package/plugins/specweave/lib/hooks/translate-file.js +3 -2
- package/plugins/specweave/lib/hooks/translate-file.ts +3 -2
- package/plugins/specweave/lib/hooks/translate-living-docs.js +4 -3
- package/plugins/specweave/lib/hooks/translate-living-docs.ts +4 -3
- package/plugins/specweave/lib/hooks/update-tasks-md.js +3 -3
- package/plugins/specweave/lib/hooks/update-tasks-md.ts +3 -3
- package/plugins/specweave/lib/utils/fs-native.js +182 -0
- package/plugins/specweave/lib/utils/fs-native.ts +283 -0
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.d.ts +8 -4
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js +45 -21
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js.map +1 -1
- package/plugins/specweave/skills/SKILLS-INDEX.md +26 -2
- package/plugins/specweave/skills/increment-planner/SKILL.md +2 -2
- package/plugins/specweave-ado/commands/specweave-ado-close-workitem.md +1 -1
- package/plugins/specweave-ado/commands/specweave-ado-create-workitem.md +1 -1
- package/plugins/specweave-ado/commands/specweave-ado-status.md +1 -1
- package/plugins/specweave-ado/commands/specweave-ado-sync.md +1 -1
- package/plugins/specweave-diagrams/agents/diagrams-architect/AGENT.md +1 -1
- package/plugins/specweave-diagrams/skills/diagrams-generator/SKILL.md +4 -4
- package/plugins/specweave-github/lib/github-client-v2.js +2 -1
- package/plugins/specweave-github/lib/github-client-v2.ts +1 -1
- package/plugins/specweave-github/lib/github-feature-sync.js +30 -17
- package/plugins/specweave-github/lib/github-feature-sync.ts +54 -24
- package/plugins/specweave-github/lib/user-story-issue-builder.js +24 -0
- package/plugins/specweave-github/lib/user-story-issue-builder.ts +33 -0
- package/plugins/specweave-mobile/README.md +1 -1
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +72 -0
- package/src/templates/CLAUDE.md.template +13 -0
- package/plugins/specweave/skills/task-builder/README.md +0 -84
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DesyncDetector - Detect and fix status desyncs between metadata.json and spec.md
|
|
3
|
+
*
|
|
4
|
+
* Prevents silent failures by validating source-of-truth consistency.
|
|
5
|
+
* Critical for maintaining data integrity across increment lifecycle.
|
|
6
|
+
*
|
|
7
|
+
* Incident Reference: 2025-11-20 - Silent failure in /specweave:done caused
|
|
8
|
+
* increment 0047 to have metadata.json="completed" while spec.md="active",
|
|
9
|
+
* breaking status line and user trust.
|
|
10
|
+
*
|
|
11
|
+
* CLAUDE.md Rule #7: spec.md and metadata.json are BOTH source of truth and MUST stay in sync.
|
|
12
|
+
*/
|
|
13
|
+
import { IncrementStatus } from '../types/increment-metadata.js';
|
|
14
|
+
import { Logger } from '../../utils/logger.js';
|
|
15
|
+
/**
|
|
16
|
+
* Desync detection result for a single increment
|
|
17
|
+
*/
|
|
18
|
+
export interface DesyncResult {
|
|
19
|
+
incrementId: string;
|
|
20
|
+
hasDesync: boolean;
|
|
21
|
+
metadataStatus: IncrementStatus | null;
|
|
22
|
+
specStatus: IncrementStatus | null;
|
|
23
|
+
metadataPath: string;
|
|
24
|
+
specPath: string;
|
|
25
|
+
error?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Desync scan report for multiple increments
|
|
29
|
+
*/
|
|
30
|
+
export interface DesyncScanReport {
|
|
31
|
+
totalScanned: number;
|
|
32
|
+
totalDesyncs: number;
|
|
33
|
+
desyncs: DesyncResult[];
|
|
34
|
+
healthy: string[];
|
|
35
|
+
errors: string[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Options for desync detection
|
|
39
|
+
*/
|
|
40
|
+
export interface DesyncDetectorOptions {
|
|
41
|
+
logger?: Logger;
|
|
42
|
+
projectRoot?: string;
|
|
43
|
+
autoFix?: boolean;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* DesyncDetector - Validates status consistency between metadata.json and spec.md
|
|
47
|
+
*
|
|
48
|
+
* Key Features:
|
|
49
|
+
* - Detects status desyncs across all increments
|
|
50
|
+
* - Provides detailed desync reports
|
|
51
|
+
* - Auto-fix capability (optional)
|
|
52
|
+
* - Validates before critical operations
|
|
53
|
+
*
|
|
54
|
+
* Usage:
|
|
55
|
+
* ```typescript
|
|
56
|
+
* // Check single increment
|
|
57
|
+
* const detector = new DesyncDetector();
|
|
58
|
+
* const result = await detector.checkIncrement('0047-us-task-linkage');
|
|
59
|
+
* if (result.hasDesync) {
|
|
60
|
+
* console.error('Desync detected!', result);
|
|
61
|
+
* }
|
|
62
|
+
*
|
|
63
|
+
* // Scan all increments
|
|
64
|
+
* const report = await detector.scanAll();
|
|
65
|
+
* console.log(`Found ${report.totalDesyncs} desyncs`);
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export declare class DesyncDetector {
|
|
69
|
+
private logger;
|
|
70
|
+
private projectRoot;
|
|
71
|
+
constructor(options?: DesyncDetectorOptions);
|
|
72
|
+
/**
|
|
73
|
+
* Check single increment for status desync
|
|
74
|
+
*
|
|
75
|
+
* @param incrementId - Increment ID (e.g., "0047-us-task-linkage")
|
|
76
|
+
* @returns Desync result with detailed status info
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```typescript
|
|
80
|
+
* const result = await detector.checkIncrement('0047-us-task-linkage');
|
|
81
|
+
* if (result.hasDesync) {
|
|
82
|
+
* console.error(`Desync: metadata=${result.metadataStatus}, spec=${result.specStatus}`);
|
|
83
|
+
* }
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
checkIncrement(incrementId: string): Promise<DesyncResult>;
|
|
87
|
+
/**
|
|
88
|
+
* Scan all increments for desyncs
|
|
89
|
+
*
|
|
90
|
+
* @returns Comprehensive scan report
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```typescript
|
|
94
|
+
* const report = await detector.scanAll();
|
|
95
|
+
* console.log(`Scanned ${report.totalScanned} increments`);
|
|
96
|
+
* console.log(`Found ${report.totalDesyncs} desyncs`);
|
|
97
|
+
* report.desyncs.forEach(d => console.error(`- ${d.incrementId}`));
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
scanAll(): Promise<DesyncScanReport>;
|
|
101
|
+
/**
|
|
102
|
+
* Fix desync by updating spec.md to match metadata.json (source of truth for status)
|
|
103
|
+
*
|
|
104
|
+
* IMPORTANT: metadata.json is considered the source of truth because it's updated
|
|
105
|
+
* atomically and used by the CLI. spec.md should mirror it.
|
|
106
|
+
*
|
|
107
|
+
* @param incrementId - Increment ID to fix
|
|
108
|
+
* @returns true if fixed successfully, false if no desync or fix failed
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```typescript
|
|
112
|
+
* const fixed = await detector.fixDesync('0047-us-task-linkage');
|
|
113
|
+
* if (fixed) {
|
|
114
|
+
* console.log('Desync fixed!');
|
|
115
|
+
* }
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
fixDesync(incrementId: string): Promise<boolean>;
|
|
119
|
+
/**
|
|
120
|
+
* Validate increment has no desync, throw if found
|
|
121
|
+
*
|
|
122
|
+
* Use this before critical operations (e.g., closing increment, archiving)
|
|
123
|
+
*
|
|
124
|
+
* @param incrementId - Increment ID to validate
|
|
125
|
+
* @throws Error if desync detected
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```typescript
|
|
129
|
+
* await detector.validateOrThrow('0047-us-task-linkage');
|
|
130
|
+
* // Throws if desync found, continues if healthy
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
validateOrThrow(incrementId: string): Promise<void>;
|
|
134
|
+
/**
|
|
135
|
+
* Generate human-readable report for desyncs
|
|
136
|
+
*
|
|
137
|
+
* @param report - Scan report
|
|
138
|
+
* @returns Formatted report string
|
|
139
|
+
*/
|
|
140
|
+
formatReport(report: DesyncScanReport): string;
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=desync-detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"desync-detector.d.ts","sourceRoot":"","sources":["../../../../src/core/increment/desync-detector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,MAAM,EAAiB,MAAM,uBAAuB,CAAC;AAE9D;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,eAAe,GAAG,IAAI,CAAC;IACvC,UAAU,EAAE,eAAe,GAAG,IAAI,CAAC;IACnC,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,WAAW,CAAS;gBAEhB,OAAO,GAAE,qBAA0B;IAK/C;;;;;;;;;;;;;OAaG;IACG,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IA4DhE;;;;;;;;;;;;OAYG;IACG,OAAO,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAwC1C;;;;;;;;;;;;;;;;OAgBG;IACG,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAyCtD;;;;;;;;;;;;;OAaG;IACG,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBzD;;;;;OAKG;IACH,YAAY,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM;CAoD/C"}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DesyncDetector - Detect and fix status desyncs between metadata.json and spec.md
|
|
3
|
+
*
|
|
4
|
+
* Prevents silent failures by validating source-of-truth consistency.
|
|
5
|
+
* Critical for maintaining data integrity across increment lifecycle.
|
|
6
|
+
*
|
|
7
|
+
* Incident Reference: 2025-11-20 - Silent failure in /specweave:done caused
|
|
8
|
+
* increment 0047 to have metadata.json="completed" while spec.md="active",
|
|
9
|
+
* breaking status line and user trust.
|
|
10
|
+
*
|
|
11
|
+
* CLAUDE.md Rule #7: spec.md and metadata.json are BOTH source of truth and MUST stay in sync.
|
|
12
|
+
*/
|
|
13
|
+
import fs from 'fs-extra';
|
|
14
|
+
import path from 'path';
|
|
15
|
+
import matter from 'gray-matter';
|
|
16
|
+
import { consoleLogger } from '../../utils/logger.js';
|
|
17
|
+
/**
|
|
18
|
+
* DesyncDetector - Validates status consistency between metadata.json and spec.md
|
|
19
|
+
*
|
|
20
|
+
* Key Features:
|
|
21
|
+
* - Detects status desyncs across all increments
|
|
22
|
+
* - Provides detailed desync reports
|
|
23
|
+
* - Auto-fix capability (optional)
|
|
24
|
+
* - Validates before critical operations
|
|
25
|
+
*
|
|
26
|
+
* Usage:
|
|
27
|
+
* ```typescript
|
|
28
|
+
* // Check single increment
|
|
29
|
+
* const detector = new DesyncDetector();
|
|
30
|
+
* const result = await detector.checkIncrement('0047-us-task-linkage');
|
|
31
|
+
* if (result.hasDesync) {
|
|
32
|
+
* console.error('Desync detected!', result);
|
|
33
|
+
* }
|
|
34
|
+
*
|
|
35
|
+
* // Scan all increments
|
|
36
|
+
* const report = await detector.scanAll();
|
|
37
|
+
* console.log(`Found ${report.totalDesyncs} desyncs`);
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export class DesyncDetector {
|
|
41
|
+
constructor(options = {}) {
|
|
42
|
+
this.logger = options.logger ?? consoleLogger;
|
|
43
|
+
this.projectRoot = options.projectRoot ?? process.cwd();
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Check single increment for status desync
|
|
47
|
+
*
|
|
48
|
+
* @param incrementId - Increment ID (e.g., "0047-us-task-linkage")
|
|
49
|
+
* @returns Desync result with detailed status info
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* const result = await detector.checkIncrement('0047-us-task-linkage');
|
|
54
|
+
* if (result.hasDesync) {
|
|
55
|
+
* console.error(`Desync: metadata=${result.metadataStatus}, spec=${result.specStatus}`);
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
async checkIncrement(incrementId) {
|
|
60
|
+
const metadataPath = path.join(this.projectRoot, '.specweave', 'increments', incrementId, 'metadata.json');
|
|
61
|
+
const specPath = path.join(this.projectRoot, '.specweave', 'increments', incrementId, 'spec.md');
|
|
62
|
+
try {
|
|
63
|
+
// Read metadata.json status
|
|
64
|
+
let metadataStatus = null;
|
|
65
|
+
if (await fs.pathExists(metadataPath)) {
|
|
66
|
+
const metadata = await fs.readJson(metadataPath);
|
|
67
|
+
metadataStatus = metadata.status ?? null;
|
|
68
|
+
}
|
|
69
|
+
// Read spec.md status
|
|
70
|
+
let specStatus = null;
|
|
71
|
+
if (await fs.pathExists(specPath)) {
|
|
72
|
+
const content = await fs.readFile(specPath, 'utf-8');
|
|
73
|
+
const parsed = matter(content);
|
|
74
|
+
specStatus = parsed.data.status ?? null;
|
|
75
|
+
}
|
|
76
|
+
// Detect desync
|
|
77
|
+
const hasDesync = metadataStatus !== null &&
|
|
78
|
+
specStatus !== null &&
|
|
79
|
+
metadataStatus !== specStatus;
|
|
80
|
+
return {
|
|
81
|
+
incrementId,
|
|
82
|
+
hasDesync,
|
|
83
|
+
metadataStatus,
|
|
84
|
+
specStatus,
|
|
85
|
+
metadataPath,
|
|
86
|
+
specPath,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
return {
|
|
91
|
+
incrementId,
|
|
92
|
+
hasDesync: false,
|
|
93
|
+
metadataStatus: null,
|
|
94
|
+
specStatus: null,
|
|
95
|
+
metadataPath,
|
|
96
|
+
specPath,
|
|
97
|
+
error: error instanceof Error ? error.message : String(error),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Scan all increments for desyncs
|
|
103
|
+
*
|
|
104
|
+
* @returns Comprehensive scan report
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```typescript
|
|
108
|
+
* const report = await detector.scanAll();
|
|
109
|
+
* console.log(`Scanned ${report.totalScanned} increments`);
|
|
110
|
+
* console.log(`Found ${report.totalDesyncs} desyncs`);
|
|
111
|
+
* report.desyncs.forEach(d => console.error(`- ${d.incrementId}`));
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
async scanAll() {
|
|
115
|
+
const incrementsDir = path.join(this.projectRoot, '.specweave', 'increments');
|
|
116
|
+
const report = {
|
|
117
|
+
totalScanned: 0,
|
|
118
|
+
totalDesyncs: 0,
|
|
119
|
+
desyncs: [],
|
|
120
|
+
healthy: [],
|
|
121
|
+
errors: [],
|
|
122
|
+
};
|
|
123
|
+
if (!(await fs.pathExists(incrementsDir))) {
|
|
124
|
+
return report;
|
|
125
|
+
}
|
|
126
|
+
// Get all increment directories
|
|
127
|
+
const entries = await fs.readdir(incrementsDir, { withFileTypes: true });
|
|
128
|
+
const incrementDirs = entries
|
|
129
|
+
.filter((entry) => entry.isDirectory() && !entry.name.startsWith('_'))
|
|
130
|
+
.map((entry) => entry.name);
|
|
131
|
+
// Check each increment
|
|
132
|
+
for (const incrementId of incrementDirs) {
|
|
133
|
+
report.totalScanned++;
|
|
134
|
+
const result = await this.checkIncrement(incrementId);
|
|
135
|
+
if (result.error) {
|
|
136
|
+
report.errors.push(`${incrementId}: ${result.error}`);
|
|
137
|
+
}
|
|
138
|
+
else if (result.hasDesync) {
|
|
139
|
+
report.totalDesyncs++;
|
|
140
|
+
report.desyncs.push(result);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
report.healthy.push(incrementId);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return report;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Fix desync by updating spec.md to match metadata.json (source of truth for status)
|
|
150
|
+
*
|
|
151
|
+
* IMPORTANT: metadata.json is considered the source of truth because it's updated
|
|
152
|
+
* atomically and used by the CLI. spec.md should mirror it.
|
|
153
|
+
*
|
|
154
|
+
* @param incrementId - Increment ID to fix
|
|
155
|
+
* @returns true if fixed successfully, false if no desync or fix failed
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```typescript
|
|
159
|
+
* const fixed = await detector.fixDesync('0047-us-task-linkage');
|
|
160
|
+
* if (fixed) {
|
|
161
|
+
* console.log('Desync fixed!');
|
|
162
|
+
* }
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
165
|
+
async fixDesync(incrementId) {
|
|
166
|
+
const result = await this.checkIncrement(incrementId);
|
|
167
|
+
if (!result.hasDesync) {
|
|
168
|
+
this.logger.log(`No desync detected for ${incrementId}`);
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
if (result.metadataStatus === null) {
|
|
172
|
+
this.logger.error(`Cannot fix desync - metadata.json missing for ${incrementId}`);
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
// Read spec.md
|
|
177
|
+
const content = await fs.readFile(result.specPath, 'utf-8');
|
|
178
|
+
const parsed = matter(content);
|
|
179
|
+
// Update status to match metadata.json (source of truth)
|
|
180
|
+
parsed.data.status = result.metadataStatus;
|
|
181
|
+
// Write back atomically
|
|
182
|
+
const updatedContent = matter.stringify(parsed.content, parsed.data);
|
|
183
|
+
const tempPath = `${result.specPath}.tmp`;
|
|
184
|
+
await fs.writeFile(tempPath, updatedContent, 'utf-8');
|
|
185
|
+
await fs.rename(tempPath, result.specPath);
|
|
186
|
+
this.logger.log(`Fixed desync for ${incrementId}: spec.md updated from "${result.specStatus}" to "${result.metadataStatus}"`);
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
this.logger.error(`Failed to fix desync for ${incrementId}`, error instanceof Error ? error : new Error(String(error)));
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Validate increment has no desync, throw if found
|
|
196
|
+
*
|
|
197
|
+
* Use this before critical operations (e.g., closing increment, archiving)
|
|
198
|
+
*
|
|
199
|
+
* @param incrementId - Increment ID to validate
|
|
200
|
+
* @throws Error if desync detected
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* ```typescript
|
|
204
|
+
* await detector.validateOrThrow('0047-us-task-linkage');
|
|
205
|
+
* // Throws if desync found, continues if healthy
|
|
206
|
+
* ```
|
|
207
|
+
*/
|
|
208
|
+
async validateOrThrow(incrementId) {
|
|
209
|
+
const result = await this.checkIncrement(incrementId);
|
|
210
|
+
if (result.error) {
|
|
211
|
+
throw new Error(`Cannot validate ${incrementId} - error reading status: ${result.error}`);
|
|
212
|
+
}
|
|
213
|
+
if (result.hasDesync) {
|
|
214
|
+
throw new Error(`CRITICAL: Status desync detected for ${incrementId}!\n` +
|
|
215
|
+
`metadata.json: ${result.metadataStatus}\n` +
|
|
216
|
+
`spec.md: ${result.specStatus}\n\n` +
|
|
217
|
+
`This is a source-of-truth violation (CLAUDE.md Rule #7).\n` +
|
|
218
|
+
`Run: /specweave:sync-status ${incrementId} to fix`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Generate human-readable report for desyncs
|
|
223
|
+
*
|
|
224
|
+
* @param report - Scan report
|
|
225
|
+
* @returns Formatted report string
|
|
226
|
+
*/
|
|
227
|
+
formatReport(report) {
|
|
228
|
+
const lines = [];
|
|
229
|
+
lines.push('━'.repeat(80));
|
|
230
|
+
lines.push('STATUS DESYNC DETECTION REPORT');
|
|
231
|
+
lines.push('━'.repeat(80));
|
|
232
|
+
lines.push('');
|
|
233
|
+
lines.push(`Total Scanned: ${report.totalScanned} increments`);
|
|
234
|
+
lines.push(`Healthy: ${report.healthy.length}`);
|
|
235
|
+
lines.push(`Desyncs Found: ${report.totalDesyncs} ⚠️`);
|
|
236
|
+
lines.push(`Errors: ${report.errors.length}`);
|
|
237
|
+
lines.push('');
|
|
238
|
+
if (report.totalDesyncs > 0) {
|
|
239
|
+
lines.push('━'.repeat(80));
|
|
240
|
+
lines.push('DESYNCS DETECTED (CRITICAL!)');
|
|
241
|
+
lines.push('━'.repeat(80));
|
|
242
|
+
lines.push('');
|
|
243
|
+
report.desyncs.forEach((desync) => {
|
|
244
|
+
lines.push(`❌ ${desync.incrementId}`);
|
|
245
|
+
lines.push(` metadata.json: ${desync.metadataStatus}`);
|
|
246
|
+
lines.push(` spec.md: ${desync.specStatus}`);
|
|
247
|
+
lines.push('');
|
|
248
|
+
});
|
|
249
|
+
lines.push('Fix command: /specweave:sync-status');
|
|
250
|
+
lines.push('');
|
|
251
|
+
}
|
|
252
|
+
if (report.errors.length > 0) {
|
|
253
|
+
lines.push('━'.repeat(80));
|
|
254
|
+
lines.push('ERRORS');
|
|
255
|
+
lines.push('━'.repeat(80));
|
|
256
|
+
lines.push('');
|
|
257
|
+
report.errors.forEach((error) => {
|
|
258
|
+
lines.push(`⚠️ ${error}`);
|
|
259
|
+
});
|
|
260
|
+
lines.push('');
|
|
261
|
+
}
|
|
262
|
+
if (report.totalDesyncs === 0 && report.errors.length === 0) {
|
|
263
|
+
lines.push('✅ All increments healthy - no desyncs detected!');
|
|
264
|
+
lines.push('');
|
|
265
|
+
}
|
|
266
|
+
lines.push('━'.repeat(80));
|
|
267
|
+
return lines.join('\n');
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
//# sourceMappingURL=desync-detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"desync-detector.js","sourceRoot":"","sources":["../../../../src/core/increment/desync-detector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,MAAM,MAAM,aAAa,CAAC;AAEjC,OAAO,EAAU,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAmC9D;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,OAAO,cAAc;IAIzB,YAAY,UAAiC,EAAE;QAC7C,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,aAAa,CAAC;QAC9C,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAC1D,CAAC;IAED;;;;;;;;;;;;;OAaG;IACH,KAAK,CAAC,cAAc,CAAC,WAAmB;QACtC,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAC5B,IAAI,CAAC,WAAW,EAChB,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,eAAe,CAChB,CAAC;QAEF,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CACxB,IAAI,CAAC,WAAW,EAChB,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,SAAS,CACV,CAAC;QAEF,IAAI,CAAC;YACH,4BAA4B;YAC5B,IAAI,cAAc,GAA2B,IAAI,CAAC;YAClD,IAAI,MAAM,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;gBACtC,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;gBACjD,cAAc,GAAG,QAAQ,CAAC,MAAM,IAAI,IAAI,CAAC;YAC3C,CAAC;YAED,sBAAsB;YACtB,IAAI,UAAU,GAA2B,IAAI,CAAC;YAC9C,IAAI,MAAM,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAClC,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;gBACrD,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;gBAC/B,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC;YAC1C,CAAC;YAED,gBAAgB;YAChB,MAAM,SAAS,GACb,cAAc,KAAK,IAAI;gBACvB,UAAU,KAAK,IAAI;gBACnB,cAAc,KAAK,UAAU,CAAC;YAEhC,OAAO;gBACL,WAAW;gBACX,SAAS;gBACT,cAAc;gBACd,UAAU;gBACV,YAAY;gBACZ,QAAQ;aACT,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO;gBACL,WAAW;gBACX,SAAS,EAAE,KAAK;gBAChB,cAAc,EAAE,IAAI;gBACpB,UAAU,EAAE,IAAI;gBAChB,YAAY;gBACZ,QAAQ;gBACR,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;aAC9D,CAAC;QACJ,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,OAAO;QACX,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,YAAY,EAAE,YAAY,CAAC,CAAC;QAE9E,MAAM,MAAM,GAAqB;YAC/B,YAAY,EAAE,CAAC;YACf,YAAY,EAAE,CAAC;YACf,OAAO,EAAE,EAAE;YACX,OAAO,EAAE,EAAE;YACX,MAAM,EAAE,EAAE;SACX,CAAC;QAEF,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC;YAC1C,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,gCAAgC;QAChC,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QACzE,MAAM,aAAa,GAAG,OAAO;aAC1B,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;aACrE,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAE9B,uBAAuB;QACvB,KAAK,MAAM,WAAW,IAAI,aAAa,EAAE,CAAC;YACxC,MAAM,CAAC,YAAY,EAAE,CAAC;YAEtB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;YAEtD,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;gBACjB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,WAAW,KAAK,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;YACxD,CAAC;iBAAM,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;gBAC5B,MAAM,CAAC,YAAY,EAAE,CAAC;gBACtB,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC9B,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YACnC,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,KAAK,CAAC,SAAS,CAAC,WAAmB;QACjC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;QAEtD,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;YACtB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,0BAA0B,WAAW,EAAE,CAAC,CAAC;YACzD,OAAO,KAAK,CAAC;QACf,CAAC;QAED,IAAI,MAAM,CAAC,cAAc,KAAK,IAAI,EAAE,CAAC;YACnC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,iDAAiD,WAAW,EAAE,CAAC,CAAC;YAClF,OAAO,KAAK,CAAC;QACf,CAAC;QAED,IAAI,CAAC;YACH,eAAe;YACf,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YAC5D,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;YAE/B,yDAAyD;YACzD,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,cAAc,CAAC;YAE3C,wBAAwB;YACxB,MAAM,cAAc,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;YACrE,MAAM,QAAQ,GAAG,GAAG,MAAM,CAAC,QAAQ,MAAM,CAAC;YAC1C,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,cAAc,EAAE,OAAO,CAAC,CAAC;YACtD,MAAM,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;YAE3C,IAAI,CAAC,MAAM,CAAC,GAAG,CACb,oBAAoB,WAAW,2BAA2B,MAAM,CAAC,UAAU,SAAS,MAAM,CAAC,cAAc,GAAG,CAC7G,CAAC;YAEF,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,4BAA4B,WAAW,EAAE,EACzC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAC1D,CAAC;YACF,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;;OAaG;IACH,KAAK,CAAC,eAAe,CAAC,WAAmB;QACvC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;QAEtD,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CACb,mBAAmB,WAAW,4BAA4B,MAAM,CAAC,KAAK,EAAE,CACzE,CAAC;QACJ,CAAC;QAED,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CACb,wCAAwC,WAAW,KAAK;gBACtD,kBAAkB,MAAM,CAAC,cAAc,IAAI;gBAC3C,YAAY,MAAM,CAAC,UAAU,MAAM;gBACnC,4DAA4D;gBAC5D,+BAA+B,WAAW,SAAS,CACtD,CAAC;QACJ,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACH,YAAY,CAAC,MAAwB;QACnC,MAAM,KAAK,GAAa,EAAE,CAAC;QAE3B,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;QAC3B,KAAK,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;QAC7C,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;QAC3B,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAEf,KAAK,CAAC,IAAI,CAAC,kBAAkB,MAAM,CAAC,YAAY,aAAa,CAAC,CAAC;QAC/D,KAAK,CAAC,IAAI,CAAC,YAAY,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QAChD,KAAK,CAAC,IAAI,CAAC,kBAAkB,MAAM,CAAC,YAAY,KAAK,CAAC,CAAC;QACvD,KAAK,CAAC,IAAI,CAAC,WAAW,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QAC9C,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAEf,IAAI,MAAM,CAAC,YAAY,GAAG,CAAC,EAAE,CAAC;YAC5B,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;YAC3B,KAAK,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;YAC3C,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;YAC3B,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAEf,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE;gBAChC,KAAK,CAAC,IAAI,CAAC,KAAK,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;gBACtC,KAAK,CAAC,IAAI,CAAC,qBAAqB,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC;gBACzD,KAAK,CAAC,IAAI,CAAC,qBAAqB,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC;gBACrD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACjB,CAAC,CAAC,CAAC;YAEH,KAAK,CAAC,IAAI,CAAC,qCAAqC,CAAC,CAAC;YAClD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACjB,CAAC;QAED,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC7B,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;YAC3B,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACrB,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;YAC3B,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAEf,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;gBAC9B,KAAK,CAAC,IAAI,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC;YAC7B,CAAC,CAAC,CAAC;YACH,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACjB,CAAC;QAED,IAAI,MAAM,CAAC,YAAY,KAAK,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5D,KAAK,CAAC,IAAI,CAAC,iDAAiD,CAAC,CAAC;YAC9D,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACjB,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;QAE3B,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;CACF"}
|
|
@@ -41,12 +41,12 @@ export declare class MetadataManager {
|
|
|
41
41
|
/**
|
|
42
42
|
* Check if metadata file exists
|
|
43
43
|
*/
|
|
44
|
-
static exists(incrementId: string): boolean;
|
|
44
|
+
static exists(incrementId: string, rootDir?: string): boolean;
|
|
45
45
|
/**
|
|
46
46
|
* Read metadata from file
|
|
47
47
|
* Creates default metadata if file doesn't exist (lazy initialization)
|
|
48
48
|
*/
|
|
49
|
-
static read(incrementId: string): IncrementMetadata;
|
|
49
|
+
static read(incrementId: string, rootDir?: string): IncrementMetadata;
|
|
50
50
|
/**
|
|
51
51
|
* Reserved increment IDs that cannot be used
|
|
52
52
|
* These are status values, special folders, and state files
|
|
@@ -65,12 +65,16 @@ export declare class MetadataManager {
|
|
|
65
65
|
/**
|
|
66
66
|
* Write metadata to file
|
|
67
67
|
* Uses atomic write (temp file → rename)
|
|
68
|
+
*
|
|
69
|
+
* @param incrementId - Increment ID
|
|
70
|
+
* @param metadata - Metadata to write
|
|
71
|
+
* @param rootDir - Optional root directory (defaults to process.cwd())
|
|
68
72
|
*/
|
|
69
|
-
static write(incrementId: string, metadata: IncrementMetadata): void;
|
|
73
|
+
static write(incrementId: string, metadata: IncrementMetadata, rootDir?: string): void;
|
|
70
74
|
/**
|
|
71
75
|
* Delete metadata file
|
|
72
76
|
*/
|
|
73
|
-
static delete(incrementId: string): void;
|
|
77
|
+
static delete(incrementId: string, rootDir?: string): void;
|
|
74
78
|
/**
|
|
75
79
|
* Update increment status
|
|
76
80
|
* Validates transition and updates timestamps
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metadata-manager.d.ts","sourceRoot":"","sources":["../../../../src/core/increment/metadata-manager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EACL,iBAAiB,EACjB,yBAAyB,EACzB,eAAe,EACf,aAAa,EAKd,MAAM,gCAAgC,CAAC;AAIxC,OAAO,EAAE,MAAM,EAAiB,MAAM,uBAAuB,CAAC;AAE9D;;GAEG;AACH,qBAAa,aAAc,SAAQ,KAAK;IACF,WAAW,EAAE,MAAM;IAAS,KAAK,CAAC,EAAE,KAAK;gBAAjE,OAAO,EAAE,MAAM,EAAS,WAAW,EAAE,MAAM,EAAS,KAAK,CAAC,EAAE,KAAK;CAI9E;AAED;;;;GAIG;AACH,qBAAa,eAAe;IAC1B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,MAAM,CAAyB;IAE9C;;;;OAIG;IACH,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAItC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,eAAe;IAK9B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAK/B;;OAEG;IACH,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;
|
|
1
|
+
{"version":3,"file":"metadata-manager.d.ts","sourceRoot":"","sources":["../../../../src/core/increment/metadata-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -39,40 +39,40 @@ export class MetadataManager {
|
|
|
39
39
|
/**
|
|
40
40
|
* Get metadata file path for increment
|
|
41
41
|
*/
|
|
42
|
-
static getMetadataPath(incrementId) {
|
|
43
|
-
const specweavePath = path.join(process.cwd(), '.specweave');
|
|
42
|
+
static getMetadataPath(incrementId, rootDir) {
|
|
43
|
+
const specweavePath = path.join(rootDir || process.cwd(), '.specweave');
|
|
44
44
|
return path.join(specweavePath, 'increments', incrementId, 'metadata.json');
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
47
|
* Get increment directory path
|
|
48
48
|
*/
|
|
49
|
-
static getIncrementPath(incrementId) {
|
|
50
|
-
const specweavePath = path.join(process.cwd(), '.specweave');
|
|
49
|
+
static getIncrementPath(incrementId, rootDir) {
|
|
50
|
+
const specweavePath = path.join(rootDir || process.cwd(), '.specweave');
|
|
51
51
|
return path.join(specweavePath, 'increments', incrementId);
|
|
52
52
|
}
|
|
53
53
|
/**
|
|
54
54
|
* Check if metadata file exists
|
|
55
55
|
*/
|
|
56
|
-
static exists(incrementId) {
|
|
57
|
-
const metadataPath = this.getMetadataPath(incrementId);
|
|
56
|
+
static exists(incrementId, rootDir) {
|
|
57
|
+
const metadataPath = this.getMetadataPath(incrementId, rootDir);
|
|
58
58
|
return fs.existsSync(metadataPath);
|
|
59
59
|
}
|
|
60
60
|
/**
|
|
61
61
|
* Read metadata from file
|
|
62
62
|
* Creates default metadata if file doesn't exist (lazy initialization)
|
|
63
63
|
*/
|
|
64
|
-
static read(incrementId) {
|
|
65
|
-
const metadataPath = this.getMetadataPath(incrementId);
|
|
64
|
+
static read(incrementId, rootDir) {
|
|
65
|
+
const metadataPath = this.getMetadataPath(incrementId, rootDir);
|
|
66
66
|
// Lazy initialization: Create metadata if doesn't exist
|
|
67
67
|
if (!fs.existsSync(metadataPath)) {
|
|
68
68
|
// Check if increment folder exists
|
|
69
|
-
const incrementPath = this.getIncrementPath(incrementId);
|
|
69
|
+
const incrementPath = this.getIncrementPath(incrementId, rootDir);
|
|
70
70
|
if (!fs.existsSync(incrementPath)) {
|
|
71
71
|
throw new MetadataError(`Increment not found: ${incrementId}`, incrementId);
|
|
72
72
|
}
|
|
73
73
|
// Create default metadata
|
|
74
74
|
const defaultMetadata = createDefaultMetadata(incrementId);
|
|
75
|
-
this.write(incrementId, defaultMetadata);
|
|
75
|
+
this.write(incrementId, defaultMetadata, rootDir);
|
|
76
76
|
// **CRITICAL**: Update active increment state if default status is ACTIVE
|
|
77
77
|
// This ensures that newly created increments are immediately tracked for status line
|
|
78
78
|
// Skip validation to prevent circular dependency during lazy initialization
|
|
@@ -148,10 +148,14 @@ export class MetadataManager {
|
|
|
148
148
|
/**
|
|
149
149
|
* Write metadata to file
|
|
150
150
|
* Uses atomic write (temp file → rename)
|
|
151
|
+
*
|
|
152
|
+
* @param incrementId - Increment ID
|
|
153
|
+
* @param metadata - Metadata to write
|
|
154
|
+
* @param rootDir - Optional root directory (defaults to process.cwd())
|
|
151
155
|
*/
|
|
152
|
-
static write(incrementId, metadata) {
|
|
153
|
-
const metadataPath = this.getMetadataPath(incrementId);
|
|
154
|
-
const incrementPath = this.getIncrementPath(incrementId);
|
|
156
|
+
static write(incrementId, metadata, rootDir) {
|
|
157
|
+
const metadataPath = this.getMetadataPath(incrementId, rootDir);
|
|
158
|
+
const incrementPath = this.getIncrementPath(incrementId, rootDir);
|
|
155
159
|
// Ensure increment directory exists
|
|
156
160
|
if (!fs.existsSync(incrementPath)) {
|
|
157
161
|
throw new MetadataError(`Increment directory not found: ${incrementId}`, incrementId);
|
|
@@ -172,8 +176,8 @@ export class MetadataManager {
|
|
|
172
176
|
/**
|
|
173
177
|
* Delete metadata file
|
|
174
178
|
*/
|
|
175
|
-
static delete(incrementId) {
|
|
176
|
-
const metadataPath = this.getMetadataPath(incrementId);
|
|
179
|
+
static delete(incrementId, rootDir) {
|
|
180
|
+
const metadataPath = this.getMetadataPath(incrementId, rootDir);
|
|
177
181
|
if (!fs.existsSync(metadataPath)) {
|
|
178
182
|
return; // Already deleted
|
|
179
183
|
}
|
|
@@ -224,20 +228,40 @@ export class MetadataManager {
|
|
|
224
228
|
metadata.abandonedReason = reason || 'No reason provided';
|
|
225
229
|
metadata.abandonedAt = new Date().toISOString();
|
|
226
230
|
}
|
|
227
|
-
|
|
228
|
-
//
|
|
231
|
+
// **CRITICAL FIX (2025-11-20)**: Atomic transaction with rollback
|
|
232
|
+
// Update spec.md FIRST, then metadata.json. If spec.md fails, no desync occurs.
|
|
233
|
+
// This prevents the silent failure bug that caused increment 0047 desync.
|
|
234
|
+
//
|
|
235
|
+
// Previous bug: metadata.json updated, spec.md failed silently → desync
|
|
236
|
+
// New behavior: spec.md fails → error thrown → metadata.json never written
|
|
237
|
+
//
|
|
229
238
|
// AC-US2-01: updateStatus() updates both metadata.json AND spec.md frontmatter
|
|
230
239
|
// AC-US2-03: All status transitions update spec.md
|
|
231
240
|
// SYNCHRONOUS call to ensure spec.md is updated before returning
|
|
232
241
|
// This prevents race conditions and ensures data consistency
|
|
233
242
|
try {
|
|
234
|
-
//
|
|
243
|
+
// Step 1: Update spec.md (may throw if spec.md exists but has errors)
|
|
235
244
|
this.updateSpecMdStatusSync(incrementId, newStatus);
|
|
245
|
+
// Step 2: Update metadata.json (only if spec.md succeeded)
|
|
246
|
+
this.write(incrementId, metadata);
|
|
236
247
|
}
|
|
237
248
|
catch (error) {
|
|
238
|
-
//
|
|
239
|
-
|
|
240
|
-
|
|
249
|
+
// CRITICAL: spec.md update failed - prevent desync by NOT updating metadata.json
|
|
250
|
+
this.logger.error(`CRITICAL: Failed to update status for ${incrementId} - aborting to prevent desync`, error);
|
|
251
|
+
// Throw detailed error with fix instructions
|
|
252
|
+
throw new MetadataError(`Cannot update increment status - spec.md sync failed.\n` +
|
|
253
|
+
`\n` +
|
|
254
|
+
`This prevents source-of-truth violations (CLAUDE.md Rule #7).\n` +
|
|
255
|
+
`Both metadata.json AND spec.md must update atomically.\n` +
|
|
256
|
+
`\n` +
|
|
257
|
+
`Error: ${error instanceof Error ? error.message : String(error)}\n` +
|
|
258
|
+
`\n` +
|
|
259
|
+
`If this persists, check:\n` +
|
|
260
|
+
`1. File permissions for .specweave/increments/${incrementId}/spec.md\n` +
|
|
261
|
+
`2. YAML frontmatter syntax in spec.md\n` +
|
|
262
|
+
`3. Disk space availability\n` +
|
|
263
|
+
`\n` +
|
|
264
|
+
`To check for desyncs, run: /specweave:sync-status`, incrementId, error instanceof Error ? error : undefined);
|
|
241
265
|
}
|
|
242
266
|
// **CRITICAL**: Update active increment state
|
|
243
267
|
const activeManager = new ActiveIncrementManager();
|