@ktpartners/dgs-platform 3.5.1 → 3.5.3

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 (31) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/deliver-great-systems/bin/lib/core.cjs +21 -0
  3. package/deliver-great-systems/bin/lib/core.test.cjs +66 -0
  4. package/deliver-great-systems/bin/lib/ideas.cjs +39 -11
  5. package/deliver-great-systems/bin/lib/ideas.test.cjs +32 -3
  6. package/deliver-great-systems/bin/lib/init.cjs +23 -0
  7. package/deliver-great-systems/bin/lib/init.test.cjs +78 -0
  8. package/deliver-great-systems/bin/lib/jobs.cjs +78 -26
  9. package/deliver-great-systems/bin/lib/jobs.test.cjs +132 -3
  10. package/deliver-great-systems/bin/lib/overlap.cjs +14 -4
  11. package/deliver-great-systems/bin/lib/overlap.test.cjs +13 -0
  12. package/deliver-great-systems/bin/lib/package-scan-report.cjs +19 -0
  13. package/deliver-great-systems/bin/lib/package-scan-report.test.cjs +18 -0
  14. package/deliver-great-systems/bin/lib/phase.cjs +12 -4
  15. package/deliver-great-systems/bin/lib/phase.test.cjs +69 -0
  16. package/deliver-great-systems/bin/lib/projects.cjs +13 -3
  17. package/deliver-great-systems/bin/lib/projects.test.cjs +8 -0
  18. package/deliver-great-systems/bin/lib/roadmap.cjs +14 -0
  19. package/deliver-great-systems/bin/lib/roadmap.test.cjs +86 -0
  20. package/deliver-great-systems/bin/lib/search.cjs +62 -15
  21. package/deliver-great-systems/bin/lib/search.test.cjs +94 -0
  22. package/deliver-great-systems/bin/lib/verify.cjs +37 -20
  23. package/deliver-great-systems/bin/lib/verify.test.cjs +58 -0
  24. package/deliver-great-systems/workflows/audit-milestone.md +1 -1
  25. package/deliver-great-systems/workflows/cleanup.md +1 -1
  26. package/deliver-great-systems/workflows/complete-milestone.md +2 -2
  27. package/deliver-great-systems/workflows/discuss-phase.md +5 -1
  28. package/deliver-great-systems/workflows/pause-work.md +1 -1
  29. package/deliver-great-systems/workflows/plan-phase.md +6 -2
  30. package/deliver-great-systems/workflows/resume-project.md +2 -2
  31. package/package.json +1 -1
@@ -5,7 +5,7 @@
5
5
  const fs = require('fs');
6
6
  const path = require('path');
7
7
  const os = require('os');
8
- const { safeReadFile, normalizePhaseName, execGit, findPhaseInternal, getMilestoneInfo, output, error } = require('./core.cjs');
8
+ const { safeReadFile, normalizePhaseName, execGit, findPhaseInternal, getMilestoneInfo, output, error, phasesDir } = require('./core.cjs');
9
9
  const { getPlanningRoot } = require('./paths.cjs');
10
10
  const { extractFrontmatter, parseMustHavesBlock } = require('./frontmatter.cjs');
11
11
  const { parseReposMd, validateRepoPaths } = require('./repos.cjs');
