@ktpartners/dgs-platform 2.6.3 → 2.7.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.
Files changed (30) hide show
  1. package/agents/dgs-executor.md +51 -0
  2. package/commands/dgs/sync.md +70 -0
  3. package/deliver-great-systems/bin/dgs-tools.cjs +290 -4
  4. package/deliver-great-systems/bin/lib/config.cjs +259 -67
  5. package/deliver-great-systems/bin/lib/core.cjs +49 -8
  6. package/deliver-great-systems/bin/lib/core.test.cjs +35 -14
  7. package/deliver-great-systems/bin/lib/init.cjs +61 -6
  8. package/deliver-great-systems/bin/lib/init.test.cjs +9 -9
  9. package/deliver-great-systems/bin/lib/migration.cjs +1 -1
  10. package/deliver-great-systems/bin/lib/migration.test.cjs +7 -9
  11. package/deliver-great-systems/bin/lib/path-audit.test.cjs +1 -1
  12. package/deliver-great-systems/bin/lib/paths.cjs +32 -22
  13. package/deliver-great-systems/bin/lib/paths.test.cjs +16 -6
  14. package/deliver-great-systems/bin/lib/projects.cjs +1 -1
  15. package/deliver-great-systems/bin/lib/projects.test.cjs +1 -1
  16. package/deliver-great-systems/bin/lib/repos.cjs +29 -10
  17. package/deliver-great-systems/bin/lib/state.cjs +2 -2
  18. package/deliver-great-systems/bin/lib/sync.cjs +878 -0
  19. package/deliver-great-systems/bin/lib/test-helpers.cjs +44 -12
  20. package/deliver-great-systems/references/git-integration.md +81 -0
  21. package/deliver-great-systems/references/planning-config.md +154 -31
  22. package/deliver-great-systems/references/sync-cadence.md +191 -0
  23. package/deliver-great-systems/references/sync-hooks.md +96 -0
  24. package/deliver-great-systems/test/cadence.test.cjs +160 -0
  25. package/deliver-great-systems/test/sync-workflow.test.cjs +562 -0
  26. package/deliver-great-systems/workflows/execute-phase.md +111 -4
  27. package/deliver-great-systems/workflows/init-product.md +6 -2
  28. package/deliver-great-systems/workflows/run-job.md +77 -2
  29. package/deliver-great-systems/workflows/settings.md +82 -1
  30. package/package.json +1 -1
@@ -517,6 +517,56 @@ node "$HOME/.claude/deliver-great-systems/bin/dgs-tools.cjs" commit "docs({phase
517
517
  Separate from per-task commits — captures execution results only.
518
518
  </final_commit>
519
519
 
520
+ <codereview_gate>
521
+ Check if code review is enabled:
522
+
523
+ ```bash
524
+ CODEREVIEW=$(node "$HOME/.claude/deliver-great-systems/bin/dgs-tools.cjs" config-get workflow.codereview 2>/dev/null || echo "false")
525
+ ```
526
+
527
+ If `CODEREVIEW` is `true`:
528
+
529
+ Display:
530
+ ```
531
+ ------------------------------------------------------------
532
+ DGS > SPAWNING CODE REVIEW
533
+ ------------------------------------------------------------
534
+
535
+ Reviewing {phase}-{plan} changes across 3 passes...
536
+ ```
537
+
538
+ Compute diff reference for the plan's task commits:
539
+
540
+ ```bash
541
+ FIRST_TASK_COMMIT=$(git log --oneline --grep="feat(${PHASE}-${PLAN}):" --grep="fix(${PHASE}-${PLAN}):" --grep="test(${PHASE}-${PLAN}):" --grep="refactor(${PHASE}-${PLAN}):" --reverse | head -1 | cut -d' ' -f1)
542
+ ```
543
+
544
+ If FIRST_TASK_COMMIT is empty (no task commits found), skip codereview with message: "No task commits found for {phase}-{plan}, skipping code review."
545
+
546
+ Otherwise, invoke the codereview workflow:
547
+
548
+ ```
549
+ @~/.claude/deliver-great-systems/workflows/codereview.md
550
+ ```
551
+
552
+ With inputs:
553
+ - PHASE: ${PHASE}
554
+ - PLAN: ${PLAN}
555
+ - PLAN_PATH: ${phase_dir}/{phase}-{plan}-PLAN.md
556
+ - PHASE_DIR: ${phase_dir}
557
+ - DIFF_REF: ${FIRST_TASK_COMMIT}^..HEAD
558
+
559
+ After codereview completes:
560
+ - If auto-fixes were committed, the fix commit hash is noted in the codereview output
561
+ - The SUMMARY.md already has codereview findings appended and CODEREVIEW.md has been created with the full report (both done by codereview workflow)
562
+ - If the codereview workflow committed auto-fixes, amend the metadata commit to include the updated SUMMARY.md and CODEREVIEW.md:
563
+ ```bash
564
+ node "$HOME/.claude/deliver-great-systems/bin/dgs-tools.cjs" commit "" --files ${phase_dir}/{phase}-{plan}-SUMMARY.md ${phase_dir}/{phase}-{plan}-CODEREVIEW.md --amend
565
+ ```
566
+
567
+ If `CODEREVIEW` is `false` or absent: skip silently (no output).
568
+ </codereview_gate>
569
+
520
570
  <completion_format>
