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.
Files changed (106) hide show
  1. package/CLAUDE.md +367 -0
  2. package/dist/plugins/specweave/lib/utils/fs-native.d.ts +133 -0
  3. package/dist/plugins/specweave/lib/utils/fs-native.d.ts.map +1 -0
  4. package/dist/plugins/specweave/lib/utils/fs-native.js +224 -0
  5. package/dist/plugins/specweave/lib/utils/fs-native.js.map +1 -0
  6. package/dist/plugins/specweave-github/lib/github-client-v2.js +1 -1
  7. package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
  8. package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts.map +1 -1
  9. package/dist/plugins/specweave-github/lib/github-feature-sync.js +52 -20
  10. package/dist/plugins/specweave-github/lib/github-feature-sync.js.map +1 -1
  11. package/dist/plugins/specweave-github/lib/user-story-issue-builder.d.ts.map +1 -1
  12. package/dist/plugins/specweave-github/lib/user-story-issue-builder.js +24 -0
  13. package/dist/plugins/specweave-github/lib/user-story-issue-builder.js.map +1 -1
  14. package/dist/src/cli/helpers/init/initial-increment-generator.d.ts.map +1 -1
  15. package/dist/src/cli/helpers/init/initial-increment-generator.js +2 -1
  16. package/dist/src/cli/helpers/init/initial-increment-generator.js.map +1 -1
  17. package/dist/src/core/ac-test-validator-cli.d.ts +16 -0
  18. package/dist/src/core/ac-test-validator-cli.d.ts.map +1 -0
  19. package/dist/src/core/ac-test-validator-cli.js +118 -0
  20. package/dist/src/core/ac-test-validator-cli.js.map +1 -0
  21. package/dist/src/core/ac-test-validator.d.ts +111 -0
  22. package/dist/src/core/ac-test-validator.d.ts.map +1 -0
  23. package/dist/src/core/ac-test-validator.js +292 -0
  24. package/dist/src/core/ac-test-validator.js.map +1 -0
  25. package/dist/src/core/increment/desync-detector.d.ts +142 -0
  26. package/dist/src/core/increment/desync-detector.d.ts.map +1 -0
  27. package/dist/src/core/increment/desync-detector.js +270 -0
  28. package/dist/src/core/increment/desync-detector.js.map +1 -0
  29. package/dist/src/core/increment/metadata-manager.d.ts +8 -4
  30. package/dist/src/core/increment/metadata-manager.d.ts.map +1 -1
  31. package/dist/src/core/increment/metadata-manager.js +45 -21
  32. package/dist/src/core/increment/metadata-manager.js.map +1 -1
  33. package/dist/src/core/qa/qa-runner.js +9 -2
  34. package/dist/src/core/qa/qa-runner.js.map +1 -1
  35. package/dist/src/sync/sync-coordinator.d.ts +1 -1
  36. package/dist/src/sync/sync-coordinator.d.ts.map +1 -1
  37. package/dist/src/sync/sync-coordinator.js +40 -2
  38. package/dist/src/sync/sync-coordinator.js.map +1 -1
  39. package/dist/src/utils/fs-native.d.ts +133 -0
  40. package/dist/src/utils/fs-native.d.ts.map +1 -0
  41. package/dist/src/utils/fs-native.js +224 -0
  42. package/dist/src/utils/fs-native.js.map +1 -0
  43. package/package.json +1 -1
  44. package/plugins/specweave/.claude-plugin/plugin.json +12 -0
  45. package/plugins/specweave/agents/AGENTS-INDEX.md +216 -0
  46. package/plugins/specweave/agents/architect/AGENT.md +17 -0
  47. package/plugins/specweave/agents/code-standards-detective/AGENT.md +16 -0
  48. package/plugins/specweave/agents/docs-writer/AGENT.md +16 -0
  49. package/plugins/specweave/agents/increment-quality-judge-v2/AGENT.md +704 -0
  50. package/plugins/specweave/agents/infrastructure/AGENT.md +16 -0
  51. package/plugins/specweave/agents/performance/AGENT.md +16 -0
  52. package/plugins/specweave/agents/pm/AGENT.md +17 -0
  53. package/plugins/specweave/agents/qa-lead/AGENT.md +15 -0
  54. package/plugins/specweave/agents/reflective-reviewer/AGENT.md +16 -0
  55. package/plugins/specweave/agents/security/AGENT.md +16 -0
  56. package/plugins/specweave/agents/tdd-orchestrator/AGENT.md +16 -0
  57. package/plugins/specweave/agents/tech-lead/AGENT.md +16 -0
  58. package/plugins/specweave/agents/test-aware-planner/AGENT.md +16 -0
  59. package/plugins/specweave/agents/translator/AGENT.md +13 -0
  60. package/plugins/specweave/commands/specweave-done.md +14 -0
  61. package/plugins/specweave/commands/specweave-qa.md +11 -1
  62. package/plugins/specweave/commands/specweave-sync-status.md +356 -0
  63. package/plugins/specweave/commands/specweave-validate.md +10 -1
  64. package/plugins/specweave/hooks/pre-task-completion.sh +196 -0
  65. package/plugins/specweave/lib/hooks/git-diff-analyzer.js +3 -3
  66. package/plugins/specweave/lib/hooks/git-diff-analyzer.ts +3 -3
  67. package/plugins/specweave/lib/hooks/invoke-translator-skill.js +3 -2
  68. package/plugins/specweave/lib/hooks/invoke-translator-skill.ts +3 -2
  69. package/plugins/specweave/lib/hooks/prepare-reflection-context.js +3 -3
  70. package/plugins/specweave/lib/hooks/prepare-reflection-context.ts +3 -3
  71. package/plugins/specweave/lib/hooks/reflection-config-loader.js +4 -4
  72. package/plugins/specweave/lib/hooks/reflection-config-loader.ts +4 -4
  73. package/plugins/specweave/lib/hooks/reflection-storage.js +9 -9
  74. package/plugins/specweave/lib/hooks/reflection-storage.ts +9 -9
  75. package/plugins/specweave/lib/hooks/sync-cache.js +9 -8
  76. package/plugins/specweave/lib/hooks/sync-living-docs.js +57 -6
  77. package/plugins/specweave/lib/hooks/sync-us-tasks.js +6 -6
  78. package/plugins/specweave/lib/hooks/translate-file.js +3 -2
  79. package/plugins/specweave/lib/hooks/translate-file.ts +3 -2
  80. package/plugins/specweave/lib/hooks/translate-living-docs.js +4 -3
  81. package/plugins/specweave/lib/hooks/translate-living-docs.ts +4 -3
  82. package/plugins/specweave/lib/hooks/update-tasks-md.js +3 -3
  83. package/plugins/specweave/lib/hooks/update-tasks-md.ts +3 -3
  84. package/plugins/specweave/lib/utils/fs-native.js +182 -0
  85. package/plugins/specweave/lib/utils/fs-native.ts +283 -0
  86. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.d.ts +8 -4
  87. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js +45 -21
  88. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js.map +1 -1
  89. package/plugins/specweave/skills/SKILLS-INDEX.md +26 -2
  90. package/plugins/specweave/skills/increment-planner/SKILL.md +2 -2
  91. package/plugins/specweave-ado/commands/specweave-ado-close-workitem.md +1 -1
  92. package/plugins/specweave-ado/commands/specweave-ado-create-workitem.md +1 -1
  93. package/plugins/specweave-ado/commands/specweave-ado-status.md +1 -1
  94. package/plugins/specweave-ado/commands/specweave-ado-sync.md +1 -1
  95. package/plugins/specweave-diagrams/agents/diagrams-architect/AGENT.md +1 -1
  96. package/plugins/specweave-diagrams/skills/diagrams-generator/SKILL.md +4 -4
  97. package/plugins/specweave-github/lib/github-client-v2.js +2 -1
  98. package/plugins/specweave-github/lib/github-client-v2.ts +1 -1
  99. package/plugins/specweave-github/lib/github-feature-sync.js +30 -17
  100. package/plugins/specweave-github/lib/github-feature-sync.ts +54 -24
  101. package/plugins/specweave-github/lib/user-story-issue-builder.js +24 -0
  102. package/plugins/specweave-github/lib/user-story-issue-builder.ts +33 -0
  103. package/plugins/specweave-mobile/README.md +1 -1
  104. package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +72 -0
  105. package/src/templates/CLAUDE.md.template +13 -0
  106. 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":"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"}
@@ -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":"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"}
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
- this.write(incrementId, metadata);
228
- // **NEW (T-005)**: Update spec.md frontmatter to keep in sync with metadata.json
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
- // Use sync version to avoid race conditions in tests
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
- // Log error but don't fail the status update
239
- // This maintains backward compatibility if spec.md doesn't exist or has issues
240
- this.logger.error(`Failed to update spec.md for ${incrementId}`, error);
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();