@@ -399,7 +399,15 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
399
399
  function cmdValidateConsistency(cwd, raw) {
400
400
  const planRoot = getPlanningRoot(cwd);
401
401
  const roadmapPath = path.join(planRoot, 'ROADMAP.md');
402
- const phasesDir = path.join(planRoot, 'phases');
402
+ // Resolve phases directory via the canonical version-aware resolver; keep the
403
+ // soft flat-layout fallback (matches core.findPhaseInternal). Under a versioned
404
+ // milestone layout this descends phases/<version>/ so diskPhases is populated.
405
+ // NB: join to planRoot (the git-resolved planning root used everywhere else in
406
+ // this function), NOT cwd — on macOS git realpaths /var → /private/var, so a
407
+ // cwd-based join would diverge from planRoot and break downstream path.relative.
408
+ let phasesRel;
409
+ try { phasesRel = phasesDir(cwd); } catch { phasesRel = 'phases'; }
410
+ const phasesAbs = path.join(planRoot, phasesRel);
403
411
  const errors = [];
404
412
  const warnings = [];
405
413
 
@@ -423,7 +431,7 @@ function cmdValidateConsistency(cwd, raw) {
423
431
  // Get phases on disk
424
432
  const diskPhases = new Set();
425
433
  try {
426
- const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
434
+ const entries = fs.readdirSync(phasesAbs, { withFileTypes: true });
427
435
  const dirs = entries.filter(e => e.isDirectory()).map(e => e.name);
428
436
  for (const dir of dirs) {
429
437
  const dm = dir.match(/^(\d+[A-Z]?(?:\.\d+)?)/i);
@@ -460,11 +468,11 @@ function cmdValidateConsistency(cwd, raw) {
460
468
 
461
469
  // Check: plan numbering within phases
462
470
  try {
463
- const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
471
+ const entries = fs.readdirSync(phasesAbs, { withFileTypes: true });
464
472
  const dirs = entries.filter(e => e.isDirectory()).map(e => e.name).sort();
465
473
 
466
474
  for (const dir of dirs) {
467
- const phaseFiles = fs.readdirSync(path.join(phasesDir, dir));
475
+ const phaseFiles = fs.readdirSync(path.join(phasesAbs, dir));
468
476
  const plans = phaseFiles.filter(f => f.endsWith('-PLAN.md')).sort();
469
477
 
470
478
  // Extract plan numbers
@@ -495,15 +503,15 @@ function cmdValidateConsistency(cwd, raw) {
495
503
 
496
504
  // Check: frontmatter in plans has required fields
497
505
  try {
498
- const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
506
+ const entries = fs.readdirSync(phasesAbs, { withFileTypes: true });
499
507
  const dirs = entries.filter(e => e.isDirectory()).map(e => e.name);
500
508
 
501
509
  for (const dir of dirs) {
502
- const phaseFiles = fs.readdirSync(path.join(phasesDir, dir));
510
+ const phaseFiles = fs.readdirSync(path.join(phasesAbs, dir));
503
511
  const plans = phaseFiles.filter(f => f.endsWith('-PLAN.md'));
504
512
 
505
513
  for (const plan of plans) {
506
- const content = fs.readFileSync(path.join(phasesDir, dir, plan), 'utf-8');
514
+ const content = fs.readFileSync(path.join(phasesAbs, dir, plan), 'utf-8');
507
515
  const fm = extractFrontmatter(content);
508
516
 
509
517
  if (!fm.wave) {
@@ -523,7 +531,16 @@ function cmdValidateHealth(cwd, options, raw) {
523
531
  const roadmapPath = path.join(planningDir, 'ROADMAP.md');
524
532
  const statePath = path.join(planningDir, 'STATE.md');
525
533
  const configPath = path.join(planningDir, 'config.json');
526
- const phasesDir = path.join(planningDir, 'phases');
534
+ // Version-aware phases dir (descends phases/<version>/ when present) with the
535
+ // canonical soft flat-layout fallback. All checks below walk this root so the
536
+ // Check-12 W010 walker and consistency checks see versioned-layout phase dirs.
537
+ // NB: join to planningDir (the git-resolved root used by the W010 walker's
538
+ // path.relative + git ls-files), NOT cwd — on macOS git realpaths /var →
539
+ // /private/var, so a cwd-based join would diverge and mis-report tracked
540
+ // artifacts as untracked.
541
+ let phasesRel;
542
+ try { phasesRel = phasesDir(cwd); } catch { phasesRel = 'phases'; }
543
+ const phasesAbs = path.join(planningDir, phasesRel);
527
544
 
528
545
  const errors = [];
529
546
  const warnings = [];
@@ -577,7 +594,7 @@ function cmdValidateHealth(cwd, options, raw) {
577
594
  // Get disk phases
578
595
  const diskPhases = new Set();
579
596
  try {
580
- const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
597
+ const entries = fs.readdirSync(phasesAbs, { withFileTypes: true });
581
598
  for (const e of entries) {
582
599
  if (e.isDirectory()) {
583
600
  const m = e.name.match(/^(\d+(?:\.\d+)?)/);
@@ -619,7 +636,7 @@ function cmdValidateHealth(cwd, options, raw) {
619
636
 
620
637
  // ─── Check 6: Phase directory naming (NN-name format) ─────────────────────
621
638
  try {
622
- const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
639
+ const entries = fs.readdirSync(phasesAbs, { withFileTypes: true });
623
640
  for (const e of entries) {
624
641
  if (e.isDirectory() && !e.name.match(/^\d{2,}(?:\.\d+)*-[\w-]+$/)) {
625
642
  addIssue('warning', 'W005', `Phase directory "${e.name}" doesn't follow NN-name format`, 'Rename to match pattern (e.g., 01-setup)');
@@ -629,10 +646,10 @@ function cmdValidateHealth(cwd, options, raw) {
629
646
 
630
647
  // ─── Check 7: Orphaned plans (PLAN without SUMMARY) ───────────────────────
631
648
  try {
632
- const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
649
+ const entries = fs.readdirSync(phasesAbs, { withFileTypes: true });
633
650
  for (const e of entries) {
634
651
  if (!e.isDirectory()) continue;
635
- const phaseFiles = fs.readdirSync(path.join(phasesDir, e.name));
652
+ const phaseFiles = fs.readdirSync(path.join(phasesAbs, e.name));
636
653
  const plans = phaseFiles.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md');
637
654
  const summaries = phaseFiles.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
638
655
  const summaryBases = new Set(summaries.map(s => s.replace('-SUMMARY.md', '').replace('SUMMARY.md', '')));
@@ -659,7 +676,7 @@ function cmdValidateHealth(cwd, options, raw) {
659
676
 
660
677
  const diskPhases = new Set();
661
678
  try {
662
- const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
679
+ const entries = fs.readdirSync(phasesAbs, { withFileTypes: true });
663
680
  for (const e of entries) {
664
681
  if (e.isDirectory()) {
665
682
  const dm = e.name.match(/^(\d+[A-Z]?(?:\.\d+)?)/i);
@@ -756,7 +773,7 @@ function cmdValidateHealth(cwd, options, raw) {
756
773
  const trackingVerif = [];
757
774
  if (completedPhases.size > 0) {
758
775
  try {
759
- const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
776
+ const entries = fs.readdirSync(phasesAbs, { withFileTypes: true });
760
777
  for (const e of entries) {
761
778
  if (!e.isDirectory()) continue;
762
779
  const dm = e.name.match(/^(\d+[A-Z]?(?:\.\d+)*)/i);
@@ -764,10 +781,10 @@ function cmdValidateHealth(cwd, options, raw) {
764
781
  const phaseNum = dm[1];
765
782
  const unpadded = String(parseInt(phaseNum, 10));
766
783
  if (!completedPhases.has(phaseNum) && !completedPhases.has(unpadded)) continue;
767
- const phaseFiles = fs.readdirSync(path.join(phasesDir, e.name));
784
+ const phaseFiles = fs.readdirSync(path.join(phasesAbs, e.name));
768
785
  for (const f of phaseFiles) {
769
786
  if (/-VERIFICATION\.md$/i.test(f)) {
770
- trackingVerif.push(path.relative(gitRoot, path.join(phasesDir, e.name, f)));
787
+ trackingVerif.push(path.relative(gitRoot, path.join(phasesAbs, e.name, f)));
771
788
  }
772
789
  }
773
790
  }
@@ -858,12 +875,12 @@ function cmdValidateHealth(cwd, options, raw) {
858
875
  // the dangling artifact on the next /dgs:health run.
859
876
  try {
860
877
  const untracked = [];
861
- if (fs.existsSync(phasesDir)) {
878
+ if (fs.existsSync(phasesAbs)) {
862
879
  const ARTIFACT_RE = /^[0-9].*-(PLAN|CONTEXT|RESEARCH|UAT|VERIFICATION)\.md$/;
863
- const phaseEntries = fs.readdirSync(phasesDir, { withFileTypes: true });
880
+ const phaseEntries = fs.readdirSync(phasesAbs, { withFileTypes: true });
864
881
  for (const entry of phaseEntries) {
865
882
  if (!entry.isDirectory()) continue;
866
- const phaseDirAbs = path.join(phasesDir, entry.name);
883
+ const phaseDirAbs = path.join(phasesAbs, entry.name);
867
884
  let files;
868
885
  try { files = fs.readdirSync(phaseDirAbs); } catch { continue; }
869
886
  for (const f of files) {
@@ -80,3 +80,61 @@ test('REL-12: cmdValidateHealth does NOT flag tracked .gitkeep files', () => {
80
80
  });
81
81
 
82
82
  // REL-12 sentinel — flag this block as a Wave-0 RED scaffold for plan 04.
83
+
84
+ // ─── 260628-p59: versioned phases/<version>/ layout regression ──────────────
85
+ // cmdValidateConsistency must descend phases/<version>/ (via core.phasesDir) so a
86
+ // phase dir living under phases/v25.0/NN-slug is seen on disk and does NOT trigger
87
+ // a spurious "Phase N in ROADMAP.md but no directory on disk" warning.
88
+
89
+ function setupVersionedConsistencyFixture() {
90
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'p59-consist-'));
91
+ // getPlanningRoot requires a git repo (it process.exit(1)s otherwise, which would
92
+ // make the child shim emit empty stdout and falsely "pass"). Initialise one.
93
+ execSync('git init -q', { cwd: root });
94
+ execSync('git config user.email test@test', { cwd: root });
95
+ execSync('git config user.name test', { cwd: root });
96
+ fs.writeFileSync(path.join(root, 'STATE.md'), '---\nmilestone: v25.0\n---\n# State\n');
97
+ fs.writeFileSync(path.join(root, 'ROADMAP.md'), '# Roadmap\n\n### Phase 3: Foo\n');
98
+ const phaseDir = path.join(root, 'phases', 'v25.0', '03-foo');
99
+ fs.mkdirSync(phaseDir, { recursive: true });
100
+ fs.writeFileSync(path.join(phaseDir, '03-01-PLAN.md'),
101
+ '---\nphase: 3\nplan: 01\nwave: 1\n---\n# Plan\n');
102
+ return root;
103
+ }
104
+
105
+ function captureConsistencyOutput(root) {
106
+ // cmdValidateConsistency calls output() which process.exit(0)s — run in a child
107
+ // process and capture stdout JSON (mirrors captureHealthOutput above).
108
+ const verifyPath = path.resolve(__dirname, 'verify.cjs');
109
+ // cmdValidateConsistency passes a status word as output()'s third arg, so it
110
+ // must be called with raw=false to emit the JSON payload (not the status word).
111
+ const shim = [
112
+ `const v = require(${JSON.stringify(verifyPath)});`,
113
+ `v.cmdValidateConsistency(${JSON.stringify(root)}, false);`,
114
+ ].join('\n');
115
+ const shimPath = path.join(os.tmpdir(), `p59-consist-shim-${process.pid}-${Date.now()}.cjs`);
116
+ fs.writeFileSync(shimPath, shim);
117
+ try {
118
+ const stdout = execSync(`node ${JSON.stringify(shimPath)}`, { encoding: 'utf-8' });
119
+ try { return JSON.parse(stdout); } catch { return { stdout, warnings: [] }; }
120
+ } catch (err) {
121
+ const out = err.stdout && err.stdout.toString();
122
+ try { return JSON.parse(out); } catch { return { stdout: out, warnings: [] }; }
123
+ } finally {
124
+ try { fs.unlinkSync(shimPath); } catch { /* ignore */ }
125
+ }
126
+ }
127
+
128
+ test('260628-p59: cmdValidateConsistency descends phases/<version>/ (no spurious missing-directory warning)', () => {
129
+ const root = setupVersionedConsistencyFixture();
130
+ const result = captureConsistencyOutput(root);
131
+ // Guard against a silent false-pass: confirm the JSON payload actually parsed
132
+ // (a crashed child would yield {warnings: []} and trivially pass the filter).
133
+ assert.ok(Object.prototype.hasOwnProperty.call(result, 'passed'),
134
+ 'expected a parsed consistency result with a `passed` field');
135
+ const missingDirWarnings = (result.warnings || []).filter(
136
+ w => /Phase 3 in ROADMAP.*no directory on disk/.test(w)
137
+ );
138
+ assert.strictEqual(missingDirWarnings.length, 0,
139
+ 'phases/v25.0/03-foo should be seen on disk; no missing-directory warning expected');
140
+ });
@@ -149,7 +149,7 @@ For each phase's VERIFICATION.md, extract the expanded requirements table:
149
149
 
150
150
  For each phase's SUMMARY.md, extract `requirements-completed` from YAML frontmatter:
151
151
  ```bash
152
- for summary in ${project_root}/phases/*-*/*-SUMMARY.md; do
152
+ for summary in $(find ${project_root}/phases -name '*-SUMMARY.md' 2>/dev/null); do
153
153
  node "$HOME/.claude/deliver-great-systems/bin/dgs-tools.cjs" summary-extract "$summary" --fields requirements_completed | jq -r '.requirements_completed'
154
154
  done
155
155
  ```
@@ -66,7 +66,7 @@ Extract phase numbers and names from the archived roadmap (e.g., Phase 1: Founda
66
66
  Check which of those phase directories still exist in `${project_root}/phases/`:
67
67
 
68
68
  ```bash
69
- ls -d ${project_root}/phases/*/ 2>/dev/null
69
+ find ${project_root}/phases -mindepth 1 -maxdepth 2 -type d -name '[0-9]*-*' 2>/dev/null
70
70
  ```
71
71
 
72
72
  Match phase directories to milestone membership. Only include directories that still exist in `${project_root}/phases/`.
@@ -461,7 +461,7 @@ Extract one-liners from SUMMARY.md files using summary-extract:
461
461
 
462
462
  ```bash
463
463
  # For each phase in milestone, extract one-liner
464
- for summary in ${project_root}/phases/*-*/*-SUMMARY.md; do
464
+ for summary in ${phases_dir}/*/*-SUMMARY.md; do
465
465
  node "$HOME/.claude/deliver-great-systems/bin/dgs-tools.cjs" summary-extract "$summary" --fields one_liner | jq -r '.one_liner'
466
466
  done
467
467
  ```
@@ -494,7 +494,7 @@ Full PROJECT.md evolution review at milestone completion.
494
494
  Read all phase summaries:
495
495
 
496
496
  ```bash
497
- cat ${project_root}/phases/*-*/*-SUMMARY.md
497
+ cat ${phases_dir}/*/*-SUMMARY.md
498
498
  ```
499
499
 
500
500
  **Full review checklist:**
@@ -482,7 +482,11 @@ Use values from init: `phase_dir`, `phase_slug`, `padded_phase`.
482
482
 
483
483
  If `phase_dir` is null (phase exists in roadmap but no directory). Use `project_root` from init:
484
484
  ```bash
485
- mkdir -p "${project_root}/phases/${padded_phase}-${phase_slug}"
485
+ # Resolve version-aware phases base (mirror phasesDir(): versioned -> phases/<version>/, flat -> phases/)
486
+ PHASES_BASE="${project_root}/phases"
487
+ VERSION_DIR=$(ls -d ${project_root}/phases/v[0-9]*.[0-9]*/ 2>/dev/null | tail -1)
488
+ [ -n "$VERSION_DIR" ] && PHASES_BASE="${VERSION_DIR%/}"
489
+ mkdir -p "${PHASES_BASE}/${padded_phase}-${phase_slug}"
486
490
  ```
487
491
 
488
492
  **File location:** `${phase_dir}/${padded_phase}-CONTEXT.md`
@@ -25,7 +25,7 @@ Find current phase directory from most recently modified files:
25
25
 
26
26
  ```bash
27
27
  # Find most recent phase directory with work
28
- ls -lt ${project_root}/phases/*/PLAN.md 2>/dev/null | head -1 | grep -oP 'phases/\K[^/]+'
28
+ ls -t ${project_root}/phases/*/*-PLAN.md ${project_root}/phases/*/*/*-PLAN.md 2>/dev/null | head -1 | xargs -r dirname | xargs -r basename
29
29
  ```
30
30
 
31
31
  If no active phase detected, ask user which phase they're pausing work on.
@@ -53,7 +53,11 @@ Set `NON_INTERACTIVE = true` if `--non-interactive` flag present OR `--auto` fla
53
53
 
54
54
  **If `phase_found` is false:** Validate phase exists in ROADMAP.md. If valid, create the directory using `phase_slug` and `padded_phase` from init. Use `project_root` from init for the base path:
55
55
  ```bash
56
- mkdir -p "${project_root}/phases/${padded_phase}-${phase_slug}"
56
+ # Resolve version-aware phases base (mirror phasesDir(): versioned -> phases/<version>/, flat -> phases/)
57
+ PHASES_BASE="${project_root}/phases"
58
+ VERSION_DIR=$(ls -d ${project_root}/phases/v[0-9]*.[0-9]*/ 2>/dev/null | tail -1)
59
+ [ -n "$VERSION_DIR" ] && PHASES_BASE="${VERSION_DIR%/}"
60
+ mkdir -p "${PHASES_BASE}/${padded_phase}-${phase_slug}"
57
61
  ```
58
62
 
59
63
  **Existing artifacts from init:** `has_research`, `has_plans`, `plan_count`.
@@ -548,7 +552,7 @@ Before constructing the checker prompt, discover up to 3 most recent completed p
548
552
 
549
553
  ```bash
550
554
  # Find SUMMARY files from completed phases, sorted by modification time (most recent first)
551
- SUMMARY_FILES=$(ls -t ${project_root}/phases/*/[0-9]*-*-SUMMARY.md 2>/dev/null | head -9)
555
+ SUMMARY_FILES=$(ls -t ${project_root}/phases/*/[0-9]*-*-SUMMARY.md ${project_root}/phases/*/*/[0-9]*-*-SUMMARY.md 2>/dev/null | head -9)
552
556
  # Filter to only the 3 most recent unique phases (a phase may have multiple SUMMARYs)
553
557
  SEEN_PHASES=""
554
558
  SUMMARY_PATHS=""
@@ -68,10 +68,10 @@ Look for incomplete work that needs attention:
68
68
 
69
69
  ```bash
70
70
  # Check for continue-here files (mid-plan resumption)
71
- ls ${project_root}/phases/*/.continue-here*.md 2>/dev/null
71
+ find ${project_root}/phases -name '.continue-here*.md' 2>/dev/null
72
72
 
73
73
  # Check for plans without summaries (incomplete execution)
74
- for plan in ${project_root}/phases/*/*-PLAN.md; do
74
+ for plan in $(find ${project_root}/phases -name '*-PLAN.md' 2>/dev/null); do
75
75
  summary="${plan/PLAN/SUMMARY}"
76
76
  [ ! -f "$summary" ] && echo "Incomplete: $plan"
77
77
  done 2>/dev/null
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.5.1",
11
+ "version": "3.5.3",
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"