@ktpartners/dgs-platform 3.0.4 → 3.3.0
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 +115 -0
- package/README.md +8 -1
- package/agents/dgs-executor.md +124 -3
- package/agents/dgs-idea-researcher.md +447 -0
- package/agents/dgs-plan-checker.md +32 -0
- package/agents/dgs-planner.md +41 -8
- package/bin/install.js +44 -0
- package/commands/dgs/audit-milestone.md +2 -1
- package/commands/dgs/diff-report.md +124 -0
- package/commands/dgs/new-project.md +8 -21
- package/commands/dgs/package-scan.md +43 -0
- package/commands/dgs/research-idea.md +1 -0
- package/commands/dgs/switch-project.md +13 -0
- package/deliver-great-systems/bin/dgs-tools.cjs +120 -5
- package/deliver-great-systems/bin/lib/audit-tolerance.cjs +77 -0
- package/deliver-great-systems/bin/lib/audit-tolerance.test.cjs +101 -0
- package/deliver-great-systems/bin/lib/commands.cjs +311 -16
- package/deliver-great-systems/bin/lib/commands.test.cjs +115 -0
- package/deliver-great-systems/bin/lib/commit-verify.test.cjs +236 -0
- package/deliver-great-systems/bin/lib/config.cjs +41 -0
- package/deliver-great-systems/bin/lib/config.test.cjs +309 -0
- package/deliver-great-systems/bin/lib/core.cjs +7 -3
- package/deliver-great-systems/bin/lib/core.test.cjs +79 -1
- package/deliver-great-systems/bin/lib/fast-routing.cjs +199 -0
- package/deliver-great-systems/bin/lib/fast-routing.test.cjs +108 -0
- package/deliver-great-systems/bin/lib/final-commit-precondition.test.cjs +87 -0
- package/deliver-great-systems/bin/lib/fixtures/package-scan/bundler-audit-gemfile.json +21 -0
- package/deliver-great-systems/bin/lib/fixtures/package-scan/gate-parity-expected.md +186 -0
- package/deliver-great-systems/bin/lib/fixtures/package-scan/gate-parity-runresult.json +235 -0
- package/deliver-great-systems/bin/lib/fixtures/package-scan/govulncheck-import.json +3 -0
- package/deliver-great-systems/bin/lib/fixtures/package-scan/npm-audit-v10.json +37 -0
- package/deliver-great-systems/bin/lib/fixtures/package-scan/osv-clean.json +3 -0
- package/deliver-great-systems/bin/lib/fixtures/package-scan/osv-vulns.json +77 -0
- package/deliver-great-systems/bin/lib/fixtures/package-scan/pip-audit-requirements.json +28 -0
- package/deliver-great-systems/bin/lib/fixtures/package-scan/snyk-lodash.json +30 -0
- package/deliver-great-systems/bin/lib/fixtures/package-scan/snyk-workspaces.json +55 -0
- package/deliver-great-systems/bin/lib/frontmatter.cjs +1 -1
- package/deliver-great-systems/bin/lib/governance.cjs +211 -0
- package/deliver-great-systems/bin/lib/governance.test.cjs +339 -0
- package/deliver-great-systems/bin/lib/health-untracked-phase.test.cjs +269 -0
- package/deliver-great-systems/bin/lib/init.cjs +56 -27
- package/deliver-great-systems/bin/lib/init.test.cjs +212 -5
- package/deliver-great-systems/bin/lib/jobs.cjs +7 -4
- package/deliver-great-systems/bin/lib/milestone.cjs +101 -3
- package/deliver-great-systems/bin/lib/milestone.test.cjs +203 -0
- package/deliver-great-systems/bin/lib/package-adapters.cjs +530 -0
- package/deliver-great-systems/bin/lib/package-adapters.test.cjs +618 -0
- package/deliver-great-systems/bin/lib/package-ecosystems.cjs +350 -0
- package/deliver-great-systems/bin/lib/package-ecosystems.test.cjs +348 -0
- package/deliver-great-systems/bin/lib/package-runner.cjs +199 -0
- package/deliver-great-systems/bin/lib/package-runner.test.cjs +198 -0
- package/deliver-great-systems/bin/lib/package-scan-provenance.cjs +56 -0
- package/deliver-great-systems/bin/lib/package-scan-provenance.test.cjs +103 -0
- package/deliver-great-systems/bin/lib/package-scan-report.cjs +1140 -0
- package/deliver-great-systems/bin/lib/package-scan-report.test.cjs +1963 -0
- package/deliver-great-systems/bin/lib/package-scan-skill.cjs +96 -0
- package/deliver-great-systems/bin/lib/package-scan-skill.test.cjs +136 -0
- package/deliver-great-systems/bin/lib/package-scan.cjs +919 -0
- package/deliver-great-systems/bin/lib/package-scan.test.cjs +2147 -0
- package/deliver-great-systems/bin/lib/phase.cjs +18 -1
- package/deliver-great-systems/bin/lib/plan-number-validity.test.cjs +48 -0
- package/deliver-great-systems/bin/lib/projects.cjs +38 -3
- package/deliver-great-systems/bin/lib/projects.test.cjs +112 -2
- package/deliver-great-systems/bin/lib/quick.cjs +178 -23
- package/deliver-great-systems/bin/lib/quick.test.cjs +138 -4
- package/deliver-great-systems/bin/lib/repos.cjs +12 -12
- package/deliver-great-systems/bin/lib/review.cjs +1821 -0
- package/deliver-great-systems/bin/lib/state.cjs +7 -3
- package/deliver-great-systems/bin/lib/summary-frontmatter.cjs +54 -0
- package/deliver-great-systems/bin/lib/summary-frontmatter.test.cjs +78 -0
- package/deliver-great-systems/bin/lib/sweep-scope.test.cjs +263 -0
- package/deliver-great-systems/bin/lib/verify.cjs +118 -6
- package/deliver-great-systems/bin/lib/verify.test.cjs +82 -0
- package/deliver-great-systems/bin/lib/wave-0-template-rename.test.cjs +40 -0
- package/deliver-great-systems/bin/lib/worktrees.cjs +27 -1
- package/deliver-great-systems/bin/lib/worktrees.test.cjs +76 -0
- package/deliver-great-systems/references/agent-step-reliability.md +60 -0
- package/deliver-great-systems/references/conflict-resolution.md +4 -0
- package/deliver-great-systems/references/context-tiers.md +4 -0
- package/deliver-great-systems/references/package-scan-config.md +151 -0
- package/deliver-great-systems/references/questioning.md +0 -30
- package/deliver-great-systems/references/spec-review-loop.md +1 -2
- package/deliver-great-systems/references/workflow-conventions.md +29 -0
- package/deliver-great-systems/skills/dgs-tests/package-scan.md +44 -0
- package/deliver-great-systems/templates/REVIEW.md +35 -0
- package/deliver-great-systems/templates/VALIDATION.md +1 -1
- package/deliver-great-systems/templates/claude-md.md +11 -0
- package/deliver-great-systems/templates/package-scan-report.md +108 -0
- package/deliver-great-systems/templates/project.md +6 -170
- package/deliver-great-systems/templates/summary.md +3 -1
- package/deliver-great-systems/workflows/add-phase.md +5 -0
- package/deliver-great-systems/workflows/audit-milestone.md +66 -10
- package/deliver-great-systems/workflows/cancel-job.md +1 -1
- package/deliver-great-systems/workflows/codereview.md +103 -9
- package/deliver-great-systems/workflows/complete-milestone.md +26 -7
- package/deliver-great-systems/workflows/complete-quick.md +40 -2
- package/deliver-great-systems/workflows/discuss-phase.md +3 -2
- package/deliver-great-systems/workflows/execute-phase.md +89 -2
- package/deliver-great-systems/workflows/execute-plan.md +10 -1
- package/deliver-great-systems/workflows/help.md +51 -18
- package/deliver-great-systems/workflows/import-spec.md +65 -7
- package/deliver-great-systems/workflows/init-product.md +46 -152
- package/deliver-great-systems/workflows/new-milestone.md +115 -14
- package/deliver-great-systems/workflows/new-project.md +60 -331
- package/deliver-great-systems/workflows/package-scan.md +59 -0
- package/deliver-great-systems/workflows/plan-phase.md +79 -1
- package/deliver-great-systems/workflows/quick-complete.md +40 -2
- package/deliver-great-systems/workflows/quick.md +183 -10
- package/deliver-great-systems/workflows/research-idea.md +80 -142
- package/deliver-great-systems/workflows/run-job.md +21 -35
- package/deliver-great-systems/workflows/settings.md +13 -77
- package/deliver-great-systems/workflows/write-spec.md +9 -11
- package/hooks/dist/dgs-enforce-discipline.js +196 -0
- package/package.json +1 -1
- package/scripts/build-hooks.js +1 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* package-ecosystems.cjs -- Manifest detection and monorepo workspace expansion
|
|
3
|
+
*
|
|
4
|
+
* LEAF MODULE: Imports only Node built-ins (fs, path) and `safeReadFile`
|
|
5
|
+
* from core.cjs. MUST NOT import from other DGS modules. Enforced by
|
|
6
|
+
* path-audit.test.cjs.
|
|
7
|
+
*
|
|
8
|
+
* Exports:
|
|
9
|
+
* detectEcosystems(repoDir) -- returns Array<{ ecosystem, manifest_path }>
|
|
10
|
+
* ECOSYSTEM_MANIFESTS -- ordered priority list (for tests/orchestrator)
|
|
11
|
+
*
|
|
12
|
+
* Covers roadmap requirements PKG-08, PKG-09, PKG-10, PKG-31.
|
|
13
|
+
* Does NOT implement PKG-41 (Gradle sub-projects) or PKG-42 (Python src-layout)
|
|
14
|
+
* -- both deferred to a later milestone (see CONTEXT.md deferred section).
|
|
15
|
+
*/
|
|
16
|
+
'use strict';
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const { safeReadFile } = require('./core.cjs');
|
|
20
|
+
|
|
21
|
+
const ECOSYSTEM_MANIFESTS = [
|
|
22
|
+
{ eco: 'node', manifests: ['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'package.json'] },
|
|
23
|
+
{ eco: 'python', manifests: ['poetry.lock', 'Pipfile.lock', 'requirements.txt', 'pyproject.toml', 'setup.py'] },
|
|
24
|
+
{ eco: 'go', manifests: ['go.sum', 'go.mod'] },
|
|
25
|
+
{ eco: 'ruby', manifests: ['Gemfile.lock', 'Gemfile'] },
|
|
26
|
+
{ eco: 'java', manifests: ['pom.xml', 'build.gradle', 'build.gradle.kts'] },
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
function _posix(p) {
|
|
32
|
+
if (!p) return p;
|
|
33
|
+
return p.split(path.sep).join('/').replace(/^\.\//, '');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function _exists(p) {
|
|
37
|
+
try {
|
|
38
|
+
return fs.existsSync(p);
|
|
39
|
+
} catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function _hasAnyManifest(repoDir, manifests) {
|
|
45
|
+
for (const m of manifests) {
|
|
46
|
+
if (_exists(path.join(repoDir, m))) return true;
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function _tryParseJson(raw) {
|
|
52
|
+
if (!raw) return null;
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(raw);
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Minimal pnpm-workspace.yaml parser. Looks for a top-level `packages:` list
|
|
61
|
+
// with one entry per indented `- ...` line. Handles quotes but nothing fancy.
|
|
62
|
+
function _parsePnpmWorkspace(raw) {
|
|
63
|
+
if (!raw) return [];
|
|
64
|
+
const lines = raw.split(/\r?\n/);
|
|
65
|
+
let inPackages = false;
|
|
66
|
+
const result = [];
|
|
67
|
+
for (const line of lines) {
|
|
68
|
+
const trimmed = line.trim();
|
|
69
|
+
if (/^packages\s*:/.test(trimmed)) {
|
|
70
|
+
inPackages = true;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (inPackages) {
|
|
74
|
+
if (/^[a-zA-Z_][\w-]*\s*:/.test(trimmed)) {
|
|
75
|
+
// new top-level key
|
|
76
|
+
inPackages = false;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const m = trimmed.match(/^-\s*['"]?([^'"\s]+)['"]?\s*$/);
|
|
80
|
+
if (m) result.push(m[1]);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Expand a workspace glob pattern relative to repoDir. Supports a single
|
|
87
|
+
// trailing `*` at a depth of at most one (e.g., packages/*, apps/*, *). Does
|
|
88
|
+
// NOT support `**` or nested stars. Returns absolute workspace directories
|
|
89
|
+
// that contain a package.json.
|
|
90
|
+
function _expandWorkspaceGlob(repoDir, pattern) {
|
|
91
|
+
if (!pattern) return [];
|
|
92
|
+
// Strip a trailing slash if present.
|
|
93
|
+
let pat = pattern.replace(/\/$/, '');
|
|
94
|
+
// If no wildcard, treat as a literal path (still cap at one level).
|
|
95
|
+
if (!pat.includes('*')) {
|
|
96
|
+
const abs = path.join(repoDir, pat);
|
|
97
|
+
if (_exists(path.join(abs, 'package.json'))) return [abs];
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
// Only allow one `*` (either bare `*` or `<dir>/*`).
|
|
101
|
+
const parts = pat.split('/');
|
|
102
|
+
if (parts.length > 2) return []; // cap at one directory level
|
|
103
|
+
let baseDir = repoDir;
|
|
104
|
+
let starSegment = parts[0];
|
|
105
|
+
if (parts.length === 2) {
|
|
106
|
+
baseDir = path.join(repoDir, parts[0]);
|
|
107
|
+
starSegment = parts[1];
|
|
108
|
+
}
|
|
109
|
+
if (starSegment !== '*') return []; // only support trailing bare `*`
|
|
110
|
+
let entries;
|
|
111
|
+
try {
|
|
112
|
+
entries = fs.readdirSync(baseDir, { withFileTypes: true });
|
|
113
|
+
} catch {
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
const results = [];
|
|
117
|
+
for (const e of entries) {
|
|
118
|
+
if (!e.isDirectory()) continue;
|
|
119
|
+
const full = path.join(baseDir, e.name);
|
|
120
|
+
if (_exists(path.join(full, 'package.json'))) {
|
|
121
|
+
results.push(full);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return results;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Expand a list of workspace patterns, deduplicating and returning absolute
|
|
128
|
+
// workspace directories (each containing package.json).
|
|
129
|
+
function _collectNodeWorkspaces(repoDir, patterns) {
|
|
130
|
+
const seen = new Set();
|
|
131
|
+
const out = [];
|
|
132
|
+
for (const pattern of patterns) {
|
|
133
|
+
const dirs = _expandWorkspaceGlob(repoDir, pattern);
|
|
134
|
+
for (const d of dirs) {
|
|
135
|
+
if (!seen.has(d)) {
|
|
136
|
+
seen.add(d);
|
|
137
|
+
out.push(d);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Parse root package.json for workspace declarations. Handles array form and
|
|
145
|
+
// object form ({ packages: [...] }). Returns patterns[] (possibly empty).
|
|
146
|
+
function _nodeWorkspacePatterns(rootPkg) {
|
|
147
|
+
if (!rootPkg || typeof rootPkg !== 'object') return [];
|
|
148
|
+
const ws = rootPkg.workspaces;
|
|
149
|
+
if (Array.isArray(ws)) return ws.slice();
|
|
150
|
+
if (ws && typeof ws === 'object' && Array.isArray(ws.packages)) return ws.packages.slice();
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Check whether a package.json carries real root-level dependencies (beyond
|
|
155
|
+
// the `workspaces` field). Returns true if either `dependencies` or
|
|
156
|
+
// `devDependencies` is a non-empty object.
|
|
157
|
+
function _hasRootDeps(rootPkg) {
|
|
158
|
+
if (!rootPkg || typeof rootPkg !== 'object') return false;
|
|
159
|
+
const hasDeps = rootPkg.dependencies && typeof rootPkg.dependencies === 'object'
|
|
160
|
+
&& Object.keys(rootPkg.dependencies).length > 0;
|
|
161
|
+
const hasDev = rootPkg.devDependencies && typeof rootPkg.devDependencies === 'object'
|
|
162
|
+
&& Object.keys(rootPkg.devDependencies).length > 0;
|
|
163
|
+
return Boolean(hasDeps || hasDev);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Parse <modules> from a root pom.xml (regex walk, no XML dep). Returns
|
|
167
|
+
// module names (strings) as declared.
|
|
168
|
+
function _parseMavenModules(rawXml) {
|
|
169
|
+
if (!rawXml) return [];
|
|
170
|
+
const modulesBlock = rawXml.match(/<modules>([\s\S]*?)<\/modules>/);
|
|
171
|
+
if (!modulesBlock) return [];
|
|
172
|
+
const moduleMatches = modulesBlock[1].match(/<module>\s*([^<\s][^<]*?)\s*<\/module>/g) || [];
|
|
173
|
+
return moduleMatches.map(m => {
|
|
174
|
+
const inner = m.match(/<module>\s*([\s\S]*?)\s*<\/module>/);
|
|
175
|
+
return inner ? inner[1].trim() : null;
|
|
176
|
+
}).filter(Boolean);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Detect whether a pom.xml declares <dependencies> alongside any <modules>.
|
|
180
|
+
// Excludes <dependencyManagement><dependencies> so a pure BOM root doesn't
|
|
181
|
+
// count as a scannable target.
|
|
182
|
+
function _mavenHasDependencies(rawXml) {
|
|
183
|
+
if (!rawXml) return false;
|
|
184
|
+
// Strip dependencyManagement block to avoid false positives.
|
|
185
|
+
const stripped = rawXml.replace(/<dependencyManagement>[\s\S]*?<\/dependencyManagement>/g, '');
|
|
186
|
+
return /<dependencies>[\s\S]*?<dependency>/.test(stripped);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Parse the `use (...)` block (multi-line or single-line) from a go.work file.
|
|
190
|
+
// Returns module directory names as declared (strings starting with ./ or a bare name).
|
|
191
|
+
function _parseGoWorkUse(raw) {
|
|
192
|
+
if (!raw) return [];
|
|
193
|
+
const results = [];
|
|
194
|
+
// Multi-line form: use ( ./a\n./b\n )
|
|
195
|
+
const multiMatch = raw.match(/use\s*\(([\s\S]*?)\)/);
|
|
196
|
+
if (multiMatch) {
|
|
197
|
+
const inner = multiMatch[1];
|
|
198
|
+
for (const line of inner.split(/\r?\n/)) {
|
|
199
|
+
const trimmed = line.trim();
|
|
200
|
+
if (!trimmed || trimmed.startsWith('//')) continue;
|
|
201
|
+
results.push(trimmed);
|
|
202
|
+
}
|
|
203
|
+
return results;
|
|
204
|
+
}
|
|
205
|
+
// Single-line form: use ./a (one per line)
|
|
206
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
207
|
+
const trimmed = line.trim();
|
|
208
|
+
const m = trimmed.match(/^use\s+(\S+)\s*$/);
|
|
209
|
+
if (m) results.push(m[1]);
|
|
210
|
+
}
|
|
211
|
+
return results;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ─── Main export ─────────────────────────────────────────────────────────────
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Detect scannable manifests and monorepo workspaces at a repo root.
|
|
218
|
+
*
|
|
219
|
+
* Returns one entry per scannable target. For simple repos (no workspaces)
|
|
220
|
+
* each entry has manifest_path: null. For monorepos (npm/pnpm/Yarn workspaces,
|
|
221
|
+
* Maven multi-module, Go workspaces), emits one entry per workspace with
|
|
222
|
+
* manifest_path populated as repo-relative POSIX. Gradle sub-projects (PKG-41)
|
|
223
|
+
* and Python src-layout (PKG-42) are explicitly deferred.
|
|
224
|
+
*
|
|
225
|
+
* @param {string} repoDir - Absolute path to the repo root
|
|
226
|
+
* @returns {Array<{ ecosystem: 'node'|'python'|'go'|'ruby'|'java', manifest_path: string|null }>}
|
|
227
|
+
*/
|
|
228
|
+
function detectEcosystems(repoDir) {
|
|
229
|
+
if (!repoDir || !_exists(repoDir)) return [];
|
|
230
|
+
|
|
231
|
+
// Per-ecosystem builders — each returns an ordered array of entries.
|
|
232
|
+
const groups = [];
|
|
233
|
+
|
|
234
|
+
// ─── node ──────────────────────────────────────────────────────────────────
|
|
235
|
+
{
|
|
236
|
+
const nodeEntries = [];
|
|
237
|
+
const hasRoot = _hasAnyManifest(repoDir, ECOSYSTEM_MANIFESTS[0].manifests);
|
|
238
|
+
// Workspace expansion
|
|
239
|
+
const rootPkgRaw = safeReadFile(path.join(repoDir, 'package.json'));
|
|
240
|
+
const rootPkg = _tryParseJson(rootPkgRaw);
|
|
241
|
+
let patterns = _nodeWorkspacePatterns(rootPkg);
|
|
242
|
+
// pnpm-workspace.yaml may supply (or supplement) patterns
|
|
243
|
+
const pnpmRaw = safeReadFile(path.join(repoDir, 'pnpm-workspace.yaml'));
|
|
244
|
+
if (pnpmRaw) {
|
|
245
|
+
const pnpmPatterns = _parsePnpmWorkspace(pnpmRaw);
|
|
246
|
+
patterns = patterns.concat(pnpmPatterns);
|
|
247
|
+
}
|
|
248
|
+
const workspaceDirs = patterns.length > 0
|
|
249
|
+
? _collectNodeWorkspaces(repoDir, patterns)
|
|
250
|
+
: [];
|
|
251
|
+
|
|
252
|
+
// Collect workspace entries
|
|
253
|
+
const wsEntries = workspaceDirs.map(abs => ({
|
|
254
|
+
ecosystem: 'node',
|
|
255
|
+
manifest_path: _posix(path.relative(repoDir, path.join(abs, 'package.json'))),
|
|
256
|
+
}));
|
|
257
|
+
wsEntries.sort((a, b) => a.manifest_path.localeCompare(b.manifest_path));
|
|
258
|
+
|
|
259
|
+
if (wsEntries.length > 0) {
|
|
260
|
+
nodeEntries.push(...wsEntries);
|
|
261
|
+
// Emit root only if root package.json has own deps
|
|
262
|
+
if (_hasRootDeps(rootPkg)) {
|
|
263
|
+
nodeEntries.push({ ecosystem: 'node', manifest_path: 'package.json' });
|
|
264
|
+
}
|
|
265
|
+
} else if (hasRoot) {
|
|
266
|
+
nodeEntries.push({ ecosystem: 'node', manifest_path: null });
|
|
267
|
+
}
|
|
268
|
+
if (nodeEntries.length > 0) groups.push(nodeEntries);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ─── python ────────────────────────────────────────────────────────────────
|
|
272
|
+
{
|
|
273
|
+
const hasRoot = _hasAnyManifest(repoDir, ECOSYSTEM_MANIFESTS[1].manifests);
|
|
274
|
+
if (hasRoot) {
|
|
275
|
+
groups.push([{ ecosystem: 'python', manifest_path: null }]);
|
|
276
|
+
}
|
|
277
|
+
// PKG-42 deferred — do not scan src/ or subdirs.
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ─── go ────────────────────────────────────────────────────────────────────
|
|
281
|
+
{
|
|
282
|
+
const goEntries = [];
|
|
283
|
+
const goWorkRaw = safeReadFile(path.join(repoDir, 'go.work'));
|
|
284
|
+
if (goWorkRaw) {
|
|
285
|
+
const declared = _parseGoWorkUse(goWorkRaw);
|
|
286
|
+
const wsEntries = [];
|
|
287
|
+
for (const decl of declared) {
|
|
288
|
+
const modDir = path.resolve(repoDir, decl);
|
|
289
|
+
const modFile = path.join(modDir, 'go.mod');
|
|
290
|
+
if (_exists(modFile)) {
|
|
291
|
+
const rel = _posix(path.relative(repoDir, modFile));
|
|
292
|
+
wsEntries.push({ ecosystem: 'go', manifest_path: rel });
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
wsEntries.sort((a, b) => a.manifest_path.localeCompare(b.manifest_path));
|
|
296
|
+
goEntries.push(...wsEntries);
|
|
297
|
+
// Go: never emit root entry when go.work is present
|
|
298
|
+
} else {
|
|
299
|
+
const hasRoot = _hasAnyManifest(repoDir, ECOSYSTEM_MANIFESTS[2].manifests);
|
|
300
|
+
if (hasRoot) goEntries.push({ ecosystem: 'go', manifest_path: null });
|
|
301
|
+
}
|
|
302
|
+
if (goEntries.length > 0) groups.push(goEntries);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ─── ruby ──────────────────────────────────────────────────────────────────
|
|
306
|
+
{
|
|
307
|
+
const hasRoot = _hasAnyManifest(repoDir, ECOSYSTEM_MANIFESTS[3].manifests);
|
|
308
|
+
if (hasRoot) groups.push([{ ecosystem: 'ruby', manifest_path: null }]);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ─── java (Maven + Gradle) ─────────────────────────────────────────────────
|
|
312
|
+
{
|
|
313
|
+
const javaEntries = [];
|
|
314
|
+
const pomPath = path.join(repoDir, 'pom.xml');
|
|
315
|
+
const pomRaw = safeReadFile(pomPath);
|
|
316
|
+
if (pomRaw) {
|
|
317
|
+
const modules = _parseMavenModules(pomRaw);
|
|
318
|
+
if (modules.length > 0) {
|
|
319
|
+
const wsEntries = [];
|
|
320
|
+
for (const mod of modules) {
|
|
321
|
+
const modPom = path.join(repoDir, mod, 'pom.xml');
|
|
322
|
+
if (_exists(modPom)) {
|
|
323
|
+
const rel = _posix(path.relative(repoDir, modPom));
|
|
324
|
+
wsEntries.push({ ecosystem: 'java', manifest_path: rel });
|
|
325
|
+
}
|
|
326
|
+
// Missing modules silently skipped — malformed-tolerance
|
|
327
|
+
}
|
|
328
|
+
wsEntries.sort((a, b) => a.manifest_path.localeCompare(b.manifest_path));
|
|
329
|
+
javaEntries.push(...wsEntries);
|
|
330
|
+
// Root entry only if pom has both <modules> AND <dependencies>
|
|
331
|
+
if (_mavenHasDependencies(pomRaw)) {
|
|
332
|
+
javaEntries.push({ ecosystem: 'java', manifest_path: 'pom.xml' });
|
|
333
|
+
}
|
|
334
|
+
} else {
|
|
335
|
+
javaEntries.push({ ecosystem: 'java', manifest_path: null });
|
|
336
|
+
}
|
|
337
|
+
} else if (_exists(path.join(repoDir, 'build.gradle')) || _exists(path.join(repoDir, 'build.gradle.kts'))) {
|
|
338
|
+
// PKG-41 deferred — single-module Java only
|
|
339
|
+
javaEntries.push({ ecosystem: 'java', manifest_path: null });
|
|
340
|
+
}
|
|
341
|
+
if (javaEntries.length > 0) groups.push(javaEntries);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Flatten groups into the final array — ecosystem order matches ECOSYSTEM_MANIFESTS.
|
|
345
|
+
const out = [];
|
|
346
|
+
for (const g of groups) out.push(...g);
|
|
347
|
+
return out;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
module.exports = { detectEcosystems, ECOSYSTEM_MANIFESTS };
|