specweave 0.17.7 â 0.17.9
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 +365 -9
- 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 +9 -4
- package/dist/plugins/specweave/lib/hooks/sync-living-docs.d.ts.map +1 -1
- package/dist/plugins/specweave/lib/hooks/sync-living-docs.js +82 -12
- package/dist/plugins/specweave/lib/hooks/sync-living-docs.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-spec-content-sync.d.ts +2 -2
- package/dist/plugins/specweave-github/lib/github-spec-content-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-spec-content-sync.js +69 -10
- package/dist/plugins/specweave-github/lib/github-spec-content-sync.js.map +1 -1
- package/dist/src/cli/commands/detect-project.d.ts +9 -0
- package/dist/src/cli/commands/detect-project.d.ts.map +1 -0
- package/dist/src/cli/commands/detect-project.js +85 -0
- package/dist/src/cli/commands/detect-project.js.map +1 -0
- package/dist/src/cli/commands/detect-specs.d.ts +9 -0
- package/dist/src/cli/commands/detect-specs.d.ts.map +1 -0
- package/dist/src/cli/commands/detect-specs.js +73 -0
- package/dist/src/cli/commands/detect-specs.js.map +1 -0
- package/dist/src/cli/commands/init.d.ts.map +1 -1
- package/dist/src/cli/commands/init.js +8 -15
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/core/living-docs/content-classifier.d.ts +83 -0
- package/dist/src/core/living-docs/content-classifier.d.ts.map +1 -0
- package/dist/src/core/living-docs/content-classifier.js +393 -0
- package/dist/src/core/living-docs/content-classifier.js.map +1 -0
- package/dist/src/core/living-docs/content-distributor.d.ts +126 -0
- package/dist/src/core/living-docs/content-distributor.d.ts.map +1 -0
- package/dist/src/core/living-docs/content-distributor.js +339 -0
- package/dist/src/core/living-docs/content-distributor.js.map +1 -0
- package/dist/src/core/living-docs/content-parser.d.ts +140 -0
- package/dist/src/core/living-docs/content-parser.d.ts.map +1 -0
- package/dist/src/core/living-docs/content-parser.js +316 -0
- package/dist/src/core/living-docs/content-parser.js.map +1 -0
- package/dist/src/core/living-docs/cross-linker.d.ts +126 -0
- package/dist/src/core/living-docs/cross-linker.d.ts.map +1 -0
- package/dist/src/core/living-docs/cross-linker.js +374 -0
- package/dist/src/core/living-docs/cross-linker.js.map +1 -0
- package/dist/src/core/living-docs/index.d.ts +89 -0
- package/dist/src/core/living-docs/index.d.ts.map +1 -0
- package/dist/src/core/living-docs/index.js +169 -0
- package/dist/src/core/living-docs/index.js.map +1 -0
- package/dist/src/core/living-docs/project-detector.d.ts +103 -0
- package/dist/src/core/living-docs/project-detector.d.ts.map +1 -0
- package/dist/src/core/living-docs/project-detector.js +314 -0
- package/dist/src/core/living-docs/project-detector.js.map +1 -0
- package/dist/src/core/repo-structure/prompt-consolidator.d.ts +1 -1
- package/dist/src/core/repo-structure/prompt-consolidator.d.ts.map +1 -1
- package/dist/src/core/repo-structure/prompt-consolidator.js +51 -22
- package/dist/src/core/repo-structure/prompt-consolidator.js.map +1 -1
- package/dist/src/core/repo-structure/repo-structure-manager.d.ts +2 -0
- package/dist/src/core/repo-structure/repo-structure-manager.d.ts.map +1 -1
- package/dist/src/core/repo-structure/repo-structure-manager.js +126 -62
- package/dist/src/core/repo-structure/repo-structure-manager.js.map +1 -1
- package/dist/src/core/spec-content-sync.d.ts +6 -0
- package/dist/src/core/spec-content-sync.d.ts.map +1 -1
- package/dist/src/core/spec-content-sync.js +25 -7
- package/dist/src/core/spec-content-sync.js.map +1 -1
- package/dist/src/core/spec-detector.d.ts +54 -0
- package/dist/src/core/spec-detector.d.ts.map +1 -0
- package/dist/src/core/spec-detector.js +197 -0
- package/dist/src/core/spec-detector.js.map +1 -0
- package/dist/src/core/spec-identifier-detector.d.ts +49 -0
- package/dist/src/core/spec-identifier-detector.d.ts.map +1 -0
- package/dist/src/core/spec-identifier-detector.js +198 -0
- package/dist/src/core/spec-identifier-detector.js.map +1 -0
- package/dist/src/core/types/spec-identifier.d.ts +67 -0
- package/dist/src/core/types/spec-identifier.d.ts.map +1 -0
- package/dist/src/core/types/spec-identifier.js +23 -0
- package/dist/src/core/types/spec-identifier.js.map +1 -0
- package/dist/src/utils/docs-preview/config-generator.d.ts.map +1 -1
- package/dist/src/utils/docs-preview/config-generator.js +11 -0
- package/dist/src/utils/docs-preview/config-generator.js.map +1 -1
- package/package.json +1 -1
- package/plugins/specweave/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave/lib/hooks/sync-living-docs.js +61 -5
- package/plugins/specweave/lib/hooks/sync-living-docs.ts +102 -13
- package/plugins/specweave-ado/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-docs-preview/commands/preview.md +11 -11
- package/plugins/specweave-github/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-github/hooks/post-task-completion.sh +59 -30
- package/plugins/specweave-github/lib/github-spec-content-sync.js +55 -6
- package/plugins/specweave-github/lib/github-spec-content-sync.ts +84 -12
- package/plugins/specweave-jira/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-mobile/.claude-plugin/plugin.json +1 -4
- package/plugins/specweave-release/.claude-plugin/plugin.json +3 -15
- package/src/templates/AGENTS.md.template +297 -0
- package/plugins/specweave/plugin.json +0 -22
- package/plugins/specweave-ado/plugin.json +0 -20
- package/plugins/specweave-github/plugin.json +0 -19
- package/plugins/specweave-jira/plugin.json +0 -20
|
@@ -18,20 +18,76 @@ async function syncLivingDocs(incrementId) {
|
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
20
20
|
console.log("\u2705 Living docs sync enabled");
|
|
21
|
-
const
|
|
22
|
-
|
|
21
|
+
const intelligentEnabled = config.livingDocs?.intelligent?.enabled ?? false;
|
|
22
|
+
let specCopied = false;
|
|
23
|
+
let changedDocs = [];
|
|
24
|
+
if (intelligentEnabled) {
|
|
25
|
+
console.log("\u{1F9E0} Using intelligent sync mode (v0.18.0+)");
|
|
26
|
+
const result = await intelligentSyncLivingDocs(incrementId, config);
|
|
27
|
+
specCopied = result.success;
|
|
28
|
+
changedDocs = result.changedFiles;
|
|
29
|
+
} else {
|
|
30
|
+
console.log("\u{1F4CB} Using simple sync mode (legacy)");
|
|
31
|
+
specCopied = await copyIncrementSpecToLivingDocs(incrementId);
|
|
32
|
+
changedDocs = detectChangedDocs();
|
|
33
|
+
}
|
|
23
34
|
if (changedDocs.length === 0 && !specCopied) {
|
|
24
|
-
console.log("\u2139\uFE0F No living docs changed
|
|
35
|
+
console.log("\u2139\uFE0F No living docs changed");
|
|
25
36
|
return;
|
|
26
37
|
}
|
|
27
|
-
console.log(`\u{1F4C4}
|
|
28
|
-
changedDocs.forEach((doc) => console.log(` - ${doc}`));
|
|
38
|
+
console.log(`\u{1F4C4} Changed/created ${changedDocs.length} file(s)`);
|
|
29
39
|
await syncToGitHub(incrementId, changedDocs);
|
|
30
40
|
console.log("\u2705 Living docs sync complete\n");
|
|
31
41
|
} catch (error) {
|
|
32
42
|
console.error("\u274C Error syncing living docs:", error);
|
|
33
43
|
}
|
|
34
44
|
}
|
|
45
|
+
async function intelligentSyncLivingDocs(incrementId, config) {
|
|
46
|
+
try {
|
|
47
|
+
const { syncIncrement } = await import("../../../../src/core/living-docs/index.js");
|
|
48
|
+
console.log(" \u{1F4D6} Parsing and classifying spec sections...");
|
|
49
|
+
const result = await syncIncrement(incrementId, {
|
|
50
|
+
verbose: false,
|
|
51
|
+
// We'll log our own summary
|
|
52
|
+
dryRun: false,
|
|
53
|
+
parser: {
|
|
54
|
+
preserveCodeBlocks: true,
|
|
55
|
+
preserveLinks: true,
|
|
56
|
+
preserveImages: true
|
|
57
|
+
},
|
|
58
|
+
distributor: {
|
|
59
|
+
generateFrontmatter: true,
|
|
60
|
+
preserveOriginal: config.livingDocs?.intelligent?.preserveOriginal ?? true
|
|
61
|
+
},
|
|
62
|
+
linker: {
|
|
63
|
+
generateBacklinks: config.livingDocs?.intelligent?.generateCrossLinks ?? true,
|
|
64
|
+
updateExisting: true
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
console.log(` \u2705 Intelligent sync complete:`);
|
|
68
|
+
console.log(` Project: ${result.project.name} (${(result.project.confidence * 100).toFixed(0)}% confidence)`);
|
|
69
|
+
console.log(` Files created: ${result.distribution.summary.filesCreated}`);
|
|
70
|
+
console.log(` Files updated: ${result.distribution.summary.filesUpdated}`);
|
|
71
|
+
console.log(` Cross-links: ${result.links.length}`);
|
|
72
|
+
console.log(` Duration: ${result.duration}ms`);
|
|
73
|
+
const changedFiles = [
|
|
74
|
+
...result.distribution.created.map((f) => f.path),
|
|
75
|
+
...result.distribution.updated.map((f) => f.path)
|
|
76
|
+
];
|
|
77
|
+
return {
|
|
78
|
+
success: result.success,
|
|
79
|
+
changedFiles
|
|
80
|
+
};
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error(` \u274C Intelligent sync failed: ${error}`);
|
|
83
|
+
console.error(" Falling back to simple sync mode...");
|
|
84
|
+
const copied = await copyIncrementSpecToLivingDocs(incrementId);
|
|
85
|
+
return {
|
|
86
|
+
success: copied,
|
|
87
|
+
changedFiles: copied ? [path.join(process.cwd(), ".specweave", "docs", "internal", "specs", `spec-${incrementId}.md`)] : []
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
35
91
|
async function copyIncrementSpecToLivingDocs(incrementId) {
|
|
36
92
|
try {
|
|
37
93
|
const incrementSpecPath = path.join(process.cwd(), ".specweave", "increments", incrementId, "spec.md");
|
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Automatically syncs living documentation after task completion.
|
|
7
7
|
*
|
|
8
|
+
* Supports two modes:
|
|
9
|
+
* 1. Simple mode: Copy spec.md to living docs (legacy)
|
|
10
|
+
* 2. Intelligent mode: Parse, classify, and distribute content (v0.18.0+)
|
|
11
|
+
*
|
|
8
12
|
* Usage:
|
|
9
13
|
* node dist/hooks/lib/sync-living-docs.js <incrementId>
|
|
10
14
|
*
|
|
@@ -13,12 +17,13 @@
|
|
|
13
17
|
*
|
|
14
18
|
* What it does:
|
|
15
19
|
* 1. Checks if sync_living_docs enabled in config
|
|
16
|
-
* 2. Detects
|
|
17
|
-
* 3.
|
|
18
|
-
* 4.
|
|
20
|
+
* 2. Detects sync mode (simple or intelligent)
|
|
21
|
+
* 3. Runs appropriate sync strategy
|
|
22
|
+
* 4. Syncs to external tools (GitHub/Jira/ADO)
|
|
23
|
+
* 5. Logs sync actions
|
|
19
24
|
*
|
|
20
25
|
* @author SpecWeave Team
|
|
21
|
-
* @version
|
|
26
|
+
* @version 2.0.0
|
|
22
27
|
*/
|
|
23
28
|
|
|
24
29
|
import fs from 'fs-extra';
|
|
@@ -31,6 +36,16 @@ interface Config {
|
|
|
31
36
|
sync_living_docs?: boolean;
|
|
32
37
|
};
|
|
33
38
|
};
|
|
39
|
+
livingDocs?: {
|
|
40
|
+
intelligent?: {
|
|
41
|
+
enabled?: boolean;
|
|
42
|
+
splitByCategory?: boolean;
|
|
43
|
+
generateCrossLinks?: boolean;
|
|
44
|
+
preserveOriginal?: boolean;
|
|
45
|
+
classificationConfidenceThreshold?: number;
|
|
46
|
+
fallbackProject?: string;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
34
49
|
}
|
|
35
50
|
|
|
36
51
|
/**
|
|
@@ -59,19 +74,31 @@ async function syncLivingDocs(incrementId: string): Promise<void> {
|
|
|
59
74
|
|
|
60
75
|
console.log('â
Living docs sync enabled');
|
|
61
76
|
|
|
62
|
-
// 3.
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
77
|
+
// 3. Determine sync mode (simple or intelligent)
|
|
78
|
+
const intelligentEnabled = config.livingDocs?.intelligent?.enabled ?? false;
|
|
79
|
+
|
|
80
|
+
let specCopied = false;
|
|
81
|
+
let changedDocs: string[] = [];
|
|
82
|
+
|
|
83
|
+
if (intelligentEnabled) {
|
|
84
|
+
// Intelligent mode: Parse, classify, and distribute
|
|
85
|
+
console.log('đ§ Using intelligent sync mode (v0.18.0+)');
|
|
86
|
+
const result = await intelligentSyncLivingDocs(incrementId, config);
|
|
87
|
+
specCopied = result.success;
|
|
88
|
+
changedDocs = result.changedFiles;
|
|
89
|
+
} else {
|
|
90
|
+
// Simple mode: Copy spec to living docs (legacy)
|
|
91
|
+
console.log('đ Using simple sync mode (legacy)');
|
|
92
|
+
specCopied = await copyIncrementSpecToLivingDocs(incrementId);
|
|
93
|
+
changedDocs = detectChangedDocs();
|
|
94
|
+
}
|
|
67
95
|
|
|
68
96
|
if (changedDocs.length === 0 && !specCopied) {
|
|
69
|
-
console.log('âšī¸ No living docs changed
|
|
97
|
+
console.log('âšī¸ No living docs changed');
|
|
70
98
|
return;
|
|
71
99
|
}
|
|
72
100
|
|
|
73
|
-
console.log(`đ
|
|
74
|
-
changedDocs.forEach((doc) => console.log(` - ${doc}`));
|
|
101
|
+
console.log(`đ Changed/created ${changedDocs.length} file(s)`);
|
|
75
102
|
|
|
76
103
|
// 4. Sync to GitHub if configured
|
|
77
104
|
await syncToGitHub(incrementId, changedDocs);
|
|
@@ -85,7 +112,69 @@ async function syncLivingDocs(incrementId: string): Promise<void> {
|
|
|
85
112
|
}
|
|
86
113
|
|
|
87
114
|
/**
|
|
88
|
-
*
|
|
115
|
+
* Intelligent sync using the new architecture (v0.18.0+)
|
|
116
|
+
*/
|
|
117
|
+
async function intelligentSyncLivingDocs(
|
|
118
|
+
incrementId: string,
|
|
119
|
+
config: Config
|
|
120
|
+
): Promise<{ success: boolean; changedFiles: string[] }> {
|
|
121
|
+
try {
|
|
122
|
+
// Dynamic import to avoid circular dependencies
|
|
123
|
+
const { syncIncrement } = await import('../../../../src/core/living-docs/index.js');
|
|
124
|
+
|
|
125
|
+
console.log(' đ Parsing and classifying spec sections...');
|
|
126
|
+
|
|
127
|
+
const result = await syncIncrement(incrementId, {
|
|
128
|
+
verbose: false, // We'll log our own summary
|
|
129
|
+
dryRun: false,
|
|
130
|
+
parser: {
|
|
131
|
+
preserveCodeBlocks: true,
|
|
132
|
+
preserveLinks: true,
|
|
133
|
+
preserveImages: true,
|
|
134
|
+
},
|
|
135
|
+
distributor: {
|
|
136
|
+
generateFrontmatter: true,
|
|
137
|
+
preserveOriginal: config.livingDocs?.intelligent?.preserveOriginal ?? true,
|
|
138
|
+
},
|
|
139
|
+
linker: {
|
|
140
|
+
generateBacklinks: config.livingDocs?.intelligent?.generateCrossLinks ?? true,
|
|
141
|
+
updateExisting: true,
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Log summary
|
|
146
|
+
console.log(` â
Intelligent sync complete:`);
|
|
147
|
+
console.log(` Project: ${result.project.name} (${(result.project.confidence * 100).toFixed(0)}% confidence)`);
|
|
148
|
+
console.log(` Files created: ${result.distribution.summary.filesCreated}`);
|
|
149
|
+
console.log(` Files updated: ${result.distribution.summary.filesUpdated}`);
|
|
150
|
+
console.log(` Cross-links: ${result.links.length}`);
|
|
151
|
+
console.log(` Duration: ${result.duration}ms`);
|
|
152
|
+
|
|
153
|
+
// Collect changed file paths
|
|
154
|
+
const changedFiles = [
|
|
155
|
+
...result.distribution.created.map((f) => f.path),
|
|
156
|
+
...result.distribution.updated.map((f) => f.path),
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
success: result.success,
|
|
161
|
+
changedFiles,
|
|
162
|
+
};
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.error(` â Intelligent sync failed: ${error}`);
|
|
165
|
+
console.error(' Falling back to simple sync mode...');
|
|
166
|
+
|
|
167
|
+
// Fallback to simple mode
|
|
168
|
+
const copied = await copyIncrementSpecToLivingDocs(incrementId);
|
|
169
|
+
return {
|
|
170
|
+
success: copied,
|
|
171
|
+
changedFiles: copied ? [path.join(process.cwd(), '.specweave', 'docs', 'internal', 'specs', `spec-${incrementId}.md`)] : [],
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Copy increment spec to living docs (legacy/simple mode)
|
|
89
178
|
* Returns true if spec was copied, false if skipped
|
|
90
179
|
*/
|
|
91
180
|
async function copyIncrementSpecToLivingDocs(incrementId: string): Promise<boolean> {
|
|
@@ -51,7 +51,7 @@ if (fs.existsSync(configPath)) {
|
|
|
51
51
|
const docsConfig = config.documentation?.preview || {
|
|
52
52
|
enabled: true,
|
|
53
53
|
autoInstall: true,
|
|
54
|
-
port:
|
|
54
|
+
port: 3016,
|
|
55
55
|
openBrowser: true,
|
|
56
56
|
theme: 'default',
|
|
57
57
|
excludeFolders: ['legacy', 'node_modules']
|
|
@@ -103,7 +103,7 @@ if (setupNeeded) {
|
|
|
103
103
|
```typescript
|
|
104
104
|
try {
|
|
105
105
|
const options = {
|
|
106
|
-
port: docsConfig.port ||
|
|
106
|
+
port: docsConfig.port || 3016,
|
|
107
107
|
openBrowser: docsConfig.openBrowser !== false,
|
|
108
108
|
theme: docsConfig.theme || 'default',
|
|
109
109
|
excludeFolders: docsConfig.excludeFolders || ['legacy', 'node_modules']
|
|
@@ -135,8 +135,8 @@ try {
|
|
|
135
135
|
} else if (error.message.includes('port')) {
|
|
136
136
|
console.log('đĄ Solution:');
|
|
137
137
|
console.log(' âĸ Change port in .specweave/config.json');
|
|
138
|
-
console.log(' âĸ Or stop the service using port ' + (docsConfig.port ||
|
|
139
|
-
console.log(' âĸ Check with: lsof -i :' + (docsConfig.port ||
|
|
138
|
+
console.log(' âĸ Or stop the service using port ' + (docsConfig.port || 3016));
|
|
139
|
+
console.log(' âĸ Check with: lsof -i :' + (docsConfig.port || 3016) + '\n');
|
|
140
140
|
} else {
|
|
141
141
|
console.log('đĄ Troubleshooting:');
|
|
142
142
|
console.log(' âĸ Check Node.js version (18+ required): node --version');
|
|
@@ -160,7 +160,7 @@ The command uses settings from `.specweave/config.json`:
|
|
|
160
160
|
"preview": {
|
|
161
161
|
"enabled": true,
|
|
162
162
|
"autoInstall": true,
|
|
163
|
-
"port":
|
|
163
|
+
"port": 3016,
|
|
164
164
|
"openBrowser": true,
|
|
165
165
|
"theme": "default",
|
|
166
166
|
"excludeFolders": ["legacy", "node_modules"]
|
|
@@ -250,11 +250,11 @@ Changes to markdown files in `.specweave/docs/internal/` are detected automatica
|
|
|
250
250
|
â Packages installed successfully
|
|
251
251
|
â Configuration generated
|
|
252
252
|
â Sidebar generated (42 documents, 8 categories)
|
|
253
|
-
â Server started on http://localhost:
|
|
253
|
+
â Server started on http://localhost:3016
|
|
254
254
|
|
|
255
255
|
â
Documentation server started successfully!
|
|
256
256
|
|
|
257
|
-
đ URL: http://localhost:
|
|
257
|
+
đ URL: http://localhost:3016
|
|
258
258
|
đ Hot reload enabled - edit markdown files to see changes instantly
|
|
259
259
|
đī¸ Sidebar auto-generated from folder structure
|
|
260
260
|
đ Mermaid diagrams supported
|
|
@@ -270,11 +270,11 @@ Changes to markdown files in `.specweave/docs/internal/` are detected automatica
|
|
|
270
270
|
â Configuration up-to-date
|
|
271
271
|
|
|
272
272
|
â Sidebar generated (42 documents, 8 categories)
|
|
273
|
-
â Server started on http://localhost:
|
|
273
|
+
â Server started on http://localhost:3016
|
|
274
274
|
|
|
275
275
|
â
Documentation server started successfully!
|
|
276
276
|
|
|
277
|
-
đ URL: http://localhost:
|
|
277
|
+
đ URL: http://localhost:3016
|
|
278
278
|
đ Hot reload enabled - edit markdown files to see changes instantly
|
|
279
279
|
đī¸ Sidebar auto-generated from folder structure
|
|
280
280
|
đ Mermaid diagrams supported
|
|
@@ -288,11 +288,11 @@ Changes to markdown files in `.specweave/docs/internal/` are detected automatica
|
|
|
288
288
|
|
|
289
289
|
**Port Already in Use:**
|
|
290
290
|
```
|
|
291
|
-
Error: Port
|
|
291
|
+
Error: Port 3016 is already in use
|
|
292
292
|
```
|
|
293
293
|
Solution:
|
|
294
294
|
1. Change port in `.specweave/config.json` â `documentation.preview.port`
|
|
295
|
-
2. Or stop the service: `lsof -i :
|
|
295
|
+
2. Or stop the service: `lsof -i :3016` then `kill -9 <PID>`
|
|
296
296
|
|
|
297
297
|
**Node.js Version:**
|
|
298
298
|
```
|
|
@@ -95,10 +95,10 @@ EOF
|
|
|
95
95
|
fi
|
|
96
96
|
|
|
97
97
|
# ============================================================================
|
|
98
|
-
# DETECT
|
|
98
|
+
# DETECT ALL SPECS (Multi-Spec Support)
|
|
99
99
|
# ============================================================================
|
|
100
100
|
|
|
101
|
-
# Strategy:
|
|
101
|
+
# Strategy: Use multi-spec detector to find ALL specs referenced in current increment
|
|
102
102
|
|
|
103
103
|
# 1. Detect current increment (temporary context)
|
|
104
104
|
CURRENT_INCREMENT=$(ls -t .specweave/increments/ 2>/dev/null | grep -v "_backlog" | head -1)
|
|
@@ -108,49 +108,78 @@ if [ -z "$CURRENT_INCREMENT" ]; then
|
|
|
108
108
|
# Fall through to sync all changed specs
|
|
109
109
|
fi
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
# 2. Use TypeScript CLI to detect all specs
|
|
112
|
+
DETECT_CLI="$PROJECT_ROOT/dist/src/cli/commands/detect-specs.js"
|
|
112
113
|
|
|
113
|
-
if [ -
|
|
114
|
-
|
|
115
|
-
SPEC_FILE=".specweave/increments/$CURRENT_INCREMENT/spec.md"
|
|
114
|
+
if [ -f "$DETECT_CLI" ]; then
|
|
115
|
+
echo "[$(date)] [GitHub] đ Detecting all specs in increment $CURRENT_INCREMENT..." >> "$DEBUG_LOG" 2>/dev/null || true
|
|
116
116
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
SPEC_REF=$(grep -E "^(Implements|See|References).*SPEC-[0-9]+" "$SPEC_FILE" 2>/dev/null | head -1 || echo "")
|
|
117
|
+
# Call detect-specs CLI and capture JSON output
|
|
118
|
+
DETECTION_RESULT=$(node "$DETECT_CLI" 2>> "$DEBUG_LOG" || echo "{}")
|
|
120
119
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
120
|
+
# Extract spec count
|
|
121
|
+
SPEC_COUNT=$(echo "$DETECTION_RESULT" | node -e "const fs=require('fs'); const data=JSON.parse(fs.readFileSync(0,'utf-8')); console.log(data.specs?.length || 0)")
|
|
122
|
+
|
|
123
|
+
echo "[$(date)] [GitHub] đ Detected $SPEC_COUNT spec(s)" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
124
|
+
|
|
125
|
+
# Store detection result for later use
|
|
126
|
+
echo "$DETECTION_RESULT" > /tmp/specweave-detected-specs.json
|
|
127
|
+
else
|
|
128
|
+
echo "[$(date)] [GitHub] â ī¸ detect-specs CLI not found at $DETECT_CLI, falling back to git diff" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
129
|
+
SPEC_COUNT=0
|
|
127
130
|
fi
|
|
128
131
|
|
|
129
132
|
# ============================================================================
|
|
130
|
-
# SYNC
|
|
133
|
+
# SYNC ALL DETECTED SPECS TO GITHUB (Multi-Spec Support)
|
|
131
134
|
# ============================================================================
|
|
132
135
|
|
|
133
|
-
if [ -
|
|
134
|
-
#
|
|
135
|
-
|
|
136
|
+
if [ -f /tmp/specweave-detected-specs.json ] && [ "$SPEC_COUNT" -gt 0 ]; then
|
|
137
|
+
# Multi-spec sync: Loop through all detected specs
|
|
138
|
+
echo "[$(date)] [GitHub] đ Syncing $SPEC_COUNT spec(s) to GitHub..." >> "$DEBUG_LOG" 2>/dev/null || true
|
|
139
|
+
|
|
140
|
+
# Extract spec paths using Node.js
|
|
141
|
+
SPEC_PATHS=$(node -e "
|
|
142
|
+
const fs = require('fs');
|
|
143
|
+
const data = JSON.parse(fs.readFileSync('/tmp/specweave-detected-specs.json', 'utf-8'));
|
|
144
|
+
const syncable = data.specs.filter(s => s.syncEnabled && s.project !== '_parent');
|
|
145
|
+
syncable.forEach(s => console.log(s.path));
|
|
146
|
+
" 2>> "$DEBUG_LOG")
|
|
147
|
+
|
|
148
|
+
# Count syncable specs
|
|
149
|
+
SYNCABLE_COUNT=$(echo "$SPEC_PATHS" | grep -v '^$' | wc -l | tr -d ' ')
|
|
136
150
|
|
|
137
|
-
if [
|
|
138
|
-
|
|
139
|
-
echo "[$(date)] [GitHub] đ Syncing spec $SPEC_ID ($SPEC_FILE) to GitHub..." >> "$DEBUG_LOG" 2>/dev/null || true
|
|
151
|
+
if [ "$SYNCABLE_COUNT" -gt 0 ]; then
|
|
152
|
+
echo "[$(date)] [GitHub] đ Syncing $SYNCABLE_COUNT syncable spec(s) (excluding _parent)" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
140
153
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
154
|
+
# Sync each spec
|
|
155
|
+
echo "$SPEC_PATHS" | while read -r SPEC_FILE; do
|
|
156
|
+
if [ -n "$SPEC_FILE" ] && [ -f "$SPEC_FILE" ]; then
|
|
157
|
+
# Extract project and spec ID from path
|
|
158
|
+
SPEC_NAME=$(basename "$SPEC_FILE" .md)
|
|
159
|
+
PROJECT=$(basename "$(dirname "$SPEC_FILE")")
|
|
144
160
|
|
|
145
|
-
|
|
161
|
+
echo "[$(date)] [GitHub] đ Syncing $PROJECT/$SPEC_NAME..." >> "$DEBUG_LOG" 2>/dev/null || true
|
|
162
|
+
|
|
163
|
+
(cd "$PROJECT_ROOT" && node "$SYNC_CLI" --spec "$SPEC_FILE" --provider github) 2>&1 | tee -a "$DEBUG_LOG" >/dev/null || {
|
|
164
|
+
echo "[$(date)] [GitHub] â ī¸ Spec sync failed for $PROJECT/$SPEC_NAME (non-blocking)" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
echo "[$(date)] [GitHub] â
Synced $PROJECT/$SPEC_NAME" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
168
|
+
fi
|
|
169
|
+
done
|
|
170
|
+
|
|
171
|
+
echo "[$(date)] [GitHub] â
Multi-spec sync complete ($SYNCABLE_COUNT synced)" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
146
172
|
else
|
|
147
|
-
echo "[$(date)] [GitHub]
|
|
173
|
+
echo "[$(date)] [GitHub] âšī¸ No syncable specs (all specs are _parent or syncEnabled=false)" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
148
174
|
fi
|
|
175
|
+
|
|
176
|
+
# Cleanup temp file
|
|
177
|
+
rm -f /tmp/specweave-detected-specs.json 2>/dev/null || true
|
|
149
178
|
else
|
|
150
|
-
# Sync all modified specs (check git diff)
|
|
179
|
+
# Fallback: Sync all modified specs (check git diff)
|
|
151
180
|
echo "[$(date)] [GitHub] đ Checking for modified specs..." >> "$DEBUG_LOG" 2>/dev/null || true
|
|
152
181
|
|
|
153
|
-
MODIFIED_SPECS=$(git diff --name-only HEAD .specweave/docs/internal/specs
|
|
182
|
+
MODIFIED_SPECS=$(git diff --name-only HEAD .specweave/docs/internal/specs/**/*.md 2>/dev/null || echo "")
|
|
154
183
|
|
|
155
184
|
if [ -n "$MODIFIED_SPECS" ]; then
|
|
156
185
|
echo "[$(date)] [GitHub] đ Found modified specs:" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
@@ -158,7 +187,7 @@ else
|
|
|
158
187
|
|
|
159
188
|
# Sync each modified spec
|
|
160
189
|
echo "$MODIFIED_SPECS" | while read -r SPEC_FILE; do
|
|
161
|
-
if [ -n "$SPEC_FILE" ]; then
|
|
190
|
+
if [ -n "$SPEC_FILE" ] && [ -f "$SPEC_FILE" ]; then
|
|
162
191
|
echo "[$(date)] [GitHub] đ Syncing $SPEC_FILE..." >> "$DEBUG_LOG" 2>/dev/null || true
|
|
163
192
|
(cd "$PROJECT_ROOT" && node "$SYNC_CLI" --spec "$SPEC_FILE" --provider github) 2>&1 | tee -a "$DEBUG_LOG" >/dev/null || true
|
|
164
193
|
fi
|
|
@@ -8,8 +8,34 @@ import {
|
|
|
8
8
|
} from "../../../src/core/spec-content-sync.js";
|
|
9
9
|
import path from "path";
|
|
10
10
|
import fs from "fs/promises";
|
|
11
|
+
async function getGitHubRepoForProject(project, specPath) {
|
|
12
|
+
try {
|
|
13
|
+
let currentDir = path.dirname(specPath);
|
|
14
|
+
let configPath = path.join(currentDir, ".specweave", "config.json");
|
|
15
|
+
while (!await fs.access(configPath).then(() => true).catch(() => false)) {
|
|
16
|
+
const parentDir = path.dirname(currentDir);
|
|
17
|
+
if (parentDir === currentDir) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
currentDir = parentDir;
|
|
21
|
+
configPath = path.join(currentDir, ".specweave", "config.json");
|
|
22
|
+
}
|
|
23
|
+
const configContent = await fs.readFile(configPath, "utf-8");
|
|
24
|
+
const config = JSON.parse(configContent);
|
|
25
|
+
const projectConfig = config.specs?.projects?.[project];
|
|
26
|
+
if (!projectConfig?.github) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
owner: projectConfig.github.owner,
|
|
31
|
+
repo: projectConfig.github.repo
|
|
32
|
+
};
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
11
37
|
async function syncSpecContentToGitHub(options) {
|
|
12
|
-
|
|
38
|
+
let { specPath, owner, repo, dryRun = false, verbose = false } = options;
|
|
13
39
|
try {
|
|
14
40
|
const spec = await parseSpecContent(specPath);
|
|
15
41
|
if (!spec) {
|
|
@@ -20,10 +46,26 @@ async function syncSpecContentToGitHub(options) {
|
|
|
20
46
|
};
|
|
21
47
|
}
|
|
22
48
|
if (verbose) {
|
|
23
|
-
console.log(`\u{1F4C4} Parsed spec: ${spec.
|
|
49
|
+
console.log(`\u{1F4C4} Parsed spec: ${spec.identifier.compact}`);
|
|
50
|
+
console.log(` Project: ${spec.project}`);
|
|
24
51
|
console.log(` Title: ${spec.title}`);
|
|
25
52
|
console.log(` User Stories: ${spec.userStories.length}`);
|
|
26
53
|
}
|
|
54
|
+
if (!owner || !repo) {
|
|
55
|
+
const repoConfig = await getGitHubRepoForProject(spec.project, specPath);
|
|
56
|
+
if (!repoConfig) {
|
|
57
|
+
return {
|
|
58
|
+
success: false,
|
|
59
|
+
action: "error",
|
|
60
|
+
error: `No GitHub repository configured for project "${spec.project}". Add specs.projects.${spec.project}.github in config.json`
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
owner = repoConfig.owner;
|
|
64
|
+
repo = repoConfig.repo;
|
|
65
|
+
if (verbose) {
|
|
66
|
+
console.log(` Auto-detected repo: ${owner}/${repo}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
27
69
|
const existingIssueNumber = await hasExternalLink(specPath, "github");
|
|
28
70
|
const client = GitHubClientV2.fromRepo(owner, repo);
|
|
29
71
|
if (existingIssueNumber) {
|
|
@@ -42,7 +84,7 @@ async function syncSpecContentToGitHub(options) {
|
|
|
42
84
|
async function createGitHubIssue(client, spec, options) {
|
|
43
85
|
const { specPath, dryRun, verbose } = options;
|
|
44
86
|
try {
|
|
45
|
-
const title = `[${spec.
|
|
87
|
+
const title = `[${spec.identifier.compact}] ${spec.title}`;
|
|
46
88
|
const body = buildExternalDescription(spec);
|
|
47
89
|
if (verbose) {
|
|
48
90
|
console.log(`
|
|
@@ -62,7 +104,13 @@ ${body}`);
|
|
|
62
104
|
externalUrl: "https://github.com/DRY-RUN"
|
|
63
105
|
};
|
|
64
106
|
}
|
|
65
|
-
const labels = [
|
|
107
|
+
const labels = [
|
|
108
|
+
"specweave",
|
|
109
|
+
"spec",
|
|
110
|
+
spec.project,
|
|
111
|
+
// backend, frontend, mobile, etc.
|
|
112
|
+
spec.metadata.priority || "P2"
|
|
113
|
+
].filter(Boolean);
|
|
66
114
|
const issue = await client.createEpicIssue(title, body, void 0, labels);
|
|
67
115
|
if (verbose) {
|
|
68
116
|
console.log(`\u2705 Created issue #${issue.number}`);
|
|
@@ -96,8 +144,9 @@ async function updateGitHubIssue(client, spec, issueNumber, options) {
|
|
|
96
144
|
console.log(`
|
|
97
145
|
\u{1F504} Checking for changes in issue #${issueNumber}`);
|
|
98
146
|
}
|
|
147
|
+
const cleanTitle = issue.title.replace(/^\[[A-Z]{2,4}-[A-Z0-9-]+\]\s*/, "");
|
|
99
148
|
const changes = detectContentChanges(spec, {
|
|
100
|
-
title:
|
|
149
|
+
title: cleanTitle,
|
|
101
150
|
description: issue.body || "",
|
|
102
151
|
userStoryCount: countUserStoriesInBody(issue.body || "")
|
|
103
152
|
});
|
|
@@ -118,7 +167,7 @@ async function updateGitHubIssue(client, spec, issueNumber, options) {
|
|
|
118
167
|
console.log(` - ${change}`);
|
|
119
168
|
}
|
|
120
169
|
}
|
|
121
|
-
const newTitle = `[${spec.
|
|
170
|
+
const newTitle = `[${spec.identifier.compact}] ${spec.title}`;
|
|
122
171
|
const newBody = buildExternalDescription(spec);
|
|
123
172
|
if (dryRun) {
|
|
124
173
|
console.log("\n\u{1F50D} Dry run - would update issue:");
|