@ktpartners/dgs-platform 3.3.0 → 3.4.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 +32 -0
- package/README.md +4 -1
- package/bin/install.js +1 -1
- package/commands/dgs/abandon-milestone.md +28 -0
- package/commands/dgs/new-milestone.md +3 -1
- package/deliver-great-systems/bin/dgs-tools.cjs +22 -4
- package/deliver-great-systems/bin/lib/context.cjs +45 -15
- package/deliver-great-systems/bin/lib/core.cjs +2 -6
- package/deliver-great-systems/bin/lib/docs.cjs +95 -41
- package/deliver-great-systems/bin/lib/docs.test.cjs +49 -6
- package/deliver-great-systems/bin/lib/init.cjs +60 -13
- package/deliver-great-systems/bin/lib/init.test.cjs +61 -3
- package/deliver-great-systems/bin/lib/milestone.cjs +470 -2
- package/deliver-great-systems/bin/lib/milestone.test.cjs +653 -0
- package/deliver-great-systems/bin/lib/search.cjs +5 -16
- package/deliver-great-systems/bin/lib/state.cjs +152 -1
- package/deliver-great-systems/bin/lib/sync.cjs +2 -6
- package/deliver-great-systems/bin/lib/verify.cjs +2 -1
- package/deliver-great-systems/bin/lib/worktrees.cjs +182 -1
- package/deliver-great-systems/bin/lib/worktrees.test.cjs +409 -0
- package/deliver-great-systems/templates/claude-md.md +2 -0
- package/deliver-great-systems/templates/state.md +16 -0
- package/deliver-great-systems/workflows/abandon-milestone.md +120 -0
- package/deliver-great-systems/workflows/complete-milestone.md +58 -4
- package/deliver-great-systems/workflows/create-milestone-job.md +15 -0
- package/deliver-great-systems/workflows/help.md +7 -0
- package/deliver-great-systems/workflows/new-milestone.md +69 -0
- package/deliver-great-systems/workflows/progress.md +5 -1
- package/deliver-great-systems/workflows/run-job.md +23 -1
- package/hooks/dist/dgs-enforce-discipline.js +34 -1
- package/package.json +1 -1
|
@@ -12,6 +12,7 @@ const { parseReposMd, validateReposMdEager } = require('./repos.cjs');
|
|
|
12
12
|
const { getCadence, pullAll } = require('./sync.cjs');
|
|
13
13
|
const { detectQuickMode, generateQuickId, getActiveQuick } = require('./quick.cjs');
|
|
14
14
|
const { listProjectsReadonly } = require('./projects.cjs');
|
|
15
|
+
const { resolveMilestoneSlug } = require('./worktrees.cjs');
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Safely resolve the current git author string.
|
|
@@ -88,6 +89,54 @@ function applySyncPull(cwd, workflowName, result) {
|
|
|
88
89
|
result.needs_pull = false;
|
|
89
90
|
}
|
|
90
91
|
|
|
92
|
+
// ─── Brownfield detection ───────────────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Detect whether the directory tree rooted at cwd contains any source
|
|
96
|
+
* files in a small set of common languages, ignoring node_modules and .git,
|
|
97
|
+
* with a maximum recursion depth of 3 (cwd itself is depth 0).
|
|
98
|
+
*
|
|
99
|
+
* Returns true on first match (early termination).
|
|
100
|
+
* Returns false on any error (e.g. permission denied on top-level dir).
|
|
101
|
+
*
|
|
102
|
+
* Pure Node — replaces the prior `find ... | grep ... | head -5` shell
|
|
103
|
+
* pipeline that does not exist on Windows.
|
|
104
|
+
*
|
|
105
|
+
* @param {string} cwd - Directory to scan.
|
|
106
|
+
* @returns {boolean}
|
|
107
|
+
*/
|
|
108
|
+
function hasCodeFiles(cwd) {
|
|
109
|
+
const CODE_EXTENSIONS = new Set(['.ts', '.js', '.py', '.go', '.rs', '.swift', '.java']);
|
|
110
|
+
const SKIP_DIRS = new Set(['node_modules', '.git']);
|
|
111
|
+
const MAX_DEPTH = 3;
|
|
112
|
+
|
|
113
|
+
function walk(dir, depth) {
|
|
114
|
+
if (depth > MAX_DEPTH) return false;
|
|
115
|
+
let entries;
|
|
116
|
+
try {
|
|
117
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
118
|
+
} catch {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
for (const entry of entries) {
|
|
122
|
+
if (entry.isDirectory()) {
|
|
123
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
124
|
+
if (walk(path.join(dir, entry.name), depth + 1)) return true;
|
|
125
|
+
} else if (entry.isFile()) {
|
|
126
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
127
|
+
if (CODE_EXTENSIONS.has(ext)) return true;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
return walk(cwd, 0);
|
|
135
|
+
} catch {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
91
140
|
// ─── v2 Project Context Resolution ──────────────────────────────────────────
|
|
92
141
|
|
|
93
142
|
/**
|
|
@@ -204,6 +253,10 @@ function cmdInitExecutePhase(cwd, phase, raw) {
|
|
|
204
253
|
: null;
|
|
205
254
|
const phase_req_ids = (reqExtracted && reqExtracted !== 'TBD') ? reqExtracted : null;
|
|
206
255
|
|
|
256
|
+
// Multi-project determination for the structural milestone slug's project
|
|
257
|
+
// prefix: only prefix with <project> when more than one ACTIVE project exists.
|
|
258
|
+
const multiProject = listProjectsReadonly(cwd).projects.length > 1;
|
|
259
|
+
|
|
207
260
|
// Resolve {project} in a branch template — errors if template uses {project} but current_project is not set
|
|
208
261
|
function resolveProjectInTemplate(template, currentProject) {
|
|
209
262
|
if (template.includes('{project}')) {
|
|
@@ -253,13 +306,13 @@ function cmdInitExecutePhase(cwd, phase, raw) {
|
|
|
253
306
|
if (resolved.error) return resolved.error;
|
|
254
307
|
return resolved.value
|
|
255
308
|
.replace('{milestone}', milestone.version)
|
|
256
|
-
.replace('{slug}',
|
|
309
|
+
.replace('{slug}', resolveMilestoneSlug(cwd, ctx.current_project, { version: milestone.version, name: milestone.name, multiProject }));
|
|
257
310
|
})(),
|
|
258
311
|
|
|
259
312
|
// Milestone info
|
|
260
313
|
milestone_version: milestone.version,
|
|
261
314
|
milestone_name: milestone.name,
|
|
262
|
-
milestone_slug:
|
|
315
|
+
milestone_slug: resolveMilestoneSlug(cwd, ctx.current_project, { version: milestone.version, name: milestone.name, multiProject }),
|
|
263
316
|
|
|
264
317
|
// File existence
|
|
265
318
|
state_exists: ctx.root ? pathExistsInternal(cwd, path.join(ctx.root, 'STATE.md')) : false,
|
|
@@ -411,17 +464,9 @@ function cmdInitNewProject(cwd, slugArg, raw) {
|
|
|
411
464
|
const braveKeyFile = path.join(homedir, '.dgs', 'brave_api_key');
|
|
412
465
|
const hasBraveSearch = !!(process.env.BRAVE_API_KEY || fs.existsSync(braveKeyFile));
|
|
413
466
|
|
|
414
|
-
// Detect existing code
|
|
415
|
-
|
|
467
|
+
// Detect existing code (pure-Node walk, Windows-safe)
|
|
468
|
+
const hasCode = hasCodeFiles(cwd);
|
|
416
469
|
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
470
|
|
|
426
471
|
hasPackageFile = pathExistsInternal(cwd, 'package.json') ||
|
|
427
472
|
pathExistsInternal(cwd, 'requirements.txt') ||
|
|
@@ -1005,6 +1050,7 @@ function cmdInitMilestoneOp(cwd, raw, workflow) {
|
|
|
1005
1050
|
const milestone = getMilestoneInfo(cwd);
|
|
1006
1051
|
const planRootRel = path.relative(cwd, getPlanningRoot(cwd)) || '.';
|
|
1007
1052
|
const cadence = getCadence(workflow || 'complete-milestone');
|
|
1053
|
+
const multiProject = listProjectsReadonly(cwd).projects.length > 1;
|
|
1008
1054
|
|
|
1009
1055
|
// Count phases (project-qualified)
|
|
1010
1056
|
let phaseCount = 0;
|
|
@@ -1049,7 +1095,7 @@ function cmdInitMilestoneOp(cwd, raw, workflow) {
|
|
|
1049
1095
|
// Current milestone
|
|
1050
1096
|
milestone_version: milestone.version,
|
|
1051
1097
|
milestone_name: milestone.name,
|
|
1052
|
-
milestone_slug:
|
|
1098
|
+
milestone_slug: resolveMilestoneSlug(cwd, ctx.current_project, { version: milestone.version, name: milestone.name, multiProject }),
|
|
1053
1099
|
|
|
1054
1100
|
// Phase counts
|
|
1055
1101
|
phase_count: phaseCount,
|
|
@@ -1547,4 +1593,5 @@ module.exports = {
|
|
|
1547
1593
|
cmdInitProgress,
|
|
1548
1594
|
cmdInitProgressAll,
|
|
1549
1595
|
applySyncPull,
|
|
1596
|
+
hasCodeFiles,
|
|
1550
1597
|
};
|
|
@@ -1177,7 +1177,10 @@ describe('branch_name with {project} resolution', () => {
|
|
|
1177
1177
|
});
|
|
1178
1178
|
try {
|
|
1179
1179
|
const result = runInit(fixture.cwd, 'execute-phase 3');
|
|
1180
|
-
|
|
1180
|
+
// Structural slug now embeds the version segment (v1.0 -> v1-0) so a
|
|
1181
|
+
// placeholder/default 'milestone' name no longer collapses to a bare
|
|
1182
|
+
// milestone/milestone branch (branch-name collision fix).
|
|
1183
|
+
assert.equal(result.branch_name, 'dgs/myapp/v1.0-v1-0-milestone');
|
|
1181
1184
|
} finally {
|
|
1182
1185
|
fixture.cleanup();
|
|
1183
1186
|
}
|
|
@@ -1196,7 +1199,7 @@ describe('branch_name with {project} resolution', () => {
|
|
|
1196
1199
|
});
|
|
1197
1200
|
try {
|
|
1198
1201
|
const result = runInit(fixture.cwd, 'execute-phase 1');
|
|
1199
|
-
assert.equal(result.branch_name, 'dgs/checkout/v1.0-milestone');
|
|
1202
|
+
assert.equal(result.branch_name, 'dgs/checkout/v1.0-v1-0-milestone');
|
|
1200
1203
|
} finally {
|
|
1201
1204
|
fixture.cleanup();
|
|
1202
1205
|
}
|
|
@@ -1216,7 +1219,7 @@ describe('branch_name with {project} resolution', () => {
|
|
|
1216
1219
|
});
|
|
1217
1220
|
try {
|
|
1218
1221
|
const result = runInit(fixture.cwd, 'execute-phase 3');
|
|
1219
|
-
assert.equal(result.branch_name, 'dgs/myapp/v1.0-milestone');
|
|
1222
|
+
assert.equal(result.branch_name, 'dgs/myapp/v1.0-v1-0-milestone');
|
|
1220
1223
|
} finally {
|
|
1221
1224
|
fixture.cleanup();
|
|
1222
1225
|
}
|
|
@@ -1240,6 +1243,61 @@ describe('branch_name with {project} resolution', () => {
|
|
|
1240
1243
|
});
|
|
1241
1244
|
});
|
|
1242
1245
|
|
|
1246
|
+
// ─── milestone_slug resolution (stamped / legacy fallback) ───────────────────
|
|
1247
|
+
|
|
1248
|
+
describe('milestone_slug resolution via resolveMilestoneSlug', () => {
|
|
1249
|
+
// ROADMAP carries an in-progress marker so version+name are deterministic:
|
|
1250
|
+
// 'v25.0' + 'Cool Thing' -> composed slug 'v25-0-cool-thing';
|
|
1251
|
+
// old bare-name slug 'cool-thing'.
|
|
1252
|
+
const roadmap = '# Roadmap\n\n- 🚧 **v25.0 Cool Thing** — Phases 1-2 (in progress)\n';
|
|
1253
|
+
|
|
1254
|
+
function fixtureWithWorktrees(worktrees) {
|
|
1255
|
+
return createFixture({
|
|
1256
|
+
'config.json': JSON.stringify({ current_project: 'test-project' }),
|
|
1257
|
+
'config.local.json': JSON.stringify({
|
|
1258
|
+
current_project: 'test-project',
|
|
1259
|
+
projects: { 'test-project': { worktrees } },
|
|
1260
|
+
}),
|
|
1261
|
+
'PROJECTS.md': '# Projects\n\n| Project | Status |\n|---------|--------|\n| test-project | Active |\n',
|
|
1262
|
+
'REPOS.md': '# Repos\n\n| Name | Path |\n|------|------|\n',
|
|
1263
|
+
'projects/test-project/STATE.md': '# State',
|
|
1264
|
+
'projects/test-project/ROADMAP.md': roadmap,
|
|
1265
|
+
'projects/test-project/REQUIREMENTS.md': '# Requirements',
|
|
1266
|
+
'projects/test-project/PROJECT.md': '# Project',
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
it('returns the stamped NEW composed slug when an entry exists under it', () => {
|
|
1271
|
+
const fixture = fixtureWithWorktrees({ 'v25-0-cool-thing': { type: 'milestone', milestone_slug: 'v25-0-cool-thing', slug_formula: 'v2' } });
|
|
1272
|
+
try {
|
|
1273
|
+
const result = runInit(fixture.cwd, 'milestone-op');
|
|
1274
|
+
assert.equal(result.milestone_slug, 'v25-0-cool-thing');
|
|
1275
|
+
} finally {
|
|
1276
|
+
fixture.cleanup();
|
|
1277
|
+
}
|
|
1278
|
+
});
|
|
1279
|
+
|
|
1280
|
+
it('LEGACY FALLBACK: returns the OLD bare-name slug when only an old-style entry exists (no orphaning)', () => {
|
|
1281
|
+
const fixture = fixtureWithWorktrees({ 'cool-thing': { type: 'milestone' } });
|
|
1282
|
+
try {
|
|
1283
|
+
const result = runInit(fixture.cwd, 'milestone-op');
|
|
1284
|
+
assert.equal(result.milestone_slug, 'cool-thing', 'pre-upgrade in-flight milestone must still resolve');
|
|
1285
|
+
} finally {
|
|
1286
|
+
fixture.cleanup();
|
|
1287
|
+
}
|
|
1288
|
+
});
|
|
1289
|
+
|
|
1290
|
+
it('returns the NEW composed slug when neither entry exists (fresh)', () => {
|
|
1291
|
+
const fixture = fixtureWithWorktrees({});
|
|
1292
|
+
try {
|
|
1293
|
+
const result = runInit(fixture.cwd, 'milestone-op');
|
|
1294
|
+
assert.equal(result.milestone_slug, 'v25-0-cool-thing');
|
|
1295
|
+
} finally {
|
|
1296
|
+
fixture.cleanup();
|
|
1297
|
+
}
|
|
1298
|
+
});
|
|
1299
|
+
});
|
|
1300
|
+
|
|
1243
1301
|
// ─── Backward Compatibility ──────────────────────────────────────────────────
|
|
1244
1302
|
|
|
1245
1303
|
// ─── Sync Pull: needs_pull field tests ────────────────────────────────────────
|