521
571
  ```markdown
522
572
  ## PLAN COMPLETE
@@ -546,5 +596,6 @@ Plan execution complete when:
546
596
  - [ ] STATE.md updated (position, decisions, issues, session)
547
597
  - [ ] ROADMAP.md updated with plan progress (via `roadmap update-plan-progress`)
548
598
  - [ ] Final metadata commit made (includes SUMMARY.md, STATE.md, ROADMAP.md)
599
+ - [ ] Codereview gate checked (if enabled: codereview invoked, if auto-fixes committed: metadata commit amended)
549
600
  - [ ] Completion format returned to orchestrator
550
601
  </success_criteria>
@@ -0,0 +1,70 @@
1
+ ---
2
+ name: dgs:sync
3
+ description: "Pull from and push to all registered repos"
4
+ allowed-tools:
5
+ - Read
6
+ - Bash
7
+ ---
8
+
9
+ # /dgs:sync -- Remote Synchronisation
10
+
11
+ Sync your planning and code repos with their remotes.
12
+
13
+ ## Usage
14
+
15
+ ```
16
+ /dgs:sync -- Pull then push all repos
17
+ /dgs:sync pull -- Pull only (fetch + fast-forward merge)
18
+ /dgs:sync push -- Push only
19
+ /dgs:sync status -- Dry-run check (no changes)
20
+ ```
21
+
22
+ ## Steps
23
+
24
+ <step name="determine_operation">
25
+ Parse the user's argument to determine the operation:
26
+ - No argument or "all" -> pull then push
27
+ - "pull" -> pull only
28
+ - "push" -> push only
29
+ - "status" or "dry-run" -> dry-run pull check
30
+
31
+ Store as `SYNC_OP`.
32
+ </step>
33
+
34
+ <step name="execute_sync">
35
+ Run the appropriate dgs-tools sync command(s):
36
+
37
+ **If SYNC_OP is "pull" or "all":**
38
+ ```bash
39
+ PULL_RESULT=$(node "$HOME/.claude/deliver-great-systems/bin/dgs-tools.cjs" sync pull 2>&1)
40
+ echo "$PULL_RESULT"
41
+ ```
42
+
43
+ **If SYNC_OP is "push" or "all":**
44
+ ```bash
45
+ PUSH_RESULT=$(node "$HOME/.claude/deliver-great-systems/bin/dgs-tools.cjs" sync push 2>&1)
46
+ echo "$PUSH_RESULT"
47
+ ```
48
+
49
+ **If SYNC_OP is "status":**
50
+ ```bash
51
+ PULL_STATUS=$(node "$HOME/.claude/deliver-great-systems/bin/dgs-tools.cjs" sync pull --dry-run 2>&1)
52
+ PUSH_STATUS=$(node "$HOME/.claude/deliver-great-systems/bin/dgs-tools.cjs" sync push --dry-run 2>&1)
53
+ echo "$PULL_STATUS"
54
+ echo "$PUSH_STATUS"
55
+ ```
56
+ </step>
57
+
58
+ <step name="report">
59
+ Display the results to the user in a clear format:
60
+ - Show per-repo status (updated/pushed/current/skipped/failed)
61
+ - Show summary line
62
+ - If any repo failed, highlight the failure and suggested fix
63
+ - If pre-flight failed, show all problems and suggest --force or manual fixes
64
+ </step>
65
+
66
+ ## Success Criteria
67
+
68
+ - [ ] Pull and push operations complete across all registered repos
69
+ - [ ] Per-repo results displayed clearly
70
+ - [ ] Failures reported with actionable fix suggestions
@@ -169,6 +169,15 @@
169
169
  * repos validate-consistency <plan> Validate <repos>/<files> consistency
170
170
  * repos migrate <slug> Migrate v1 install to v2 structure
171
171
  *
172
+ * Sync:
173
+ * sync pull [--dry-run] [--force] Pull from all repos (ff-only)
174
+ * sync push [--dry-run] [--force] Push to all repos
175
+ * [--repos name1,name2] Filter to specific repos
176
+ * sync workflow-pull <workflow> Workflow-level pull check/execute
177
+ * [--check-only] [--record-yes] [--mark-hint-shown]
178
+ * sync workflow-push <workflow> Workflow-level push check/execute
179
+ * [--check-only] [--mid-workflow] [--record-yes]
180
+ *
172
181
  * Ideas Operations:
173
182
  * ideas create --title T --body B Create new idea with auto-assigned ID
174
183
  * [--tags "t1,t2"]
@@ -246,7 +255,7 @@
246
255
  * init progress All context for progress workflow
247
256
  */
248
257
 
249
- const { error } = require('./lib/core.cjs');
258
+ const { error, loadConfig } = require('./lib/core.cjs');
250
259
  const state = require('./lib/state.cjs');
251
260
  const phase = require('./lib/phase.cjs');
252
261
  const roadmap = require('./lib/roadmap.cjs');
@@ -269,6 +278,7 @@ const jobs = require('./lib/jobs.cjs');
269
278
  const mergeConflicts = require('./lib/merge-conflicts.cjs');
270
279
  const conflictAgent = require('./lib/conflict-agent.cjs');
271
280
  const { requireGitIdentity, formatAuthorString, cmdIdentityResolve } = require('./lib/identity.cjs');
281
+ const { pullAll, pushAll, getCadence, checkStaleState, shouldShowFirstRunHint, markFirstRunHintShown, isWithinSuppressionWindow, recordPromptYes } = require('./lib/sync.cjs');
272
282
 
273
283
  // ─── Identity Gate ────────────────────────────────────────────────────────────
274
284
 
@@ -288,6 +298,261 @@ function gateIdentity() {
288
298
  }
289
299
  }
290
300
 
301
+ // ─── Sync Commands ────────────────────────────────────────────────────────────
302
+
303
+ function cmdSyncPull(cwd, raw, args) {
304
+ const dryRun = args.includes('--dry-run');
305
+ const force = args.includes('--force');
306
+
307
+ const result = pullAll(cwd, { dryRun, force });
308
+
309
+ // If pre-flight failed, output problems
310
+ if (result.problems) {
311
+ if (!raw) {
312
+ process.stderr.write('Pre-flight check failed:\n');
313
+ for (const p of result.problems) {
314
+ process.stderr.write(` * ${p.repo}: ${p.issue}\n`);
315
+ process.stderr.write(` Fix: ${p.fix}\n`);
316
+ }
317
+ process.stderr.write('\nUse --force to skip pre-flight checks.\n');
318
+ }
319
+ console.log(JSON.stringify(result));
320
+ if (!result.ok) process.exitCode = 1;
321
+ return;
322
+ }
323
+
324
+ // Format results for human-readable output
325
+ if (!raw) {
326
+ for (const r of result.results) {
327
+ const icon = r.status === 'updated' ? '+' : r.status === 'current' ? '.' : r.status === 'skipped' ? '-' : 'x';
328
+ process.stderr.write(` ${icon} ${r.repo}: ${r.message}\n`);
329
+ }
330
+ process.stderr.write(`\n${result.summary}\n`);
331
+ }
332
+
333
+ console.log(JSON.stringify(result));
334
+ if (!result.ok) process.exitCode = 1;
335
+ }
336
+
337
+ function cmdSyncPush(cwd, raw, args) {
338
+ const dryRun = args.includes('--dry-run');
339
+ const force = args.includes('--force');
340
+
341
+ // Parse --repos flag
342
+ let repoFilter = null;
343
+ const reposIdx = args.indexOf('--repos');
344
+ if (reposIdx !== -1 && args[reposIdx + 1]) {
345
+ repoFilter = args[reposIdx + 1].split(',').map(r => r.trim()).filter(Boolean);
346
+ }
347
+
348
+ const result = pushAll(cwd, { dryRun, force, repos: repoFilter });
349
+
350
+ // If pre-flight failed, output problems
351
+ if (result.problems) {
352
+ if (!raw) {
353
+ process.stderr.write('Pre-flight check failed:\n');
354
+ for (const p of result.problems) {
355
+ process.stderr.write(` * ${p.repo}: ${p.issue}\n`);
356
+ process.stderr.write(` Fix: ${p.fix}\n`);
357
+ }
358
+ process.stderr.write('\nUse --force to skip pre-flight checks.\n');
359
+ }
360
+ console.log(JSON.stringify(result));
361
+ if (!result.ok) process.exitCode = 1;
362
+ return;
363
+ }
364
+
365
+ // Format results
366
+ if (!raw) {
367
+ for (const r of result.results) {
368
+ const icon = r.status === 'pushed' ? '+' : r.status === 'current' ? '.' : r.status === 'skipped' ? '-' : 'x';
369
+ process.stderr.write(` ${icon} ${r.repo}: ${r.message}\n`);
370
+ }
371
+ process.stderr.write(`\n${result.summary}\n`);
372
+ }
373
+
374
+ console.log(JSON.stringify(result));
375
+ if (!result.ok) process.exitCode = 1;
376
+ }
377
+
378
+ function cmdSyncHelp(raw) {
379
+ const result = {
380
+ command: 'sync',
381
+ description: 'Pull from and push to all registered repos',
382
+ subcommands: [
383
+ { name: 'pull', usage: 'sync pull [--dry-run] [--force]', description: 'Pull from all repos (fetch + ff-only merge)' },
384
+ { name: 'push', usage: 'sync push [--dry-run] [--force] [--repos name1,name2]', description: 'Push to all repos' },
385
+ { name: 'workflow-pull', usage: 'sync workflow-pull <workflow> [--check-only] [--record-yes] [--mark-hint-shown]', description: 'Workflow-level pull check or execute' },
386
+ { name: 'workflow-push', usage: 'sync workflow-push <workflow> [--check-only] [--mid-workflow] [--record-yes]', description: 'Workflow-level push check or execute' },
387
+ ],
388
+ };
389
+ if (raw) {
390
+ console.log(JSON.stringify(result));
391
+ } else {
392
+ process.stderr.write('Usage:\n');
393
+ for (const s of result.subcommands) {
394
+ process.stderr.write(` ${s.usage}\n ${s.description}\n`);
395
+ }
396
+ console.log(JSON.stringify(result));
397
+ }
398
+ }
399
+
400
+ function cmdSyncWorkflowPull(cwd, raw, args) {
401
+ const workflow = args.find(a => !a.startsWith('--'));
402
+ if (!workflow) {
403
+ error('Usage: sync workflow-pull <workflow> [--check-only] [--record-yes] [--mark-hint-shown]');
404
+ }
405
+
406
+ const checkOnly = args.includes('--check-only');
407
+ const doRecordYes = args.includes('--record-yes');
408
+ const doMarkHint = args.includes('--mark-hint-shown');
409
+
410
+ // Handle side-effect-only flags
411
+ if (doRecordYes) {
412
+ recordPromptYes('pull');
413
+ console.log(JSON.stringify({ recorded: true, direction: 'pull' }));
414
+ return;
415
+ }
416
+ if (doMarkHint) {
417
+ markFirstRunHintShown(cwd);
418
+ console.log(JSON.stringify({ marked: true }));
419
+ return;
420
+ }
421
+
422
+ // Load config for sync mode
423
+ const config = loadConfig(cwd);
424
+ const syncMode = config.sync_pull || 'off';
425
+ const cadence = getCadence(workflow);
426
+
427
+ if (checkOnly) {
428
+ // Return status without executing
429
+ const suppressed = isWithinSuppressionWindow('pull');
430
+ const stale = cadence.pull && syncMode === 'off' ? checkStaleState(cwd) : { stale: false, commitsBehind: 0, branch: null };
431
+ const firstRunHint = shouldShowFirstRunHint(cwd) ? 'Tip: Remote detected. Enable sync with /dgs:settings' : null;
432
+
433
+ const result = {
434
+ should_pull: cadence.pull && syncMode !== 'off',
435
+ mode: syncMode,
436
+ cadence_pull: cadence.pull,
437
+ stale,
438
+ first_run_hint: firstRunHint,
439
+ suppressed,
440
+ };
441
+ console.log(JSON.stringify(result));
442
+ return;
443
+ }
444
+
445
+ // Execute pull
446
+ const result = pullAll(cwd, { force: false });
447
+
448
+ // Format results for stderr
449
+ if (!raw) {
450
+ for (const r of result.results) {
451
+ const icon = r.status === 'updated' ? '+' : r.status === 'current' ? '.' : r.status === 'skipped' ? '-' : 'x';
452
+ process.stderr.write(` ${icon} ${r.repo}: ${r.message}\n`);
453
+ }
454
+ if (result.problems) {
455
+ for (const p of result.problems) {
456
+ process.stderr.write(` x ${p.repo}: ${p.issue}\n`);
457
+ }
458
+ }
459
+ }
460
+
461
+ // Build diagnosis for failures
462
+ let diagnosis = null;
463
+ if (!result.ok) {
464
+ const failedRepos = result.results?.filter(r => r.status === 'failed') || [];
465
+ const diag = failedRepos.map(r => `${r.repo}: ${r.message}`).join('; ');
466
+ const preflightProblems = result.problems?.map(p => `${p.repo}: ${p.issue} -- ${p.fix}`).join('; ');
467
+ diagnosis = preflightProblems || diag || result.summary;
468
+ }
469
+
470
+ const output = {
471
+ action: result.ok ? 'pulled' : 'aborted',
472
+ ok: result.ok,
473
+ summary: result.summary,
474
+ diagnosis,
475
+ };
476
+ console.log(JSON.stringify(output));
477
+ if (!result.ok) process.exitCode = 1;
478
+ }
479
+
480
+ function cmdSyncWorkflowPush(cwd, raw, args) {
481
+ const workflow = args.find(a => !a.startsWith('--'));
482
+ if (!workflow) {
483
+ error('Usage: sync workflow-push <workflow> [--check-only] [--mid-workflow] [--record-yes]');
484
+ }
485
+
486
+ const checkOnly = args.includes('--check-only');
487
+ const midWorkflow = args.includes('--mid-workflow');
488
+ const doRecordYes = args.includes('--record-yes');
489
+
490
+ // Handle side-effect-only flags
491
+ if (doRecordYes) {
492
+ recordPromptYes('push');
493
+ console.log(JSON.stringify({ recorded: true, direction: 'push' }));
494
+ return;
495
+ }
496
+
497
+ // Load config for sync mode
498
+ const config = loadConfig(cwd);
499
+ const syncMode = config.sync_push || 'off';
500
+ const cadence = getCadence(workflow);
501
+
502
+ if (checkOnly) {
503
+ const suppressed = isWithinSuppressionWindow('push');
504
+ const result = {
505
+ should_push: cadence.push && syncMode !== 'off',
506
+ mode: syncMode,
507
+ cadence_push: cadence.push,
508
+ suppressed,
509
+ };
510
+ console.log(JSON.stringify(result));
511
+ return;
512
+ }
513
+
514
+ // Determine retry count based on mode
515
+ const maxRetries = midWorkflow ? (syncMode === 'auto' ? 3 : 1) : 0;
516
+
517
+ // Execute push with retries
518
+ let lastResult;
519
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
520
+ lastResult = pushAll(cwd, { force: true }); // force: bypass preflight (already committed)
521
+ if (lastResult.ok) break;
522
+ if (!midWorkflow) break;
523
+ }
524
+
525
+ // Format results for stderr
526
+ if (!raw) {
527
+ for (const r of lastResult.results) {
528
+ const icon = r.status === 'pushed' ? '+' : r.status === 'current' ? '.' : r.status === 'skipped' ? '-' : 'x';
529
+ process.stderr.write(` ${icon} ${r.repo}: ${r.message}\n`);
530
+ }
531
+ }
532
+
533
+ // Build summary
534
+ const totalCommits = lastResult.results.reduce((sum, r) => sum + (r.commits || 0), 0);
535
+ const repoCount = lastResult.results.filter(r => r.status === 'pushed').length;
536
+ let summaryMsg = lastResult.summary;
537
+
538
+ if (lastResult.ok && repoCount > 0) {
539
+ if (repoCount > 1) {
540
+ summaryMsg = `Pushed ${totalCommits} commit${totalCommits !== 1 ? 's' : ''} across ${repoCount} repos`;
541
+ } else if (totalCommits > 0) {
542
+ summaryMsg = `Pushed ${totalCommits} commit${totalCommits !== 1 ? 's' : ''}`;
543
+ }
544
+ }
545
+
546
+ const output = {
547
+ action: lastResult.ok ? 'pushed' : 'warning',
548
+ ok: lastResult.ok,
549
+ summary: summaryMsg,
550
+ message: lastResult.ok ? summaryMsg : `Warning: push failed. Work saved locally.`,
551
+ };
552
+ console.log(JSON.stringify(output));
553
+ if (!lastResult.ok) process.exitCode = 1;
554
+ }
555
+
291
556
  // ─── CLI Router ───────────────────────────────────────────────────────────────
292
557
 
293
558
  async function main() {
@@ -852,13 +1117,13 @@ async function main() {
852
1117
  init.cmdInitAuditPhase(cwd, args[2], raw);
853
1118
  break;
854
1119
  case 'phase-op':
855
- init.cmdInitPhaseOp(cwd, args[2], raw);
1120
+ init.cmdInitPhaseOp(cwd, args[2], raw, args[3] || null);
856
1121
  break;
857
1122
  case 'todos':
858
- init.cmdInitTodos(cwd, args[2], raw);
1123
+ init.cmdInitTodos(cwd, args[2], raw, args[3] || null);
859
1124
  break;
860
1125
  case 'milestone-op':
861
- init.cmdInitMilestoneOp(cwd, raw);
1126
+ init.cmdInitMilestoneOp(cwd, raw, args[2] || null);
862
1127
  break;
863
1128
  case 'map-codebase': {
864
1129
  const onlyIdx = args.indexOf('--only');
@@ -1436,6 +1701,27 @@ async function main() {
1436
1701
  break;
1437
1702
  }
1438
1703
 
1704
+ case 'sync': {
1705
+ const syncSub = args[1];
1706
+ switch (syncSub) {
1707
+ case 'pull':
1708
+ cmdSyncPull(cwd, raw, args.slice(2));
1709
+ break;
1710
+ case 'push':
1711
+ cmdSyncPush(cwd, raw, args.slice(2));
1712
+ break;
1713
+ case 'workflow-pull':
1714
+ cmdSyncWorkflowPull(cwd, raw, args.slice(2));
1715
+ break;
1716
+ case 'workflow-push':
1717
+ cmdSyncWorkflowPush(cwd, raw, args.slice(2));
1718
+ break;
1719
+ default:
1720
+ cmdSyncHelp(raw);
1721
+ }
1722
+ break;
1723
+ }
1724
+
1439
1725
  default:
1440
1726
  error(`Unknown command: ${command}`);
1441
1727
  }