specweave 0.17.16 → 0.17.19
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 +405 -2495
- package/README.md +92 -2
- package/dist/locales/de/.gitkeep +0 -0
- package/dist/locales/de/cli.json +108 -0
- package/dist/locales/en/cli.json +287 -0
- package/dist/locales/en/errors.json +7 -0
- package/dist/locales/en/templates.json +6 -0
- package/dist/locales/es/.gitkeep +0 -0
- package/dist/locales/es/cli.json +41 -0
- package/dist/locales/fr/.gitkeep +0 -0
- package/dist/locales/fr/cli.json +108 -0
- package/dist/locales/ja/.gitkeep +0 -0
- package/dist/locales/ja/cli.json +108 -0
- package/dist/locales/ko/.gitkeep +0 -0
- package/dist/locales/ko/cli.json +108 -0
- package/dist/locales/pt/.gitkeep +0 -0
- package/dist/locales/pt/cli.json +108 -0
- package/dist/locales/ru/.gitkeep +0 -0
- package/dist/locales/ru/cli.json +269 -0
- package/dist/locales/zh/.gitkeep +0 -0
- package/dist/locales/zh/cli.json +108 -0
- package/dist/plugins/specweave/lib/hooks/sync-living-docs.d.ts.map +1 -1
- package/dist/plugins/specweave/lib/hooks/sync-living-docs.js +188 -36
- package/dist/plugins/specweave/lib/hooks/sync-living-docs.js.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-spec-content-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-spec-content-sync.js +65 -6
- package/dist/plugins/specweave-ado/lib/ado-spec-content-sync.js.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-status-sync.d.ts +54 -0
- package/dist/plugins/specweave-ado/lib/ado-status-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-ado/lib/ado-status-sync.js +86 -0
- package/dist/plugins/specweave-ado/lib/ado-status-sync.js.map +1 -0
- package/dist/plugins/specweave-ado/lib/enhanced-ado-sync.d.ts +25 -0
- package/dist/plugins/specweave-ado/lib/enhanced-ado-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-ado/lib/enhanced-ado-sync.js +191 -0
- package/dist/plugins/specweave-ado/lib/enhanced-ado-sync.js.map +1 -0
- package/dist/plugins/specweave-github/lib/duplicate-detector.d.ts +139 -0
- package/dist/plugins/specweave-github/lib/duplicate-detector.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/duplicate-detector.js +389 -0
- package/dist/plugins/specweave-github/lib/duplicate-detector.js.map +1 -0
- package/dist/plugins/specweave-github/lib/enhanced-github-sync.d.ts +26 -0
- package/dist/plugins/specweave-github/lib/enhanced-github-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/enhanced-github-sync.js +249 -0
- package/dist/plugins/specweave-github/lib/enhanced-github-sync.js.map +1 -0
- package/dist/plugins/specweave-github/lib/epic-content-builder.d.ts +63 -0
- package/dist/plugins/specweave-github/lib/epic-content-builder.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/epic-content-builder.js +216 -0
- package/dist/plugins/specweave-github/lib/epic-content-builder.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-client.d.ts +1 -1
- package/dist/plugins/specweave-github/lib/github-client.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-client.js +25 -13
- package/dist/plugins/specweave-github/lib/github-client.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-epic-sync.d.ts +83 -0
- package/dist/plugins/specweave-github/lib/github-epic-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/github-epic-sync.js +466 -0
- package/dist/plugins/specweave-github/lib/github-epic-sync.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-status-sync.d.ts +43 -0
- package/dist/plugins/specweave-github/lib/github-status-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/github-status-sync.js +82 -0
- package/dist/plugins/specweave-github/lib/github-status-sync.js.map +1 -0
- package/dist/plugins/specweave-github/lib/task-sync.d.ts +5 -0
- package/dist/plugins/specweave-github/lib/task-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/task-sync.js +38 -2
- package/dist/plugins/specweave-github/lib/task-sync.js.map +1 -1
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.d.ts +28 -0
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.js +156 -0
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.js.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-epic-sync.d.ts +66 -0
- package/dist/plugins/specweave-jira/lib/jira-epic-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-epic-sync.js +274 -0
- package/dist/plugins/specweave-jira/lib/jira-epic-sync.js.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-status-sync.d.ts +56 -0
- package/dist/plugins/specweave-jira/lib/jira-status-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-status-sync.js +93 -0
- package/dist/plugins/specweave-jira/lib/jira-status-sync.js.map +1 -0
- package/dist/spec-parser.js +629 -0
- package/dist/src/cli/commands/init.d.ts.map +1 -1
- package/dist/src/cli/commands/init.js +107 -3
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/index.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/index.js +48 -3
- package/dist/src/cli/helpers/issue-tracker/index.js.map +1 -1
- package/dist/src/core/deduplication/command-deduplicator.d.ts +166 -0
- package/dist/src/core/deduplication/command-deduplicator.d.ts.map +1 -0
- package/dist/src/core/deduplication/command-deduplicator.js +254 -0
- package/dist/src/core/deduplication/command-deduplicator.js.map +1 -0
- package/dist/src/core/living-docs/hierarchy-mapper.d.ts +142 -0
- package/dist/src/core/living-docs/hierarchy-mapper.d.ts.map +1 -0
- package/dist/src/core/living-docs/hierarchy-mapper.js +453 -0
- package/dist/src/core/living-docs/hierarchy-mapper.js.map +1 -0
- package/dist/src/core/living-docs/index.d.ts +10 -84
- package/dist/src/core/living-docs/index.d.ts.map +1 -1
- package/dist/src/core/living-docs/index.js +10 -164
- package/dist/src/core/living-docs/index.js.map +1 -1
- package/dist/src/core/living-docs/spec-distributor.d.ts +106 -0
- package/dist/src/core/living-docs/spec-distributor.d.ts.map +1 -0
- package/dist/src/core/living-docs/spec-distributor.js +823 -0
- package/dist/src/core/living-docs/spec-distributor.js.map +1 -0
- package/dist/src/core/living-docs/types.d.ts +201 -0
- package/dist/src/core/living-docs/types.d.ts.map +1 -0
- package/dist/src/core/living-docs/types.js +15 -0
- package/dist/src/core/living-docs/types.js.map +1 -0
- package/dist/src/core/logging/prompt-logger.d.ts +70 -0
- package/dist/src/core/logging/prompt-logger.d.ts.map +1 -0
- package/dist/src/core/logging/prompt-logger.js +247 -0
- package/dist/src/core/logging/prompt-logger.js.map +1 -0
- package/dist/src/core/status-line/status-line-manager.d.ts +15 -24
- package/dist/src/core/status-line/status-line-manager.d.ts.map +1 -1
- package/dist/src/core/status-line/status-line-manager.js +33 -70
- package/dist/src/core/status-line/status-line-manager.js.map +1 -1
- package/dist/src/core/status-line/types.d.ts +19 -31
- package/dist/src/core/status-line/types.d.ts.map +1 -1
- package/dist/src/core/status-line/types.js +5 -9
- package/dist/src/core/status-line/types.js.map +1 -1
- package/dist/src/core/sync/conflict-resolver.d.ts +66 -0
- package/dist/src/core/sync/conflict-resolver.d.ts.map +1 -0
- package/dist/src/core/sync/conflict-resolver.js +108 -0
- package/dist/src/core/sync/conflict-resolver.js.map +1 -0
- package/dist/src/core/sync/enhanced-content-builder.d.ts +55 -0
- package/dist/src/core/sync/enhanced-content-builder.d.ts.map +1 -0
- package/dist/src/core/sync/enhanced-content-builder.js +202 -0
- package/dist/src/core/sync/enhanced-content-builder.js.map +1 -0
- package/dist/src/core/sync/label-detector.d.ts +66 -0
- package/dist/src/core/sync/label-detector.d.ts.map +1 -0
- package/dist/src/core/sync/label-detector.js +211 -0
- package/dist/src/core/sync/label-detector.js.map +1 -0
- package/dist/src/core/sync/retry-logic.d.ts +64 -0
- package/dist/src/core/sync/retry-logic.d.ts.map +1 -0
- package/dist/src/core/sync/retry-logic.js +165 -0
- package/dist/src/core/sync/retry-logic.js.map +1 -0
- package/dist/src/core/sync/spec-content-sync.d.ts +88 -0
- package/dist/src/core/sync/spec-content-sync.d.ts.map +1 -0
- package/dist/src/core/sync/spec-content-sync.js +5 -0
- package/dist/src/core/sync/spec-content-sync.js.map +1 -0
- package/dist/src/core/sync/spec-increment-mapper.d.ts +100 -0
- package/dist/src/core/sync/spec-increment-mapper.d.ts.map +1 -0
- package/dist/src/core/sync/spec-increment-mapper.js +424 -0
- package/dist/src/core/sync/spec-increment-mapper.js.map +1 -0
- package/dist/src/core/sync/status-cache.d.ts +91 -0
- package/dist/src/core/sync/status-cache.d.ts.map +1 -0
- package/dist/src/core/sync/status-cache.js +140 -0
- package/dist/src/core/sync/status-cache.js.map +1 -0
- package/dist/src/core/sync/status-mapper.d.ts +69 -0
- package/dist/src/core/sync/status-mapper.d.ts.map +1 -0
- package/dist/src/core/sync/status-mapper.js +90 -0
- package/dist/src/core/sync/status-mapper.js.map +1 -0
- package/dist/src/core/sync/status-sync-engine.d.ts +162 -0
- package/dist/src/core/sync/status-sync-engine.d.ts.map +1 -0
- package/dist/src/core/sync/status-sync-engine.js +347 -0
- package/dist/src/core/sync/status-sync-engine.js.map +1 -0
- package/dist/src/core/sync/sync-event-logger.d.ts +99 -0
- package/dist/src/core/sync/sync-event-logger.d.ts.map +1 -0
- package/dist/src/core/sync/sync-event-logger.js +103 -0
- package/dist/src/core/sync/sync-event-logger.js.map +1 -0
- package/dist/src/core/sync/types.d.ts +52 -0
- package/dist/src/core/sync/types.d.ts.map +1 -0
- package/dist/src/core/sync/types.js +5 -0
- package/dist/src/core/sync/types.js.map +1 -0
- package/dist/src/core/sync/workflow-detector.d.ts +95 -0
- package/dist/src/core/sync/workflow-detector.d.ts.map +1 -0
- package/dist/src/core/sync/workflow-detector.js +175 -0
- package/dist/src/core/sync/workflow-detector.js.map +1 -0
- package/dist/src/core/types/config.d.ts +51 -0
- package/dist/src/core/types/config.d.ts.map +1 -1
- package/dist/src/core/types/config.js +47 -0
- package/dist/src/core/types/config.js.map +1 -1
- package/dist/src/core/types/increment-metadata.d.ts +4 -0
- package/dist/src/core/types/increment-metadata.d.ts.map +1 -1
- package/dist/src/core/types/increment-metadata.js.map +1 -1
- package/dist/src/utils/github-url.d.ts +53 -0
- package/dist/src/utils/github-url.d.ts.map +1 -0
- package/dist/src/utils/github-url.js +90 -0
- package/dist/src/utils/github-url.js.map +1 -0
- package/dist/src/utils/spec-parser.d.ts +145 -0
- package/dist/src/utils/spec-parser.d.ts.map +1 -0
- package/dist/src/utils/spec-parser.js +640 -0
- package/dist/src/utils/spec-parser.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +1 -1
- package/plugins/specweave/agents/pm/AGENT.md +160 -13
- package/plugins/specweave/agents/pm/templates/increment-spec.md +158 -0
- package/plugins/specweave/agents/pm/templates/living-docs-spec.md +113 -0
- package/plugins/specweave/commands/specweave-done.md +163 -0
- package/plugins/specweave/commands/specweave.md +70 -405
- package/plugins/specweave/hooks/hooks.json +4 -0
- package/plugins/specweave/hooks/lib/sync-spec-content.sh +2 -2
- package/plugins/specweave/hooks/lib/update-status-line.sh +79 -111
- package/plugins/specweave/hooks/post-increment-planning.sh +133 -37
- package/plugins/specweave/hooks/pre-command-deduplication.sh +86 -0
- package/plugins/specweave/lib/hooks/sync-living-docs.js +139 -34
- package/plugins/specweave/lib/hooks/sync-living-docs.ts +234 -38
- package/plugins/specweave/skills/SKILLS-INDEX.md +4 -24
- package/plugins/specweave/skills/increment-planner/SKILL.md +94 -0
- package/plugins/specweave/skills/increment-work-router/SKILL.md +466 -0
- package/plugins/specweave-ado/commands/specweave-ado-sync-spec.md +1 -1
- package/plugins/specweave-ado/lib/ado-spec-content-sync.js +49 -5
- package/plugins/specweave-ado/lib/ado-spec-content-sync.ts +72 -6
- package/plugins/specweave-ado/lib/ado-status-sync.js +80 -0
- package/plugins/specweave-ado/lib/ado-status-sync.ts +121 -0
- package/plugins/specweave-ado/lib/enhanced-ado-sync.js +170 -0
- package/plugins/specweave-github/commands/specweave-github-cleanup-duplicates.md +205 -0
- package/plugins/specweave-github/commands/specweave-github-sync-epic.md +248 -0
- package/plugins/specweave-github/commands/specweave-github-sync-spec.md +1 -1
- package/plugins/specweave-github/hooks/post-task-completion.sh +32 -0
- package/plugins/specweave-github/lib/duplicate-detector.js +370 -0
- package/plugins/specweave-github/lib/duplicate-detector.ts +525 -0
- package/plugins/specweave-github/lib/enhanced-github-sync.js +220 -0
- package/plugins/specweave-github/lib/enhanced-github-sync.ts +322 -0
- package/plugins/specweave-github/lib/epic-content-builder.js +227 -0
- package/plugins/specweave-github/lib/epic-content-builder.ts +317 -0
- package/plugins/specweave-github/lib/github-client.js +21 -10
- package/plugins/specweave-github/lib/github-client.ts +27 -16
- package/plugins/specweave-github/lib/github-epic-sync.js +488 -0
- package/plugins/specweave-github/lib/github-epic-sync.ts +715 -0
- package/plugins/specweave-github/lib/github-status-sync.js +71 -0
- package/plugins/specweave-github/lib/github-status-sync.ts +107 -0
- package/plugins/specweave-github/lib/task-sync.js +33 -2
- package/plugins/specweave-github/lib/task-sync.ts +44 -2
- package/plugins/specweave-jira/commands/specweave-jira-sync-epic.md +267 -0
- package/plugins/specweave-jira/commands/specweave-jira-sync-spec.md +1 -1
- package/plugins/specweave-jira/lib/enhanced-jira-sync.js +134 -0
- package/plugins/specweave-jira/lib/enhanced-jira-sync.ts +196 -0
- package/plugins/specweave-jira/lib/jira-epic-sync.js +304 -0
- package/plugins/specweave-jira/lib/jira-epic-sync.ts +459 -0
- package/plugins/specweave-jira/lib/jira-status-sync.js +79 -0
- package/plugins/specweave-jira/lib/jira-status-sync.ts +139 -0
- package/plugins/specweave-release/commands/specweave-release-platform.md +1 -1
- package/plugins/specweave-release/hooks/post-task-completion.sh +2 -2
- package/src/templates/AGENTS.md.template +88 -1
- package/src/templates/CLAUDE.md.template +49 -0
- package/plugins/specweave/skills/increment-quality-judge/SKILL.md +0 -524
- package/plugins/specweave/skills/plugin-installer/SKILL.md +0 -353
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global Command Deduplication System
|
|
3
|
+
*
|
|
4
|
+
* Prevents ANY command/tool from being invoked twice within a configurable time window.
|
|
5
|
+
* Tracks all SlashCommand, Write, Edit, and other tool invocations.
|
|
6
|
+
*
|
|
7
|
+
* Architecture:
|
|
8
|
+
* - File-based cache: `.specweave/state/command-invocations.json`
|
|
9
|
+
* - Hash-based deduplication: command + args → unique fingerprint
|
|
10
|
+
* - Time-windowed checks: Configurable window (default: 1000ms)
|
|
11
|
+
* - Automatic cleanup: Removes old entries to prevent bloat
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { CommandDeduplicator } from './command-deduplicator.js';
|
|
16
|
+
*
|
|
17
|
+
* const dedup = new CommandDeduplicator();
|
|
18
|
+
* const isDuplicate = await dedup.checkDuplicate('/specweave:do', ['0031']);
|
|
19
|
+
*
|
|
20
|
+
* if (isDuplicate) {
|
|
21
|
+
* console.log('⚠️ Duplicate invocation blocked!');
|
|
22
|
+
* return;
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* await dedup.recordInvocation('/specweave:do', ['0031']);
|
|
26
|
+
* // ... execute command
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @module core/deduplication
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* Invocation record stored in cache
|
|
33
|
+
*/
|
|
34
|
+
export interface InvocationRecord {
|
|
35
|
+
/** Unique fingerprint of command + args */
|
|
36
|
+
fingerprint: string;
|
|
37
|
+
/** Command name (e.g., '/specweave:do') */
|
|
38
|
+
command: string;
|
|
39
|
+
/** Command arguments */
|
|
40
|
+
args: string[];
|
|
41
|
+
/** Timestamp when invoked (ms since epoch) */
|
|
42
|
+
timestamp: number;
|
|
43
|
+
/** Human-readable timestamp */
|
|
44
|
+
date: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Cache structure
|
|
48
|
+
*/
|
|
49
|
+
export interface InvocationCache {
|
|
50
|
+
/** List of invocation records */
|
|
51
|
+
invocations: InvocationRecord[];
|
|
52
|
+
/** Last cleanup timestamp */
|
|
53
|
+
lastCleanup: number;
|
|
54
|
+
/** Total invocations tracked */
|
|
55
|
+
totalInvocations: number;
|
|
56
|
+
/** Total duplicates blocked */
|
|
57
|
+
totalDuplicatesBlocked: number;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Configuration for deduplication
|
|
61
|
+
*/
|
|
62
|
+
export interface DeduplicationConfig {
|
|
63
|
+
/** Time window in milliseconds to check for duplicates (default: 1000ms) */
|
|
64
|
+
windowMs?: number;
|
|
65
|
+
/** Path to cache file (default: .specweave/state/command-invocations.json) */
|
|
66
|
+
cachePath?: string;
|
|
67
|
+
/** Maximum cache entries before cleanup (default: 1000) */
|
|
68
|
+
maxCacheSize?: number;
|
|
69
|
+
/** Enable debug logging (default: false) */
|
|
70
|
+
debug?: boolean;
|
|
71
|
+
/** Cleanup interval in milliseconds (default: 60000ms = 1 minute) */
|
|
72
|
+
cleanupIntervalMs?: number;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Global command deduplication system
|
|
76
|
+
*/
|
|
77
|
+
export declare class CommandDeduplicator {
|
|
78
|
+
private projectRoot;
|
|
79
|
+
private config;
|
|
80
|
+
private cache;
|
|
81
|
+
private lastCleanupCheck;
|
|
82
|
+
/**
|
|
83
|
+
* Create new deduplicator instance
|
|
84
|
+
*
|
|
85
|
+
* @param config - Configuration options
|
|
86
|
+
* @param projectRoot - Project root directory (default: process.cwd())
|
|
87
|
+
*/
|
|
88
|
+
constructor(config?: DeduplicationConfig, projectRoot?: string);
|
|
89
|
+
/**
|
|
90
|
+
* Check if command invocation is a duplicate
|
|
91
|
+
*
|
|
92
|
+
* @param command - Command name (e.g., '/specweave:do')
|
|
93
|
+
* @param args - Command arguments
|
|
94
|
+
* @returns true if duplicate detected, false otherwise
|
|
95
|
+
*/
|
|
96
|
+
checkDuplicate(command: string, args?: string[]): Promise<boolean>;
|
|
97
|
+
/**
|
|
98
|
+
* Record command invocation
|
|
99
|
+
*
|
|
100
|
+
* @param command - Command name
|
|
101
|
+
* @param args - Command arguments
|
|
102
|
+
*/
|
|
103
|
+
recordInvocation(command: string, args?: string[]): Promise<void>;
|
|
104
|
+
/**
|
|
105
|
+
* Create unique fingerprint for command + args
|
|
106
|
+
*
|
|
107
|
+
* @param command - Command name
|
|
108
|
+
* @param args - Command arguments
|
|
109
|
+
* @returns SHA256 hash of command + args
|
|
110
|
+
*/
|
|
111
|
+
private createFingerprint;
|
|
112
|
+
/**
|
|
113
|
+
* Load cache from disk
|
|
114
|
+
*
|
|
115
|
+
* @returns Invocation cache
|
|
116
|
+
*/
|
|
117
|
+
private loadCache;
|
|
118
|
+
/**
|
|
119
|
+
* Save cache to disk
|
|
120
|
+
*/
|
|
121
|
+
private saveCache;
|
|
122
|
+
/**
|
|
123
|
+
* Clean up old invocation records
|
|
124
|
+
*
|
|
125
|
+
* Removes records older than 10x the deduplication window to prevent cache bloat.
|
|
126
|
+
*/
|
|
127
|
+
private cleanup;
|
|
128
|
+
/**
|
|
129
|
+
* Get statistics about deduplication
|
|
130
|
+
*
|
|
131
|
+
* @returns Statistics object
|
|
132
|
+
*/
|
|
133
|
+
getStats(): {
|
|
134
|
+
totalInvocations: number;
|
|
135
|
+
totalDuplicatesBlocked: number;
|
|
136
|
+
currentCacheSize: number;
|
|
137
|
+
lastCleanup: string;
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* Clear all cached invocations (useful for testing)
|
|
141
|
+
*/
|
|
142
|
+
clear(): Promise<void>;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Get global deduplicator instance
|
|
146
|
+
*
|
|
147
|
+
* @param config - Configuration (only used on first call)
|
|
148
|
+
* @returns Global deduplicator instance
|
|
149
|
+
*/
|
|
150
|
+
export declare function getGlobalDeduplicator(config?: DeduplicationConfig): CommandDeduplicator;
|
|
151
|
+
/**
|
|
152
|
+
* Convenience function: Check if command is duplicate
|
|
153
|
+
*
|
|
154
|
+
* @param command - Command name
|
|
155
|
+
* @param args - Command arguments
|
|
156
|
+
* @returns true if duplicate, false otherwise
|
|
157
|
+
*/
|
|
158
|
+
export declare function isDuplicate(command: string, args?: string[]): Promise<boolean>;
|
|
159
|
+
/**
|
|
160
|
+
* Convenience function: Record command invocation
|
|
161
|
+
*
|
|
162
|
+
* @param command - Command name
|
|
163
|
+
* @param args - Command arguments
|
|
164
|
+
*/
|
|
165
|
+
export declare function recordCommand(command: string, args?: string[]): Promise<void>;
|
|
166
|
+
//# sourceMappingURL=command-deduplicator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-deduplicator.d.ts","sourceRoot":"","sources":["../../../../src/core/deduplication/command-deduplicator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAMH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,2CAA2C;IAC3C,WAAW,EAAE,MAAM,CAAC;IAEpB,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;IAEhB,wBAAwB;IACxB,IAAI,EAAE,MAAM,EAAE,CAAC;IAEf,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;IAElB,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,iCAAiC;IACjC,WAAW,EAAE,gBAAgB,EAAE,CAAC;IAEhC,6BAA6B;IAC7B,WAAW,EAAE,MAAM,CAAC;IAEpB,gCAAgC;IAChC,gBAAgB,EAAE,MAAM,CAAC;IAEzB,+BAA+B;IAC/B,sBAAsB,EAAE,MAAM,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,8EAA8E;IAC9E,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,2DAA2D;IAC3D,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,4CAA4C;IAC5C,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB,qEAAqE;IACrE,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;GAEG;AACH,qBAAa,mBAAmB;IAWgB,OAAO,CAAC,WAAW;IAVjE,OAAO,CAAC,MAAM,CAAgC;IAC9C,OAAO,CAAC,KAAK,CAAkB;IAC/B,OAAO,CAAC,gBAAgB,CAAa;IAErC;;;;;OAKG;gBACS,MAAM,GAAE,mBAAwB,EAAU,WAAW,GAAE,MAAsB;IAYzF;;;;;;OAMG;IACU,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,EAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAiCnF;;;;;OAKG;IACU,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,EAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IA4BlF;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IAKzB;;;;OAIG;IACH,OAAO,CAAC,SAAS;IA0BjB;;OAEG;YACW,SAAS;IAavB;;;;OAIG;YACW,OAAO;IAsBrB;;;;OAIG;IACI,QAAQ,IAAI;QACjB,gBAAgB,EAAE,MAAM,CAAC;QACzB,sBAAsB,EAAE,MAAM,CAAC;QAC/B,gBAAgB,EAAE,MAAM,CAAC;QACzB,WAAW,EAAE,MAAM,CAAC;KACrB;IASD;;OAEG;IACU,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAapC;AAOD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,CAAC,EAAE,mBAAmB,GAAG,mBAAmB,CAKvF;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,EAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAExF;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,EAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAEvF"}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global Command Deduplication System
|
|
3
|
+
*
|
|
4
|
+
* Prevents ANY command/tool from being invoked twice within a configurable time window.
|
|
5
|
+
* Tracks all SlashCommand, Write, Edit, and other tool invocations.
|
|
6
|
+
*
|
|
7
|
+
* Architecture:
|
|
8
|
+
* - File-based cache: `.specweave/state/command-invocations.json`
|
|
9
|
+
* - Hash-based deduplication: command + args → unique fingerprint
|
|
10
|
+
* - Time-windowed checks: Configurable window (default: 1000ms)
|
|
11
|
+
* - Automatic cleanup: Removes old entries to prevent bloat
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { CommandDeduplicator } from './command-deduplicator.js';
|
|
16
|
+
*
|
|
17
|
+
* const dedup = new CommandDeduplicator();
|
|
18
|
+
* const isDuplicate = await dedup.checkDuplicate('/specweave:do', ['0031']);
|
|
19
|
+
*
|
|
20
|
+
* if (isDuplicate) {
|
|
21
|
+
* console.log('⚠️ Duplicate invocation blocked!');
|
|
22
|
+
* return;
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* await dedup.recordInvocation('/specweave:do', ['0031']);
|
|
26
|
+
* // ... execute command
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @module core/deduplication
|
|
30
|
+
*/
|
|
31
|
+
import * as fs from 'fs-extra';
|
|
32
|
+
import * as path from 'path';
|
|
33
|
+
import * as crypto from 'crypto';
|
|
34
|
+
/**
|
|
35
|
+
* Global command deduplication system
|
|
36
|
+
*/
|
|
37
|
+
export class CommandDeduplicator {
|
|
38
|
+
/**
|
|
39
|
+
* Create new deduplicator instance
|
|
40
|
+
*
|
|
41
|
+
* @param config - Configuration options
|
|
42
|
+
* @param projectRoot - Project root directory (default: process.cwd())
|
|
43
|
+
*/
|
|
44
|
+
constructor(config = {}, projectRoot = process.cwd()) {
|
|
45
|
+
this.projectRoot = projectRoot;
|
|
46
|
+
this.lastCleanupCheck = 0;
|
|
47
|
+
this.config = {
|
|
48
|
+
windowMs: config.windowMs ?? 1000,
|
|
49
|
+
cachePath: config.cachePath ?? path.join(projectRoot, '.specweave', 'state', 'command-invocations.json'),
|
|
50
|
+
maxCacheSize: config.maxCacheSize ?? 1000,
|
|
51
|
+
debug: config.debug ?? false,
|
|
52
|
+
cleanupIntervalMs: config.cleanupIntervalMs ?? 60000
|
|
53
|
+
};
|
|
54
|
+
this.cache = this.loadCache();
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Check if command invocation is a duplicate
|
|
58
|
+
*
|
|
59
|
+
* @param command - Command name (e.g., '/specweave:do')
|
|
60
|
+
* @param args - Command arguments
|
|
61
|
+
* @returns true if duplicate detected, false otherwise
|
|
62
|
+
*/
|
|
63
|
+
async checkDuplicate(command, args = []) {
|
|
64
|
+
const fingerprint = this.createFingerprint(command, args);
|
|
65
|
+
const now = Date.now();
|
|
66
|
+
const windowStart = now - this.config.windowMs;
|
|
67
|
+
// Check for recent invocations with same fingerprint
|
|
68
|
+
const recentDuplicates = this.cache.invocations.filter(inv => inv.fingerprint === fingerprint &&
|
|
69
|
+
inv.timestamp >= windowStart);
|
|
70
|
+
if (recentDuplicates.length > 0) {
|
|
71
|
+
const mostRecent = recentDuplicates[recentDuplicates.length - 1];
|
|
72
|
+
const timeSince = now - mostRecent.timestamp;
|
|
73
|
+
if (this.config.debug) {
|
|
74
|
+
console.log(`[CommandDeduplicator] 🚫 DUPLICATE DETECTED!`);
|
|
75
|
+
console.log(` Command: ${command}`);
|
|
76
|
+
console.log(` Args: ${JSON.stringify(args)}`);
|
|
77
|
+
console.log(` Time since last: ${timeSince}ms`);
|
|
78
|
+
console.log(` Window: ${this.config.windowMs}ms`);
|
|
79
|
+
}
|
|
80
|
+
// Increment duplicate counter
|
|
81
|
+
this.cache.totalDuplicatesBlocked++;
|
|
82
|
+
await this.saveCache();
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Record command invocation
|
|
89
|
+
*
|
|
90
|
+
* @param command - Command name
|
|
91
|
+
* @param args - Command arguments
|
|
92
|
+
*/
|
|
93
|
+
async recordInvocation(command, args = []) {
|
|
94
|
+
const fingerprint = this.createFingerprint(command, args);
|
|
95
|
+
const now = Date.now();
|
|
96
|
+
const record = {
|
|
97
|
+
fingerprint,
|
|
98
|
+
command,
|
|
99
|
+
args,
|
|
100
|
+
timestamp: now,
|
|
101
|
+
date: new Date(now).toISOString()
|
|
102
|
+
};
|
|
103
|
+
this.cache.invocations.push(record);
|
|
104
|
+
this.cache.totalInvocations++;
|
|
105
|
+
if (this.config.debug) {
|
|
106
|
+
console.log(`[CommandDeduplicator] ✅ Recorded invocation: ${command} ${args.join(' ')}`);
|
|
107
|
+
}
|
|
108
|
+
// Trigger cleanup if needed
|
|
109
|
+
if (now - this.lastCleanupCheck > this.config.cleanupIntervalMs) {
|
|
110
|
+
await this.cleanup();
|
|
111
|
+
this.lastCleanupCheck = now;
|
|
112
|
+
}
|
|
113
|
+
await this.saveCache();
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Create unique fingerprint for command + args
|
|
117
|
+
*
|
|
118
|
+
* @param command - Command name
|
|
119
|
+
* @param args - Command arguments
|
|
120
|
+
* @returns SHA256 hash of command + args
|
|
121
|
+
*/
|
|
122
|
+
createFingerprint(command, args) {
|
|
123
|
+
const data = JSON.stringify({ command, args });
|
|
124
|
+
return crypto.createHash('sha256').update(data).digest('hex');
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Load cache from disk
|
|
128
|
+
*
|
|
129
|
+
* @returns Invocation cache
|
|
130
|
+
*/
|
|
131
|
+
loadCache() {
|
|
132
|
+
try {
|
|
133
|
+
if (fs.existsSync(this.config.cachePath)) {
|
|
134
|
+
const data = fs.readJsonSync(this.config.cachePath);
|
|
135
|
+
if (this.config.debug) {
|
|
136
|
+
console.log(`[CommandDeduplicator] 📂 Loaded cache: ${data.invocations.length} invocations`);
|
|
137
|
+
}
|
|
138
|
+
return data;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
if (this.config.debug) {
|
|
143
|
+
console.log(`[CommandDeduplicator] ⚠️ Failed to load cache: ${error}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Return empty cache
|
|
147
|
+
return {
|
|
148
|
+
invocations: [],
|
|
149
|
+
lastCleanup: Date.now(),
|
|
150
|
+
totalInvocations: 0,
|
|
151
|
+
totalDuplicatesBlocked: 0
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Save cache to disk
|
|
156
|
+
*/
|
|
157
|
+
async saveCache() {
|
|
158
|
+
try {
|
|
159
|
+
await fs.ensureDir(path.dirname(this.config.cachePath));
|
|
160
|
+
await fs.writeJson(this.config.cachePath, this.cache, { spaces: 2 });
|
|
161
|
+
if (this.config.debug) {
|
|
162
|
+
console.log(`[CommandDeduplicator] 💾 Saved cache: ${this.cache.invocations.length} invocations`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
console.error(`[CommandDeduplicator] ❌ Failed to save cache: ${error}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Clean up old invocation records
|
|
171
|
+
*
|
|
172
|
+
* Removes records older than 10x the deduplication window to prevent cache bloat.
|
|
173
|
+
*/
|
|
174
|
+
async cleanup() {
|
|
175
|
+
const now = Date.now();
|
|
176
|
+
const cutoff = now - (this.config.windowMs * 10); // Keep 10x window
|
|
177
|
+
const before = this.cache.invocations.length;
|
|
178
|
+
this.cache.invocations = this.cache.invocations.filter(inv => inv.timestamp >= cutoff);
|
|
179
|
+
const after = this.cache.invocations.length;
|
|
180
|
+
// Also enforce max cache size
|
|
181
|
+
if (this.cache.invocations.length > this.config.maxCacheSize) {
|
|
182
|
+
this.cache.invocations = this.cache.invocations.slice(-this.config.maxCacheSize);
|
|
183
|
+
}
|
|
184
|
+
this.cache.lastCleanup = now;
|
|
185
|
+
if (this.config.debug && before > after) {
|
|
186
|
+
console.log(`[CommandDeduplicator] 🧹 Cleanup: Removed ${before - after} old records`);
|
|
187
|
+
}
|
|
188
|
+
await this.saveCache();
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Get statistics about deduplication
|
|
192
|
+
*
|
|
193
|
+
* @returns Statistics object
|
|
194
|
+
*/
|
|
195
|
+
getStats() {
|
|
196
|
+
return {
|
|
197
|
+
totalInvocations: this.cache.totalInvocations,
|
|
198
|
+
totalDuplicatesBlocked: this.cache.totalDuplicatesBlocked,
|
|
199
|
+
currentCacheSize: this.cache.invocations.length,
|
|
200
|
+
lastCleanup: new Date(this.cache.lastCleanup).toISOString()
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Clear all cached invocations (useful for testing)
|
|
205
|
+
*/
|
|
206
|
+
async clear() {
|
|
207
|
+
this.cache = {
|
|
208
|
+
invocations: [],
|
|
209
|
+
lastCleanup: Date.now(),
|
|
210
|
+
totalInvocations: 0,
|
|
211
|
+
totalDuplicatesBlocked: 0
|
|
212
|
+
};
|
|
213
|
+
await this.saveCache();
|
|
214
|
+
if (this.config.debug) {
|
|
215
|
+
console.log(`[CommandDeduplicator] 🗑️ Cache cleared`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Global singleton instance (convenience)
|
|
221
|
+
*/
|
|
222
|
+
let globalInstance = null;
|
|
223
|
+
/**
|
|
224
|
+
* Get global deduplicator instance
|
|
225
|
+
*
|
|
226
|
+
* @param config - Configuration (only used on first call)
|
|
227
|
+
* @returns Global deduplicator instance
|
|
228
|
+
*/
|
|
229
|
+
export function getGlobalDeduplicator(config) {
|
|
230
|
+
if (!globalInstance) {
|
|
231
|
+
globalInstance = new CommandDeduplicator(config);
|
|
232
|
+
}
|
|
233
|
+
return globalInstance;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Convenience function: Check if command is duplicate
|
|
237
|
+
*
|
|
238
|
+
* @param command - Command name
|
|
239
|
+
* @param args - Command arguments
|
|
240
|
+
* @returns true if duplicate, false otherwise
|
|
241
|
+
*/
|
|
242
|
+
export async function isDuplicate(command, args = []) {
|
|
243
|
+
return await getGlobalDeduplicator().checkDuplicate(command, args);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Convenience function: Record command invocation
|
|
247
|
+
*
|
|
248
|
+
* @param command - Command name
|
|
249
|
+
* @param args - Command arguments
|
|
250
|
+
*/
|
|
251
|
+
export async function recordCommand(command, args = []) {
|
|
252
|
+
await getGlobalDeduplicator().recordInvocation(command, args);
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=command-deduplicator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-deduplicator.js","sourceRoot":"","sources":["../../../../src/core/deduplication/command-deduplicator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SpecWeave Hierarchy Mapper
|
|
3
|
+
*
|
|
4
|
+
* Maps increments to Feature Spec (FS-*) folders following Universal Hierarchy architecture.
|
|
5
|
+
*
|
|
6
|
+
* Supports three complexity levels:
|
|
7
|
+
* 1. Simple: Increment → Issue/Epic/Feature (flat)
|
|
8
|
+
* 2. Standard: FS-* (Epic) → US-* (User Stories) → T-* (Tasks)
|
|
9
|
+
* 3. Enterprise: Domain → FS-* (Epic) → US-* → T-*
|
|
10
|
+
*
|
|
11
|
+
* @author SpecWeave Team
|
|
12
|
+
* @version 3.0.0
|
|
13
|
+
*/
|
|
14
|
+
export interface HierarchyConfig {
|
|
15
|
+
level: 'simple' | 'standard' | 'enterprise' | 'auto';
|
|
16
|
+
specsBaseDir: string;
|
|
17
|
+
projectId: string;
|
|
18
|
+
featureFolderPattern: '{name}';
|
|
19
|
+
userStoriesSubdir: string;
|
|
20
|
+
detectFeatureFrom: ('frontmatter' | 'increment-name' | 'config')[];
|
|
21
|
+
fallbackFeature?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface FeatureMapping {
|
|
24
|
+
featureId: string;
|
|
25
|
+
featureFolder: string;
|
|
26
|
+
featurePath: string;
|
|
27
|
+
userStoriesPath: string;
|
|
28
|
+
confidence: number;
|
|
29
|
+
detectionMethod: 'frontmatter' | 'increment-name' | 'config' | 'fallback';
|
|
30
|
+
}
|
|
31
|
+
export type EpicMapping = FeatureMapping;
|
|
32
|
+
/**
|
|
33
|
+
* HierarchyMapper - Maps increments to feature folders (named by concept, not increment number)
|
|
34
|
+
*
|
|
35
|
+
* NEW ARCHITECTURE (v0.18.0+):
|
|
36
|
+
* - Features are PERMANENT (named by concept: "release-management", "external-tool-sync")
|
|
37
|
+
* - Increments are TEMPORARY (named by execution: "0023-release-management-enhancements")
|
|
38
|
+
* - Multiple increments can contribute to same feature
|
|
39
|
+
* - No FS-### prefix (features aren't numbered)
|
|
40
|
+
*/
|
|
41
|
+
export declare class HierarchyMapper {
|
|
42
|
+
private config;
|
|
43
|
+
private projectRoot;
|
|
44
|
+
constructor(projectRoot: string, config?: Partial<HierarchyConfig>);
|
|
45
|
+
/**
|
|
46
|
+
* Detect which feature folder this increment belongs to
|
|
47
|
+
*
|
|
48
|
+
* NEW: Features are named by CONCEPT (release-management), not INCREMENT NUMBER (FS-023)
|
|
49
|
+
*/
|
|
50
|
+
detectFeatureMapping(incrementId: string): Promise<FeatureMapping>;
|
|
51
|
+
/**
|
|
52
|
+
* Legacy alias for backward compatibility
|
|
53
|
+
*/
|
|
54
|
+
detectEpicMapping(incrementId: string): Promise<EpicMapping>;
|
|
55
|
+
/**
|
|
56
|
+
* Detect feature from frontmatter (epic: feature-name)
|
|
57
|
+
*
|
|
58
|
+
* FORMAT (v0.18.1+): Accepts simple feature names (external-tool-status-sync)
|
|
59
|
+
* LEGACY: Also accepts old FS-### format (FS-001) - extracts from increment name instead
|
|
60
|
+
*/
|
|
61
|
+
private detectFromFrontmatter;
|
|
62
|
+
/**
|
|
63
|
+
* Detect feature from increment NAME
|
|
64
|
+
*
|
|
65
|
+
* FORMAT (v0.18.1+): Simple descriptive names (no date prefixes)
|
|
66
|
+
*
|
|
67
|
+
* Extracts the descriptive part of increment ID and normalizes it to feature name
|
|
68
|
+
* Examples:
|
|
69
|
+
* 0023-release-management-enhancements → release-management (new or existing)
|
|
70
|
+
* 0031-external-tool-status-sync → external-tool-status-sync
|
|
71
|
+
*/
|
|
72
|
+
private detectFromIncrementName;
|
|
73
|
+
/**
|
|
74
|
+
* Detect feature from config mapping (explicit increment → feature mapping)
|
|
75
|
+
*/
|
|
76
|
+
private detectFromConfig;
|
|
77
|
+
/**
|
|
78
|
+
* Create fallback mapping (extract feature name from increment ID)
|
|
79
|
+
*
|
|
80
|
+
* FORMAT (v0.18.1+): Simple descriptive names (no prefixes)
|
|
81
|
+
* - Uses core feature name extracted from increment ID
|
|
82
|
+
* - No date prefixes (removed to prevent duplicates)
|
|
83
|
+
* - No FS- prefixes (features are not numbered)
|
|
84
|
+
*
|
|
85
|
+
* Example: 0023-release-management-enhancements → release-management
|
|
86
|
+
*/
|
|
87
|
+
private createFallbackMapping;
|
|
88
|
+
/**
|
|
89
|
+
* Get increment creation date in yy-mm-dd format
|
|
90
|
+
* Tries: metadata.json → spec.md frontmatter → current date
|
|
91
|
+
*/
|
|
92
|
+
private getIncrementCreationDate;
|
|
93
|
+
/**
|
|
94
|
+
* Format date as yy-mm-dd
|
|
95
|
+
* Input: "2025-11-14" or "2025-11-14T12:00:00Z"
|
|
96
|
+
* Output: "25-11-14"
|
|
97
|
+
*/
|
|
98
|
+
private formatDateShort;
|
|
99
|
+
/**
|
|
100
|
+
* NEW: Find feature folder by name (exact match or fuzzy match)
|
|
101
|
+
*
|
|
102
|
+
* Examples:
|
|
103
|
+
* release-management → release-management/
|
|
104
|
+
* external-tool-sync → external-tool-status-sync/ (fuzzy match)
|
|
105
|
+
*/
|
|
106
|
+
private findFeatureFolder;
|
|
107
|
+
/**
|
|
108
|
+
* LEGACY: Find FS-* epic folder (for backward compatibility)
|
|
109
|
+
*/
|
|
110
|
+
private findEpicFolder;
|
|
111
|
+
/**
|
|
112
|
+
* Get all feature folders (NEW: no FS- prefix filtering)
|
|
113
|
+
*/
|
|
114
|
+
getAllFeatureFolders(): Promise<string[]>;
|
|
115
|
+
/**
|
|
116
|
+
* LEGACY: Get all epic folders (for backward compatibility)
|
|
117
|
+
*/
|
|
118
|
+
getAllEpicFolders(): Promise<string[]>;
|
|
119
|
+
/**
|
|
120
|
+
* Validate feature folder structure (NEW: checks for FEATURE.md)
|
|
121
|
+
*/
|
|
122
|
+
validateFeatureFolder(featureFolder: string): Promise<{
|
|
123
|
+
valid: boolean;
|
|
124
|
+
missing: string[];
|
|
125
|
+
}>;
|
|
126
|
+
/**
|
|
127
|
+
* LEGACY: Validate epic folder structure (for backward compatibility)
|
|
128
|
+
*/
|
|
129
|
+
validateEpicFolder(epicFolder: string): Promise<{
|
|
130
|
+
valid: boolean;
|
|
131
|
+
missing: string[];
|
|
132
|
+
}>;
|
|
133
|
+
/**
|
|
134
|
+
* Create feature folder structure if missing (NEW: creates FEATURE.md)
|
|
135
|
+
*/
|
|
136
|
+
createFeatureFolderStructure(featureFolder: string, title: string): Promise<void>;
|
|
137
|
+
/**
|
|
138
|
+
* LEGACY: Create epic folder structure (for backward compatibility)
|
|
139
|
+
*/
|
|
140
|
+
createEpicFolderStructure(epicFolder: string, title: string): Promise<void>;
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=hierarchy-mapper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hierarchy-mapper.d.ts","sourceRoot":"","sources":["../../../../src/core/living-docs/hierarchy-mapper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,QAAQ,GAAG,UAAU,GAAG,YAAY,GAAG,MAAM,CAAC;IACrD,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,oBAAoB,EAAE,QAAQ,CAAC;IAC/B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,CAAC,aAAa,GAAG,gBAAgB,GAAG,QAAQ,CAAC,EAAE,CAAC;IACnE,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,aAAa,GAAG,gBAAgB,GAAG,QAAQ,GAAG,UAAU,CAAC;CAC3E;AAGD,MAAM,MAAM,WAAW,GAAG,cAAc,CAAC;AAEzC;;;;;;;;GAQG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,WAAW,CAAS;gBAEhB,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC;IAelE;;;;OAIG;IACG,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAkCxE;;OAEG;IACG,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAIlE;;;;;OAKG;YACW,qBAAqB;IAuCnC;;;;;;;;;OASG;YACW,uBAAuB;IA0CrC;;OAEG;YACW,gBAAgB;IA+B9B;;;;;;;;;OASG;YACW,qBAAqB;IAqCnC;;;OAGG;YACW,wBAAwB;IAoCtC;;;;OAIG;IACH,OAAO,CAAC,eAAe;IAQvB;;;;;;OAMG;YACW,iBAAiB;IA8B/B;;OAEG;YACW,cAAc;IAI5B;;OAEG;IACG,oBAAoB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IA0B/C;;OAEG;IACG,iBAAiB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAI5C;;OAEG;IACG,qBAAqB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAiBlG;;OAEG;IACG,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAI5F;;OAEG;IACG,4BAA4B,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAqEvF;;OAEG;IACG,yBAAyB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGlF"}
|