specweave 0.23.2 → 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.
Files changed (98) hide show
  1. package/CLAUDE.md +268 -0
  2. package/dist/plugins/specweave/lib/utils/fs-native.d.ts +133 -0
  3. package/dist/plugins/specweave/lib/utils/fs-native.d.ts.map +1 -0
  4. package/dist/plugins/specweave/lib/utils/fs-native.js +224 -0
  5. package/dist/plugins/specweave/lib/utils/fs-native.js.map +1 -0
  6. package/dist/plugins/specweave-github/lib/github-client-v2.js +1 -1
  7. package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
  8. package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts.map +1 -1
  9. package/dist/plugins/specweave-github/lib/github-feature-sync.js +52 -20
  10. package/dist/plugins/specweave-github/lib/github-feature-sync.js.map +1 -1
  11. package/dist/src/cli/helpers/init/initial-increment-generator.d.ts.map +1 -1
  12. package/dist/src/cli/helpers/init/initial-increment-generator.js +2 -1
  13. package/dist/src/cli/helpers/init/initial-increment-generator.js.map +1 -1
  14. package/dist/src/core/ac-test-validator-cli.d.ts +16 -0
  15. package/dist/src/core/ac-test-validator-cli.d.ts.map +1 -0
  16. package/dist/src/core/ac-test-validator-cli.js +118 -0
  17. package/dist/src/core/ac-test-validator-cli.js.map +1 -0
  18. package/dist/src/core/ac-test-validator.d.ts +111 -0
  19. package/dist/src/core/ac-test-validator.d.ts.map +1 -0
  20. package/dist/src/core/ac-test-validator.js +292 -0
  21. package/dist/src/core/ac-test-validator.js.map +1 -0
  22. package/dist/src/core/increment/desync-detector.d.ts +142 -0
  23. package/dist/src/core/increment/desync-detector.d.ts.map +1 -0
  24. package/dist/src/core/increment/desync-detector.js +270 -0
  25. package/dist/src/core/increment/desync-detector.js.map +1 -0
  26. package/dist/src/core/increment/metadata-manager.d.ts +8 -4
  27. package/dist/src/core/increment/metadata-manager.d.ts.map +1 -1
  28. package/dist/src/core/increment/metadata-manager.js +45 -21
  29. package/dist/src/core/increment/metadata-manager.js.map +1 -1
  30. package/dist/src/core/qa/qa-runner.js +9 -2
  31. package/dist/src/core/qa/qa-runner.js.map +1 -1
  32. package/dist/src/sync/sync-coordinator.d.ts +1 -1
  33. package/dist/src/sync/sync-coordinator.d.ts.map +1 -1
  34. package/dist/src/sync/sync-coordinator.js +40 -2
  35. package/dist/src/sync/sync-coordinator.js.map +1 -1
  36. package/dist/src/utils/fs-native.d.ts +133 -0
  37. package/dist/src/utils/fs-native.d.ts.map +1 -0
  38. package/dist/src/utils/fs-native.js +224 -0
  39. package/dist/src/utils/fs-native.js.map +1 -0
  40. package/package.json +1 -1
  41. package/plugins/specweave/.claude-plugin/plugin.json +12 -0
  42. package/plugins/specweave/agents/AGENTS-INDEX.md +216 -0
  43. package/plugins/specweave/agents/architect/AGENT.md +17 -0
  44. package/plugins/specweave/agents/code-standards-detective/AGENT.md +16 -0
  45. package/plugins/specweave/agents/docs-writer/AGENT.md +16 -0
  46. package/plugins/specweave/agents/increment-quality-judge-v2/AGENT.md +704 -0
  47. package/plugins/specweave/agents/infrastructure/AGENT.md +16 -0
  48. package/plugins/specweave/agents/performance/AGENT.md +16 -0
  49. package/plugins/specweave/agents/pm/AGENT.md +17 -0
  50. package/plugins/specweave/agents/qa-lead/AGENT.md +15 -0
  51. package/plugins/specweave/agents/reflective-reviewer/AGENT.md +16 -0
  52. package/plugins/specweave/agents/security/AGENT.md +16 -0
  53. package/plugins/specweave/agents/tdd-orchestrator/AGENT.md +16 -0
  54. package/plugins/specweave/agents/tech-lead/AGENT.md +16 -0
  55. package/plugins/specweave/agents/test-aware-planner/AGENT.md +16 -0
  56. package/plugins/specweave/agents/translator/AGENT.md +13 -0
  57. package/plugins/specweave/commands/specweave-done.md +14 -0
  58. package/plugins/specweave/commands/specweave-qa.md +11 -1
  59. package/plugins/specweave/commands/specweave-sync-status.md +356 -0
  60. package/plugins/specweave/commands/specweave-validate.md +10 -1
  61. package/plugins/specweave/hooks/pre-task-completion.sh +196 -0
  62. package/plugins/specweave/lib/hooks/git-diff-analyzer.js +3 -3
  63. package/plugins/specweave/lib/hooks/git-diff-analyzer.ts +3 -3
  64. package/plugins/specweave/lib/hooks/invoke-translator-skill.js +3 -2
  65. package/plugins/specweave/lib/hooks/invoke-translator-skill.ts +3 -2
  66. package/plugins/specweave/lib/hooks/prepare-reflection-context.js +3 -3
  67. package/plugins/specweave/lib/hooks/prepare-reflection-context.ts +3 -3
  68. package/plugins/specweave/lib/hooks/reflection-config-loader.js +4 -4
  69. package/plugins/specweave/lib/hooks/reflection-config-loader.ts +4 -4
  70. package/plugins/specweave/lib/hooks/reflection-storage.js +9 -9
  71. package/plugins/specweave/lib/hooks/reflection-storage.ts +9 -9
  72. package/plugins/specweave/lib/hooks/sync-cache.js +9 -8
  73. package/plugins/specweave/lib/hooks/sync-living-docs.js +57 -6
  74. package/plugins/specweave/lib/hooks/sync-us-tasks.js +6 -6
  75. package/plugins/specweave/lib/hooks/translate-file.js +3 -2
  76. package/plugins/specweave/lib/hooks/translate-file.ts +3 -2
  77. package/plugins/specweave/lib/hooks/translate-living-docs.js +4 -3
  78. package/plugins/specweave/lib/hooks/translate-living-docs.ts +4 -3
  79. package/plugins/specweave/lib/utils/fs-native.js +182 -0
  80. package/plugins/specweave/lib/utils/fs-native.ts +283 -0
  81. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.d.ts +8 -4
  82. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js +45 -21
  83. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js.map +1 -1
  84. package/plugins/specweave/skills/SKILLS-INDEX.md +26 -2
  85. package/plugins/specweave/skills/increment-planner/SKILL.md +2 -2
  86. package/plugins/specweave-ado/commands/specweave-ado-close-workitem.md +1 -1
  87. package/plugins/specweave-ado/commands/specweave-ado-create-workitem.md +1 -1
  88. package/plugins/specweave-ado/commands/specweave-ado-status.md +1 -1
  89. package/plugins/specweave-ado/commands/specweave-ado-sync.md +1 -1
  90. package/plugins/specweave-diagrams/agents/diagrams-architect/AGENT.md +1 -1
  91. package/plugins/specweave-diagrams/skills/diagrams-generator/SKILL.md +4 -4
  92. package/plugins/specweave-github/lib/github-client-v2.js +2 -1
  93. package/plugins/specweave-github/lib/github-client-v2.ts +1 -1
  94. package/plugins/specweave-github/lib/github-feature-sync.js +30 -17
  95. package/plugins/specweave-github/lib/github-feature-sync.ts +54 -24
  96. package/plugins/specweave-mobile/README.md +1 -1
  97. package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +72 -0
  98. package/plugins/specweave/skills/task-builder/README.md +0 -84
