@ktpartners/dgs-platform 3.3.0 → 3.3.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/CHANGELOG.md +9 -0
- package/deliver-great-systems/bin/lib/core.cjs +2 -6
- package/deliver-great-systems/bin/lib/docs.cjs +22 -12
- package/deliver-great-systems/bin/lib/init.cjs +51 -10
- package/deliver-great-systems/bin/lib/sync.cjs +2 -6
- package/deliver-great-systems/bin/lib/verify.cjs +2 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,15 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [3.3.1] - 2026-05-26
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- **`execGit` works on Windows** — `bin/lib/core.cjs:execGit` previously POSIX-quoted args (`'arg with spaces'`) then ran them through `execSync`'s shell. On Windows `execSync` invokes `cmd.exe`, which treats single quotes as literal characters — so `git commit -m 'docs(88): create phase plan'` was tokenised into separate args and git interpreted the trailing words as pathspecs. Every multi-word commit message broke on Windows, affecting essentially every DGS command that commits (`/dgs:fast`, `/dgs:quick`, `/dgs:execute-phase`, `/dgs:complete-quick`, the v1→v2 migration self-commit, etc.). Replaced with `execFileSync('git', args, ...)` which bypasses the shell entirely on both platforms — no quoting needed since `args` is already an array. Preserved the existing `{exitCode, stdout, stderr}` return shape and `.trim()` behaviour. Added round-trip regression test in `tests/core.test.cjs` that asserts a message with spaces, parens, colon, ampersand, and percent (`docs(88): create phase plan & 100% coverage`) survives `init`→`commit`→`log` verbatim (quick-260526-dip).
|
|
15
|
+
- **`execGitWithTimeout` works on Windows** — `bin/lib/sync.cjs:execGitWithTimeout` was a fork of `execGit` (added a `timeout` option per its `(NOT execGit)` comment) and inherited the identical POSIX-quoting bug. Affected `/dgs:sync` pre-flight checks across every registered repo (`remote`, `rev-parse --abbrev-ref HEAD`, etc.). Same fix: `execFileSync('git', args, { ..., timeout: timeoutMs })`. Preserved the `isTimeout`/`isAuth` error classification in the catch block (quick-260526-e5l).
|
|
16
|
+
- **`hasCodeFiles` no longer shells out** — `bin/lib/init.cjs` used `execSync('find . -maxdepth 3 \\( -name "*.ts" -o ... \\) | grep -v node_modules | head -5')` during `/dgs:new-project` to detect existing source files for template selection. `find`/`grep`/`head` aren't on Windows by default and the bash-style `\\(` escaping is shell-specific; the catch-block silently fell back to `hasCode = false`, producing wrong init heuristics on Windows. Extracted a pure-Node `hasCodeFiles(cwd)` helper using `fs.readdirSync({ withFileTypes: true })` — depth 3, skips `node_modules` and `.git`, early-exits at 5 matches across `.ts/.js/.py/.go/.rs/.swift/.java`. Exported for testability; added unit tests in `tests/init.test.cjs` covering depth-3 boundary, depth-4 cap, `node_modules`/`.git` skip, the 7-extension matrix, and nonexistent paths (quick-260526-e5l).
|
|
17
|
+
- **`~/` tilde expansion works on Windows** — `bin/lib/verify.cjs` resolved `~/path` canonical_refs via `path.join(process.env.HOME || '', cleanRef.slice(2))`. On Windows `HOME` is unset, so `|| ''` produced a relative path and `cmdVerifyReferences` falsely reported the ref as missing. Replaced with `path.join(os.homedir(), cleanRef.slice(2))` — `os.homedir()` returns `USERPROFILE` on Windows and `HOME` elsewhere. Added a sentinel-file test in `tests/verify.test.cjs` that creates a real file under `os.homedir()` and verifies the `~/` resolution finds it (quick-260526-e5l).
|
|
18
|
+
- **PDF/DOCX/XLSX extraction no longer shells out** — `bin/lib/docs.cjs` ran the async `pdf-parse`/`mammoth`/`exceljs` libs in a sync context via three near-identical `` execSync(`node -e ${JSON.stringify(script)}`) `` sites. Although `JSON.stringify`'s double quotes are cmd.exe-friendly, this path (a) triggered cmd.exe %-variable expansion on filenames containing `%`, and (b) pushed the script body toward cmd.exe's 8191-char command-line limit on long inputs. Replaced all three with `spawnSync('node', ['-e', script], { shell: false, ... })` — same script body, no shell at all. Added structural source-grep guard in `tests/docs.test.cjs` (`!/execSync\(\s*\`node -e/.test(src)`) so the bug pattern can't regress (quick-260526-e5l).
|
|
19
|
+
|
|
11
20
|
## [3.3.0] - 2026-05-15
|
|
12
21
|
|
|
13
22
|
### Added
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const path = require('path');
|
|
7
|
-
const { execSync } = require('child_process');
|
|
7
|
+
const { execSync, execFileSync } = require('child_process');
|
|
8
8
|
const { getPlanningRoot, isV2Install, PROJECTS_DIR } = require('./paths.cjs');
|
|
9
9
|
|
|
10
10
|
// ─── Path helpers ────────────────────────────────────────────────────────────
|
|
@@ -181,11 +181,7 @@ function isGitIgnored(cwd, targetPath) {
|
|
|
181
181
|
|
|
182
182
|
function execGit(cwd, args) {
|
|
183
183
|
try {
|
|
184
|
-
const
|
|
185
|
-
if (/^[a-zA-Z0-9._\-/=:@]+$/.test(a)) return a;
|
|
186
|
-
return "'" + a.replace(/'/g, "'\\''") + "'";
|
|
187
|
-
});
|
|
188
|
-
const stdout = execSync('git ' + escaped.join(' '), {
|
|
184
|
+
const stdout = execFileSync('git', args, {
|
|
189
185
|
cwd,
|
|
190
186
|
stdio: 'pipe',
|
|
191
187
|
encoding: 'utf-8',
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
const fs = require('fs');
|
|
15
15
|
const path = require('path');
|
|
16
16
|
const crypto = require('crypto');
|
|
17
|
+
const { spawnSync } = require('child_process');
|
|
17
18
|
const { safeReadFile, execGit, generateSlugInternal, output, error } = require('./core.cjs');
|
|
18
19
|
const { getPlanningRoot } = require('./paths.cjs');
|
|
19
20
|
const { findIdeaFile } = require('./ideas.cjs');
|
|
@@ -129,10 +130,11 @@ function extractText(filePath, ext) {
|
|
|
129
130
|
try {
|
|
130
131
|
const pdfParse = require('pdf-parse');
|
|
131
132
|
const buffer = fs.readFileSync(filePath);
|
|
132
|
-
// pdf-parse returns a promise;
|
|
133
|
-
//
|
|
133
|
+
// pdf-parse returns a promise; run it in a child node process via
|
|
134
|
+
// spawnSync so the parent can stay synchronous. spawnSync (not
|
|
135
|
+
// execSync with a shell-built command) is required on Windows where
|
|
136
|
+
// cmd.exe applies different quoting rules.
|
|
134
137
|
let result = null;
|
|
135
|
-
const { execSync } = require('child_process');
|
|
136
138
|
const script = `
|
|
137
139
|
const pdfParse = require('pdf-parse');
|
|
138
140
|
const fs = require('fs');
|
|
@@ -143,12 +145,15 @@ function extractText(filePath, ext) {
|
|
|
143
145
|
process.stdout.write(JSON.stringify({ error: err.message }));
|
|
144
146
|
});
|
|
145
147
|
`;
|
|
146
|
-
const
|
|
148
|
+
const r = spawnSync('node', ['-e', script], {
|
|
147
149
|
encoding: 'utf-8',
|
|
148
150
|
timeout: 30000,
|
|
149
151
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
150
152
|
});
|
|
151
|
-
|
|
153
|
+
if (r.status !== 0) {
|
|
154
|
+
return { text: null, error: `PDF extraction failed: ${r.stderr || r.error?.message || 'unknown'}` };
|
|
155
|
+
}
|
|
156
|
+
result = JSON.parse(r.stdout.trim());
|
|
152
157
|
if (result.error) {
|
|
153
158
|
return { text: null, error: result.error };
|
|
154
159
|
}
|
|
@@ -160,7 +165,6 @@ function extractText(filePath, ext) {
|
|
|
160
165
|
|
|
161
166
|
if (ext === '.xlsx') {
|
|
162
167
|
try {
|
|
163
|
-
const { execSync } = require('child_process');
|
|
164
168
|
const script = `
|
|
165
169
|
const ExcelJS = require('exceljs');
|
|
166
170
|
const workbook = new ExcelJS.Workbook();
|
|
@@ -178,11 +182,15 @@ function extractText(filePath, ext) {
|
|
|
178
182
|
process.stdout.write(JSON.stringify({ error: err.message }));
|
|
179
183
|
});
|
|
180
184
|
`;
|
|
181
|
-
const
|
|
185
|
+
const r = spawnSync('node', ['-e', script], {
|
|
182
186
|
encoding: 'utf-8',
|
|
183
187
|
timeout: 30000,
|
|
184
|
-
|
|
185
|
-
|
|
188
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
189
|
+
});
|
|
190
|
+
if (r.status !== 0) {
|
|
191
|
+
return { text: null, error: `XLSX extraction failed: ${r.stderr || r.error?.message || 'unknown'}` };
|
|
192
|
+
}
|
|
193
|
+
const parsed = JSON.parse(r.stdout.trim());
|
|
186
194
|
if (parsed.error) return { text: null, error: parsed.error };
|
|
187
195
|
return { text: parsed.text, error: null };
|
|
188
196
|
} catch (e) {
|
|
@@ -202,7 +210,6 @@ function extractText(filePath, ext) {
|
|
|
202
210
|
if (ext === '.docx') {
|
|
203
211
|
try {
|
|
204
212
|
const mammoth = require('mammoth');
|
|
205
|
-
const { execSync } = require('child_process');
|
|
206
213
|
const script = `
|
|
207
214
|
const mammoth = require('mammoth');
|
|
208
215
|
mammoth.extractRawText({ path: ${JSON.stringify(filePath)} })
|
|
@@ -213,12 +220,15 @@ function extractText(filePath, ext) {
|
|
|
213
220
|
process.stdout.write(JSON.stringify({ error: err.message }));
|
|
214
221
|
});
|
|
215
222
|
`;
|
|
216
|
-
const
|
|
223
|
+
const r = spawnSync('node', ['-e', script], {
|
|
217
224
|
encoding: 'utf-8',
|
|
218
225
|
timeout: 30000,
|
|
219
226
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
220
227
|
});
|
|
221
|
-
|
|
228
|
+
if (r.status !== 0) {
|
|
229
|
+
return { text: null, error: `DOCX extraction failed: ${r.stderr || r.error?.message || 'unknown'}` };
|
|
230
|
+
}
|
|
231
|
+
const result = JSON.parse(r.stdout.trim());
|
|
222
232
|
if (result.error) {
|
|
223
233
|
return { text: null, error: result.error };
|
|
224
234
|
}
|
|
@@ -88,6 +88,54 @@ function applySyncPull(cwd, workflowName, result) {
|
|
|
88
88
|
result.needs_pull = false;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
// ─── Brownfield detection ───────────────────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Detect whether the directory tree rooted at cwd contains any source
|
|
95
|
+
* files in a small set of common languages, ignoring node_modules and .git,
|
|
96
|
+
* with a maximum recursion depth of 3 (cwd itself is depth 0).
|
|
97
|
+
*
|
|
98
|
+
* Returns true on first match (early termination).
|
|
99
|
+
* Returns false on any error (e.g. permission denied on top-level dir).
|
|
100
|
+
*
|
|
101
|
+
* Pure Node — replaces the prior `find ... | grep ... | head -5` shell
|
|
102
|
+
* pipeline that does not exist on Windows.
|
|
103
|
+
*
|
|
104
|
+
* @param {string} cwd - Directory to scan.
|
|
105
|
+
* @returns {boolean}
|
|
106
|
+
*/
|
|
107
|
+
function hasCodeFiles(cwd) {
|
|
108
|
+
const CODE_EXTENSIONS = new Set(['.ts', '.js', '.py', '.go', '.rs', '.swift', '.java']);
|
|
109
|
+
const SKIP_DIRS = new Set(['node_modules', '.git']);
|
|
110
|
+
const MAX_DEPTH = 3;
|
|
111
|
+
|
|
112
|
+
function walk(dir, depth) {
|
|
113
|
+
if (depth > MAX_DEPTH) return false;
|
|
114
|
+
let entries;
|
|
115
|
+
try {
|
|
116
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
117
|
+
} catch {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
for (const entry of entries) {
|
|
121
|
+
if (entry.isDirectory()) {
|
|
122
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
123
|
+
if (walk(path.join(dir, entry.name), depth + 1)) return true;
|
|
124
|
+
} else if (entry.isFile()) {
|
|
125
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
126
|
+
if (CODE_EXTENSIONS.has(ext)) return true;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
return walk(cwd, 0);
|
|
134
|
+
} catch {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
91
139
|
// ─── v2 Project Context Resolution ──────────────────────────────────────────
|
|
92
140
|
|
|
93
141
|
/**
|
|
@@ -411,17 +459,9 @@ function cmdInitNewProject(cwd, slugArg, raw) {
|
|
|
411
459
|
const braveKeyFile = path.join(homedir, '.dgs', 'brave_api_key');
|
|
412
460
|
const hasBraveSearch = !!(process.env.BRAVE_API_KEY || fs.existsSync(braveKeyFile));
|
|
413
461
|
|
|
414
|
-
// Detect existing code
|
|
415
|
-
|
|
462
|
+
// Detect existing code (pure-Node walk, Windows-safe)
|
|
463
|
+
const hasCode = hasCodeFiles(cwd);
|
|
416
464
|
let hasPackageFile = false;
|
|
417
|
-
try {
|
|
418
|
-
const files = execSync('find . -maxdepth 3 \\( -name "*.ts" -o -name "*.js" -o -name "*.py" -o -name "*.go" -o -name "*.rs" -o -name "*.swift" -o -name "*.java" \\) 2>/dev/null | grep -v node_modules | grep -v .git | head -5', {
|
|
419
|
-
cwd,
|
|
420
|
-
encoding: 'utf-8',
|
|
421
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
422
|
-
});
|
|
423
|
-
hasCode = files.trim().length > 0;
|
|
424
|
-
} catch {}
|
|
425
465
|
|
|
426
466
|
hasPackageFile = pathExistsInternal(cwd, 'package.json') ||
|
|
427
467
|
pathExistsInternal(cwd, 'requirements.txt') ||
|
|
@@ -1547,4 +1587,5 @@ module.exports = {
|
|
|
1547
1587
|
cmdInitProgress,
|
|
1548
1588
|
cmdInitProgressAll,
|
|
1549
1589
|
applySyncPull,
|
|
1590
|
+
hasCodeFiles,
|
|
1550
1591
|
};
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* with pre-flight checks, error handling, and structured result reporting.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
const { execSync } = require('child_process');
|
|
8
|
+
const { execSync, execFileSync } = require('child_process');
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const fs = require('fs');
|
|
11
11
|
const { execGit, safeReadFile, loadConfig } = require('./core.cjs');
|
|
@@ -29,11 +29,7 @@ const { getLocalConfigPath } = require('./config.cjs');
|
|
|
29
29
|
*/
|
|
30
30
|
function execGitWithTimeout(cwd, args, timeoutMs = 30000) {
|
|
31
31
|
try {
|
|
32
|
-
const
|
|
33
|
-
if (/^[a-zA-Z0-9._\-/=:@]+$/.test(a)) return a;
|
|
34
|
-
return "'" + a.replace(/'/g, "'\\''") + "'";
|
|
35
|
-
});
|
|
36
|
-
const stdout = execSync('git ' + escaped.join(' '), {
|
|
32
|
+
const stdout = execFileSync('git', args, {
|
|
37
33
|
cwd,
|
|
38
34
|
stdio: 'pipe',
|
|
39
35
|
encoding: 'utf-8',
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const path = require('path');
|
|
7
|
+
const os = require('os');
|
|
7
8
|
const { safeReadFile, normalizePhaseName, execGit, findPhaseInternal, getMilestoneInfo, output, error } = require('./core.cjs');
|
|
8
9
|
const { getPlanningRoot } = require('./paths.cjs');
|
|
9
10
|
const { extractFrontmatter, parseMustHavesBlock } = require('./frontmatter.cjs');
|
|
@@ -227,7 +228,7 @@ function cmdVerifyReferences(cwd, filePath, raw) {
|
|
|
227
228
|
for (const ref of atRefs) {
|
|
228
229
|
const cleanRef = ref.slice(1); // remove @
|
|
229
230
|
const resolved = cleanRef.startsWith('~/')
|
|
230
|
-
? path.join(
|
|
231
|
+
? path.join(os.homedir(), cleanRef.slice(2))
|
|
231
232
|
: path.join(cwd, cleanRef);
|
|
232
233
|
if (fs.existsSync(resolved)) {
|
|
233
234
|
found.push(cleanRef);
|
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"bugs": {
|
|
9
9
|
"url": "https://github.com/KT-Partners-Ltd/dgs-platform-docs/issues"
|
|
10
10
|
},
|
|
11
|
-
"version": "3.3.
|
|
11
|
+
"version": "3.3.1",
|
|
12
12
|
"description": "Deliver Great Systems Platform — A meta-prompting, context engineering and spec-driven development system for Claude Code and Gemini by KT Partners.",
|
|
13
13
|
"bin": {
|
|
14
14
|
"dgs": "bin/install.js"
|