specweave 0.28.68 → 0.29.0
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 +3 -2
- package/README.md +19 -2
- package/dist/src/cli/commands/discrepancies.d.ts +89 -0
- package/dist/src/cli/commands/discrepancies.d.ts.map +1 -0
- package/dist/src/cli/commands/discrepancies.js +385 -0
- package/dist/src/cli/commands/discrepancies.js.map +1 -0
- package/dist/src/cli/commands/notifications.d.ts +70 -0
- package/dist/src/cli/commands/notifications.d.ts.map +1 -0
- package/dist/src/cli/commands/notifications.js +236 -0
- package/dist/src/cli/commands/notifications.js.map +1 -0
- package/dist/src/cli/commands/sync-logs.d.ts +54 -0
- package/dist/src/cli/commands/sync-logs.d.ts.map +1 -0
- package/dist/src/cli/commands/sync-logs.js +240 -0
- package/dist/src/cli/commands/sync-logs.js.map +1 -0
- package/dist/src/cli/commands/sync-monitor.d.ts +42 -0
- package/dist/src/cli/commands/sync-monitor.d.ts.map +1 -0
- package/dist/src/cli/commands/sync-monitor.js +191 -0
- package/dist/src/cli/commands/sync-monitor.js.map +1 -0
- package/dist/src/cli/helpers/init/brownfield-analysis.d.ts +45 -0
- package/dist/src/cli/helpers/init/brownfield-analysis.d.ts.map +1 -0
- package/dist/src/cli/helpers/init/brownfield-analysis.js +431 -0
- package/dist/src/cli/helpers/init/brownfield-analysis.js.map +1 -0
- package/dist/src/cli/helpers/init/index.d.ts +1 -0
- package/dist/src/cli/helpers/init/index.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/index.js +2 -0
- package/dist/src/cli/helpers/init/index.js.map +1 -1
- package/dist/src/cli/workers/brownfield-worker.d.ts +66 -0
- package/dist/src/cli/workers/brownfield-worker.d.ts.map +1 -0
- package/dist/src/cli/workers/brownfield-worker.js +417 -0
- package/dist/src/cli/workers/brownfield-worker.js.map +1 -0
- package/dist/src/core/background/brownfield-launcher.d.ts +86 -0
- package/dist/src/core/background/brownfield-launcher.d.ts.map +1 -0
- package/dist/src/core/background/brownfield-launcher.js +295 -0
- package/dist/src/core/background/brownfield-launcher.js.map +1 -0
- package/dist/src/core/background/index.d.ts +2 -0
- package/dist/src/core/background/index.d.ts.map +1 -1
- package/dist/src/core/background/index.js +2 -0
- package/dist/src/core/background/index.js.map +1 -1
- package/dist/src/core/background/types.d.ts +23 -2
- package/dist/src/core/background/types.d.ts.map +1 -1
- package/dist/src/core/config/index.d.ts +1 -0
- package/dist/src/core/config/index.d.ts.map +1 -1
- package/dist/src/core/config/index.js +1 -0
- package/dist/src/core/config/index.js.map +1 -1
- package/dist/src/core/config/types.d.ts +6 -0
- package/dist/src/core/config/types.d.ts.map +1 -1
- package/dist/src/core/config/types.js.map +1 -1
- package/dist/src/core/dashboard/dashboard-data.d.ts +156 -0
- package/dist/src/core/dashboard/dashboard-data.d.ts.map +1 -0
- package/dist/src/core/dashboard/dashboard-data.js +191 -0
- package/dist/src/core/dashboard/dashboard-data.js.map +1 -0
- package/dist/src/core/dashboard/index.d.ts +9 -0
- package/dist/src/core/dashboard/index.d.ts.map +1 -0
- package/dist/src/core/dashboard/index.js +9 -0
- package/dist/src/core/dashboard/index.js.map +1 -0
- package/dist/src/core/discrepancy/analyzers/api-route-analyzer.d.ts +77 -0
- package/dist/src/core/discrepancy/analyzers/api-route-analyzer.d.ts.map +1 -0
- package/dist/src/core/discrepancy/analyzers/api-route-analyzer.js +286 -0
- package/dist/src/core/discrepancy/analyzers/api-route-analyzer.js.map +1 -0
- package/dist/src/core/discrepancy/analyzers/index.d.ts +8 -0
- package/dist/src/core/discrepancy/analyzers/index.d.ts.map +1 -0
- package/dist/src/core/discrepancy/analyzers/index.js +8 -0
- package/dist/src/core/discrepancy/analyzers/index.js.map +1 -0
- package/dist/src/core/discrepancy/analyzers/typescript-analyzer.d.ts +96 -0
- package/dist/src/core/discrepancy/analyzers/typescript-analyzer.d.ts.map +1 -0
- package/dist/src/core/discrepancy/analyzers/typescript-analyzer.js +247 -0
- package/dist/src/core/discrepancy/analyzers/typescript-analyzer.js.map +1 -0
- package/dist/src/core/discrepancy/brownfield-manager.d.ts +88 -0
- package/dist/src/core/discrepancy/brownfield-manager.d.ts.map +1 -0
- package/dist/src/core/discrepancy/brownfield-manager.js +520 -0
- package/dist/src/core/discrepancy/brownfield-manager.js.map +1 -0
- package/dist/src/core/discrepancy/brownfield-types.d.ts +174 -0
- package/dist/src/core/discrepancy/brownfield-types.d.ts.map +1 -0
- package/dist/src/core/discrepancy/brownfield-types.js +11 -0
- package/dist/src/core/discrepancy/brownfield-types.js.map +1 -0
- package/dist/src/core/discrepancy/detector.d.ts +92 -0
- package/dist/src/core/discrepancy/detector.d.ts.map +1 -0
- package/dist/src/core/discrepancy/detector.js +346 -0
- package/dist/src/core/discrepancy/detector.js.map +1 -0
- package/dist/src/core/discrepancy/increment-generator.d.ts +51 -0
- package/dist/src/core/discrepancy/increment-generator.d.ts.map +1 -0
- package/dist/src/core/discrepancy/increment-generator.js +234 -0
- package/dist/src/core/discrepancy/increment-generator.js.map +1 -0
- package/dist/src/core/discrepancy/index.d.ts +18 -0
- package/dist/src/core/discrepancy/index.d.ts.map +1 -0
- package/dist/src/core/discrepancy/index.js +24 -0
- package/dist/src/core/discrepancy/index.js.map +1 -0
- package/dist/src/core/discrepancy/severity-classifier.d.ts +81 -0
- package/dist/src/core/discrepancy/severity-classifier.d.ts.map +1 -0
- package/dist/src/core/discrepancy/severity-classifier.js +289 -0
- package/dist/src/core/discrepancy/severity-classifier.js.map +1 -0
- package/dist/src/core/discrepancy/spec-parser.d.ts +74 -0
- package/dist/src/core/discrepancy/spec-parser.d.ts.map +1 -0
- package/dist/src/core/discrepancy/spec-parser.js +213 -0
- package/dist/src/core/discrepancy/spec-parser.js.map +1 -0
- package/dist/src/core/discrepancy/update-recommender.d.ts +77 -0
- package/dist/src/core/discrepancy/update-recommender.d.ts.map +1 -0
- package/dist/src/core/discrepancy/update-recommender.js +323 -0
- package/dist/src/core/discrepancy/update-recommender.js.map +1 -0
- package/dist/src/core/living-docs/living-docs-sync.d.ts +13 -16
- package/dist/src/core/living-docs/living-docs-sync.d.ts.map +1 -1
- package/dist/src/core/living-docs/living-docs-sync.js +31 -112
- package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
- package/dist/src/core/logs/index.d.ts +10 -0
- package/dist/src/core/logs/index.d.ts.map +1 -0
- package/dist/src/core/logs/index.js +10 -0
- package/dist/src/core/logs/index.js.map +1 -0
- package/dist/src/core/logs/log-aggregator.d.ts +130 -0
- package/dist/src/core/logs/log-aggregator.d.ts.map +1 -0
- package/dist/src/core/logs/log-aggregator.js +206 -0
- package/dist/src/core/logs/log-aggregator.js.map +1 -0
- package/dist/src/core/logs/log-exporter.d.ts +81 -0
- package/dist/src/core/logs/log-exporter.d.ts.map +1 -0
- package/dist/src/core/logs/log-exporter.js +141 -0
- package/dist/src/core/logs/log-exporter.js.map +1 -0
- package/dist/src/core/notifications/command-integration.d.ts +82 -0
- package/dist/src/core/notifications/command-integration.d.ts.map +1 -0
- package/dist/src/core/notifications/command-integration.js +80 -0
- package/dist/src/core/notifications/command-integration.js.map +1 -0
- package/dist/src/core/notifications/index.d.ts +12 -0
- package/dist/src/core/notifications/index.d.ts.map +1 -0
- package/dist/src/core/notifications/index.js +12 -0
- package/dist/src/core/notifications/index.js.map +1 -0
- package/dist/src/core/notifications/notification-display.d.ts +70 -0
- package/dist/src/core/notifications/notification-display.d.ts.map +1 -0
- package/dist/src/core/notifications/notification-display.js +177 -0
- package/dist/src/core/notifications/notification-display.js.map +1 -0
- package/dist/src/core/notifications/notification-manager.d.ts +126 -0
- package/dist/src/core/notifications/notification-manager.d.ts.map +1 -0
- package/dist/src/core/notifications/notification-manager.js +287 -0
- package/dist/src/core/notifications/notification-manager.js.map +1 -0
- package/dist/src/core/notifications/notification-types.d.ts +159 -0
- package/dist/src/core/notifications/notification-types.d.ts.map +1 -0
- package/dist/src/core/notifications/notification-types.js +93 -0
- package/dist/src/core/notifications/notification-types.js.map +1 -0
- package/dist/src/core/scheduler/index.d.ts +11 -0
- package/dist/src/core/scheduler/index.d.ts.map +1 -0
- package/dist/src/core/scheduler/index.js +11 -0
- package/dist/src/core/scheduler/index.js.map +1 -0
- package/dist/src/core/scheduler/job-scheduler.d.ts +179 -0
- package/dist/src/core/scheduler/job-scheduler.d.ts.map +1 -0
- package/dist/src/core/scheduler/job-scheduler.js +282 -0
- package/dist/src/core/scheduler/job-scheduler.js.map +1 -0
- package/dist/src/core/scheduler/schedule-persistence.d.ts +83 -0
- package/dist/src/core/scheduler/schedule-persistence.d.ts.map +1 -0
- package/dist/src/core/scheduler/schedule-persistence.js +180 -0
- package/dist/src/core/scheduler/schedule-persistence.js.map +1 -0
- package/dist/src/core/scheduler/scheduled-job.d.ts +188 -0
- package/dist/src/core/scheduler/scheduled-job.d.ts.map +1 -0
- package/dist/src/core/scheduler/scheduled-job.js +182 -0
- package/dist/src/core/scheduler/scheduled-job.js.map +1 -0
- package/dist/src/core/sync/permission-enforcer.d.ts +206 -0
- package/dist/src/core/sync/permission-enforcer.d.ts.map +1 -0
- package/dist/src/core/sync/permission-enforcer.js +268 -0
- package/dist/src/core/sync/permission-enforcer.js.map +1 -0
- package/dist/src/core/sync/sync-audit-logger.d.ts +217 -0
- package/dist/src/core/sync/sync-audit-logger.d.ts.map +1 -0
- package/dist/src/core/sync/sync-audit-logger.js +327 -0
- package/dist/src/core/sync/sync-audit-logger.js.map +1 -0
- package/dist/src/core/sync/sync-interceptor.d.ts +190 -0
- package/dist/src/core/sync/sync-interceptor.d.ts.map +1 -0
- package/dist/src/core/sync/sync-interceptor.js +224 -0
- package/dist/src/core/sync/sync-interceptor.js.map +1 -0
- package/dist/src/core/types/increment-metadata.d.ts +5 -2
- package/dist/src/core/types/increment-metadata.d.ts.map +1 -1
- package/dist/src/core/types/sync-config.d.ts +267 -0
- package/dist/src/core/types/sync-config.d.ts.map +1 -0
- package/dist/src/core/types/sync-config.js +304 -0
- package/dist/src/core/types/sync-config.js.map +1 -0
- package/dist/src/hooks/index.d.ts +11 -0
- package/dist/src/hooks/index.d.ts.map +1 -0
- package/dist/src/hooks/index.js +11 -0
- package/dist/src/hooks/index.js.map +1 -0
- package/dist/src/hooks/platform.d.ts +125 -0
- package/dist/src/hooks/platform.d.ts.map +1 -0
- package/dist/src/hooks/platform.js +325 -0
- package/dist/src/hooks/platform.js.map +1 -0
- package/dist/src/hooks/processor.d.ts +20 -0
- package/dist/src/hooks/processor.d.ts.map +1 -0
- package/dist/src/hooks/processor.js +317 -0
- package/dist/src/hooks/processor.js.map +1 -0
- package/dist/src/hooks/scheduler-startup.d.ts +19 -0
- package/dist/src/hooks/scheduler-startup.d.ts.map +1 -0
- package/dist/src/hooks/scheduler-startup.js +92 -0
- package/dist/src/hooks/scheduler-startup.js.map +1 -0
- package/dist/src/hooks/session-start.d.ts +16 -0
- package/dist/src/hooks/session-start.d.ts.map +1 -0
- package/dist/src/hooks/session-start.js +92 -0
- package/dist/src/hooks/session-start.js.map +1 -0
- package/dist/src/importers/duplicate-detector.d.ts +13 -2
- package/dist/src/importers/duplicate-detector.d.ts.map +1 -1
- package/dist/src/importers/duplicate-detector.js +21 -2
- package/dist/src/importers/duplicate-detector.js.map +1 -1
- package/dist/src/importers/item-converter.d.ts +41 -2
- package/dist/src/importers/item-converter.d.ts.map +1 -1
- package/dist/src/importers/item-converter.js +225 -38
- package/dist/src/importers/item-converter.js.map +1 -1
- package/dist/src/living-docs/fs-id-allocator.d.ts +7 -0
- package/dist/src/living-docs/fs-id-allocator.d.ts.map +1 -1
- package/dist/src/living-docs/fs-id-allocator.js +30 -4
- package/dist/src/living-docs/fs-id-allocator.js.map +1 -1
- package/dist/src/sync/ado-sync-wrapper.d.ts +137 -0
- package/dist/src/sync/ado-sync-wrapper.d.ts.map +1 -0
- package/dist/src/sync/ado-sync-wrapper.js +148 -0
- package/dist/src/sync/ado-sync-wrapper.js.map +1 -0
- package/dist/src/sync/github-sync-wrapper.d.ts +195 -0
- package/dist/src/sync/github-sync-wrapper.d.ts.map +1 -0
- package/dist/src/sync/github-sync-wrapper.js +220 -0
- package/dist/src/sync/github-sync-wrapper.js.map +1 -0
- package/dist/src/sync/jira-sync-wrapper.d.ts +155 -0
- package/dist/src/sync/jira-sync-wrapper.d.ts.map +1 -0
- package/dist/src/sync/jira-sync-wrapper.js +175 -0
- package/dist/src/sync/jira-sync-wrapper.js.map +1 -0
- package/dist/src/utils/feature-id-derivation.d.ts +58 -0
- package/dist/src/utils/feature-id-derivation.d.ts.map +1 -0
- package/dist/src/utils/feature-id-derivation.js +77 -0
- package/dist/src/utils/feature-id-derivation.js.map +1 -0
- package/package.json +1 -1
- package/plugins/specweave/commands/specweave-discrepancies.md +141 -0
- package/plugins/specweave/commands/specweave-discrepancy-to-increment.md +160 -0
- package/plugins/specweave/commands/specweave-jobs.md +45 -2
- package/plugins/specweave/commands/specweave-notifications.md +92 -0
- package/plugins/specweave/commands/specweave-sync-logs.md +131 -0
- package/plugins/specweave/commands/specweave-sync-monitor.md +57 -0
- package/plugins/specweave/hooks/hooks.json +3 -3
- package/plugins/specweave/hooks/lib/scheduler-startup.sh +72 -0
- package/plugins/specweave/hooks/universal/dispatcher.mjs +246 -0
- package/plugins/specweave/hooks/universal/session-start.cmd +16 -0
- package/plugins/specweave/hooks/universal/session-start.ps1 +16 -0
- package/plugins/specweave/hooks/v2/dispatchers/session-start.sh +14 -5
- package/plugins/specweave/lib/vendor/core/types/increment-metadata.d.ts +5 -2
- package/plugins/specweave/skills/discrepancy-viewer.md +154 -0
- package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +34 -0
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +51 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update Recommender
|
|
3
|
+
*
|
|
4
|
+
* Generates recommendations for handling discrepancies and creates update patches.
|
|
5
|
+
*
|
|
6
|
+
* @module core/discrepancy/update-recommender
|
|
7
|
+
*/
|
|
8
|
+
import { Logger } from '../../utils/logger.js';
|
|
9
|
+
import { Discrepancy, DiscrepancyRecommendation } from './detector.js';
|
|
10
|
+
/**
|
|
11
|
+
* An update patch for a spec file.
|
|
12
|
+
*/
|
|
13
|
+
export interface UpdatePatch {
|
|
14
|
+
filePath: string;
|
|
15
|
+
lineNumber: number;
|
|
16
|
+
originalText: string;
|
|
17
|
+
newText: string;
|
|
18
|
+
reason: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Recommendation with optional patch.
|
|
22
|
+
*/
|
|
23
|
+
export interface RecommendationResult {
|
|
24
|
+
discrepancyId: string;
|
|
25
|
+
recommendation: DiscrepancyRecommendation;
|
|
26
|
+
patch?: UpdatePatch;
|
|
27
|
+
explanation: string;
|
|
28
|
+
riskLevel: 'low' | 'medium' | 'high';
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Configuration for the update recommender.
|
|
32
|
+
*/
|
|
33
|
+
export interface UpdateRecommenderConfig {
|
|
34
|
+
/** Auto-update trivial changes */
|
|
35
|
+
autoUpdateTrivial?: boolean;
|
|
36
|
+
/** Generate patches for review-required changes */
|
|
37
|
+
generatePatches?: boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Generates recommendations for handling discrepancies.
|
|
41
|
+
*/
|
|
42
|
+
export declare class UpdateRecommender {
|
|
43
|
+
private readonly logger;
|
|
44
|
+
private readonly config;
|
|
45
|
+
constructor(options?: {
|
|
46
|
+
logger?: Logger;
|
|
47
|
+
config?: UpdateRecommenderConfig;
|
|
48
|
+
});
|
|
49
|
+
/**
|
|
50
|
+
* Generate recommendations for a list of discrepancies.
|
|
51
|
+
*/
|
|
52
|
+
recommendAll(discrepancies: Discrepancy[]): RecommendationResult[];
|
|
53
|
+
/**
|
|
54
|
+
* Generate a recommendation for a single discrepancy.
|
|
55
|
+
*/
|
|
56
|
+
recommend(discrepancy: Discrepancy): RecommendationResult;
|
|
57
|
+
/**
|
|
58
|
+
* Generate an update patch for a discrepancy.
|
|
59
|
+
*/
|
|
60
|
+
generatePatch(discrepancy: Discrepancy): UpdatePatch | undefined;
|
|
61
|
+
private mapSeverityToRecommendation;
|
|
62
|
+
private generateExplanation;
|
|
63
|
+
private assessRisk;
|
|
64
|
+
private shouldGeneratePatch;
|
|
65
|
+
private generateApiRoutePatch;
|
|
66
|
+
private generateFunctionPatch;
|
|
67
|
+
private generateTypePatch;
|
|
68
|
+
private generateApiDocSection;
|
|
69
|
+
private generateFunctionDocSection;
|
|
70
|
+
private generateTypeDocSection;
|
|
71
|
+
/**
|
|
72
|
+
* Generate a summary report of recommendations.
|
|
73
|
+
*/
|
|
74
|
+
generateReport(recommendations: RecommendationResult[]): string;
|
|
75
|
+
private groupBy;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=update-recommender.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-recommender.d.ts","sourceRoot":"","sources":["../../../../src/core/discrepancy/update-recommender.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,MAAM,EAAiB,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAuB,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAE5F;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,yBAAyB,CAAC;IAC1C,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;CACtC;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,kCAAkC;IAClC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,mDAAmD;IACnD,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;GAEG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoC;gBAE/C,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,uBAAuB,CAAA;KAAO;IAQ/E;;OAEG;IACH,YAAY,CAAC,aAAa,EAAE,WAAW,EAAE,GAAG,oBAAoB,EAAE;IAIlE;;OAEG;IACH,SAAS,CAAC,WAAW,EAAE,WAAW,GAAG,oBAAoB;IAoBzD;;OAEG;IACH,aAAa,CAAC,WAAW,EAAE,WAAW,GAAG,WAAW,GAAG,SAAS;IAkBhE,OAAO,CAAC,2BAA2B;IAanC,OAAO,CAAC,mBAAmB;IAsC3B,OAAO,CAAC,UAAU;IA6BlB,OAAO,CAAC,mBAAmB;IAmB3B,OAAO,CAAC,qBAAqB;IAsC7B,OAAO,CAAC,qBAAqB;IAwB7B,OAAO,CAAC,iBAAiB;IAczB,OAAO,CAAC,qBAAqB;IAgB7B,OAAO,CAAC,0BAA0B;IAgBlC,OAAO,CAAC,sBAAsB;IAY9B;;OAEG;IACH,cAAc,CAAC,eAAe,EAAE,oBAAoB,EAAE,GAAG,MAAM;IA8D/D,OAAO,CAAC,OAAO;CAWhB"}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update Recommender
|
|
3
|
+
*
|
|
4
|
+
* Generates recommendations for handling discrepancies and creates update patches.
|
|
5
|
+
*
|
|
6
|
+
* @module core/discrepancy/update-recommender
|
|
7
|
+
*/
|
|
8
|
+
import { consoleLogger } from '../../utils/logger.js';
|
|
9
|
+
/**
|
|
10
|
+
* Generates recommendations for handling discrepancies.
|
|
11
|
+
*/
|
|
12
|
+
export class UpdateRecommender {
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
this.logger = options.logger ?? consoleLogger;
|
|
15
|
+
this.config = {
|
|
16
|
+
autoUpdateTrivial: options.config?.autoUpdateTrivial ?? false,
|
|
17
|
+
generatePatches: options.config?.generatePatches ?? true,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Generate recommendations for a list of discrepancies.
|
|
22
|
+
*/
|
|
23
|
+
recommendAll(discrepancies) {
|
|
24
|
+
return discrepancies.map(d => this.recommend(d));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Generate a recommendation for a single discrepancy.
|
|
28
|
+
*/
|
|
29
|
+
recommend(discrepancy) {
|
|
30
|
+
const recommendation = this.mapSeverityToRecommendation(discrepancy.severity);
|
|
31
|
+
const explanation = this.generateExplanation(discrepancy, recommendation);
|
|
32
|
+
const riskLevel = this.assessRisk(discrepancy);
|
|
33
|
+
const result = {
|
|
34
|
+
discrepancyId: discrepancy.id,
|
|
35
|
+
recommendation,
|
|
36
|
+
explanation,
|
|
37
|
+
riskLevel,
|
|
38
|
+
};
|
|
39
|
+
// Generate patch if applicable
|
|
40
|
+
if (this.config.generatePatches && this.shouldGeneratePatch(discrepancy, recommendation)) {
|
|
41
|
+
result.patch = this.generatePatch(discrepancy);
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Generate an update patch for a discrepancy.
|
|
47
|
+
*/
|
|
48
|
+
generatePatch(discrepancy) {
|
|
49
|
+
// Can only generate patches for modifications and additions with spec files
|
|
50
|
+
if (!discrepancy.specPath && discrepancy.category !== 'added') {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
switch (discrepancy.type) {
|
|
54
|
+
case 'api-route':
|
|
55
|
+
return this.generateApiRoutePatch(discrepancy);
|
|
56
|
+
case 'function-signature':
|
|
57
|
+
return this.generateFunctionPatch(discrepancy);
|
|
58
|
+
case 'type-definition':
|
|
59
|
+
return this.generateTypePatch(discrepancy);
|
|
60
|
+
default:
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
mapSeverityToRecommendation(severity) {
|
|
65
|
+
switch (severity) {
|
|
66
|
+
case 'trivial':
|
|
67
|
+
return 'auto-update';
|
|
68
|
+
case 'minor':
|
|
69
|
+
return 'review-required';
|
|
70
|
+
case 'major':
|
|
71
|
+
return 'notify';
|
|
72
|
+
case 'breaking':
|
|
73
|
+
return 'alert';
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
generateExplanation(discrepancy, recommendation) {
|
|
77
|
+
const parts = [];
|
|
78
|
+
// Category explanation
|
|
79
|
+
switch (discrepancy.category) {
|
|
80
|
+
case 'added':
|
|
81
|
+
parts.push(`New ${discrepancy.type.replace('-', ' ')} found in code that is not documented.`);
|
|
82
|
+
break;
|
|
83
|
+
case 'removed':
|
|
84
|
+
parts.push(`Documented ${discrepancy.type.replace('-', ' ')} no longer exists in code.`);
|
|
85
|
+
break;
|
|
86
|
+
case 'modified':
|
|
87
|
+
parts.push(`${discrepancy.type.replace('-', ' ')} has changed from documented version.`);
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
// Code is source of truth reminder
|
|
91
|
+
parts.push('Code is the source of truth - specs should be updated to match.');
|
|
92
|
+
// Recommendation explanation
|
|
93
|
+
switch (recommendation) {
|
|
94
|
+
case 'auto-update':
|
|
95
|
+
parts.push('This is a trivial change that can be safely auto-updated.');
|
|
96
|
+
break;
|
|
97
|
+
case 'review-required':
|
|
98
|
+
parts.push('Please review the change before updating documentation.');
|
|
99
|
+
break;
|
|
100
|
+
case 'notify':
|
|
101
|
+
parts.push('This is a significant change that should be communicated to stakeholders.');
|
|
102
|
+
break;
|
|
103
|
+
case 'alert':
|
|
104
|
+
parts.push('⚠️ This is a breaking change! Consumers may be affected.');
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
return parts.join(' ');
|
|
108
|
+
}
|
|
109
|
+
assessRisk(discrepancy) {
|
|
110
|
+
// Breaking changes are high risk
|
|
111
|
+
if (discrepancy.severity === 'breaking') {
|
|
112
|
+
return 'high';
|
|
113
|
+
}
|
|
114
|
+
// Removals are always at least medium risk
|
|
115
|
+
if (discrepancy.category === 'removed') {
|
|
116
|
+
return 'medium';
|
|
117
|
+
}
|
|
118
|
+
// API route changes are medium risk
|
|
119
|
+
if (discrepancy.type === 'api-route' && discrepancy.category === 'modified') {
|
|
120
|
+
return 'medium';
|
|
121
|
+
}
|
|
122
|
+
// Additions are low risk (new undocumented functionality)
|
|
123
|
+
if (discrepancy.category === 'added') {
|
|
124
|
+
return 'low';
|
|
125
|
+
}
|
|
126
|
+
// Minor modifications are low risk
|
|
127
|
+
if (discrepancy.severity === 'minor' || discrepancy.severity === 'trivial') {
|
|
128
|
+
return 'low';
|
|
129
|
+
}
|
|
130
|
+
return 'medium';
|
|
131
|
+
}
|
|
132
|
+
shouldGeneratePatch(discrepancy, recommendation) {
|
|
133
|
+
// Don't generate patches for alerts (need manual handling)
|
|
134
|
+
if (recommendation === 'alert') {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
// Generate patches for auto-update and review-required
|
|
138
|
+
if (recommendation === 'auto-update' || recommendation === 'review-required') {
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
// Generate patches for notify if it's an addition
|
|
142
|
+
if (recommendation === 'notify' && discrepancy.category === 'added') {
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
generateApiRoutePatch(discrepancy) {
|
|
148
|
+
if (discrepancy.category === 'added') {
|
|
149
|
+
// Generate new documentation section
|
|
150
|
+
const method = discrepancy.codeValue.split(' ')[0];
|
|
151
|
+
const path = discrepancy.codeValue.split(' ')[1] || discrepancy.codeValue;
|
|
152
|
+
return {
|
|
153
|
+
filePath: discrepancy.codePath || 'docs/api.md',
|
|
154
|
+
lineNumber: 0, // Append to end
|
|
155
|
+
originalText: '',
|
|
156
|
+
newText: this.generateApiDocSection(method, path),
|
|
157
|
+
reason: 'Add documentation for new API route',
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
if (discrepancy.category === 'modified' && discrepancy.specPath) {
|
|
161
|
+
return {
|
|
162
|
+
filePath: discrepancy.specPath,
|
|
163
|
+
lineNumber: discrepancy.specLineNumber,
|
|
164
|
+
originalText: discrepancy.specValue,
|
|
165
|
+
newText: discrepancy.codeValue,
|
|
166
|
+
reason: discrepancy.description,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
if (discrepancy.category === 'removed' && discrepancy.specPath) {
|
|
170
|
+
return {
|
|
171
|
+
filePath: discrepancy.specPath,
|
|
172
|
+
lineNumber: discrepancy.specLineNumber,
|
|
173
|
+
originalText: discrepancy.specValue,
|
|
174
|
+
newText: `~~${discrepancy.specValue}~~ *(DEPRECATED - removed from code)*`,
|
|
175
|
+
reason: 'Mark removed API route as deprecated',
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
generateFunctionPatch(discrepancy) {
|
|
181
|
+
if (discrepancy.category === 'added') {
|
|
182
|
+
return {
|
|
183
|
+
filePath: discrepancy.codePath || 'docs/api.md',
|
|
184
|
+
lineNumber: 0,
|
|
185
|
+
originalText: '',
|
|
186
|
+
newText: this.generateFunctionDocSection(discrepancy.codeValue),
|
|
187
|
+
reason: 'Add documentation for new function',
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
if (discrepancy.category === 'modified' && discrepancy.specPath) {
|
|
191
|
+
return {
|
|
192
|
+
filePath: discrepancy.specPath,
|
|
193
|
+
lineNumber: discrepancy.specLineNumber,
|
|
194
|
+
originalText: discrepancy.specValue,
|
|
195
|
+
newText: discrepancy.codeValue,
|
|
196
|
+
reason: discrepancy.description,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
generateTypePatch(discrepancy) {
|
|
202
|
+
if (discrepancy.category === 'added') {
|
|
203
|
+
return {
|
|
204
|
+
filePath: discrepancy.codePath || 'docs/types.md',
|
|
205
|
+
lineNumber: 0,
|
|
206
|
+
originalText: '',
|
|
207
|
+
newText: this.generateTypeDocSection(discrepancy.codeValue),
|
|
208
|
+
reason: 'Add documentation for new type',
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
generateApiDocSection(method, path) {
|
|
214
|
+
return `
|
|
215
|
+
## ${method} ${path}
|
|
216
|
+
|
|
217
|
+
*TODO: Add description*
|
|
218
|
+
|
|
219
|
+
### Request
|
|
220
|
+
|
|
221
|
+
*TODO: Document request parameters*
|
|
222
|
+
|
|
223
|
+
### Response
|
|
224
|
+
|
|
225
|
+
*TODO: Document response format*
|
|
226
|
+
`.trim();
|
|
227
|
+
}
|
|
228
|
+
generateFunctionDocSection(signature) {
|
|
229
|
+
return `
|
|
230
|
+
### \`${signature}\`
|
|
231
|
+
|
|
232
|
+
*TODO: Add description*
|
|
233
|
+
|
|
234
|
+
#### Parameters
|
|
235
|
+
|
|
236
|
+
*TODO: Document parameters*
|
|
237
|
+
|
|
238
|
+
#### Returns
|
|
239
|
+
|
|
240
|
+
*TODO: Document return value*
|
|
241
|
+
`.trim();
|
|
242
|
+
}
|
|
243
|
+
generateTypeDocSection(typeValue) {
|
|
244
|
+
return `
|
|
245
|
+
### ${typeValue}
|
|
246
|
+
|
|
247
|
+
*TODO: Add type description*
|
|
248
|
+
|
|
249
|
+
#### Properties
|
|
250
|
+
|
|
251
|
+
*TODO: Document properties*
|
|
252
|
+
`.trim();
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Generate a summary report of recommendations.
|
|
256
|
+
*/
|
|
257
|
+
generateReport(recommendations) {
|
|
258
|
+
const lines = ['# Discrepancy Recommendations Report', ''];
|
|
259
|
+
const byRecommendation = this.groupBy(recommendations, r => r.recommendation);
|
|
260
|
+
// Summary
|
|
261
|
+
lines.push('## Summary', '');
|
|
262
|
+
lines.push(`- Total discrepancies: ${recommendations.length}`);
|
|
263
|
+
lines.push(`- Auto-update: ${byRecommendation['auto-update']?.length ?? 0}`);
|
|
264
|
+
lines.push(`- Review required: ${byRecommendation['review-required']?.length ?? 0}`);
|
|
265
|
+
lines.push(`- Notify: ${byRecommendation['notify']?.length ?? 0}`);
|
|
266
|
+
lines.push(`- Alert: ${byRecommendation['alert']?.length ?? 0}`);
|
|
267
|
+
lines.push('');
|
|
268
|
+
// Alerts first (most important)
|
|
269
|
+
if (byRecommendation['alert']?.length) {
|
|
270
|
+
lines.push('## ⚠️ Alerts (Breaking Changes)', '');
|
|
271
|
+
for (const r of byRecommendation['alert']) {
|
|
272
|
+
lines.push(`### ${r.discrepancyId}`);
|
|
273
|
+
lines.push(r.explanation);
|
|
274
|
+
lines.push('');
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
// Notifications
|
|
278
|
+
if (byRecommendation['notify']?.length) {
|
|
279
|
+
lines.push('## 📢 Notifications (Major Changes)', '');
|
|
280
|
+
for (const r of byRecommendation['notify']) {
|
|
281
|
+
lines.push(`### ${r.discrepancyId}`);
|
|
282
|
+
lines.push(r.explanation);
|
|
283
|
+
if (r.patch) {
|
|
284
|
+
lines.push('');
|
|
285
|
+
lines.push('**Suggested update:**');
|
|
286
|
+
lines.push('```');
|
|
287
|
+
lines.push(r.patch.newText);
|
|
288
|
+
lines.push('```');
|
|
289
|
+
}
|
|
290
|
+
lines.push('');
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
// Review required
|
|
294
|
+
if (byRecommendation['review-required']?.length) {
|
|
295
|
+
lines.push('## 👀 Review Required (Minor Changes)', '');
|
|
296
|
+
for (const r of byRecommendation['review-required']) {
|
|
297
|
+
lines.push(`- **${r.discrepancyId}**: ${r.explanation}`);
|
|
298
|
+
}
|
|
299
|
+
lines.push('');
|
|
300
|
+
}
|
|
301
|
+
// Auto-update
|
|
302
|
+
if (byRecommendation['auto-update']?.length) {
|
|
303
|
+
lines.push('## ✅ Auto-Update (Trivial Changes)', '');
|
|
304
|
+
for (const r of byRecommendation['auto-update']) {
|
|
305
|
+
lines.push(`- **${r.discrepancyId}**: ${r.explanation}`);
|
|
306
|
+
}
|
|
307
|
+
lines.push('');
|
|
308
|
+
}
|
|
309
|
+
return lines.join('\n');
|
|
310
|
+
}
|
|
311
|
+
groupBy(items, keyFn) {
|
|
312
|
+
const result = {};
|
|
313
|
+
for (const item of items) {
|
|
314
|
+
const key = keyFn(item);
|
|
315
|
+
if (!result[key]) {
|
|
316
|
+
result[key] = [];
|
|
317
|
+
}
|
|
318
|
+
result[key].push(item);
|
|
319
|
+
}
|
|
320
|
+
return result;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
//# sourceMappingURL=update-recommender.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-recommender.js","sourceRoot":"","sources":["../../../../src/core/discrepancy/update-recommender.ts"],"names":[],"mappings":"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"}
|
|
@@ -47,26 +47,23 @@ export declare class LivingDocsSync {
|
|
|
47
47
|
*/
|
|
48
48
|
syncIncrement(incrementId: string, options?: SyncOptions): Promise<SyncResult>;
|
|
49
49
|
/**
|
|
50
|
-
* Get feature ID for increment
|
|
50
|
+
* Get feature ID for increment
|
|
51
51
|
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* - Brownfield (imported): FS-YY-MM-DD-name (e.g., FS-25-11-14-jira-epic)
|
|
55
|
-
*/
|
|
56
|
-
private getFeatureIdForIncrement;
|
|
57
|
-
/**
|
|
58
|
-
* Update metadata.json with feature_id
|
|
52
|
+
* SIMPLIFIED (v0.29.0): Feature ID is derived from increment number
|
|
53
|
+
* No longer reads from metadata.json - derivation is the single source of truth
|
|
59
54
|
*
|
|
60
|
-
* CRITICAL FIX (2025-
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* 3. External tools (GitHub, JIRA, ADO) can find the feature
|
|
55
|
+
* CRITICAL FIX (2025-12-01): Internal features are DETERMINISTIC - no collision check!
|
|
56
|
+
* - Increment 0060 → ALWAYS FS-060, never anything else
|
|
57
|
+
* - Increment 0072 → ALWAYS FS-072, never anything else
|
|
58
|
+
* - Sync is IDEMPOTENT: if FS-060 exists, UPDATE it (don't create FS-061)
|
|
65
59
|
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
60
|
+
* Collision detection is ONLY for EXTERNAL features (FS-XXXE) during imports,
|
|
61
|
+
* NOT for internal features derived from increment numbers.
|
|
62
|
+
*
|
|
63
|
+
* @see deriveFeatureId() in src/utils/feature-id-derivation.ts
|
|
64
|
+
* @see ADR-0140 for rationale
|
|
68
65
|
*/
|
|
69
|
-
private
|
|
66
|
+
private getFeatureIdForIncrement;
|
|
70
67
|
/**
|
|
71
68
|
* Parse increment spec.md
|
|
72
69
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"living-docs-sync.d.ts","sourceRoot":"","sources":["../../../../src/core/living-docs/living-docs-sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAQH,OAAO,EAAE,MAAM,EAAiB,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"living-docs-sync.d.ts","sourceRoot":"","sources":["../../../../src/core/living-docs/living-docs-sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAQH,OAAO,EAAE,MAAM,EAAiB,MAAM,uBAAuB,CAAC;AAO9D,OAAO,KAAK,EACV,WAAW,EACX,UAAU,EACV,UAAU,EACV,aAAa,EACb,uBAAuB,EACxB,MAAM,YAAY,CAAC;AAkBpB,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,uBAAuB,EAAE,CAAC;AAE5F,qBAAa,cAAc;IACzB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAS;gBAEd,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAO;IAQlE;;;;;;;OAOG;IACH,YAAY,IAAI,MAAM;IAItB;;;;;OAKG;YACW,mBAAmB;IASjC;;OAEG;IACG,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,UAAU,CAAC;IA4JxF;;;;;;;;;;;;;;;;OAgBG;YACW,wBAAwB;IAUtC;;OAEG;YACW,kBAAkB;IAmHhC;;;;OAIG;YACW,sBAAsB;IAkCpC;;OAEG;YACW,kBAAkB;IAmChC;;;;;;;OAOG;YACW,mBAAmB;IA8CjC;;;;;;OAMG;IACH;;;;;;;;;;;OAWG;YACW,mBAAmB;IA4HjC;;;;;;;;;OASG;YACW,YAAY;IA0F1B;;OAEG;YACW,UAAU;IAKxB;;OAEG;YACW,SAAS;IAKvB;;;;;;;;OAQG;YACW,4BAA4B;CAmB3C"}
|
|
@@ -23,7 +23,9 @@ import { TaskProjectSpecificGenerator } from './task-project-specific-generator.
|
|
|
23
23
|
import { consoleLogger } from '../../utils/logger.js';
|
|
24
24
|
import { autoDetectProjectIdSync } from '../../utils/project-detection.js';
|
|
25
25
|
import { getGitHubAuthFromProject } from '../../utils/auth-helpers.js';
|
|
26
|
-
|
|
26
|
+
// NOTE (2025-12-01): findNextAvailableInternalIdSync removed - internal features don't need collision check
|
|
27
|
+
// Collision detection is only for EXTERNAL features (FS-XXXE) imported from GitHub/JIRA/ADO
|
|
28
|
+
import { deriveFeatureId } from '../../utils/feature-id-derivation.js';
|
|
27
29
|
// Import extracted helpers
|
|
28
30
|
import { calculateUSStatus, extractUserStories, extractAcceptanceCriteria, generateFeatureFile, generateUserStoryFile, pathExists, readJson, ensureDir, findExistingUserStoryFile, cleanupDuplicateFiles, cleanupTempFiles, } from './sync-helpers/index.js';
|
|
29
31
|
export class LivingDocsSync {
|
|
@@ -92,41 +94,22 @@ export class LivingDocsSync {
|
|
|
92
94
|
}
|
|
93
95
|
// Step 1: Build feature registry (auto-generates IDs for greenfield)
|
|
94
96
|
await this.featureIdManager.buildRegistry();
|
|
95
|
-
// Step 2: Get
|
|
96
|
-
//
|
|
97
|
+
// Step 2: Get feature ID (derived from increment number)
|
|
98
|
+
// SIMPLIFIED (v0.29.0): Feature ID is now always derived, not stored in metadata
|
|
97
99
|
let featureId;
|
|
98
|
-
if (options.explicitFeatureId) {
|
|
100
|
+
if (options.explicitFeatureId && /^FS-\d{3,}E?$/.test(options.explicitFeatureId)) {
|
|
101
|
+
// Allow explicit override for special cases (e.g., epic linking)
|
|
99
102
|
featureId = options.explicitFeatureId;
|
|
100
|
-
this.logger.log(`📎 Using explicit feature ID
|
|
103
|
+
this.logger.log(`📎 Using explicit feature ID: ${featureId}`);
|
|
101
104
|
}
|
|
102
105
|
else {
|
|
106
|
+
// Derive from increment number (the normal case)
|
|
103
107
|
featureId = await this.getFeatureIdForIncrement(incrementId);
|
|
104
|
-
this.logger.log(`🔄
|
|
105
|
-
}
|
|
106
|
-
// CRITICAL FIX (2025-11-26): Validate featureId to prevent "null" folder bug
|
|
107
|
-
// If featureId is invalid (null, "null", empty, or not FS-XXX format), auto-generate
|
|
108
|
-
if (!featureId || featureId === 'null' || !/^FS-\d{3,}E?$/.test(featureId)) {
|
|
109
|
-
const invalidValue = featureId;
|
|
110
|
-
// Extract increment number for auto-generation
|
|
111
|
-
const match = incrementId.match(/^(\d{4})-/);
|
|
112
|
-
if (match) {
|
|
113
|
-
const baseNum = parseInt(match[1], 10);
|
|
114
|
-
// CRITICAL FIX (2025-11-26): Check for FS-XXXE collision before using FS-XXX
|
|
115
|
-
const specsPath = path.join(this.projectRoot, '.specweave/docs/internal/specs');
|
|
116
|
-
const safeNum = findNextAvailableInternalIdSync(baseNum, specsPath, this.projectId, { logger: this.logger });
|
|
117
|
-
featureId = `FS-${String(safeNum).padStart(3, '0')}`;
|
|
118
|
-
this.logger.warn(`⚠️ Invalid feature ID "${invalidValue}" replaced with auto-generated: ${featureId}`);
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
throw new Error(`Cannot sync increment ${incrementId}: invalid feature ID "${invalidValue}" and unable to auto-generate`);
|
|
122
|
-
}
|
|
108
|
+
this.logger.log(`🔄 Derived feature ID: ${featureId}`);
|
|
123
109
|
}
|
|
124
110
|
result.featureId = featureId;
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
if (!options.dryRun) {
|
|
128
|
-
await this.updateMetadataFeatureId(incrementId, featureId);
|
|
129
|
-
}
|
|
111
|
+
// NOTE (v0.29.0): No longer write feature_id to metadata.json
|
|
112
|
+
// Feature ID is derived from increment number - see ADR-0140
|
|
130
113
|
this.logger.log(`📚 Syncing ${incrementId} → ${featureId}...`);
|
|
131
114
|
// Step 3: Parse increment spec
|
|
132
115
|
const parsed = await this.parseIncrementSpec(incrementId);
|
|
@@ -211,94 +194,30 @@ export class LivingDocsSync {
|
|
|
211
194
|
}
|
|
212
195
|
}
|
|
213
196
|
/**
|
|
214
|
-
* Get feature ID for increment
|
|
197
|
+
* Get feature ID for increment
|
|
215
198
|
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
* - Brownfield (imported): FS-YY-MM-DD-name (e.g., FS-25-11-14-jira-epic)
|
|
219
|
-
*/
|
|
220
|
-
async getFeatureIdForIncrement(incrementId) {
|
|
221
|
-
// Extract increment number (e.g., "0040-name" → 40)
|
|
222
|
-
const match = incrementId.match(/^(\d{4})-/);
|
|
223
|
-
if (!match) {
|
|
224
|
-
throw new Error(`Invalid increment ID format: ${incrementId}`);
|
|
225
|
-
}
|
|
226
|
-
const num = parseInt(match[1], 10);
|
|
227
|
-
// Check if increment has explicit feature ID
|
|
228
|
-
const metadataPath = path.join(this.projectRoot, '.specweave/increments', incrementId, 'metadata.json');
|
|
229
|
-
if (await pathExists(metadataPath)) {
|
|
230
|
-
const metadata = await readJson(metadataPath);
|
|
231
|
-
// Check if brownfield (imported from external tool)
|
|
232
|
-
const isBrownfield = metadata.imported === true || metadata.source === 'external';
|
|
233
|
-
// CRITICAL (v0.26.2): Check for feature_id field (primary) or feature (legacy)
|
|
234
|
-
// Bug fix: metadata.json uses "feature_id" not "feature"
|
|
235
|
-
// See: Living docs sync bug report (2025-11-24)
|
|
236
|
-
const featureId = metadata.feature_id || metadata.feature;
|
|
237
|
-
if (featureId) {
|
|
238
|
-
// Validate format matches increment type
|
|
239
|
-
const isDateFormat = /^FS-\d{2}-\d{2}-\d{2}/.test(featureId);
|
|
240
|
-
const isIncrementFormat = /^FS-\d{3,}$/.test(featureId);
|
|
241
|
-
if (isBrownfield && isDateFormat) {
|
|
242
|
-
// ✅ Brownfield with correct date format
|
|
243
|
-
return featureId;
|
|
244
|
-
}
|
|
245
|
-
else if (!isBrownfield && isIncrementFormat) {
|
|
246
|
-
// ✅ Greenfield with correct increment format (3+ digits)
|
|
247
|
-
return featureId;
|
|
248
|
-
}
|
|
249
|
-
else {
|
|
250
|
-
// ⚠️ Format mismatch - log warning and auto-generate correct format
|
|
251
|
-
this.logger.warn(`⚠️ Feature ID format mismatch for ${incrementId}:`);
|
|
252
|
-
this.logger.warn(` Found: ${featureId}`);
|
|
253
|
-
this.logger.warn(` Expected: ${isBrownfield ? 'FS-YY-MM-DD-name (brownfield)' : 'FS-XXX (greenfield, 3+ digits)'}`);
|
|
254
|
-
this.logger.warn(` Auto-generating correct format...`);
|
|
255
|
-
// Fall through to auto-generation
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
// Auto-generate for greenfield: FS-040, FS-041, etc.
|
|
260
|
-
// CRITICAL FIX (2025-11-26): Check for FS-XXXE collision before using FS-XXX
|
|
261
|
-
const specsPath = path.join(this.projectRoot, '.specweave/docs/internal/specs');
|
|
262
|
-
const safeNum = findNextAvailableInternalIdSync(num, specsPath, this.projectId, { logger: this.logger });
|
|
263
|
-
const autoGenId = `FS-${String(safeNum).padStart(3, '0')}`;
|
|
264
|
-
this.logger.log(` 📝 Generated feature ID: ${autoGenId}`);
|
|
265
|
-
return autoGenId;
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* Update metadata.json with feature_id
|
|
199
|
+
* SIMPLIFIED (v0.29.0): Feature ID is derived from increment number
|
|
200
|
+
* No longer reads from metadata.json - derivation is the single source of truth
|
|
269
201
|
*
|
|
270
|
-
* CRITICAL FIX (2025-
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
* 3. External tools (GitHub, JIRA, ADO) can find the feature
|
|
202
|
+
* CRITICAL FIX (2025-12-01): Internal features are DETERMINISTIC - no collision check!
|
|
203
|
+
* - Increment 0060 → ALWAYS FS-060, never anything else
|
|
204
|
+
* - Increment 0072 → ALWAYS FS-072, never anything else
|
|
205
|
+
* - Sync is IDEMPOTENT: if FS-060 exists, UPDATE it (don't create FS-061)
|
|
275
206
|
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
207
|
+
* Collision detection is ONLY for EXTERNAL features (FS-XXXE) during imports,
|
|
208
|
+
* NOT for internal features derived from increment numbers.
|
|
209
|
+
*
|
|
210
|
+
* @see deriveFeatureId() in src/utils/feature-id-derivation.ts
|
|
211
|
+
* @see ADR-0140 for rationale
|
|
278
212
|
*/
|
|
279
|
-
async
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
this.logger.warn(` ⚠️ metadata.json not found for ${incrementId}, skipping feature_id update`);
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
const metadata = await readJson(metadataPath);
|
|
287
|
-
// Only update if feature_id is null/undefined or different
|
|
288
|
-
if (metadata.feature_id !== featureId) {
|
|
289
|
-
metadata.feature_id = featureId;
|
|
290
|
-
// Atomic write: temp file → rename
|
|
291
|
-
const tempPath = `${metadataPath}.tmp`;
|
|
292
|
-
await fs.writeFile(tempPath, JSON.stringify(metadata, null, 2), 'utf-8');
|
|
293
|
-
await fs.rename(tempPath, metadataPath);
|
|
294
|
-
this.logger.log(` ✅ Updated metadata.json with feature_id: ${featureId}`);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
catch (error) {
|
|
298
|
-
// Non-fatal - log warning but continue with sync
|
|
299
|
-
this.logger.warn(` ⚠️ Failed to update metadata.json with feature_id: ${error}`);
|
|
300
|
-
}
|
|
213
|
+
async getFeatureIdForIncrement(incrementId) {
|
|
214
|
+
// Derive feature ID directly from increment number (e.g., "0081-name" → "FS-081")
|
|
215
|
+
// NO collision checking - internal feature IDs are deterministic and unique by design
|
|
216
|
+
return deriveFeatureId(incrementId);
|
|
301
217
|
}
|
|
218
|
+
// NOTE (v0.29.0): updateMetadataFeatureId() was REMOVED
|
|
219
|
+
// Feature ID is now derived from increment number, not stored in metadata
|
|
220
|
+
// See ADR-0140 for rationale
|
|
302
221
|
/**
|
|
303
222
|
* Parse increment spec.md
|
|
304
223
|
*/
|