@@ -1,15 +1,31 @@
1
1
  #!/usr/bin/env node
2
- import fs from "fs-extra";
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
- if (fs.existsSync(configPath)) {
12
- config = JSON.parse(await fs.readFile(configPath, "utf-8"));
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 (!fs.existsSync(incrementSpecPath)) {
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 = fs.existsSync(livingDocsPath);
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
- await fs.ensureDir(livingDocsDir);
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-extra';
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 (!fs.existsSync(tasksPath)) {
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 (fs.existsSync(metadataPath)) {
455
- const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf-8'));
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 (fs.existsSync(configPath)) {
464
- const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
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 fs from "fs-extra";
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 (!await fs.pathExists(filePath)) {
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 fs from 'fs-extra';
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 (!await fs.pathExists(filePath)) {
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 fs from "fs-extra";
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 (!fs.existsSync(configPath)) {
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.readJson(configPath);
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 fs from 'fs-extra';
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 (!fs.existsSync(configPath)) {
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.readJson(configPath);
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
+ };
@@ -0,0 +1,283 @@
1
+ /**
2
+ * Native Node.js fs API Helpers
3
+ *
4
+ * Drop-in replacements for fs-extra methods using only Node.js stdlib.
5
+ * All methods use native Node.js 20+ APIs with no external dependencies.
6
+ *
7
+ * Migration from fs-extra:
8
+ * - import fs from 'fs-extra' → import * as fs from './utils/fs-native.js'
9
+ * - All fs-extra methods work as drop-in replacements
10
+ *
11
+ * Benefits:
12
+ * - Zero bundle overhead (no npm packages)
13
+ * - Works in marketplace (no node_modules needed)
14
+ * - Faster startup (native APIs)
15
+ * - Better debugging (native stack traces)
16
+ */
17
+
18
+ import { promises as fsPromises, existsSync, mkdirSync, readFileSync, writeFileSync, statSync, readdirSync, rmSync, unlinkSync, copyFileSync } from 'fs';
19
+ import { execSync } from 'child_process';
20
+ import path from 'path';
21
+
22
+ /**
23
+ * Ensures that a directory exists. If the directory does not exist, it is created.
24
+ * @param dirPath - The directory path to ensure
25
+ */
26
+ export async function ensureDir(dirPath: string): Promise<void> {
27
+ if (!existsSync(dirPath)) {
28
+ await fsPromises.mkdir(dirPath, { recursive: true });
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Synchronous version of ensureDir
34
+ */
35
+ export function ensureDirSync(dirPath: string): void {
36
+ if (!existsSync(dirPath)) {
37
+ mkdirSync(dirPath, { recursive: true });
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Alias for ensureDirSync (fs-extra compatibility)
43
+ */
44
+ export function mkdirpSync(dirPath: string): void {
45
+ ensureDirSync(dirPath);
46
+ }
47
+
48
+ /**
49
+ * Check if a path exists
50
+ * @param filePath - The path to check
51
+ */
52
+ export async function pathExists(filePath: string): Promise<boolean> {
53
+ return existsSync(filePath);
54
+ }
55
+
56
+ /**
57
+ * Synchronous version of pathExists
58
+ */
59
+ export { existsSync };
60
+
61
+ /**
62
+ * Read a JSON file and parse it
63
+ * @param filePath - The JSON file path
64
+ */
65
+ export async function readJson(filePath: string): Promise<any> {
66
+ const content = await fsPromises.readFile(filePath, 'utf-8');
67
+ return JSON.parse(content);
68
+ }
69
+
70
+ /**
71
+ * Synchronous version of readJson
72
+ */
73
+ export function readJsonSync(filePath: string): any {
74
+ const content = readFileSync(filePath, 'utf-8');
75
+ return JSON.parse(content);
76
+ }
77
+
78
+ /**
79
+ * Write a JSON file with formatting
80
+ * @param filePath - The JSON file path
81
+ * @param data - The data to write
82
+ * @param options - Options (spaces for indentation)
83
+ */
84
+ export async function writeJson(
85
+ filePath: string,
86
+ data: any,
87
+ options?: { spaces?: number }
88
+ ): Promise<void> {
89
+ const spaces = options?.spaces ?? 2;
90
+ const content = JSON.stringify(data, null, spaces);
91
+ await fsPromises.writeFile(filePath, content, 'utf-8');
92
+ }
93
+
94
+ /**
95
+ * Synchronous version of writeJson
96
+ */
97
+ export function writeJsonSync(
98
+ filePath: string,
99
+ data: any,
100
+ options?: { spaces?: number }
101
+ ): void {
102
+ const spaces = options?.spaces ?? 2;
103
+ const content = JSON.stringify(data, null, spaces);
104
+ writeFileSync(filePath, content, 'utf-8');
105
+ }
106
+
107
+ /**
108
+ * Remove a file or directory (recursively)
109
+ * @param targetPath - The path to remove
110
+ */
111
+ export async function remove(targetPath: string): Promise<void> {
112
+ if (existsSync(targetPath)) {
113
+ await fsPromises.rm(targetPath, { recursive: true, force: true });
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Synchronous version of remove
119
+ */
120
+ export function removeSync(targetPath: string): void {
121
+ if (existsSync(targetPath)) {
122
+ rmSync(targetPath, { recursive: true, force: true });
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Copy a file or directory
128
+ * @param src - Source path
129
+ * @param dest - Destination path
130
+ * @param options - Copy options
131
+ */
132
+ export async function copy(
133
+ src: string,
134
+ dest: string,
135
+ options?: { overwrite?: boolean; filter?: (src: string) => boolean }
136
+ ): Promise<void> {
137
+ const srcStat = await fsPromises.stat(src);
138
+
139
+ if (srcStat.isFile()) {
140
+ // Copy single file
141
+ await fsPromises.mkdir(path.dirname(dest), { recursive: true });
142
+ await fsPromises.copyFile(src, dest);
143
+ } else if (srcStat.isDirectory()) {
144
+ // Copy directory recursively
145
+ await fsPromises.mkdir(dest, { recursive: true });
146
+ const entries = await fsPromises.readdir(src, { withFileTypes: true });
147
+
148
+ for (const entry of entries) {
149
+ const srcPath = path.join(src, entry.name);
150
+ const destPath = path.join(dest, entry.name);
151
+
152
+ // Apply filter if provided
153
+ if (options?.filter && !options.filter(srcPath)) {
154
+ continue;
155
+ }
156
+
157
+ if (entry.isDirectory()) {
158
+ await copy(srcPath, destPath, options);
159
+ } else {
160
+ await fsPromises.copyFile(srcPath, destPath);
161
+ }
162
+ }
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Synchronous version of copy
168
+ */
169
+ export function copySync(
170
+ src: string,
171
+ dest: string,
172
+ options?: { overwrite?: boolean; filter?: (src: string) => boolean }
173
+ ): void {
174
+ const srcStat = statSync(src);
175
+
176
+ if (srcStat.isFile()) {
177
+ // Copy single file
178
+ mkdirSync(path.dirname(dest), { recursive: true });
179
+ copyFileSync(src, dest);
180
+ } else if (srcStat.isDirectory()) {
181
+ // Copy directory recursively
182
+ mkdirSync(dest, { recursive: true });
183
+ const entries = readdirSync(src, { withFileTypes: true });
184
+
185
+ for (const entry of entries) {
186
+ const srcPath = path.join(src, entry.name);
187
+ const destPath = path.join(dest, entry.name);
188
+
189
+ // Apply filter if provided
190
+ if (options?.filter && !options.filter(srcPath)) {
191
+ continue;
192
+ }
193
+
194
+ if (entry.isDirectory()) {
195
+ copySync(srcPath, destPath, options);
196
+ } else {
197
+ copyFileSync(srcPath, destPath);
198
+ }
199
+ }
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Ensure a file exists (create if it doesn't)
205
+ * @param filePath - The file path
206
+ */
207
+ export async function ensureFile(filePath: string): Promise<void> {
208
+ if (!existsSync(filePath)) {
209
+ await fsPromises.mkdir(path.dirname(filePath), { recursive: true });
210
+ await fsPromises.writeFile(filePath, '', 'utf-8');
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Synchronous version of ensureFile
216
+ */
217
+ export function ensureFileSync(filePath: string): void {
218
+ if (!existsSync(filePath)) {
219
+ mkdirSync(path.dirname(filePath), { recursive: true });
220
+ writeFileSync(filePath, '', 'utf-8');
221
+ }
222
+ }
223
+
224
+ // Re-export common fs/promises methods for convenience
225
+ export const {
226
+ readFile,
227
+ writeFile,
228
+ appendFile,
229
+ stat,
230
+ readdir,
231
+ access,
232
+ unlink,
233
+ rmdir,
234
+ rename,
235
+ chmod,
236
+ } = fsPromises;
237
+
238
+ // Re-export common synchronous methods
239
+ export {
240
+ readFileSync,
241
+ writeFileSync,
242
+ statSync,
243
+ readdirSync,
244
+ unlinkSync,
245
+ mkdirSync,
246
+ rmSync,
247
+ };
248
+
249
+ // Default export for convenience
250
+ export default {
251
+ // Async methods
252
+ ensureDir,
253
+ pathExists,
254
+ readJson,
255
+ writeJson,
256
+ remove,
257
+ copy,
258
+ ensureFile,
259
+ readFile,
260
+ writeFile,
261
+ appendFile,
262
+ stat,
263
+ readdir,
264
+ access,
265
+ unlink,
266
+
267
+ // Sync methods
268
+ ensureDirSync,
269
+ mkdirpSync,
270
+ existsSync,
271
+ readJsonSync,
272
+ writeJsonSync,
273
+ removeSync,
274
+ copySync,
275
+ ensureFileSync,
276
+ readFileSync,
277
+ writeFileSync,
278
+ statSync,
279
+ readdirSync,
280
+ unlinkSync,
281
+ mkdirSync,
282
+ rmSync,
283
+ };