specweave 0.22.14 → 0.23.1
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 +178 -1
- package/dist/src/cli/commands/import-external.d.ts +22 -0
- package/dist/src/cli/commands/import-external.d.ts.map +1 -0
- package/dist/src/cli/commands/import-external.js +282 -0
- package/dist/src/cli/commands/import-external.js.map +1 -0
- package/dist/src/cli/commands/init.d.ts.map +1 -1
- package/dist/src/cli/commands/init.js +46 -0
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/helpers/github-repo-selector.d.ts +59 -0
- package/dist/src/cli/helpers/github-repo-selector.d.ts.map +1 -0
- package/dist/src/cli/helpers/github-repo-selector.js +265 -0
- package/dist/src/cli/helpers/github-repo-selector.js.map +1 -0
- package/dist/src/cli/helpers/issue-tracker/index.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/index.js +5 -17
- package/dist/src/cli/helpers/issue-tracker/index.js.map +1 -1
- package/dist/src/config/types.d.ts +16 -16
- package/dist/src/core/increment/ac-status-manager.d.ts.map +1 -1
- package/dist/src/core/increment/ac-status-manager.js +4 -2
- package/dist/src/core/increment/ac-status-manager.js.map +1 -1
- package/dist/src/core/increment/completion-validator.d.ts +30 -1
- package/dist/src/core/increment/completion-validator.d.ts.map +1 -1
- package/dist/src/core/increment/completion-validator.js +151 -3
- package/dist/src/core/increment/completion-validator.js.map +1 -1
- package/dist/src/core/increment/increment-archiver.d.ts +25 -0
- package/dist/src/core/increment/increment-archiver.d.ts.map +1 -1
- package/dist/src/core/increment/increment-archiver.js +130 -3
- package/dist/src/core/increment/increment-archiver.js.map +1 -1
- package/dist/src/core/living-docs/feature-archiver.d.ts +37 -0
- package/dist/src/core/living-docs/feature-archiver.d.ts.map +1 -1
- package/dist/src/core/living-docs/feature-archiver.js +262 -18
- package/dist/src/core/living-docs/feature-archiver.js.map +1 -1
- package/dist/src/core/living-docs/feature-id-manager.d.ts +17 -0
- package/dist/src/core/living-docs/feature-id-manager.d.ts.map +1 -1
- package/dist/src/core/living-docs/feature-id-manager.js +25 -0
- package/dist/src/core/living-docs/feature-id-manager.js.map +1 -1
- package/dist/src/core/living-docs/living-docs-sync.d.ts +14 -0
- package/dist/src/core/living-docs/living-docs-sync.d.ts.map +1 -1
- package/dist/src/core/living-docs/living-docs-sync.js +46 -0
- package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
- package/dist/src/core/repo-structure/repo-id-generator.d.ts +20 -0
- package/dist/src/core/repo-structure/repo-id-generator.d.ts.map +1 -1
- package/dist/src/core/repo-structure/repo-id-generator.js +44 -0
- package/dist/src/core/repo-structure/repo-id-generator.js.map +1 -1
- package/dist/src/core/repo-structure/repo-structure-manager.d.ts.map +1 -1
- package/dist/src/core/repo-structure/repo-structure-manager.js +5 -2
- package/dist/src/core/repo-structure/repo-structure-manager.js.map +1 -1
- package/dist/src/core/sync/sync-event-logger.d.ts +15 -1
- package/dist/src/core/sync/sync-event-logger.d.ts.map +1 -1
- package/dist/src/core/sync/sync-event-logger.js +39 -1
- package/dist/src/core/sync/sync-event-logger.js.map +1 -1
- package/dist/src/core/types/sync-config-validator.d.ts +57 -0
- package/dist/src/core/types/sync-config-validator.d.ts.map +1 -0
- package/dist/src/core/types/sync-config-validator.js +116 -0
- package/dist/src/core/types/sync-config-validator.js.map +1 -0
- package/dist/src/importers/duplicate-detector.d.ts +107 -0
- package/dist/src/importers/duplicate-detector.d.ts.map +1 -0
- package/dist/src/importers/duplicate-detector.js +189 -0
- package/dist/src/importers/duplicate-detector.js.map +1 -0
- package/dist/src/importers/import-coordinator.d.ts +15 -0
- package/dist/src/importers/import-coordinator.d.ts.map +1 -1
- package/dist/src/importers/import-coordinator.js +43 -1
- package/dist/src/importers/import-coordinator.js.map +1 -1
- package/dist/src/importers/item-converter.d.ts +5 -0
- package/dist/src/importers/item-converter.d.ts.map +1 -1
- package/dist/src/importers/item-converter.js +27 -2
- package/dist/src/importers/item-converter.js.map +1 -1
- package/dist/src/importers/rate-limiter.d.ts +128 -0
- package/dist/src/importers/rate-limiter.d.ts.map +1 -0
- package/dist/src/importers/rate-limiter.js +200 -0
- package/dist/src/importers/rate-limiter.js.map +1 -0
- package/dist/src/init/compliance/types.d.ts +2 -2
- package/dist/src/integrations/ado/ado-client.d.ts +6 -0
- package/dist/src/integrations/ado/ado-client.d.ts.map +1 -1
- package/dist/src/integrations/ado/ado-client.js +23 -0
- package/dist/src/integrations/ado/ado-client.js.map +1 -1
- package/dist/src/integrations/jira/jira-client.d.ts +6 -0
- package/dist/src/integrations/jira/jira-client.d.ts.map +1 -1
- package/dist/src/integrations/jira/jira-client.js +38 -0
- package/dist/src/integrations/jira/jira-client.js.map +1 -1
- package/dist/src/sync/external-item-sync-service.d.ts +150 -0
- package/dist/src/sync/external-item-sync-service.d.ts.map +1 -0
- package/dist/src/sync/external-item-sync-service.js +241 -0
- package/dist/src/sync/external-item-sync-service.js.map +1 -0
- package/dist/src/sync/format-preservation-sync.d.ts +90 -0
- package/dist/src/sync/format-preservation-sync.d.ts.map +1 -0
- package/dist/src/sync/format-preservation-sync.js +173 -0
- package/dist/src/sync/format-preservation-sync.js.map +1 -0
- package/dist/src/sync/index.d.ts +8 -0
- package/dist/src/sync/index.d.ts.map +1 -0
- package/dist/src/sync/index.js +6 -0
- package/dist/src/sync/index.js.map +1 -0
- package/dist/src/sync/sync-coordinator.d.ts +49 -0
- package/dist/src/sync/sync-coordinator.d.ts.map +1 -0
- package/dist/src/sync/sync-coordinator.js +248 -0
- package/dist/src/sync/sync-coordinator.js.map +1 -0
- package/dist/src/sync/sync-metadata.d.ts +75 -0
- package/dist/src/sync/sync-metadata.d.ts.map +1 -0
- package/dist/src/sync/sync-metadata.js +100 -0
- package/dist/src/sync/sync-metadata.js.map +1 -0
- package/dist/src/types/living-docs-us-file.d.ts +63 -0
- package/dist/src/types/living-docs-us-file.d.ts.map +1 -0
- package/dist/src/types/living-docs-us-file.js +27 -0
- package/dist/src/types/living-docs-us-file.js.map +1 -0
- package/dist/src/validators/format-preservation-validator.d.ts +127 -0
- package/dist/src/validators/format-preservation-validator.d.ts.map +1 -0
- package/dist/src/validators/format-preservation-validator.js +187 -0
- package/dist/src/validators/format-preservation-validator.js.map +1 -0
- package/package.json +3 -2
- package/plugins/specweave/.claude-plugin/plugin.json +20 -0
- package/plugins/specweave/commands/specweave-archive-features.md +11 -1
- package/plugins/specweave/commands/specweave-archive.md +51 -15
- package/plugins/specweave/commands/specweave-import-docs.md +88 -278
- package/plugins/specweave/commands/specweave-import-external.md +407 -0
- package/plugins/specweave/hooks/post-edit-spec.sh +94 -0
- package/plugins/specweave/hooks/post-increment-completion.sh +0 -0
- package/plugins/specweave/hooks/post-spec-update.sh +0 -0
- package/plugins/specweave/hooks/post-task-completion.sh +13 -3
- package/plugins/specweave/hooks/post-write-spec.sh +91 -0
- package/plugins/specweave/lib/hooks/auto-transition.js +1 -1
- package/plugins/specweave/lib/hooks/auto-transition.js.bak +50 -0
- package/plugins/specweave/lib/hooks/auto-transition.ts +1 -1
- package/plugins/specweave/lib/hooks/auto-transition.ts.bak +84 -0
- package/plugins/specweave/lib/hooks/git-diff-analyzer.d.js.bak +0 -0
- package/plugins/specweave/lib/hooks/git-diff-analyzer.d.ts.bak +89 -0
- package/plugins/specweave/lib/hooks/git-diff-analyzer.js.bak +142 -0
- package/plugins/specweave/lib/hooks/git-diff-analyzer.ts.bak +269 -0
- package/plugins/specweave/lib/hooks/invoke-translator-skill.d.js.bak +0 -0
- package/plugins/specweave/lib/hooks/invoke-translator-skill.d.ts.bak +60 -0
- package/plugins/specweave/lib/hooks/invoke-translator-skill.js +1 -1
- package/plugins/specweave/lib/hooks/invoke-translator-skill.js.bak +155 -0
- package/plugins/specweave/lib/hooks/invoke-translator-skill.ts +1 -1
- package/plugins/specweave/lib/hooks/invoke-translator-skill.ts.bak +264 -0
- package/plugins/specweave/lib/hooks/prepare-reflection-context.d.js.bak +0 -0
- package/plugins/specweave/lib/hooks/prepare-reflection-context.d.ts.bak +42 -0
- package/plugins/specweave/lib/hooks/prepare-reflection-context.js.bak +110 -0
- package/plugins/specweave/lib/hooks/prepare-reflection-context.ts.bak +178 -0
- package/plugins/specweave/lib/hooks/reflection-config-loader.d.js.bak +0 -0
- package/plugins/specweave/lib/hooks/reflection-config-loader.d.ts.bak +45 -0
- package/plugins/specweave/lib/hooks/reflection-config-loader.js.bak +92 -0
- package/plugins/specweave/lib/hooks/reflection-config-loader.ts.bak +156 -0
- package/plugins/specweave/lib/hooks/reflection-parser.d.js.bak +0 -0
- package/plugins/specweave/lib/hooks/reflection-parser.d.ts.bak +33 -0
- package/plugins/specweave/lib/hooks/reflection-parser.js.bak +301 -0
- package/plugins/specweave/lib/hooks/reflection-parser.ts.bak +484 -0
- package/plugins/specweave/lib/hooks/reflection-prompt-builder.d.js.bak +0 -0
- package/plugins/specweave/lib/hooks/reflection-prompt-builder.d.ts.bak +56 -0
- package/plugins/specweave/lib/hooks/reflection-prompt-builder.js.bak +182 -0
- package/plugins/specweave/lib/hooks/reflection-prompt-builder.ts.bak +306 -0
- package/plugins/specweave/lib/hooks/reflection-storage.d.js.bak +0 -0
- package/plugins/specweave/lib/hooks/reflection-storage.d.ts.bak +64 -0
- package/plugins/specweave/lib/hooks/reflection-storage.js.bak +231 -0
- package/plugins/specweave/lib/hooks/reflection-storage.ts.bak +369 -0
- package/plugins/specweave/lib/hooks/run-self-reflection.d.js.bak +0 -0
- package/plugins/specweave/lib/hooks/run-self-reflection.d.ts.bak +43 -0
- package/plugins/specweave/lib/hooks/run-self-reflection.js.bak +132 -0
- package/plugins/specweave/lib/hooks/run-self-reflection.ts.bak +258 -0
- package/plugins/specweave/lib/hooks/sync-cache.js.bak +294 -0
- package/plugins/specweave/lib/hooks/sync-living-docs.d.js.bak +1 -0
- package/plugins/specweave/lib/hooks/sync-living-docs.d.ts.bak +27 -0
- package/plugins/specweave/lib/hooks/sync-living-docs.js +35 -1
- package/plugins/specweave/lib/hooks/sync-living-docs.js.bak +339 -0
- package/plugins/specweave/lib/hooks/sync-us-tasks.js +179 -3
- package/plugins/specweave/lib/hooks/sync-us-tasks.js.bak +476 -0
- package/plugins/specweave/lib/hooks/translate-file.d.js.bak +0 -0
- package/plugins/specweave/lib/hooks/translate-file.d.ts.bak +59 -0
- package/plugins/specweave/lib/hooks/translate-file.js +1 -1
- package/plugins/specweave/lib/hooks/translate-file.js.bak +289 -0
- package/plugins/specweave/lib/hooks/translate-file.ts +1 -1
- package/plugins/specweave/lib/hooks/translate-file.ts.bak +428 -0
- package/plugins/specweave/lib/hooks/translate-living-docs.d.js.bak +0 -0
- package/plugins/specweave/lib/hooks/translate-living-docs.d.ts.bak +13 -0
- package/plugins/specweave/lib/hooks/translate-living-docs.js.bak +119 -0
- package/plugins/specweave/lib/hooks/translate-living-docs.ts.bak +224 -0
- package/plugins/specweave/lib/hooks/update-ac-status.js +1 -1
- package/plugins/specweave/lib/hooks/update-ac-status.js.bak +51 -0
- package/plugins/specweave/lib/hooks/update-ac-status.ts +1 -1
- package/plugins/specweave/lib/hooks/update-ac-status.ts.bak +103 -0
- package/plugins/specweave/lib/hooks/update-tasks-md.d.js.bak +1 -0
- package/plugins/specweave/lib/hooks/update-tasks-md.d.ts.bak +29 -0
- package/plugins/specweave/lib/hooks/update-tasks-md.js.bak +296 -0
- package/plugins/specweave/lib/hooks/update-tasks-md.ts.bak +489 -0
- package/plugins/specweave/lib/vendor/core/increment/ac-status-manager.d.ts +115 -0
- package/plugins/specweave/lib/vendor/core/increment/ac-status-manager.js +345 -0
- package/plugins/specweave/lib/vendor/core/increment/ac-status-manager.js.map +1 -0
- package/plugins/specweave/lib/vendor/core/increment/active-increment-manager.d.ts +106 -0
- package/plugins/specweave/lib/vendor/core/increment/active-increment-manager.js +220 -0
- package/plugins/specweave/lib/vendor/core/increment/active-increment-manager.js.map +1 -0
- package/plugins/specweave/lib/vendor/core/increment/auto-transition-manager.d.ts +60 -0
- package/plugins/specweave/lib/vendor/core/increment/auto-transition-manager.js +192 -0
- package/plugins/specweave/lib/vendor/core/increment/auto-transition-manager.js.map +1 -0
- package/plugins/specweave/lib/vendor/core/increment/duplicate-detector.d.ts +52 -0
- package/plugins/specweave/lib/vendor/core/increment/duplicate-detector.js +276 -0
- package/plugins/specweave/lib/vendor/core/increment/duplicate-detector.js.map +1 -0
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.d.ts +163 -0
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js +541 -0
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js.map +1 -0
- package/plugins/specweave/lib/vendor/core/types/increment-metadata.d.ts +157 -0
- package/plugins/specweave/lib/vendor/core/types/increment-metadata.js +191 -0
- package/plugins/specweave/lib/vendor/core/types/increment-metadata.js.map +1 -0
- package/plugins/specweave/lib/vendor/generators/spec/task-parser.d.ts +95 -0
- package/plugins/specweave/lib/vendor/generators/spec/task-parser.js +301 -0
- package/plugins/specweave/lib/vendor/generators/spec/task-parser.js.map +1 -0
- package/plugins/specweave/lib/vendor/utils/logger.d.ts +48 -0
- package/plugins/specweave/lib/vendor/utils/logger.js +53 -0
- package/plugins/specweave/lib/vendor/utils/logger.js.map +1 -0
- package/plugins/specweave/lib/vendor/utils/translation.d.ts +187 -0
- package/plugins/specweave/lib/vendor/utils/translation.js +414 -0
- package/plugins/specweave/lib/vendor/utils/translation.js.map +1 -0
- package/plugins/specweave-github/commands/specweave-github-update-user-story.md +1 -1
- package/plugins/specweave-github/skills/github-issue-standard/SKILL.md +1 -1
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +531 -0
|
@@ -54,7 +54,9 @@ async function syncLivingDocs(incrementId) {
|
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
// T-034E: Use FormatPreservationSyncService for origin-aware sync
|
|
58
|
+
await syncWithFormatPreservation(incrementId);
|
|
59
|
+
|
|
58
60
|
console.log("\u2705 Living docs sync complete\n");
|
|
59
61
|
} catch (error) {
|
|
60
62
|
console.error("\u274C Error syncing living docs:", error);
|
|
@@ -334,6 +336,38 @@ if (isMainModule) {
|
|
|
334
336
|
console.error("\u274C Fatal error:", error);
|
|
335
337
|
});
|
|
336
338
|
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Sync with format preservation (T-034E)
|
|
342
|
+
* Routes sync based on origin metadata (external vs internal)
|
|
343
|
+
*/
|
|
344
|
+
async function syncWithFormatPreservation(incrementId) {
|
|
345
|
+
try {
|
|
346
|
+
console.log("\n🔄 Using format-preserving sync...");
|
|
347
|
+
|
|
348
|
+
const { SyncCoordinator } = await import("../../../../dist/src/sync/sync-coordinator.js");
|
|
349
|
+
|
|
350
|
+
const coordinator = new SyncCoordinator({
|
|
351
|
+
projectRoot: process.cwd(),
|
|
352
|
+
incrementId
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
const result = await coordinator.syncIncrementCompletion();
|
|
356
|
+
|
|
357
|
+
if (result.success) {
|
|
358
|
+
console.log(`✅ Format-preserving sync complete (${result.syncMode} mode)`);
|
|
359
|
+
console.log(` ${result.userStoriesSynced} user story/stories synced`);
|
|
360
|
+
} else {
|
|
361
|
+
console.log(`⚠️ Sync completed with ${result.errors.length} error(s)`);
|
|
362
|
+
result.errors.forEach(err => console.error(` - ${err}`));
|
|
363
|
+
}
|
|
364
|
+
} catch (error) {
|
|
365
|
+
console.error("⚠️ Format-preserving sync error:", error);
|
|
366
|
+
console.log(" Falling back to legacy sync...");
|
|
367
|
+
// Don't fail - just log and continue
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
337
371
|
export {
|
|
338
372
|
syncLivingDocs
|
|
339
373
|
};
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { execSync } from "child_process";
|
|
5
|
+
async function syncLivingDocs(incrementId) {
|
|
6
|
+
try {
|
|
7
|
+
console.log(`
|
|
8
|
+
\u{1F4DA} Checking living docs sync for increment: ${incrementId}`);
|
|
9
|
+
const configPath = path.join(process.cwd(), ".specweave", "config.json");
|
|
10
|
+
let config = {};
|
|
11
|
+
if (fs.existsSync(configPath)) {
|
|
12
|
+
config = JSON.parse(await fs.readFile(configPath, "utf-8"));
|
|
13
|
+
}
|
|
14
|
+
const syncEnabled = config.hooks?.post_task_completion?.sync_living_docs ?? false;
|
|
15
|
+
if (!syncEnabled) {
|
|
16
|
+
console.log("\u2139\uFE0F Living docs sync disabled in config");
|
|
17
|
+
console.log(" To enable: Set hooks.post_task_completion.sync_living_docs = true");
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
console.log("\u2705 Living docs sync enabled");
|
|
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{1F4CA} Using hierarchical distribution mode (v2.1 - Epic + User Stories)");
|
|
31
|
+
const result = await hierarchicalDistribution(incrementId);
|
|
32
|
+
specCopied = result.success;
|
|
33
|
+
changedDocs = result.changedFiles;
|
|
34
|
+
}
|
|
35
|
+
if (changedDocs.length === 0 && !specCopied) {
|
|
36
|
+
console.log("\u2139\uFE0F No living docs changed");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
console.log(`\u{1F4C4} Changed/created ${changedDocs.length} file(s)`);
|
|
40
|
+
|
|
41
|
+
// ========================================================================
|
|
42
|
+
// CHECK PERMISSION: canUpdateExternalItems (v0.24.0 - Three-Permission Architecture)
|
|
43
|
+
// ========================================================================
|
|
44
|
+
// This permission controls whether SpecWeave can UPDATE externally-created items
|
|
45
|
+
// (full content: title, description, ACs, tasks, comments).
|
|
46
|
+
// If false, living docs sync happens locally but doesn't push to external tools.
|
|
47
|
+
const canUpdateExternal = config.sync?.settings?.canUpdateExternalItems ?? false;
|
|
48
|
+
|
|
49
|
+
if (!canUpdateExternal) {
|
|
50
|
+
console.log("\u2139\uFE0F GitHub sync skipped (canUpdateExternalItems = false)");
|
|
51
|
+
console.log(" Living docs updated locally only");
|
|
52
|
+
console.log(" To enable: Set sync.settings.canUpdateExternalItems = true in config.json");
|
|
53
|
+
console.log("\u2705 Living docs sync complete (local only)\n");
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
await syncToGitHub(incrementId, changedDocs);
|
|
58
|
+
console.log("\u2705 Living docs sync complete\n");
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error("\u274C Error syncing living docs:", error);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function intelligentSyncLivingDocs(incrementId, config) {
|
|
64
|
+
console.log(" \u26A0\uFE0F Intelligent sync not yet fully implemented");
|
|
65
|
+
console.log(" Falling back to hierarchical distribution mode...");
|
|
66
|
+
return await hierarchicalDistribution(incrementId);
|
|
67
|
+
}
|
|
68
|
+
async function hierarchicalDistribution(incrementId) {
|
|
69
|
+
try {
|
|
70
|
+
// ============================================================================
|
|
71
|
+
// LONG-TERM FIX (2025-11-19): Use LivingDocsSync instead of old SpecDistributor
|
|
72
|
+
// ============================================================================
|
|
73
|
+
//
|
|
74
|
+
// Why this change:
|
|
75
|
+
// - Old SpecDistributor.distribute() method no longer exists (removed in v3.0.0)
|
|
76
|
+
// - LivingDocsSync is the official, stable API for syncing increments
|
|
77
|
+
// - Used by /specweave:sync-docs command - battle-tested and maintained
|
|
78
|
+
// - Future-proof: Won't break when internal APIs change
|
|
79
|
+
//
|
|
80
|
+
// Architecture:
|
|
81
|
+
// - LivingDocsSync delegates to FeatureIDManager, TaskProjectSpecificGenerator, etc.
|
|
82
|
+
// - Handles greenfield/brownfield detection automatically
|
|
83
|
+
// - Returns consistent SyncResult interface
|
|
84
|
+
//
|
|
85
|
+
// Previous broken code:
|
|
86
|
+
// const { SpecDistributor } = await import("../../../../dist/src/core/living-docs/index.js");
|
|
87
|
+
// const distributor = new SpecDistributor(projectRoot, { overwriteExisting: false, createBackups: true });
|
|
88
|
+
// const result = await distributor.distribute(incrementId); // ❌ Method doesn't exist
|
|
89
|
+
//
|
|
90
|
+
// ============================================================================
|
|
91
|
+
|
|
92
|
+
const { LivingDocsSync } = await import("../../../../dist/src/core/living-docs/living-docs-sync.js");
|
|
93
|
+
|
|
94
|
+
console.log(" \u{1F4CA} Syncing increment to living docs structure...");
|
|
95
|
+
const projectRoot = process.cwd();
|
|
96
|
+
|
|
97
|
+
// ========================================================================
|
|
98
|
+
// USE FEATURE ID FROM ENVIRONMENT (NEW in v0.23.0 - Increment 0047)
|
|
99
|
+
// ========================================================================
|
|
100
|
+
// If FEATURE_ID is provided via environment variable (extracted from spec.md),
|
|
101
|
+
// use it directly instead of auto-generating. This ensures correct traceability.
|
|
102
|
+
const explicitFeatureId = process.env.FEATURE_ID;
|
|
103
|
+
if (explicitFeatureId) {
|
|
104
|
+
console.log(` \u{1F4CE} Using explicit feature ID from spec.md: ${explicitFeatureId}`);
|
|
105
|
+
} else {
|
|
106
|
+
console.log(" \u{1F504} Feature ID will be auto-generated from increment number");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Create logger adapter for LivingDocsSync
|
|
110
|
+
const logger = {
|
|
111
|
+
log: (msg) => console.log(` ${msg}`),
|
|
112
|
+
error: (msg, err) => console.error(` ${msg}`, err || ''),
|
|
113
|
+
warn: (msg) => console.warn(` ${msg}`)
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const sync = new LivingDocsSync(projectRoot, { logger });
|
|
117
|
+
const result = await sync.syncIncrement(incrementId, {
|
|
118
|
+
dryRun: false,
|
|
119
|
+
force: false,
|
|
120
|
+
// Pass explicit feature ID if available (v0.23.0+)
|
|
121
|
+
explicitFeatureId: explicitFeatureId || undefined
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
if (!result.success) {
|
|
125
|
+
console.error(` \u274C Sync failed with errors:`);
|
|
126
|
+
for (const error of result.errors) {
|
|
127
|
+
console.error(` - ${error}`);
|
|
128
|
+
}
|
|
129
|
+
return { success: false, changedFiles: [] };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
console.log(` \u2705 Living docs sync complete:`);
|
|
133
|
+
console.log(` Feature ID: ${result.featureId}`);
|
|
134
|
+
console.log(` Files created/updated: ${result.filesCreated.length + result.filesUpdated.length}`);
|
|
135
|
+
|
|
136
|
+
const changedFiles = [...result.filesCreated, ...result.filesUpdated];
|
|
137
|
+
|
|
138
|
+
// NEW (v0.23.0): Sync tasks from tasks.md to living docs US files
|
|
139
|
+
// Part of increment 0047-us-task-linkage implementation
|
|
140
|
+
try {
|
|
141
|
+
const { syncUSTasksToLivingDocs } = await import("./sync-us-tasks.js");
|
|
142
|
+
const taskSyncResult = await syncUSTasksToLivingDocs(
|
|
143
|
+
incrementId,
|
|
144
|
+
projectRoot,
|
|
145
|
+
result.featureId,
|
|
146
|
+
{}
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
if (taskSyncResult.success && taskSyncResult.updatedFiles.length > 0) {
|
|
150
|
+
changedFiles.push(...taskSyncResult.updatedFiles);
|
|
151
|
+
}
|
|
152
|
+
} catch (error) {
|
|
153
|
+
// Don't fail entire sync if US-Task sync fails (graceful degradation)
|
|
154
|
+
console.log(` \u26A0\uFE0F US-Task sync failed (non-fatal):`, error.message);
|
|
155
|
+
console.log(` \u{1F4A1} Tip: This is a new feature (v0.23.0) - may need task parser build`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
success: true,
|
|
160
|
+
changedFiles
|
|
161
|
+
};
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.error(` \u274C Living docs sync failed: ${error}`);
|
|
164
|
+
console.error(error.stack);
|
|
165
|
+
console.error(" \u26A0\uFE0F Living docs sync skipped due to error");
|
|
166
|
+
console.error(" \u{1F4A1} Tip: Run /specweave:sync-docs manually to retry");
|
|
167
|
+
return {
|
|
168
|
+
success: false,
|
|
169
|
+
changedFiles: []
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
async function extractAndMergeLivingDocs(incrementId) {
|
|
174
|
+
try {
|
|
175
|
+
const {
|
|
176
|
+
parseIncrementSpec,
|
|
177
|
+
parseLivingDocsSpec,
|
|
178
|
+
extractSpecId,
|
|
179
|
+
mergeUserStories,
|
|
180
|
+
generateRelatedDocsLinks,
|
|
181
|
+
writeLivingDocsSpec
|
|
182
|
+
} = await import("../../../../dist/src/utils/spec-parser.js");
|
|
183
|
+
const projectRoot = process.cwd();
|
|
184
|
+
const incrementSpecPath = path.join(projectRoot, ".specweave", "increments", incrementId, "spec.md");
|
|
185
|
+
if (!fs.existsSync(incrementSpecPath)) {
|
|
186
|
+
console.log(`\u26A0\uFE0F Increment spec not found: ${incrementSpecPath}`);
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
console.log(` \u{1F4D6} Parsing increment spec: ${incrementId}`);
|
|
190
|
+
const incrementSpec = await parseIncrementSpec(incrementSpecPath);
|
|
191
|
+
if (incrementSpec.userStories.length === 0) {
|
|
192
|
+
console.log(`\u2139\uFE0F No user stories found in increment spec, skipping sync`);
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
console.log(` \u2705 Found ${incrementSpec.userStories.length} user stories in increment`);
|
|
196
|
+
const specId = incrementSpec.implementsSpec || extractSpecId(incrementId);
|
|
197
|
+
const livingDocsDir = path.join(projectRoot, ".specweave", "docs", "internal", "specs", "default");
|
|
198
|
+
const livingDocsPath = path.join(livingDocsDir, `${specId}-${incrementId.replace(/^\d+-/, "")}.md`);
|
|
199
|
+
const livingDocsExists = fs.existsSync(livingDocsPath);
|
|
200
|
+
if (livingDocsExists) {
|
|
201
|
+
console.log(` \u{1F4DA} Living docs spec exists, merging user stories...`);
|
|
202
|
+
const livingSpec = await parseLivingDocsSpec(livingDocsPath);
|
|
203
|
+
const mergedStories = mergeUserStories(
|
|
204
|
+
livingSpec.userStories,
|
|
205
|
+
incrementSpec.userStories,
|
|
206
|
+
incrementId
|
|
207
|
+
);
|
|
208
|
+
const newStoriesCount = mergedStories.length - livingSpec.userStories.length;
|
|
209
|
+
const existingEntry = livingSpec.implementationHistory.find((e) => e.increment === incrementId);
|
|
210
|
+
if (!existingEntry) {
|
|
211
|
+
livingSpec.implementationHistory.push({
|
|
212
|
+
increment: incrementId,
|
|
213
|
+
stories: incrementSpec.userStories.map((s) => s.id),
|
|
214
|
+
status: "complete",
|
|
215
|
+
date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
216
|
+
});
|
|
217
|
+
} else {
|
|
218
|
+
existingEntry.status = "complete";
|
|
219
|
+
existingEntry.date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
220
|
+
}
|
|
221
|
+
livingSpec.userStories = mergedStories;
|
|
222
|
+
livingSpec.lastUpdated = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
223
|
+
await writeLivingDocsSpec(livingDocsPath, livingSpec);
|
|
224
|
+
console.log(` \u2705 Merged ${newStoriesCount} new user stories into living docs`);
|
|
225
|
+
console.log(` \u2705 Updated implementation history for ${incrementId}`);
|
|
226
|
+
} else {
|
|
227
|
+
console.log(` \u{1F4DD} Creating new living docs spec: ${specId}`);
|
|
228
|
+
const relatedDocs = generateRelatedDocsLinks(incrementSpec, projectRoot);
|
|
229
|
+
const livingSpec = {
|
|
230
|
+
id: specId,
|
|
231
|
+
title: incrementSpec.title,
|
|
232
|
+
featureArea: extractFeatureArea(incrementSpec.title),
|
|
233
|
+
overview: incrementSpec.overview,
|
|
234
|
+
userStories: incrementSpec.userStories.map((story) => ({
|
|
235
|
+
...story,
|
|
236
|
+
implementedIn: incrementId,
|
|
237
|
+
status: "complete"
|
|
238
|
+
})),
|
|
239
|
+
implementationHistory: [
|
|
240
|
+
{
|
|
241
|
+
increment: incrementId,
|
|
242
|
+
stories: incrementSpec.userStories.map((s) => s.id),
|
|
243
|
+
status: "complete",
|
|
244
|
+
date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
245
|
+
}
|
|
246
|
+
],
|
|
247
|
+
relatedDocs,
|
|
248
|
+
externalLinks: {},
|
|
249
|
+
priority: incrementSpec.priority,
|
|
250
|
+
status: "active",
|
|
251
|
+
created: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
252
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
253
|
+
};
|
|
254
|
+
await fs.ensureDir(livingDocsDir);
|
|
255
|
+
await writeLivingDocsSpec(livingDocsPath, livingSpec);
|
|
256
|
+
console.log(` \u2705 Created new living docs spec: ${specId}`);
|
|
257
|
+
console.log(` \u2705 Added ${incrementSpec.userStories.length} user stories`);
|
|
258
|
+
console.log(` \u2705 Generated links to ${relatedDocs.architecture.length} architecture docs`);
|
|
259
|
+
console.log(` \u2705 Generated links to ${relatedDocs.adrs.length} ADRs`);
|
|
260
|
+
}
|
|
261
|
+
return true;
|
|
262
|
+
} catch (error) {
|
|
263
|
+
console.error(`\u274C Error extracting/merging living docs: ${error}`);
|
|
264
|
+
console.error(error.stack);
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function extractFeatureArea(title) {
|
|
269
|
+
return title.replace(/^(Increment \d+:\s*)?/, "").trim();
|
|
270
|
+
}
|
|
271
|
+
function detectChangedDocs() {
|
|
272
|
+
try {
|
|
273
|
+
const output = execSync("git diff --name-only .specweave/docs/ 2>/dev/null || true", {
|
|
274
|
+
encoding: "utf-8",
|
|
275
|
+
cwd: process.cwd()
|
|
276
|
+
}).trim();
|
|
277
|
+
if (!output) {
|
|
278
|
+
return [];
|
|
279
|
+
}
|
|
280
|
+
const files = output.split("\n").filter((f) => f.endsWith(".md")).filter((f) => f.length > 0);
|
|
281
|
+
return files;
|
|
282
|
+
} catch (error) {
|
|
283
|
+
console.warn("\u26A0\uFE0F Could not detect git changes:", error);
|
|
284
|
+
return [];
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
async function syncToGitHub(incrementId, changedDocs) {
|
|
288
|
+
try {
|
|
289
|
+
console.log("\n\u{1F504} Syncing to GitHub...");
|
|
290
|
+
const {
|
|
291
|
+
loadIncrementMetadata,
|
|
292
|
+
detectRepo,
|
|
293
|
+
collectLivingDocs,
|
|
294
|
+
updateIssueLivingDocs,
|
|
295
|
+
postArchitectureComment
|
|
296
|
+
} = await import("../../../../dist/plugins/specweave-github/lib/github-issue-updater.js");
|
|
297
|
+
const metadata = await loadIncrementMetadata(incrementId);
|
|
298
|
+
if (!metadata?.github?.issue) {
|
|
299
|
+
console.log("\u2139\uFE0F No GitHub issue linked, skipping GitHub sync");
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const repoInfo = await detectRepo();
|
|
303
|
+
if (!repoInfo) {
|
|
304
|
+
console.log("\u26A0\uFE0F Could not detect GitHub repository, skipping GitHub sync");
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
const { owner, repo } = repoInfo;
|
|
308
|
+
const issueNumber = metadata.github.issue;
|
|
309
|
+
console.log(` Syncing to ${owner}/${repo}#${issueNumber}`);
|
|
310
|
+
const livingDocs = await collectLivingDocs(incrementId);
|
|
311
|
+
if (livingDocs.specs.length > 0 || livingDocs.architecture.length > 0 || livingDocs.diagrams.length > 0) {
|
|
312
|
+
await updateIssueLivingDocs(issueNumber, livingDocs, owner, repo);
|
|
313
|
+
}
|
|
314
|
+
for (const docPath of changedDocs) {
|
|
315
|
+
if (docPath.includes("/architecture/")) {
|
|
316
|
+
await postArchitectureComment(issueNumber, docPath, owner, repo);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
console.log("\u2705 GitHub sync complete");
|
|
320
|
+
} catch (error) {
|
|
321
|
+
console.error("\u274C Error syncing to GitHub:", error);
|
|
322
|
+
console.error(" (Non-blocking - continuing...)");
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
const isMainModule = import.meta.url === `file://${process.argv[1]}`;
|
|
326
|
+
if (isMainModule) {
|
|
327
|
+
const incrementId = process.argv[2];
|
|
328
|
+
if (!incrementId) {
|
|
329
|
+
console.error("\u274C Usage: sync-living-docs <incrementId>");
|
|
330
|
+
console.error(" Example: sync-living-docs 0006-llm-native-i18n");
|
|
331
|
+
process.exit(1);
|
|
332
|
+
}
|
|
333
|
+
syncLivingDocs(incrementId).catch((error) => {
|
|
334
|
+
console.error("\u274C Fatal error:", error);
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
export {
|
|
338
|
+
syncLivingDocs
|
|
339
|
+
};
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import fs from 'fs-extra';
|
|
14
14
|
import path from 'path';
|
|
15
|
-
import { parseTasksWithUSLinks, getAllTasks } from '
|
|
15
|
+
import { parseTasksWithUSLinks, getAllTasks } from '../vendor/generators/spec/task-parser.js';
|
|
16
16
|
import { glob } from 'glob';
|
|
17
17
|
import { getCachedTasks, needsSync, recordSync, batchFileUpdates } from './sync-cache.js';
|
|
18
18
|
|
|
@@ -165,7 +165,14 @@ async function updateUSFile(usFilePath, tasks, incrementId) {
|
|
|
165
165
|
let content = await fs.readFile(usFilePath, 'utf-8');
|
|
166
166
|
let updated = false;
|
|
167
167
|
|
|
168
|
-
// 1. Update
|
|
168
|
+
// 1. Update origin badge (NEW - T-034)
|
|
169
|
+
const updatedOrigin = updateOriginBadge(content, usFilePath);
|
|
170
|
+
if (updatedOrigin !== content) {
|
|
171
|
+
content = updatedOrigin;
|
|
172
|
+
updated = true;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 2. Update task list section
|
|
169
176
|
const taskList = generateTaskList(tasks, incrementId);
|
|
170
177
|
const newTasksSection = `## Tasks\n\n${taskList}`;
|
|
171
178
|
|
|
@@ -181,7 +188,7 @@ async function updateUSFile(usFilePath, tasks, incrementId) {
|
|
|
181
188
|
}
|
|
182
189
|
}
|
|
183
190
|
|
|
184
|
-
//
|
|
191
|
+
// 3. Update AC checkboxes based on task completion
|
|
185
192
|
const updatedACs = updateACCheckboxes(content, tasks);
|
|
186
193
|
if (updatedACs !== content) {
|
|
187
194
|
content = updatedACs;
|
|
@@ -191,6 +198,175 @@ async function updateUSFile(usFilePath, tasks, incrementId) {
|
|
|
191
198
|
return { updated, content };
|
|
192
199
|
}
|
|
193
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Update origin badge in User Story file
|
|
203
|
+
* Detects origin from US ID (E suffix = external)
|
|
204
|
+
*
|
|
205
|
+
* @param {string} content - US file content
|
|
206
|
+
* @param {string} usFilePath - Path to US file
|
|
207
|
+
* @returns {string} Updated content with origin badge
|
|
208
|
+
*/
|
|
209
|
+
function updateOriginBadge(content, usFilePath) {
|
|
210
|
+
// Extract US ID from filename (e.g., us-001e-title.md -> US-001E)
|
|
211
|
+
const filename = path.basename(usFilePath);
|
|
212
|
+
const usIdMatch = filename.match(/^(us-\d{3}e?)-/i);
|
|
213
|
+
|
|
214
|
+
if (!usIdMatch) {
|
|
215
|
+
return content; // Can't determine US ID, skip
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const usId = usIdMatch[1].toUpperCase();
|
|
219
|
+
const isExternal = usId.endsWith('E');
|
|
220
|
+
|
|
221
|
+
// Validate origin immutability (prevent internal ↔ external changes)
|
|
222
|
+
const existingOrigin = extractExistingOrigin(content);
|
|
223
|
+
if (existingOrigin) {
|
|
224
|
+
const existingIsExternal = existingOrigin !== 'internal';
|
|
225
|
+
if (isExternal !== existingIsExternal) {
|
|
226
|
+
console.log(` ⚠️ Origin immutable: Cannot change ${usId} from ${existingOrigin} to ${isExternal ? 'external' : 'internal'}`);
|
|
227
|
+
return content; // Don't modify origin
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Determine origin badge
|
|
232
|
+
let originBadge;
|
|
233
|
+
if (isExternal) {
|
|
234
|
+
// Try to detect external source from metadata
|
|
235
|
+
const externalSource = detectExternalSource(content);
|
|
236
|
+
originBadge = getExternalOriginBadge(externalSource, content);
|
|
237
|
+
} else {
|
|
238
|
+
originBadge = '🏠 **Internal**';
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Check if origin badge already exists
|
|
242
|
+
const originPattern = /\*\*Origin\*\*:\s*.*/;
|
|
243
|
+
if (originPattern.test(content)) {
|
|
244
|
+
// Replace existing origin badge
|
|
245
|
+
return content.replace(originPattern, `**Origin**: ${originBadge}`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Add origin badge after frontmatter (if exists) or at beginning
|
|
249
|
+
const frontmatterEnd = content.match(/^---\n.*?\n---\n/s);
|
|
250
|
+
if (frontmatterEnd) {
|
|
251
|
+
const insertPos = frontmatterEnd[0].length;
|
|
252
|
+
return content.slice(0, insertPos) + `\n**Origin**: ${originBadge}\n\n` + content.slice(insertPos);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// No frontmatter, add at beginning after title
|
|
256
|
+
const titleMatch = content.match(/^#\s+.+\n/);
|
|
257
|
+
if (titleMatch) {
|
|
258
|
+
const insertPos = titleMatch[0].length;
|
|
259
|
+
return content.slice(0, insertPos) + `\n**Origin**: ${originBadge}\n\n` + content.slice(insertPos);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Fallback: add at very beginning
|
|
263
|
+
return `**Origin**: ${originBadge}\n\n` + content;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Extract existing origin from content (for immutability validation)
|
|
268
|
+
*
|
|
269
|
+
* @param {string} content - US file content
|
|
270
|
+
* @returns {string|null} Existing origin (internal, github, jira, ado) or null
|
|
271
|
+
*/
|
|
272
|
+
function extractExistingOrigin(content) {
|
|
273
|
+
const originMatch = content.match(/\*\*Origin\*\*:\s*(.+)/);
|
|
274
|
+
if (!originMatch) {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const originText = originMatch[1].toLowerCase();
|
|
279
|
+
if (originText.includes('internal')) {
|
|
280
|
+
return 'internal';
|
|
281
|
+
}
|
|
282
|
+
if (originText.includes('github')) {
|
|
283
|
+
return 'github';
|
|
284
|
+
}
|
|
285
|
+
if (originText.includes('jira')) {
|
|
286
|
+
return 'jira';
|
|
287
|
+
}
|
|
288
|
+
if (originText.includes('ado') || originText.includes('azure')) {
|
|
289
|
+
return 'ado';
|
|
290
|
+
}
|
|
291
|
+
if (originText.includes('external')) {
|
|
292
|
+
return 'external'; // Generic external
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Detect external source from content metadata
|
|
300
|
+
*
|
|
301
|
+
* @param {string} content - US file content
|
|
302
|
+
* @returns {string} External source (github, jira, ado) or 'unknown'
|
|
303
|
+
*/
|
|
304
|
+
function detectExternalSource(content) {
|
|
305
|
+
// Check frontmatter for external_source or externalSource
|
|
306
|
+
const frontmatterMatch = content.match(/^---\n(.*?)\n---/s);
|
|
307
|
+
if (frontmatterMatch) {
|
|
308
|
+
const frontmatter = frontmatterMatch[1];
|
|
309
|
+
if (frontmatter.includes('external_source: github') || frontmatter.includes('externalSource: github')) {
|
|
310
|
+
return 'github';
|
|
311
|
+
}
|
|
312
|
+
if (frontmatter.includes('external_source: jira') || frontmatter.includes('externalSource: jira')) {
|
|
313
|
+
return 'jira';
|
|
314
|
+
}
|
|
315
|
+
if (frontmatter.includes('external_source: ado') || frontmatter.includes('externalSource: ado')) {
|
|
316
|
+
return 'ado';
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Check for external ID patterns
|
|
321
|
+
if (content.includes('externalId: GH-') || content.includes('external_id: GH-')) {
|
|
322
|
+
return 'github';
|
|
323
|
+
}
|
|
324
|
+
if (content.includes('externalId: JIRA-') || content.includes('external_id: JIRA-')) {
|
|
325
|
+
return 'jira';
|
|
326
|
+
}
|
|
327
|
+
if (content.includes('externalId: ADO-') || content.includes('external_id: ADO-')) {
|
|
328
|
+
return 'ado';
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return 'unknown';
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Get external origin badge with link if available
|
|
336
|
+
*
|
|
337
|
+
* @param {string} source - External source (github, jira, ado)
|
|
338
|
+
* @param {string} content - US file content
|
|
339
|
+
* @returns {string} Origin badge markdown
|
|
340
|
+
*/
|
|
341
|
+
function getExternalOriginBadge(source, content) {
|
|
342
|
+
// Extract external ID and URL from content
|
|
343
|
+
const externalIdMatch = content.match(/external_?[iI]d:\s*([^\n]+)/);
|
|
344
|
+
const externalUrlMatch = content.match(/external_?[uU]rl:\s*([^\n]+)/);
|
|
345
|
+
|
|
346
|
+
const externalId = externalIdMatch ? externalIdMatch[1].trim() : null;
|
|
347
|
+
const externalUrl = externalUrlMatch ? externalUrlMatch[1].trim() : null;
|
|
348
|
+
|
|
349
|
+
switch (source) {
|
|
350
|
+
case 'github':
|
|
351
|
+
if (externalId && externalUrl) {
|
|
352
|
+
return `🔗 [GitHub ${externalId}](${externalUrl})`;
|
|
353
|
+
}
|
|
354
|
+
return '🔗 **GitHub**';
|
|
355
|
+
case 'jira':
|
|
356
|
+
if (externalId && externalUrl) {
|
|
357
|
+
return `🎫 [JIRA ${externalId}](${externalUrl})`;
|
|
358
|
+
}
|
|
359
|
+
return '🎫 **JIRA**';
|
|
360
|
+
case 'ado':
|
|
361
|
+
if (externalId && externalUrl) {
|
|
362
|
+
return `📋 [ADO ${externalId}](${externalUrl})`;
|
|
363
|
+
}
|
|
364
|
+
return '📋 **Azure DevOps**';
|
|
365
|
+
default:
|
|
366
|
+
return '🔗 **External**';
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
194
370
|
/**
|
|
195
371
|
* Generate task list markdown
|
|
196
372
|
*
|