specweave 0.23.1 → 0.23.4
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-plugin/marketplace.json +0 -88
- package/CLAUDE.md +368 -0
- package/bin/fix-marketplace-errors.sh +8 -8
- package/dist/plugins/specweave/lib/utils/fs-native.d.ts +133 -0
- package/dist/plugins/specweave/lib/utils/fs-native.d.ts.map +1 -0
- package/dist/plugins/specweave/lib/utils/fs-native.js +224 -0
- package/dist/plugins/specweave/lib/utils/fs-native.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-client-v2.js +1 -1
- package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync.js +52 -20
- package/dist/plugins/specweave-github/lib/github-feature-sync.js.map +1 -1
- package/dist/src/cli/helpers/init/initial-increment-generator.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/initial-increment-generator.js +2 -1
- package/dist/src/cli/helpers/init/initial-increment-generator.js.map +1 -1
- package/dist/src/core/ac-test-validator-cli.d.ts +16 -0
- package/dist/src/core/ac-test-validator-cli.d.ts.map +1 -0
- package/dist/src/core/ac-test-validator-cli.js +118 -0
- package/dist/src/core/ac-test-validator-cli.js.map +1 -0
- package/dist/src/core/ac-test-validator.d.ts +111 -0
- package/dist/src/core/ac-test-validator.d.ts.map +1 -0
- package/dist/src/core/ac-test-validator.js +292 -0
- package/dist/src/core/ac-test-validator.js.map +1 -0
- package/dist/src/core/increment/desync-detector.d.ts +142 -0
- package/dist/src/core/increment/desync-detector.d.ts.map +1 -0
- package/dist/src/core/increment/desync-detector.js +270 -0
- package/dist/src/core/increment/desync-detector.js.map +1 -0
- package/dist/src/core/increment/metadata-manager.d.ts +8 -4
- package/dist/src/core/increment/metadata-manager.d.ts.map +1 -1
- package/dist/src/core/increment/metadata-manager.js +45 -21
- package/dist/src/core/increment/metadata-manager.js.map +1 -1
- package/dist/src/core/qa/qa-runner.js +9 -2
- package/dist/src/core/qa/qa-runner.js.map +1 -1
- package/dist/src/sync/sync-coordinator.d.ts +1 -1
- package/dist/src/sync/sync-coordinator.d.ts.map +1 -1
- package/dist/src/sync/sync-coordinator.js +40 -2
- package/dist/src/sync/sync-coordinator.js.map +1 -1
- package/dist/src/utils/fs-native.d.ts +133 -0
- package/dist/src/utils/fs-native.d.ts.map +1 -0
- package/dist/src/utils/fs-native.js +224 -0
- package/dist/src/utils/fs-native.js.map +1 -0
- package/package.json +1 -1
- package/plugins/specweave/.claude-plugin/plugin.json +22 -0
- package/plugins/specweave/agents/AGENTS-INDEX.md +216 -0
- package/plugins/specweave/agents/architect/AGENT.md +17 -0
- package/plugins/specweave/agents/code-standards-detective/AGENT.md +16 -0
- package/plugins/specweave/agents/docs-writer/AGENT.md +16 -0
- package/plugins/specweave/agents/increment-quality-judge-v2/AGENT.md +704 -0
- package/plugins/specweave/agents/infrastructure/AGENT.md +16 -0
- package/plugins/specweave/agents/performance/AGENT.md +16 -0
- package/plugins/specweave/agents/pm/AGENT.md +17 -0
- package/plugins/specweave/agents/qa-lead/AGENT.md +15 -0
- package/plugins/specweave/agents/reflective-reviewer/AGENT.md +16 -0
- package/plugins/specweave/agents/security/AGENT.md +16 -0
- package/plugins/specweave/agents/tdd-orchestrator/AGENT.md +16 -0
- package/plugins/specweave/agents/tech-lead/AGENT.md +16 -0
- package/plugins/specweave/agents/test-aware-planner/AGENT.md +16 -0
- package/plugins/specweave/agents/translator/AGENT.md +13 -0
- package/plugins/specweave/commands/specweave-done.md +14 -0
- package/plugins/specweave/commands/specweave-qa.md +11 -1
- package/plugins/specweave/commands/specweave-sync-status.md +356 -0
- package/plugins/specweave/commands/specweave-validate.md +10 -1
- package/plugins/specweave/hooks/post-metadata-change.sh +160 -0
- package/plugins/specweave/hooks/pre-task-completion.sh +196 -0
- package/plugins/specweave/lib/hooks/git-diff-analyzer.js +3 -3
- package/plugins/specweave/lib/hooks/git-diff-analyzer.ts +3 -3
- package/plugins/specweave/lib/hooks/invoke-translator-skill.js +3 -2
- package/plugins/specweave/lib/hooks/invoke-translator-skill.ts +3 -2
- package/plugins/specweave/lib/hooks/prepare-reflection-context.js +3 -3
- package/plugins/specweave/lib/hooks/prepare-reflection-context.ts +3 -3
- package/plugins/specweave/lib/hooks/reflection-config-loader.js +4 -4
- package/plugins/specweave/lib/hooks/reflection-config-loader.ts +4 -4
- package/plugins/specweave/lib/hooks/reflection-storage.js +9 -9
- package/plugins/specweave/lib/hooks/reflection-storage.ts +9 -9
- package/plugins/specweave/lib/hooks/sync-cache.js +9 -8
- package/plugins/specweave/lib/hooks/sync-living-docs.js +57 -6
- package/plugins/specweave/lib/hooks/sync-us-tasks.js +6 -6
- package/plugins/specweave/lib/hooks/translate-file.js +3 -2
- package/plugins/specweave/lib/hooks/translate-file.ts +3 -2
- package/plugins/specweave/lib/hooks/translate-living-docs.js +4 -3
- package/plugins/specweave/lib/hooks/translate-living-docs.ts +4 -3
- package/plugins/specweave/lib/utils/fs-native.js +182 -0
- package/plugins/specweave/lib/utils/fs-native.ts +283 -0
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.d.ts +8 -4
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js +45 -21
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js.map +1 -1
- package/plugins/specweave/skills/SKILLS-INDEX.md +26 -2
- package/plugins/specweave/skills/increment-planner/SKILL.md +2 -2
- package/plugins/specweave-ado/commands/specweave-ado-close-workitem.md +1 -1
- package/plugins/specweave-ado/commands/specweave-ado-create-workitem.md +1 -1
- package/plugins/specweave-ado/commands/specweave-ado-status.md +1 -1
- package/plugins/specweave-ado/commands/specweave-ado-sync.md +1 -1
- package/plugins/specweave-diagrams/agents/diagrams-architect/AGENT.md +1 -1
- package/plugins/specweave-diagrams/skills/diagrams-generator/SKILL.md +4 -4
- package/plugins/specweave-github/lib/github-client-v2.js +2 -1
- package/plugins/specweave-github/lib/github-client-v2.ts +1 -1
- package/plugins/specweave-github/lib/github-feature-sync.js +30 -17
- package/plugins/specweave-github/lib/github-feature-sync.ts +54 -24
- package/plugins/specweave-mobile/README.md +1 -1
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +72 -0
- package/plugins/specweave/skills/task-builder/README.md +0 -84
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
* Part of increment 0047-us-task-linkage (T-012).
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import fs from 'fs
|
|
14
|
+
import { readFileSync, statSync, existsSync, promises as fs } from 'fs';
|
|
15
15
|
import path from 'path';
|
|
16
16
|
import crypto from 'crypto';
|
|
17
|
+
import { mkdirpSync } from '../utils/fs-native.js';
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* In-memory cache with TTL
|
|
@@ -97,7 +98,7 @@ const globalCache = new SyncCache();
|
|
|
97
98
|
*/
|
|
98
99
|
export function getFileHash(filePath) {
|
|
99
100
|
try {
|
|
100
|
-
const content =
|
|
101
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
101
102
|
return crypto.createHash('sha256').update(content).digest('hex');
|
|
102
103
|
} catch (error) {
|
|
103
104
|
return null;
|
|
@@ -145,7 +146,7 @@ export function getCachedTasks(tasksPath, parser) {
|
|
|
145
146
|
*/
|
|
146
147
|
export function getCachedUSMetadata(usFilePath) {
|
|
147
148
|
try {
|
|
148
|
-
const stats =
|
|
149
|
+
const stats = statSync(usFilePath);
|
|
149
150
|
const cacheKey = `us-metadata:${usFilePath}`;
|
|
150
151
|
|
|
151
152
|
const cached = globalCache.get(cacheKey);
|
|
@@ -156,7 +157,7 @@ export function getCachedUSMetadata(usFilePath) {
|
|
|
156
157
|
}
|
|
157
158
|
|
|
158
159
|
// File changed or not in cache - read and cache
|
|
159
|
-
const content =
|
|
160
|
+
const content = readFileSync(usFilePath, 'utf-8');
|
|
160
161
|
const metadata = {
|
|
161
162
|
mtime: stats.mtimeMs,
|
|
162
163
|
metadata: {
|
|
@@ -198,7 +199,7 @@ export async function batchFileUpdates(updates) {
|
|
|
198
199
|
// Write files sequentially within same directory (better disk I/O)
|
|
199
200
|
for (const [dir, fileUpdates] of updatesByDir.entries()) {
|
|
200
201
|
// Ensure directory exists once per directory
|
|
201
|
-
await fs.
|
|
202
|
+
await fs.mkdir(dir, { recursive: true });
|
|
202
203
|
|
|
203
204
|
// Write all files in this directory
|
|
204
205
|
await Promise.all(
|
|
@@ -220,13 +221,13 @@ export async function batchFileUpdates(updates) {
|
|
|
220
221
|
export function needsSync(usFilePath, tasks, tasksPath) {
|
|
221
222
|
try {
|
|
222
223
|
// Check if US file exists
|
|
223
|
-
if (!
|
|
224
|
+
if (!existsSync(usFilePath)) {
|
|
224
225
|
return false; // File doesn't exist, can't sync
|
|
225
226
|
}
|
|
226
227
|
|
|
227
228
|
// Get file modification times
|
|
228
|
-
const usStats =
|
|
229
|
-
const tasksStats =
|
|
229
|
+
const usStats = statSync(usFilePath);
|
|
230
|
+
const tasksStats = statSync(tasksPath);
|
|
230
231
|
|
|
231
232
|
// If tasks.md is newer than US file, sync is needed
|
|
232
233
|
if (tasksStats.mtimeMs > usStats.mtimeMs) {
|
|
@@ -1,15 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import fs from "fs
|
|
2
|
+
import { promises as fs, existsSync } from "fs";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { execSync } from "child_process";
|
|
5
|
+
import { mkdirpSync } from "../utils/fs-native.js";
|
|
5
6
|
async function syncLivingDocs(incrementId) {
|
|
6
7
|
try {
|
|
7
8
|
console.log(`
|
|
8
9
|
\u{1F4DA} Checking living docs sync for increment: ${incrementId}`);
|
|
10
|
+
// Load and validate config with error handling
|
|
9
11
|
const configPath = path.join(process.cwd(), ".specweave", "config.json");
|
|
10
12
|
let config = {};
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
if (existsSync(configPath)) {
|
|
16
|
+
const rawContent = await fs.readFile(configPath, "utf-8");
|
|
17
|
+
config = JSON.parse(rawContent);
|
|
18
|
+
} else {
|
|
19
|
+
console.log("\u26A0\uFE0F No config.json found, using safe defaults (all permissions disabled)");
|
|
20
|
+
console.log(" To configure: Run 'specweave init' or create .specweave/config.json");
|
|
21
|
+
config = { sync: { settings: {} } };
|
|
22
|
+
}
|
|
23
|
+
} catch (error) {
|
|
24
|
+
console.error("\u274C Failed to load config.json:", error.message);
|
|
25
|
+
console.error(" File may be corrupted or contain invalid JSON");
|
|
26
|
+
console.error(" Using safe defaults (all permissions denied)");
|
|
27
|
+
console.error(" To fix: Check .specweave/config.json for syntax errors");
|
|
28
|
+
config = { sync: { settings: {} } };
|
|
13
29
|
}
|
|
14
30
|
const syncEnabled = config.hooks?.post_task_completion?.sync_living_docs ?? false;
|
|
15
31
|
if (!syncEnabled) {
|
|
@@ -18,6 +34,22 @@ async function syncLivingDocs(incrementId) {
|
|
|
18
34
|
return;
|
|
19
35
|
}
|
|
20
36
|
console.log("\u2705 Living docs sync enabled");
|
|
37
|
+
|
|
38
|
+
// ========================================================================
|
|
39
|
+
// GATE 1: canUpsertInternalItems (v0.24.0+ - Internal Docs Permission)
|
|
40
|
+
// ========================================================================
|
|
41
|
+
// This permission controls whether SpecWeave can CREATE/UPDATE internal docs.
|
|
42
|
+
// If false, ALL living docs sync is blocked (both local and external).
|
|
43
|
+
const canUpsertInternal = config.sync?.settings?.canUpsertInternalItems ?? false;
|
|
44
|
+
|
|
45
|
+
if (!canUpsertInternal) {
|
|
46
|
+
console.log("\u26D4 Living docs sync BLOCKED (canUpsertInternalItems = false)");
|
|
47
|
+
console.log(" To enable: Set sync.settings.canUpsertInternalItems = true in config.json");
|
|
48
|
+
console.log(" No internal docs or external tools will be updated");
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
console.log("\u2705 Internal docs sync permitted (canUpsertInternalItems = true)");
|
|
21
53
|
const intelligentEnabled = config.livingDocs?.intelligent?.enabled ?? false;
|
|
22
54
|
let specCopied = false;
|
|
23
55
|
let changedDocs = [];
|
|
@@ -54,6 +86,25 @@ async function syncLivingDocs(incrementId) {
|
|
|
54
86
|
return;
|
|
55
87
|
}
|
|
56
88
|
|
|
89
|
+
// ========================================================================
|
|
90
|
+
// GATE 3: autoSyncOnCompletion (v0.24.0+ - Automatic vs Manual Sync)
|
|
91
|
+
// ========================================================================
|
|
92
|
+
// This setting controls whether sync to external tools happens automatically
|
|
93
|
+
// on increment completion or requires manual /specweave:sync-* commands.
|
|
94
|
+
// DEFAULT: true (automatic sync enabled for better UX)
|
|
95
|
+
const autoSync = config.sync?.settings?.autoSyncOnCompletion ?? true;
|
|
96
|
+
|
|
97
|
+
if (!autoSync) {
|
|
98
|
+
console.log("\u26A0\uFE0F Automatic external sync DISABLED (autoSyncOnCompletion = false)");
|
|
99
|
+
console.log(" Living docs updated locally, but external tools NOT synced");
|
|
100
|
+
console.log(" To sync manually: Run /specweave-github:sync or /specweave-jira:sync");
|
|
101
|
+
console.log(" To enable auto-sync: Set sync.settings.autoSyncOnCompletion = true");
|
|
102
|
+
console.log("\u2705 Living docs sync complete (manual external sync required)\n");
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
console.log("\u2705 Automatic external sync permitted (autoSyncOnCompletion = true)");
|
|
107
|
+
|
|
57
108
|
// T-034E: Use FormatPreservationSyncService for origin-aware sync
|
|
58
109
|
await syncWithFormatPreservation(incrementId);
|
|
59
110
|
|
|
@@ -184,7 +235,7 @@ async function extractAndMergeLivingDocs(incrementId) {
|
|
|
184
235
|
} = await import("../../../../dist/src/utils/spec-parser.js");
|
|
185
236
|
const projectRoot = process.cwd();
|
|
186
237
|
const incrementSpecPath = path.join(projectRoot, ".specweave", "increments", incrementId, "spec.md");
|
|
187
|
-
if (!
|
|
238
|
+
if (!existsSync(incrementSpecPath)) {
|
|
188
239
|
console.log(`\u26A0\uFE0F Increment spec not found: ${incrementSpecPath}`);
|
|
189
240
|
return false;
|
|
190
241
|
}
|
|
@@ -198,7 +249,7 @@ async function extractAndMergeLivingDocs(incrementId) {
|
|
|
198
249
|
const specId = incrementSpec.implementsSpec || extractSpecId(incrementId);
|
|
199
250
|
const livingDocsDir = path.join(projectRoot, ".specweave", "docs", "internal", "specs", "default");
|
|
200
251
|
const livingDocsPath = path.join(livingDocsDir, `${specId}-${incrementId.replace(/^\d+-/, "")}.md`);
|
|
201
|
-
const livingDocsExists =
|
|
252
|
+
const livingDocsExists = existsSync(livingDocsPath);
|
|
202
253
|
if (livingDocsExists) {
|
|
203
254
|
console.log(` \u{1F4DA} Living docs spec exists, merging user stories...`);
|
|
204
255
|
const livingSpec = await parseLivingDocsSpec(livingDocsPath);
|
|
@@ -253,7 +304,7 @@ async function extractAndMergeLivingDocs(incrementId) {
|
|
|
253
304
|
created: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
254
305
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
255
306
|
};
|
|
256
|
-
|
|
307
|
+
mkdirpSync(livingDocsDir);
|
|
257
308
|
await writeLivingDocsSpec(livingDocsPath, livingSpec);
|
|
258
309
|
console.log(` \u2705 Created new living docs spec: ${specId}`);
|
|
259
310
|
console.log(` \u2705 Added ${incrementSpec.userStories.length} user stories`);
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* Part of increment 0047-us-task-linkage implementation.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import fs from 'fs
|
|
13
|
+
import { readFileSync, existsSync, promises as fs } from 'fs';
|
|
14
14
|
import path from 'path';
|
|
15
15
|
import { parseTasksWithUSLinks, getAllTasks } from '../vendor/generators/spec/task-parser.js';
|
|
16
16
|
import { glob } from 'glob';
|
|
@@ -32,7 +32,7 @@ export async function syncUSTasksToLivingDocs(incrementId, projectRoot, featureI
|
|
|
32
32
|
const tasksPath = path.join(projectRoot, '.specweave', 'increments', incrementId, 'tasks.md');
|
|
33
33
|
|
|
34
34
|
// Check if tasks.md exists
|
|
35
|
-
if (!
|
|
35
|
+
if (!existsSync(tasksPath)) {
|
|
36
36
|
console.log(` ⚠️ tasks.md not found, skipping task sync`);
|
|
37
37
|
return { success: true, updatedFiles: [], errors: [] };
|
|
38
38
|
}
|
|
@@ -451,8 +451,8 @@ function getProjectId(projectRoot, incrementId) {
|
|
|
451
451
|
try {
|
|
452
452
|
// Try to get from metadata.json
|
|
453
453
|
const metadataPath = path.join(projectRoot, '.specweave', 'increments', incrementId, 'metadata.json');
|
|
454
|
-
if (
|
|
455
|
-
const metadata = JSON.parse(
|
|
454
|
+
if (existsSync(metadataPath)) {
|
|
455
|
+
const metadata = JSON.parse(readFileSync(metadataPath, 'utf-8'));
|
|
456
456
|
if (metadata.project) {
|
|
457
457
|
return metadata.project;
|
|
458
458
|
}
|
|
@@ -460,8 +460,8 @@ function getProjectId(projectRoot, incrementId) {
|
|
|
460
460
|
|
|
461
461
|
// Try to get from config.json
|
|
462
462
|
const configPath = path.join(projectRoot, '.specweave', 'config.json');
|
|
463
|
-
if (
|
|
464
|
-
const config = JSON.parse(
|
|
463
|
+
if (existsSync(configPath)) {
|
|
464
|
+
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
465
465
|
if (config.defaultProject) {
|
|
466
466
|
return config.defaultProject;
|
|
467
467
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { existsSync } from "fs";
|
|
2
|
+
import { promises as fs } from "fs";
|
|
2
3
|
import {
|
|
3
4
|
detectLanguage,
|
|
4
5
|
prepareTranslation,
|
|
@@ -9,7 +10,7 @@ import {
|
|
|
9
10
|
} from "../vendor/utils/translation.js";
|
|
10
11
|
async function translateFile(options) {
|
|
11
12
|
const { filePath, targetLang, preview, verbose } = options;
|
|
12
|
-
if (!
|
|
13
|
+
if (!existsSync(filePath)) {
|
|
13
14
|
throw new Error(`File not found: ${filePath}`);
|
|
14
15
|
}
|
|
15
16
|
if (verbose) {
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
* @see .specweave/increments/0006-llm-native-i18n/reports/DESIGN-POST-GENERATION-TRANSLATION.md
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import
|
|
19
|
+
import { existsSync } from 'fs';
|
|
20
|
+
import { promises as fs } from 'fs';
|
|
20
21
|
import path from 'path';
|
|
21
22
|
import {
|
|
22
23
|
detectLanguage,
|
|
@@ -62,7 +63,7 @@ export async function translateFile(options: CLIOptions): Promise<FileTranslatio
|
|
|
62
63
|
const { filePath, targetLang, preview, verbose } = options;
|
|
63
64
|
|
|
64
65
|
// 1. Validate file exists
|
|
65
|
-
if (!
|
|
66
|
+
if (!existsSync(filePath)) {
|
|
66
67
|
throw new Error(`File not found: ${filePath}`);
|
|
67
68
|
}
|
|
68
69
|
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { execSync } from "child_process";
|
|
2
|
-
import
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import { promises as fs } from "fs";
|
|
3
4
|
async function translateLivingDocs(incrementId) {
|
|
4
5
|
try {
|
|
5
6
|
const configPath = ".specweave/config.json";
|
|
6
|
-
if (!
|
|
7
|
+
if (!existsSync(configPath)) {
|
|
7
8
|
console.log("[translate-living-docs] No config found, skipping translation");
|
|
8
9
|
return;
|
|
9
10
|
}
|
|
10
|
-
const config = await fs.
|
|
11
|
+
const config = JSON.parse(await fs.readFile(configPath, "utf-8"));
|
|
11
12
|
if (!config.language || config.language === "en") {
|
|
12
13
|
console.log("[translate-living-docs] Project language is English, skipping translation");
|
|
13
14
|
return;
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { execSync } from 'child_process';
|
|
11
|
-
import
|
|
11
|
+
import { existsSync } from 'fs';
|
|
12
|
+
import { promises as fs } from 'fs';
|
|
12
13
|
import path from 'path';
|
|
13
14
|
|
|
14
15
|
interface Config {
|
|
@@ -27,12 +28,12 @@ export async function translateLivingDocs(incrementId: string): Promise<void> {
|
|
|
27
28
|
try {
|
|
28
29
|
// 1. Load config
|
|
29
30
|
const configPath = '.specweave/config.json';
|
|
30
|
-
if (!
|
|
31
|
+
if (!existsSync(configPath)) {
|
|
31
32
|
console.log('[translate-living-docs] No config found, skipping translation');
|
|
32
33
|
return;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
const config: Config = await fs.
|
|
36
|
+
const config: Config = JSON.parse(await fs.readFile(configPath, 'utf-8'));
|
|
36
37
|
|
|
37
38
|
// 2. Check if translation is enabled
|
|
38
39
|
if (!config.language || config.language === 'en') {
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { promises as fsPromises, existsSync, mkdirSync, readFileSync, writeFileSync, statSync, readdirSync, rmSync, unlinkSync, copyFileSync } from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
async function ensureDir(dirPath) {
|
|
4
|
+
if (!existsSync(dirPath)) {
|
|
5
|
+
await fsPromises.mkdir(dirPath, { recursive: true });
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
function ensureDirSync(dirPath) {
|
|
9
|
+
if (!existsSync(dirPath)) {
|
|
10
|
+
mkdirSync(dirPath, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function mkdirpSync(dirPath) {
|
|
14
|
+
ensureDirSync(dirPath);
|
|
15
|
+
}
|
|
16
|
+
async function pathExists(filePath) {
|
|
17
|
+
return existsSync(filePath);
|
|
18
|
+
}
|
|
19
|
+
async function readJson(filePath) {
|
|
20
|
+
const content = await fsPromises.readFile(filePath, "utf-8");
|
|
21
|
+
return JSON.parse(content);
|
|
22
|
+
}
|
|
23
|
+
function readJsonSync(filePath) {
|
|
24
|
+
const content = readFileSync(filePath, "utf-8");
|
|
25
|
+
return JSON.parse(content);
|
|
26
|
+
}
|
|
27
|
+
async function writeJson(filePath, data, options) {
|
|
28
|
+
const spaces = options?.spaces ?? 2;
|
|
29
|
+
const content = JSON.stringify(data, null, spaces);
|
|
30
|
+
await fsPromises.writeFile(filePath, content, "utf-8");
|
|
31
|
+
}
|
|
32
|
+
function writeJsonSync(filePath, data, options) {
|
|
33
|
+
const spaces = options?.spaces ?? 2;
|
|
34
|
+
const content = JSON.stringify(data, null, spaces);
|
|
35
|
+
writeFileSync(filePath, content, "utf-8");
|
|
36
|
+
}
|
|
37
|
+
async function remove(targetPath) {
|
|
38
|
+
if (existsSync(targetPath)) {
|
|
39
|
+
await fsPromises.rm(targetPath, { recursive: true, force: true });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function removeSync(targetPath) {
|
|
43
|
+
if (existsSync(targetPath)) {
|
|
44
|
+
rmSync(targetPath, { recursive: true, force: true });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function copy(src, dest, options) {
|
|
48
|
+
const srcStat = await fsPromises.stat(src);
|
|
49
|
+
if (srcStat.isFile()) {
|
|
50
|
+
await fsPromises.mkdir(path.dirname(dest), { recursive: true });
|
|
51
|
+
await fsPromises.copyFile(src, dest);
|
|
52
|
+
} else if (srcStat.isDirectory()) {
|
|
53
|
+
await fsPromises.mkdir(dest, { recursive: true });
|
|
54
|
+
const entries = await fsPromises.readdir(src, { withFileTypes: true });
|
|
55
|
+
for (const entry of entries) {
|
|
56
|
+
const srcPath = path.join(src, entry.name);
|
|
57
|
+
const destPath = path.join(dest, entry.name);
|
|
58
|
+
if (options?.filter && !options.filter(srcPath)) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (entry.isDirectory()) {
|
|
62
|
+
await copy(srcPath, destPath, options);
|
|
63
|
+
} else {
|
|
64
|
+
await fsPromises.copyFile(srcPath, destPath);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function copySync(src, dest, options) {
|
|
70
|
+
const srcStat = statSync(src);
|
|
71
|
+
if (srcStat.isFile()) {
|
|
72
|
+
mkdirSync(path.dirname(dest), { recursive: true });
|
|
73
|
+
copyFileSync(src, dest);
|
|
74
|
+
} else if (srcStat.isDirectory()) {
|
|
75
|
+
mkdirSync(dest, { recursive: true });
|
|
76
|
+
const entries = readdirSync(src, { withFileTypes: true });
|
|
77
|
+
for (const entry of entries) {
|
|
78
|
+
const srcPath = path.join(src, entry.name);
|
|
79
|
+
const destPath = path.join(dest, entry.name);
|
|
80
|
+
if (options?.filter && !options.filter(srcPath)) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (entry.isDirectory()) {
|
|
84
|
+
copySync(srcPath, destPath, options);
|
|
85
|
+
} else {
|
|
86
|
+
copyFileSync(srcPath, destPath);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function ensureFile(filePath) {
|
|
92
|
+
if (!existsSync(filePath)) {
|
|
93
|
+
await fsPromises.mkdir(path.dirname(filePath), { recursive: true });
|
|
94
|
+
await fsPromises.writeFile(filePath, "", "utf-8");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function ensureFileSync(filePath) {
|
|
98
|
+
if (!existsSync(filePath)) {
|
|
99
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
100
|
+
writeFileSync(filePath, "", "utf-8");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const {
|
|
104
|
+
readFile,
|
|
105
|
+
writeFile,
|
|
106
|
+
appendFile,
|
|
107
|
+
stat,
|
|
108
|
+
readdir,
|
|
109
|
+
access,
|
|
110
|
+
unlink,
|
|
111
|
+
rmdir,
|
|
112
|
+
rename,
|
|
113
|
+
chmod
|
|
114
|
+
} = fsPromises;
|
|
115
|
+
var fs_native_default = {
|
|
116
|
+
// Async methods
|
|
117
|
+
ensureDir,
|
|
118
|
+
pathExists,
|
|
119
|
+
readJson,
|
|
120
|
+
writeJson,
|
|
121
|
+
remove,
|
|
122
|
+
copy,
|
|
123
|
+
ensureFile,
|
|
124
|
+
readFile,
|
|
125
|
+
writeFile,
|
|
126
|
+
appendFile,
|
|
127
|
+
stat,
|
|
128
|
+
readdir,
|
|
129
|
+
access,
|
|
130
|
+
unlink,
|
|
131
|
+
// Sync methods
|
|
132
|
+
ensureDirSync,
|
|
133
|
+
mkdirpSync,
|
|
134
|
+
existsSync,
|
|
135
|
+
readJsonSync,
|
|
136
|
+
writeJsonSync,
|
|
137
|
+
removeSync,
|
|
138
|
+
copySync,
|
|
139
|
+
ensureFileSync,
|
|
140
|
+
readFileSync,
|
|
141
|
+
writeFileSync,
|
|
142
|
+
statSync,
|
|
143
|
+
readdirSync,
|
|
144
|
+
unlinkSync,
|
|
145
|
+
mkdirSync,
|
|
146
|
+
rmSync
|
|
147
|
+
};
|
|
148
|
+
export {
|
|
149
|
+
access,
|
|
150
|
+
appendFile,
|
|
151
|
+
chmod,
|
|
152
|
+
copy,
|
|
153
|
+
copySync,
|
|
154
|
+
fs_native_default as default,
|
|
155
|
+
ensureDir,
|
|
156
|
+
ensureDirSync,
|
|
157
|
+
ensureFile,
|
|
158
|
+
ensureFileSync,
|
|
159
|
+
existsSync,
|
|
160
|
+
mkdirSync,
|
|
161
|
+
mkdirpSync,
|
|
162
|
+
pathExists,
|
|
163
|
+
readFile,
|
|
164
|
+
readFileSync,
|
|
165
|
+
readJson,
|
|
166
|
+
readJsonSync,
|
|
167
|
+
readdir,
|
|
168
|
+
readdirSync,
|
|
169
|
+
remove,
|
|
170
|
+
removeSync,
|
|
171
|
+
rename,
|
|
172
|
+
rmSync,
|
|
173
|
+
rmdir,
|
|
174
|
+
stat,
|
|
175
|
+
statSync,
|
|
176
|
+
unlink,
|
|
177
|
+
unlinkSync,
|
|
178
|
+
writeFile,
|
|
179
|
+
writeFileSync,
|
|
180
|
+
writeJson,
|
|
181
|
+
writeJsonSync
|
|
182
|
+
};
|