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
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Translation CLI Utility
|
|
3
|
+
*
|
|
4
|
+
* Translates a single file from detected source language to target language
|
|
5
|
+
* using the translation utilities and LLM invocation.
|
|
6
|
+
*
|
|
7
|
+
* This script is called from:
|
|
8
|
+
* - Post-increment-planning hook (auto-translate spec.md, plan.md, tasks.md)
|
|
9
|
+
* - Post-task-completion hook (auto-translate living docs)
|
|
10
|
+
* - Manual /specweave:translate command
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* node translate-file.js <file-path> [--target-lang en] [--preview]
|
|
14
|
+
*
|
|
15
|
+
* @see src/utils/translation.ts
|
|
16
|
+
* @see .specweave/increments/0006-llm-native-i18n/reports/DESIGN-POST-GENERATION-TRANSLATION.md
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import fs from 'fs-extra';
|
|
20
|
+
import path from 'path';
|
|
21
|
+
import {
|
|
22
|
+
detectLanguage,
|
|
23
|
+
prepareTranslation,
|
|
24
|
+
postProcessTranslation,
|
|
25
|
+
validateTranslation,
|
|
26
|
+
getLanguageName,
|
|
27
|
+
formatCost,
|
|
28
|
+
type SupportedLanguage,
|
|
29
|
+
} from '../../../../dist/src/utils/translation.js';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* CLI options
|
|
33
|
+
*/
|
|
34
|
+
interface CLIOptions {
|
|
35
|
+
filePath: string;
|
|
36
|
+
targetLang: SupportedLanguage;
|
|
37
|
+
preview: boolean;
|
|
38
|
+
verbose: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Translation result
|
|
43
|
+
*/
|
|
44
|
+
interface FileTranslationResult {
|
|
45
|
+
success: boolean;
|
|
46
|
+
filePath: string;
|
|
47
|
+
sourceLanguage: SupportedLanguage;
|
|
48
|
+
targetLanguage: SupportedLanguage;
|
|
49
|
+
warnings: string[];
|
|
50
|
+
cost: number;
|
|
51
|
+
tokensUsed: number;
|
|
52
|
+
preview?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Main translation function
|
|
57
|
+
*
|
|
58
|
+
* @param options - CLI options
|
|
59
|
+
* @returns Translation result
|
|
60
|
+
*/
|
|
61
|
+
export async function translateFile(options: CLIOptions): Promise<FileTranslationResult> {
|
|
62
|
+
const { filePath, targetLang, preview, verbose } = options;
|
|
63
|
+
|
|
64
|
+
// 1. Validate file exists
|
|
65
|
+
if (!await fs.pathExists(filePath)) {
|
|
66
|
+
throw new Error(`File not found: ${filePath}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (verbose) {
|
|
70
|
+
console.log(`📄 Reading file: ${filePath}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 2. Read original content
|
|
74
|
+
const originalContent = await fs.readFile(filePath, 'utf-8');
|
|
75
|
+
|
|
76
|
+
// 3. Detect source language
|
|
77
|
+
const detectionResult = detectLanguage(originalContent);
|
|
78
|
+
const sourceLanguage = detectionResult.language;
|
|
79
|
+
|
|
80
|
+
if (verbose) {
|
|
81
|
+
console.log(`🔍 Detected language: ${getLanguageName(sourceLanguage)} (confidence: ${(detectionResult.confidence * 100).toFixed(0)}%)`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 4. Check if already in target language
|
|
85
|
+
if (sourceLanguage === targetLang) {
|
|
86
|
+
if (verbose) {
|
|
87
|
+
console.log(`✅ File already in ${getLanguageName(targetLang)}, skipping translation`);
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
success: true,
|
|
91
|
+
filePath,
|
|
92
|
+
sourceLanguage,
|
|
93
|
+
targetLanguage: targetLang,
|
|
94
|
+
warnings: [`Already in ${getLanguageName(targetLang)}`],
|
|
95
|
+
cost: 0,
|
|
96
|
+
tokensUsed: 0,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 5. Check if source language is unknown
|
|
101
|
+
if (sourceLanguage === 'unknown') {
|
|
102
|
+
if (verbose) {
|
|
103
|
+
console.warn(`⚠️ Could not detect language, assuming English`);
|
|
104
|
+
}
|
|
105
|
+
// Assume English if detection fails
|
|
106
|
+
return {
|
|
107
|
+
success: false,
|
|
108
|
+
filePath,
|
|
109
|
+
sourceLanguage: 'unknown',
|
|
110
|
+
targetLanguage: targetLang,
|
|
111
|
+
warnings: ['Language detection failed - file may already be in English or mixed language'],
|
|
112
|
+
cost: 0,
|
|
113
|
+
tokensUsed: 0,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 6. Prepare translation
|
|
118
|
+
if (verbose) {
|
|
119
|
+
console.log(`🌐 Translating from ${getLanguageName(sourceLanguage)} to ${getLanguageName(targetLang)}...`);
|
|
120
|
+
console.log(`💰 Estimated cost: ${formatCost(0.003)} (using Haiku)`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const prepared = prepareTranslation(originalContent, sourceLanguage, targetLang);
|
|
124
|
+
|
|
125
|
+
// 7. Invoke LLM for translation
|
|
126
|
+
// NOTE: This is where we call the actual LLM
|
|
127
|
+
// For now, we'll create a simple prompt that can be used with Claude Code's Task tool
|
|
128
|
+
const translatedContent = await invokeLLMTranslation(prepared.prompt, verbose);
|
|
129
|
+
|
|
130
|
+
// 8. Post-process translation
|
|
131
|
+
const finalContent = postProcessTranslation(translatedContent, prepared.preserved);
|
|
132
|
+
|
|
133
|
+
// 9. Validate translation
|
|
134
|
+
const warnings = validateTranslation(originalContent, finalContent);
|
|
135
|
+
|
|
136
|
+
if (warnings.length > 0 && verbose) {
|
|
137
|
+
console.warn(`⚠️ Translation warnings:`);
|
|
138
|
+
warnings.forEach(w => console.warn(` - ${w}`));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 10. Preview or write
|
|
142
|
+
if (preview) {
|
|
143
|
+
if (verbose) {
|
|
144
|
+
console.log(`\n📋 PREVIEW (first 500 chars):\n`);
|
|
145
|
+
console.log(finalContent.substring(0, 500));
|
|
146
|
+
console.log(`\n... (${finalContent.length} total characters)\n`);
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
success: true,
|
|
150
|
+
filePath,
|
|
151
|
+
sourceLanguage,
|
|
152
|
+
targetLanguage: targetLang,
|
|
153
|
+
warnings,
|
|
154
|
+
cost: prepared.estimatedCost,
|
|
155
|
+
tokensUsed: prepared.estimatedTokens,
|
|
156
|
+
preview: finalContent,
|
|
157
|
+
};
|
|
158
|
+
} else {
|
|
159
|
+
// Write translated content back to file
|
|
160
|
+
await fs.writeFile(filePath, finalContent, 'utf-8');
|
|
161
|
+
|
|
162
|
+
if (verbose) {
|
|
163
|
+
console.log(`✅ Translation complete: ${filePath}`);
|
|
164
|
+
console.log(` Tokens used: ${prepared.estimatedTokens.toLocaleString()}`);
|
|
165
|
+
console.log(` Cost: ${formatCost(prepared.estimatedCost)}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
success: true,
|
|
170
|
+
filePath,
|
|
171
|
+
sourceLanguage,
|
|
172
|
+
targetLanguage: targetLang,
|
|
173
|
+
warnings,
|
|
174
|
+
cost: prepared.estimatedCost,
|
|
175
|
+
tokensUsed: prepared.estimatedTokens,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Invokes LLM for translation using Anthropic API
|
|
182
|
+
*
|
|
183
|
+
* PRODUCTION IMPLEMENTATION:
|
|
184
|
+
* 1. Checks for ANTHROPIC_API_KEY in environment
|
|
185
|
+
* 2. If available: Uses Anthropic API directly (fully automatic)
|
|
186
|
+
* 3. If not available: Provides clear instructions for manual translation
|
|
187
|
+
*
|
|
188
|
+
* @param prompt - Translation prompt
|
|
189
|
+
* @param verbose - Show detailed output
|
|
190
|
+
* @returns Translated content
|
|
191
|
+
*/
|
|
192
|
+
async function invokeLLMTranslation(prompt: string, verbose: boolean): Promise<string> {
|
|
193
|
+
// Extract the content to translate (between --- markers)
|
|
194
|
+
const contentMatch = prompt.match(/SOURCE DOCUMENT[^\n]*:\n---\n([\s\S]*?)\n---/);
|
|
195
|
+
const contentToTranslate = contentMatch ? contentMatch[1] : '';
|
|
196
|
+
|
|
197
|
+
// Check if ANTHROPIC_API_KEY is available
|
|
198
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
199
|
+
|
|
200
|
+
if (apiKey) {
|
|
201
|
+
// Fully automatic translation using Anthropic API
|
|
202
|
+
if (verbose) {
|
|
203
|
+
console.log(`\n🤖 Translating via Anthropic API (Haiku model)...`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
// Dynamic import of Anthropic SDK (allows graceful fallback if not installed)
|
|
208
|
+
const Anthropic = await import('@anthropic-ai/sdk').then(m => m.default);
|
|
209
|
+
|
|
210
|
+
const anthropic = new Anthropic({
|
|
211
|
+
apiKey,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const message = await anthropic.messages.create({
|
|
215
|
+
model: 'claude-3-haiku-20240307',
|
|
216
|
+
max_tokens: 8000,
|
|
217
|
+
messages: [
|
|
218
|
+
{
|
|
219
|
+
role: 'user',
|
|
220
|
+
content: prompt,
|
|
221
|
+
},
|
|
222
|
+
],
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// Extract translated content from response
|
|
226
|
+
const translatedContent = message.content[0].type === 'text'
|
|
227
|
+
? message.content[0].text
|
|
228
|
+
: contentToTranslate;
|
|
229
|
+
|
|
230
|
+
if (verbose) {
|
|
231
|
+
console.log(`✅ Translation complete via API`);
|
|
232
|
+
console.log(` Model: claude-3-haiku-20240307`);
|
|
233
|
+
console.log(` Input tokens: ${message.usage.input_tokens}`);
|
|
234
|
+
console.log(` Output tokens: ${message.usage.output_tokens}`);
|
|
235
|
+
console.log(` Cost: ~$${((message.usage.input_tokens * 0.25 + message.usage.output_tokens * 1.25) / 1000000).toFixed(4)}`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return translatedContent;
|
|
239
|
+
} catch (error: any) {
|
|
240
|
+
console.error(`\n❌ API translation failed: ${error.message}`);
|
|
241
|
+
console.error(` Falling back to manual translation instructions\n`);
|
|
242
|
+
// Fall through to manual instructions
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Fallback: Manual translation instructions
|
|
247
|
+
const isInteractive = process.stdout.isTTY && process.env.CLAUDE_CODE_SESSION;
|
|
248
|
+
|
|
249
|
+
if (isInteractive) {
|
|
250
|
+
// Interactive mode: Output prompt for Claude to process
|
|
251
|
+
if (verbose) {
|
|
252
|
+
console.log(`\n🤖 Invoking Claude Code translator skill...`);
|
|
253
|
+
console.log(` (Tip: Set ANTHROPIC_API_KEY for fully automatic translation)\n`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Output the translation prompt
|
|
257
|
+
// The translator skill should auto-activate on this prompt
|
|
258
|
+
console.log('\n' + '='.repeat(80));
|
|
259
|
+
console.log('TRANSLATION REQUEST (translator skill will auto-activate):');
|
|
260
|
+
console.log('='.repeat(80));
|
|
261
|
+
console.log(prompt);
|
|
262
|
+
console.log('='.repeat(80) + '\n');
|
|
263
|
+
|
|
264
|
+
// In interactive mode, we expect the user/Claude to provide translation
|
|
265
|
+
// For now, return a marker indicating manual intervention needed
|
|
266
|
+
return `<!-- ⚠️ TRANSLATION IN PROGRESS - Manual translation required via translator skill -->\n\n${contentToTranslate}`;
|
|
267
|
+
} else {
|
|
268
|
+
// Non-interactive/automated mode: Provide clear instructions
|
|
269
|
+
if (verbose) {
|
|
270
|
+
console.log(`\n🤖 Generating translation (automated mode)...`);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
console.error('\n⚠️ AUTO-TRANSLATION REQUIRES MANUAL STEP:');
|
|
274
|
+
console.error(' Option A (Recommended): Set ANTHROPIC_API_KEY environment variable');
|
|
275
|
+
console.error(' Option B: Run /specweave:translate <file-path>');
|
|
276
|
+
console.error(' Option C: Manually translate the content\n');
|
|
277
|
+
|
|
278
|
+
// Return original content with clear marker
|
|
279
|
+
return `<!-- ⚠️ AUTO-TRANSLATION PENDING -->\n<!-- Set ANTHROPIC_API_KEY for automatic translation -->\n<!-- Or run: /specweave:translate to complete -->\n<!-- Original content below -->\n\n${contentToTranslate}`;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Batch translate multiple files
|
|
285
|
+
*
|
|
286
|
+
* @param filePaths - Array of file paths to translate
|
|
287
|
+
* @param targetLang - Target language
|
|
288
|
+
* @param preview - Preview mode
|
|
289
|
+
* @param verbose - Verbose output
|
|
290
|
+
* @returns Array of translation results
|
|
291
|
+
*/
|
|
292
|
+
export async function batchTranslateFiles(
|
|
293
|
+
filePaths: string[],
|
|
294
|
+
targetLang: SupportedLanguage = 'en',
|
|
295
|
+
preview: boolean = false,
|
|
296
|
+
verbose: boolean = false
|
|
297
|
+
): Promise<FileTranslationResult[]> {
|
|
298
|
+
const results: FileTranslationResult[] = [];
|
|
299
|
+
|
|
300
|
+
if (verbose) {
|
|
301
|
+
console.log(`\n🔄 Batch translating ${filePaths.length} file(s) to ${getLanguageName(targetLang)}...\n`);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
for (const filePath of filePaths) {
|
|
305
|
+
try {
|
|
306
|
+
const result = await translateFile({
|
|
307
|
+
filePath,
|
|
308
|
+
targetLang,
|
|
309
|
+
preview,
|
|
310
|
+
verbose,
|
|
311
|
+
});
|
|
312
|
+
results.push(result);
|
|
313
|
+
} catch (error: any) {
|
|
314
|
+
if (verbose) {
|
|
315
|
+
console.error(`❌ Error translating ${filePath}: ${error.message}`);
|
|
316
|
+
}
|
|
317
|
+
results.push({
|
|
318
|
+
success: false,
|
|
319
|
+
filePath,
|
|
320
|
+
sourceLanguage: 'unknown',
|
|
321
|
+
targetLanguage: targetLang,
|
|
322
|
+
warnings: [error.message],
|
|
323
|
+
cost: 0,
|
|
324
|
+
tokensUsed: 0,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Summary
|
|
330
|
+
if (verbose) {
|
|
331
|
+
const successful = results.filter(r => r.success).length;
|
|
332
|
+
const totalCost = results.reduce((sum, r) => sum + r.cost, 0);
|
|
333
|
+
const totalTokens = results.reduce((sum, r) => sum + r.tokensUsed, 0);
|
|
334
|
+
|
|
335
|
+
console.log(`\n📊 Batch Translation Summary:`);
|
|
336
|
+
console.log(` Successful: ${successful}/${filePaths.length}`);
|
|
337
|
+
console.log(` Total tokens: ${totalTokens.toLocaleString()}`);
|
|
338
|
+
console.log(` Total cost: ${formatCost(totalCost)}`);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return results;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Parse CLI arguments
|
|
346
|
+
*/
|
|
347
|
+
function parseArgs(): CLIOptions {
|
|
348
|
+
const args = process.argv.slice(2);
|
|
349
|
+
|
|
350
|
+
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
351
|
+
console.log(`
|
|
352
|
+
Translation CLI Utility
|
|
353
|
+
|
|
354
|
+
Usage:
|
|
355
|
+
node translate-file.js <file-path> [options]
|
|
356
|
+
|
|
357
|
+
Options:
|
|
358
|
+
--target-lang <code> Target language (default: en)
|
|
359
|
+
--preview Preview translation without writing to file
|
|
360
|
+
--verbose, -v Show detailed output
|
|
361
|
+
--help, -h Show this help message
|
|
362
|
+
|
|
363
|
+
Supported Languages:
|
|
364
|
+
en (English), ru (Russian), es (Spanish), zh (Chinese),
|
|
365
|
+
de (German), fr (French), ja (Japanese), ko (Korean),
|
|
366
|
+
pt (Portuguese), ar (Arabic), he (Hebrew)
|
|
367
|
+
|
|
368
|
+
Examples:
|
|
369
|
+
# Translate Russian file to English
|
|
370
|
+
node translate-file.js .specweave/increments/0001/spec.md
|
|
371
|
+
|
|
372
|
+
# Preview translation
|
|
373
|
+
node translate-file.js spec.md --preview --verbose
|
|
374
|
+
|
|
375
|
+
# Translate to Spanish
|
|
376
|
+
node translate-file.js plan.md --target-lang es
|
|
377
|
+
`.trim());
|
|
378
|
+
process.exit(0);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const filePath = args[0];
|
|
382
|
+
let targetLang: SupportedLanguage = 'en';
|
|
383
|
+
let preview = false;
|
|
384
|
+
let verbose = false;
|
|
385
|
+
|
|
386
|
+
// Parse options
|
|
387
|
+
for (let i = 1; i < args.length; i++) {
|
|
388
|
+
const arg = args[i];
|
|
389
|
+
|
|
390
|
+
if (arg === '--target-lang' && args[i + 1]) {
|
|
391
|
+
targetLang = args[i + 1] as SupportedLanguage;
|
|
392
|
+
i++;
|
|
393
|
+
} else if (arg === '--preview') {
|
|
394
|
+
preview = true;
|
|
395
|
+
} else if (arg === '--verbose' || arg === '-v') {
|
|
396
|
+
verbose = true;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return {
|
|
401
|
+
filePath,
|
|
402
|
+
targetLang,
|
|
403
|
+
preview,
|
|
404
|
+
verbose,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* CLI entry point
|
|
410
|
+
*/
|
|
411
|
+
async function main(): Promise<void> {
|
|
412
|
+
try {
|
|
413
|
+
const options = parseArgs();
|
|
414
|
+
const result = await translateFile(options);
|
|
415
|
+
|
|
416
|
+
// Exit with appropriate code
|
|
417
|
+
process.exit(result.success ? 0 : 1);
|
|
418
|
+
} catch (error: any) {
|
|
419
|
+
console.error(`❌ Translation failed: ${error.message}`);
|
|
420
|
+
process.exit(1);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Check if running as main module (ESM)
|
|
425
|
+
const isMainModule = import.meta.url === `file://${process.argv[1]}`;
|
|
426
|
+
if (isMainModule) {
|
|
427
|
+
main();
|
|
428
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Living Docs Auto-Translation
|
|
3
|
+
*
|
|
4
|
+
* Automatically translates changed documentation files when language != 'en'
|
|
5
|
+
* and auto-translation is enabled in config.
|
|
6
|
+
*
|
|
7
|
+
* This runs as part of the post-task-completion hook.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Main function: Translate changed living docs
|
|
11
|
+
*/
|
|
12
|
+
export declare function translateLivingDocs(incrementId: string): Promise<void>;
|
|
13
|
+
//# sourceMappingURL=translate-living-docs.d.ts.map
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
async function translateLivingDocs(incrementId) {
|
|
4
|
+
try {
|
|
5
|
+
const configPath = ".specweave/config.json";
|
|
6
|
+
if (!fs.existsSync(configPath)) {
|
|
7
|
+
console.log("[translate-living-docs] No config found, skipping translation");
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const config = await fs.readJson(configPath);
|
|
11
|
+
if (!config.language || config.language === "en") {
|
|
12
|
+
console.log("[translate-living-docs] Project language is English, skipping translation");
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (!config.translation?.autoTranslateLivingDocs) {
|
|
16
|
+
console.log("[translate-living-docs] Auto-translation disabled in config");
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
console.log(`[translate-living-docs] Auto-translating docs from ${config.language} to English...`);
|
|
20
|
+
const changedFiles = await detectChangedDocs();
|
|
21
|
+
if (changedFiles.length === 0) {
|
|
22
|
+
console.log("[translate-living-docs] No documentation changes detected");
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
console.log(`[translate-living-docs] Found ${changedFiles.length} changed file(s)`);
|
|
26
|
+
for (const file of changedFiles) {
|
|
27
|
+
try {
|
|
28
|
+
await translateFile(file, "en", config.translation);
|
|
29
|
+
console.log(`[translate-living-docs] \u2713 Translated: ${file} (${config.language} \u2192 en)`);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.warn(`[translate-living-docs] \u26A0\uFE0F Failed to translate ${file}: ${error.message}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
console.log(`[translate-living-docs] \u2705 Translation complete (${changedFiles.length} files)`);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error(`[translate-living-docs] Error: ${error.message}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function detectChangedDocs() {
|
|
40
|
+
try {
|
|
41
|
+
const output = execSync(
|
|
42
|
+
"git diff --name-only .specweave/docs/",
|
|
43
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
|
|
44
|
+
);
|
|
45
|
+
const files = output.split("\n").filter((f) => f.trim() && f.endsWith(".md")).map((f) => f.trim());
|
|
46
|
+
return files;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function translateFile(filePath, targetLanguage, translationConfig) {
|
|
52
|
+
const originalContent = await fs.readFile(filePath, "utf-8");
|
|
53
|
+
if (originalContent.includes(`<!-- Translated to ${targetLanguage} -->`)) {
|
|
54
|
+
console.log(`[translate-living-docs] File already translated, skipping: ${filePath}`);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const translationPrompt = generateTranslationPrompt(
|
|
58
|
+
originalContent,
|
|
59
|
+
targetLanguage,
|
|
60
|
+
translationConfig
|
|
61
|
+
);
|
|
62
|
+
const markedContent = `<!-- Translation needed to ${targetLanguage} -->
|
|
63
|
+
<!-- Original content below -->
|
|
64
|
+
|
|
65
|
+
${originalContent}`;
|
|
66
|
+
console.log(`[translate-living-docs] Translation prompt generated for: ${filePath}`);
|
|
67
|
+
}
|
|
68
|
+
function generateTranslationPrompt(content, targetLanguage, translationConfig) {
|
|
69
|
+
const keepFrameworkTerms = translationConfig?.keepFrameworkTerms !== false;
|
|
70
|
+
const keepTechnicalTerms = translationConfig?.keepTechnicalTerms !== false;
|
|
71
|
+
return `
|
|
72
|
+
Translate the following markdown documentation to ${getLanguageName(targetLanguage)}.
|
|
73
|
+
|
|
74
|
+
CRITICAL RULES:
|
|
75
|
+
${keepFrameworkTerms ? "- Keep framework terms in English: increment, spec.md, plan.md, tasks.md, COMPLETION-SUMMARY.md, living docs, PM gate, RFC, ADR, PRD, HLD, LLD" : ""}
|
|
76
|
+
${keepTechnicalTerms ? "- Keep technical terms in English: TypeScript, npm, git, Docker, Kubernetes, API, CLI, REST, JSON, HTTP" : ""}
|
|
77
|
+
- Preserve ALL markdown formatting (headers, lists, code blocks, links)
|
|
78
|
+
- Do NOT translate code blocks
|
|
79
|
+
- Do NOT translate YAML frontmatter keys (only values if applicable)
|
|
80
|
+
- Preserve all emojis
|
|
81
|
+
- Preserve all file paths and URLs
|
|
82
|
+
|
|
83
|
+
Content to translate:
|
|
84
|
+
---
|
|
85
|
+
${content}
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
Translated version:
|
|
89
|
+
`.trim();
|
|
90
|
+
}
|
|
91
|
+
function getLanguageName(code) {
|
|
92
|
+
const names = {
|
|
93
|
+
en: "English",
|
|
94
|
+
ru: "Russian (\u0420\u0443\u0441\u0441\u043A\u0438\u0439)",
|
|
95
|
+
es: "Spanish (Espa\xF1ol)",
|
|
96
|
+
zh: "Chinese (\u4E2D\u6587)",
|
|
97
|
+
de: "German (Deutsch)",
|
|
98
|
+
fr: "French (Fran\xE7ais)",
|
|
99
|
+
ja: "Japanese (\u65E5\u672C\u8A9E)",
|
|
100
|
+
ko: "Korean (\uD55C\uAD6D\uC5B4)",
|
|
101
|
+
pt: "Portuguese (Portugu\xEAs)"
|
|
102
|
+
};
|
|
103
|
+
return names[code] || code;
|
|
104
|
+
}
|
|
105
|
+
async function main() {
|
|
106
|
+
const args = process.argv.slice(2);
|
|
107
|
+
const incrementId = args[0] || "current";
|
|
108
|
+
await translateLivingDocs(incrementId);
|
|
109
|
+
}
|
|
110
|
+
const isMainModule = import.meta.url === `file://${process.argv[1]}`;
|
|
111
|
+
if (isMainModule) {
|
|
112
|
+
main().catch((error) => {
|
|
113
|
+
console.error("[translate-living-docs] Fatal error:", error);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
export {
|
|
118
|
+
translateLivingDocs
|
|
119
|
+
};
|