@ktpartners/dgs-platform 2.9.0 → 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 +197 -0
- package/README.md +34 -2
- package/agents/dgs-executor.md +124 -3
- package/agents/dgs-idea-researcher.md +447 -0
- package/agents/dgs-plan-checker.md +61 -3
- package/agents/dgs-planner.md +51 -8
- package/bin/install.js +44 -0
- package/commands/dgs/abandon-quick.md +28 -0
- package/commands/dgs/add-tests.md +2 -2
- package/commands/dgs/audit-milestone.md +4 -3
- package/commands/dgs/capture-principle.md +11 -11
- package/commands/dgs/cleanup.md +2 -2
- package/commands/dgs/complete-milestone.md +11 -11
- package/commands/dgs/complete-quick.md +28 -0
- package/commands/dgs/create-milestone-job.md +2 -2
- package/commands/dgs/debug.md +3 -3
- package/commands/dgs/develop-idea.md +1 -1
- package/commands/dgs/diff-report.md +124 -0
- package/commands/dgs/fast.md +3 -1
- package/commands/dgs/health.md +1 -1
- package/commands/dgs/map-codebase.md +6 -6
- package/commands/dgs/new-milestone.md +5 -5
- package/commands/dgs/new-project.md +8 -21
- package/commands/dgs/package-scan.md +43 -0
- package/commands/dgs/plan-milestone-gaps.md +1 -1
- package/commands/dgs/progress.md +3 -3
- package/commands/dgs/quick-abandon.md +8 -0
- package/commands/dgs/quick-complete.md +8 -0
- package/commands/dgs/quick.md +10 -3
- package/commands/dgs/research-idea.md +3 -2
- package/commands/dgs/research-phase.md +3 -3
- package/commands/dgs/switch-project.md +14 -1
- package/commands/dgs/write-spec.md +3 -3
- package/deliver-great-systems/bin/dgs-tools.cjs +401 -32
- 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 +626 -46
- package/deliver-great-systems/bin/lib/commands.test.cjs +451 -0
- package/deliver-great-systems/bin/lib/commit-verify.test.cjs +236 -0
- package/deliver-great-systems/bin/lib/config.cjs +80 -6
- package/deliver-great-systems/bin/lib/config.test.cjs +309 -0
- package/deliver-great-systems/bin/lib/context.cjs +120 -0
- package/deliver-great-systems/bin/lib/core.cjs +35 -14
- package/deliver-great-systems/bin/lib/core.test.cjs +79 -1
- package/deliver-great-systems/bin/lib/execution.cjs +49 -17
- 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/flat-migration.test.cjs +396 -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/ideas.cjs +206 -91
- package/deliver-great-systems/bin/lib/ideas.test.cjs +244 -1
- package/deliver-great-systems/bin/lib/init.cjs +357 -61
- package/deliver-great-systems/bin/lib/init.test.cjs +625 -8
- package/deliver-great-systems/bin/lib/jobs.cjs +131 -25
- package/deliver-great-systems/bin/lib/jobs.test.cjs +193 -74
- package/deliver-great-systems/bin/lib/migration.cjs +409 -1
- package/deliver-great-systems/bin/lib/migration.test.cjs +158 -1
- package/deliver-great-systems/bin/lib/milestone.cjs +154 -31
- 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 +146 -3
- package/deliver-great-systems/bin/lib/phase.test.cjs +420 -0
- package/deliver-great-systems/bin/lib/plan-number-validity.test.cjs +48 -0
- package/deliver-great-systems/bin/lib/projects.cjs +65 -10
- package/deliver-great-systems/bin/lib/projects.test.cjs +198 -2
- package/deliver-great-systems/bin/lib/quick.cjs +739 -0
- package/deliver-great-systems/bin/lib/quick.test.cjs +730 -0
- package/deliver-great-systems/bin/lib/repos.cjs +37 -13
- package/deliver-great-systems/bin/lib/review.cjs +1821 -0
- package/deliver-great-systems/bin/lib/roadmap.cjs +34 -13
- package/deliver-great-systems/bin/lib/specs.cjs +3 -81
- package/deliver-great-systems/bin/lib/state-transition-gate.test.cjs +160 -0
- package/deliver-great-systems/bin/lib/state.cjs +147 -55
- 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/sync.cjs +75 -0
- package/deliver-great-systems/bin/lib/verify.cjs +198 -7
- 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 +790 -0
- package/deliver-great-systems/bin/lib/worktrees.test.cjs +963 -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 +27 -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/abandon-quick.md +89 -0
- package/deliver-great-systems/workflows/add-idea.md +3 -3
- package/deliver-great-systems/workflows/add-phase.md +5 -0
- package/deliver-great-systems/workflows/add-tests.md +14 -0
- package/deliver-great-systems/workflows/add-todo.md +1 -0
- package/deliver-great-systems/workflows/approve-spec.md +25 -4
- package/deliver-great-systems/workflows/audit-milestone.md +66 -10
- package/deliver-great-systems/workflows/audit-phase.md +15 -5
- package/deliver-great-systems/workflows/cancel-job.md +2 -2
- package/deliver-great-systems/workflows/check-todos.md +2 -3
- package/deliver-great-systems/workflows/codereview.md +103 -9
- package/deliver-great-systems/workflows/complete-milestone.md +218 -24
- package/deliver-great-systems/workflows/complete-quick.md +106 -0
- package/deliver-great-systems/workflows/consolidate-ideas.md +1 -1
- package/deliver-great-systems/workflows/create-milestone-job.md +4 -4
- package/deliver-great-systems/workflows/develop-idea.md +11 -11
- package/deliver-great-systems/workflows/diagnose-issues.md +14 -0
- package/deliver-great-systems/workflows/discuss-idea.md +1 -1
- package/deliver-great-systems/workflows/discuss-phase.md +3 -2
- package/deliver-great-systems/workflows/execute-phase.md +209 -33
- package/deliver-great-systems/workflows/execute-plan.md +22 -22
- package/deliver-great-systems/workflows/help.md +53 -20
- package/deliver-great-systems/workflows/import-spec.md +65 -7
- package/deliver-great-systems/workflows/init-product.md +45 -167
- package/deliver-great-systems/workflows/new-milestone.md +140 -33
- 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/progress-all.md +133 -0
- package/deliver-great-systems/workflows/quick-abandon.md +89 -0
- package/deliver-great-systems/workflows/quick-complete.md +106 -0
- package/deliver-great-systems/workflows/quick.md +328 -26
- package/deliver-great-systems/workflows/refine-spec.md +1 -1
- package/deliver-great-systems/workflows/research-idea.md +77 -139
- package/deliver-great-systems/workflows/resume-project.md +2 -2
- package/deliver-great-systems/workflows/run-job.md +29 -43
- package/deliver-great-systems/workflows/settings.md +13 -77
- package/deliver-great-systems/workflows/validate-phase.md +39 -1
- package/deliver-great-systems/workflows/verify-work.md +14 -0
- package/deliver-great-systems/workflows/write-spec.md +11 -13
- package/hooks/dist/dgs-enforce-discipline.js +196 -0
- package/package.json +1 -1
- package/scripts/build-hooks.js +1 -0
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const { execSync } = require('child_process');
|
|
8
|
-
const { loadConfig, resolveModelInternal, findPhaseInternal, getRoadmapPhaseInternal, pathExistsInternal, generateSlugInternal, getMilestoneInfo, normalizePhaseName, toPosixPath, output, error, resolveProjectPath, getProjectRoot, isV2Install, getProjectFolders, getV2Hint } = require('./core.cjs');
|
|
8
|
+
const { loadConfig, resolveModelInternal, findPhaseInternal, getRoadmapPhaseInternal, pathExistsInternal, generateSlugInternal, getMilestoneInfo, normalizePhaseName, toPosixPath, output, error, resolveProjectPath, getProjectRoot, isV2Install, getProjectFolders, getV2Hint, safeReadFile } = require('./core.cjs');
|
|
9
9
|
const { requireGitIdentity, formatAuthorString } = require('./identity.cjs');
|
|
10
10
|
const { getPlanningRoot, PROJECTS_DIR } = require('./paths.cjs');
|
|
11
11
|
const { parseReposMd, validateReposMdEager } = require('./repos.cjs');
|
|
12
12
|
const { getCadence, pullAll } = require('./sync.cjs');
|
|
13
|
+
const { detectQuickMode, generateQuickId, getActiveQuick } = require('./quick.cjs');
|
|
14
|
+
const { listProjectsReadonly } = require('./projects.cjs');
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* Safely resolve the current git author string.
|
|
@@ -221,9 +223,7 @@ function cmdInitExecutePhase(cwd, phase, raw) {
|
|
|
221
223
|
// Config flags
|
|
222
224
|
commit_docs: config.commit_docs,
|
|
223
225
|
parallelization: config.parallelization,
|
|
224
|
-
branching_strategy:
|
|
225
|
-
phase_branch_template: config.phase_branch_template,
|
|
226
|
-
milestone_branch_template: config.milestone_branch_template,
|
|
226
|
+
branching_strategy: 'milestone',
|
|
227
227
|
verifier_enabled: config.verifier,
|
|
228
228
|
base_branch: config.base_branch,
|
|
229
229
|
sync_push: config.sync_push,
|
|
@@ -246,25 +246,14 @@ function cmdInitExecutePhase(cwd, phase, raw) {
|
|
|
246
246
|
plan_count: phaseInfo?.plans?.length || 0,
|
|
247
247
|
incomplete_count: phaseInfo?.incomplete_plans?.length || 0,
|
|
248
248
|
|
|
249
|
-
// Branch name (
|
|
249
|
+
// Branch name (milestone worktree model — always uses milestone template)
|
|
250
250
|
branch_name: (() => {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
.replace('{phase}', phaseInfo.phase_number)
|
|
258
|
-
.replace('{slug}', phaseInfo.phase_slug || 'phase');
|
|
259
|
-
} else if (config.branching_strategy === 'milestone') {
|
|
260
|
-
template = config.milestone_branch_template;
|
|
261
|
-
resolved = resolveProjectInTemplate(template, ctx.current_project);
|
|
262
|
-
if (resolved.error) return resolved.error;
|
|
263
|
-
return resolved.value
|
|
264
|
-
.replace('{milestone}', milestone.version)
|
|
265
|
-
.replace('{slug}', generateSlugInternal(milestone.name) || 'milestone');
|
|
266
|
-
}
|
|
267
|
-
return null;
|
|
251
|
+
const template = 'dgs/{project}/{milestone}-{slug}';
|
|
252
|
+
const resolved = resolveProjectInTemplate(template, ctx.current_project);
|
|
253
|
+
if (resolved.error) return resolved.error;
|
|
254
|
+
return resolved.value
|
|
255
|
+
.replace('{milestone}', milestone.version)
|
|
256
|
+
.replace('{slug}', generateSlugInternal(milestone.name) || 'milestone');
|
|
268
257
|
})(),
|
|
269
258
|
|
|
270
259
|
// Milestone info
|
|
@@ -395,12 +384,28 @@ function cmdInitPlanPhase(cwd, phase, raw) {
|
|
|
395
384
|
output(result, raw);
|
|
396
385
|
}
|
|
397
386
|
|
|
398
|
-
function cmdInitNewProject(cwd, raw) {
|
|
387
|
+
function cmdInitNewProject(cwd, slugArg, raw) {
|
|
399
388
|
const config = loadConfig(cwd);
|
|
400
389
|
const ctx = resolveProjectContext(cwd);
|
|
401
390
|
const planRootRel = path.relative(cwd, getPlanningRoot(cwd)) || '.';
|
|
402
391
|
const milestone = getMilestoneInfo(cwd);
|
|
403
392
|
|
|
393
|
+
// Resolve slug override (optional positional arg from CLI).
|
|
394
|
+
// When supplied AND we're on a v2 install, override the per-slug paths
|
|
395
|
+
// so `init new-project <slug>` reflects whether THAT slug exists,
|
|
396
|
+
// independent of the currently active project. v1 mode silently falls
|
|
397
|
+
// back to ctx.root behaviour because v1 has no projects/<slug>/ layout.
|
|
398
|
+
let effectiveRoot = ctx.root;
|
|
399
|
+
let slugOverride = null;
|
|
400
|
+
if (slugArg && typeof slugArg === 'string' && slugArg.trim()) {
|
|
401
|
+
const candidate = generateSlugInternal(slugArg.trim());
|
|
402
|
+
if (candidate && isV2Install(cwd)) {
|
|
403
|
+
slugOverride = candidate;
|
|
404
|
+
effectiveRoot = path.join(planRootRel, 'projects', slugOverride);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
const rootForPaths = effectiveRoot;
|
|
408
|
+
|
|
404
409
|
// Detect Brave Search API key availability
|
|
405
410
|
const homedir = require('os').homedir();
|
|
406
411
|
const braveKeyFile = path.join(homedir, '.dgs', 'brave_api_key');
|
|
@@ -437,8 +442,8 @@ function cmdInitNewProject(cwd, raw) {
|
|
|
437
442
|
cadence_pull: getCadence('new-project').pull,
|
|
438
443
|
cadence_push: getCadence('new-project').push,
|
|
439
444
|
|
|
440
|
-
// Existing state (project-qualified)
|
|
441
|
-
project_exists:
|
|
445
|
+
// Existing state (project-qualified, slug-override aware)
|
|
446
|
+
project_exists: rootForPaths ? pathExistsInternal(cwd, path.join(rootForPaths, 'PROJECT.md')) : pathExistsInternal(cwd, path.join(planRootRel, 'PROJECT.md')),
|
|
442
447
|
has_codebase_map: pathExistsInternal(cwd, path.join(planRootRel, 'codebase')),
|
|
443
448
|
planning_exists: pathExistsInternal(cwd, planRootRel),
|
|
444
449
|
|
|
@@ -457,12 +462,12 @@ function cmdInitNewProject(cwd, raw) {
|
|
|
457
462
|
// Enhanced search
|
|
458
463
|
brave_search_available: hasBraveSearch,
|
|
459
464
|
|
|
460
|
-
// File paths (project-qualified)
|
|
461
|
-
project_path:
|
|
462
|
-
state_path:
|
|
463
|
-
roadmap_path:
|
|
464
|
-
requirements_path:
|
|
465
|
-
research_dir:
|
|
465
|
+
// File paths (project-qualified, slug-override aware)
|
|
466
|
+
project_path: rootForPaths ? path.join(rootForPaths, 'PROJECT.md') : path.join(planRootRel, 'PROJECT.md'),
|
|
467
|
+
state_path: rootForPaths ? path.join(rootForPaths, 'STATE.md') : path.join(planRootRel, 'STATE.md'),
|
|
468
|
+
roadmap_path: rootForPaths ? path.join(rootForPaths, 'ROADMAP.md') : path.join(planRootRel, 'ROADMAP.md'),
|
|
469
|
+
requirements_path: rootForPaths ? path.join(rootForPaths, 'REQUIREMENTS.md') : path.join(planRootRel, 'REQUIREMENTS.md'),
|
|
470
|
+
research_dir: rootForPaths ? path.join(rootForPaths, 'research') : path.join(planRootRel, 'research'),
|
|
466
471
|
|
|
467
472
|
// Milestone info (product-level, shared across projects)
|
|
468
473
|
milestone_version: milestone.version,
|
|
@@ -471,7 +476,13 @@ function cmdInitNewProject(cwd, raw) {
|
|
|
471
476
|
// v2 context
|
|
472
477
|
dgs_mode: ctx.dgs_mode,
|
|
473
478
|
current_project: ctx.current_project,
|
|
474
|
-
project_root
|
|
479
|
+
// project_root reflects override when a slug arg is supplied (so the
|
|
480
|
+
// workflow can write directly to the requested project's folder), otherwise
|
|
481
|
+
// it still reports the active project root.
|
|
482
|
+
project_root: rootForPaths,
|
|
483
|
+
// requested_slug: the normalized slug arg (or null when no slug arg / v1 fallback).
|
|
484
|
+
// The workflow uses this to distinguish "active project" from "requested project".
|
|
485
|
+
requested_slug: slugOverride,
|
|
475
486
|
guard: ctx.guard,
|
|
476
487
|
v2_hint: ctx.v2_hint || null,
|
|
477
488
|
};
|
|
@@ -514,6 +525,10 @@ function cmdInitNewMilestone(cwd, raw) {
|
|
|
514
525
|
roadmap_path: ctx.root ? path.join(ctx.root, 'ROADMAP.md') : path.join(planRootRel, 'ROADMAP.md'),
|
|
515
526
|
state_path: ctx.root ? path.join(ctx.root, 'STATE.md') : path.join(planRootRel, 'STATE.md'),
|
|
516
527
|
|
|
528
|
+
// Product-level MILESTONES.md (authoritative for global version/phase continuity across projects)
|
|
529
|
+
product_milestones_path: path.join(planRootRel, 'MILESTONES.md'),
|
|
530
|
+
product_milestones_exists: pathExistsInternal(cwd, path.join(planRootRel, 'MILESTONES.md')),
|
|
531
|
+
|
|
517
532
|
// Author
|
|
518
533
|
author: resolveAuthorSafe(cwd),
|
|
519
534
|
|
|
@@ -533,22 +548,50 @@ function cmdInitQuick(cwd, description, raw) {
|
|
|
533
548
|
const config = loadConfig(cwd);
|
|
534
549
|
const ctx = resolveProjectContext(cwd);
|
|
535
550
|
const now = new Date();
|
|
536
|
-
const slug = description ? generateSlugInternal(description)?.substring(0, 40) : null;
|
|
537
551
|
const planRootRel = path.relative(cwd, getPlanningRoot(cwd)) || '.';
|
|
538
552
|
|
|
539
|
-
//
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
const
|
|
550
|
-
const
|
|
551
|
-
|
|
553
|
+
// Parse --main flag out of description string
|
|
554
|
+
const forceMain = /\s*--main\s*/.test(description || '');
|
|
555
|
+
const cleanDescription = (description || '').replace(/\s*--main\s*/g, ' ').trim() || null;
|
|
556
|
+
|
|
557
|
+
// Hoist getActiveQuick lookup so both `slug` and `quickId` derive from
|
|
558
|
+
// the same canonical source (the worktree slug stored in config.local.json).
|
|
559
|
+
// The worktree slug has already been truncated to 50 chars by worktrees.cjs;
|
|
560
|
+
// re-sanitising the description here would produce a divergent (51-char)
|
|
561
|
+
// descSlug, leaving an orphan directory on disk after every quick task.
|
|
562
|
+
// See: bug 260428-k7f.
|
|
563
|
+
const activeQuick = getActiveQuick(cwd);
|
|
564
|
+
const hasActiveQuickSlug = activeQuick && /^\d{6}-[a-z0-9]{3}-/.test(activeQuick.slug);
|
|
565
|
+
|
|
566
|
+
const slug = hasActiveQuickSlug
|
|
567
|
+
? activeQuick.slug.slice(11) // strip "{quickId}-" prefix (10 chars + 1 dash)
|
|
568
|
+
: (cleanDescription ? generateSlugInternal(cleanDescription)?.substring(0, 40).replace(/-+$/, '') : null);
|
|
569
|
+
|
|
570
|
+
// Detect quick mode: product-level vs milestone-context
|
|
571
|
+
const quickMode = detectQuickMode(cwd, forceMain);
|
|
572
|
+
|
|
573
|
+
// Quick task ID: YYMMDD-xxx. For product-level quicks with an active worktree,
|
|
574
|
+
// extract the quickId from the worktree slug (startProductQuick embeds it as the
|
|
575
|
+
// prefix). For fast mode and milestone-context, generate a fresh one.
|
|
576
|
+
const isProductMode = quickMode.mode === 'product';
|
|
577
|
+
const quickBase = isProductMode
|
|
578
|
+
? path.join(planRootRel, 'quick')
|
|
579
|
+
: (ctx.root ? path.join(ctx.root, 'quick') : path.join(planRootRel, 'quick'));
|
|
580
|
+
const quickId = hasActiveQuickSlug
|
|
581
|
+
? activeQuick.slug.slice(0, 10)
|
|
582
|
+
: generateQuickId(now);
|
|
583
|
+
|
|
584
|
+
// Auto-create product-level STATE.md on demand (matches project STATE.md
|
|
585
|
+
// auto-creation behavior elsewhere). Keep skeleton minimal — the /dgs:quick
|
|
586
|
+
// workflow adds ### Quick Tasks Completed on first use; we only need the
|
|
587
|
+
// ### Blockers/Concerns anchor so later inserts succeed.
|
|
588
|
+
if (isProductMode) {
|
|
589
|
+
const absoluteStatePath = path.resolve(cwd, planRootRel, 'STATE.md');
|
|
590
|
+
if (!fs.existsSync(absoluteStatePath)) {
|
|
591
|
+
const skeleton = '# Project State\n\n## Current Position\n\n(Product-level state — quick tasks tracked here.)\n\n## Accumulated Context\n\n### Blockers/Concerns\n\nNone yet.\n';
|
|
592
|
+
fs.writeFileSync(absoluteStatePath, skeleton, 'utf-8');
|
|
593
|
+
}
|
|
594
|
+
}
|
|
552
595
|
|
|
553
596
|
const result = {
|
|
554
597
|
// Models
|
|
@@ -567,7 +610,7 @@ function cmdInitQuick(cwd, description, raw) {
|
|
|
567
610
|
// Quick task info
|
|
568
611
|
quick_id: quickId,
|
|
569
612
|
slug: slug,
|
|
570
|
-
description:
|
|
613
|
+
description: cleanDescription,
|
|
571
614
|
|
|
572
615
|
// Timestamps
|
|
573
616
|
date: now.toISOString().split('T')[0],
|
|
@@ -576,9 +619,14 @@ function cmdInitQuick(cwd, description, raw) {
|
|
|
576
619
|
// Paths (project-qualified)
|
|
577
620
|
quick_dir: quickBase,
|
|
578
621
|
task_dir: slug ? path.join(quickBase, `${quickId}-${slug}`) : null,
|
|
579
|
-
state_path:
|
|
622
|
+
state_path: isProductMode
|
|
623
|
+
? path.join(planRootRel, 'STATE.md')
|
|
624
|
+
: (ctx.root ? path.join(ctx.root, 'STATE.md') : path.join(planRootRel, 'STATE.md')),
|
|
580
625
|
|
|
581
626
|
// File existence
|
|
627
|
+
// roadmap_exists stays project-scoped for both modes: the quick workflow
|
|
628
|
+
// only reads ROADMAP.md for project context (phase/milestone labels), and
|
|
629
|
+
// product-level quicks still run against the currently active project.
|
|
582
630
|
roadmap_exists: ctx.root ? pathExistsInternal(cwd, path.join(ctx.root, 'ROADMAP.md')) : pathExistsInternal(cwd, path.join(planRootRel, 'ROADMAP.md')),
|
|
583
631
|
planning_exists: pathExistsInternal(cwd, planRootRel),
|
|
584
632
|
|
|
@@ -844,17 +892,23 @@ function cmdInitTodos(cwd, area, raw, workflow) {
|
|
|
844
892
|
const now = new Date();
|
|
845
893
|
const planRootRel = path.relative(cwd, getPlanningRoot(cwd)) || '.';
|
|
846
894
|
|
|
847
|
-
// List todos (
|
|
848
|
-
const
|
|
849
|
-
const
|
|
895
|
+
// List todos — flat directory (status in frontmatter), with legacy fallback
|
|
896
|
+
const todosBase = path.join(planRootRel, 'todos');
|
|
897
|
+
const todosDir = path.join(cwd, todosBase);
|
|
850
898
|
let count = 0;
|
|
851
899
|
const todos = [];
|
|
900
|
+
const seenFiles = new Set();
|
|
852
901
|
|
|
902
|
+
// Flat directory scan (new layout — status in frontmatter)
|
|
853
903
|
try {
|
|
854
|
-
const files = fs.readdirSync(
|
|
904
|
+
const files = fs.readdirSync(todosDir).filter(f => f.endsWith('.md'));
|
|
855
905
|
for (const file of files) {
|
|
856
906
|
try {
|
|
857
|
-
const content = fs.readFileSync(path.join(
|
|
907
|
+
const content = fs.readFileSync(path.join(todosDir, file), 'utf-8');
|
|
908
|
+
const statusMatch = content.match(/^status:\s*(.+)$/m);
|
|
909
|
+
const todoStatus = statusMatch ? statusMatch[1].trim() : 'pending';
|
|
910
|
+
if (todoStatus !== 'pending') continue; // Only show pending todos
|
|
911
|
+
|
|
858
912
|
const createdMatch = content.match(/^created:\s*(.+)$/m);
|
|
859
913
|
const titleMatch = content.match(/^title:\s*(.+)$/m);
|
|
860
914
|
const areaMatch = content.match(/^area:\s*(.+)$/m);
|
|
@@ -863,19 +917,46 @@ function cmdInitTodos(cwd, area, raw, workflow) {
|
|
|
863
917
|
if (area && todoArea !== area) continue;
|
|
864
918
|
|
|
865
919
|
count++;
|
|
920
|
+
seenFiles.add(file);
|
|
866
921
|
todos.push({
|
|
867
922
|
file,
|
|
868
923
|
created: createdMatch ? createdMatch[1].trim() : 'unknown',
|
|
869
924
|
title: titleMatch ? titleMatch[1].trim() : 'Untitled',
|
|
870
925
|
area: todoArea,
|
|
871
|
-
path:
|
|
926
|
+
path: todosBase + '/' + file,
|
|
927
|
+
});
|
|
928
|
+
} catch {}
|
|
929
|
+
}
|
|
930
|
+
} catch {}
|
|
931
|
+
|
|
932
|
+
// Legacy fallback: check todos/pending/
|
|
933
|
+
const legacyPendingDir = path.join(cwd, todosBase, 'pending');
|
|
934
|
+
try {
|
|
935
|
+
const files = fs.readdirSync(legacyPendingDir).filter(f => f.endsWith('.md'));
|
|
936
|
+
for (const file of files) {
|
|
937
|
+
if (seenFiles.has(file)) continue;
|
|
938
|
+
try {
|
|
939
|
+
const content = fs.readFileSync(path.join(legacyPendingDir, file), 'utf-8');
|
|
940
|
+
const createdMatch = content.match(/^created:\s*(.+)$/m);
|
|
941
|
+
const titleMatch = content.match(/^title:\s*(.+)$/m);
|
|
942
|
+
const areaMatch = content.match(/^area:\s*(.+)$/m);
|
|
943
|
+
const todoArea = areaMatch ? areaMatch[1].trim() : 'general';
|
|
944
|
+
|
|
945
|
+
if (area && todoArea !== area) continue;
|
|
946
|
+
|
|
947
|
+
count++;
|
|
948
|
+
todos.push({
|
|
949
|
+
file,
|
|
950
|
+
created: createdMatch ? createdMatch[1].trim() : 'unknown',
|
|
951
|
+
title: titleMatch ? titleMatch[1].trim() : 'Untitled',
|
|
952
|
+
area: todoArea,
|
|
953
|
+
path: todosBase + '/pending/' + file,
|
|
872
954
|
});
|
|
873
955
|
} catch {}
|
|
874
956
|
}
|
|
875
957
|
} catch {}
|
|
876
958
|
|
|
877
959
|
const completedBase = path.join(planRootRel, 'todos', 'completed');
|
|
878
|
-
const todosBase = path.join(planRootRel, 'todos');
|
|
879
960
|
|
|
880
961
|
const result = {
|
|
881
962
|
// Config
|
|
@@ -894,14 +975,14 @@ function cmdInitTodos(cwd, area, raw, workflow) {
|
|
|
894
975
|
todos,
|
|
895
976
|
area_filter: area || null,
|
|
896
977
|
|
|
897
|
-
// Paths (project-qualified)
|
|
898
|
-
pending_dir:
|
|
978
|
+
// Paths (project-qualified) — pending_dir now points to flat todos/
|
|
979
|
+
pending_dir: todosBase,
|
|
899
980
|
completed_dir: completedBase,
|
|
900
981
|
|
|
901
982
|
// File existence
|
|
902
983
|
planning_exists: pathExistsInternal(cwd, planRootRel),
|
|
903
984
|
todos_dir_exists: pathExistsInternal(cwd, todosBase),
|
|
904
|
-
pending_dir_exists: pathExistsInternal(cwd,
|
|
985
|
+
pending_dir_exists: pathExistsInternal(cwd, todosBase),
|
|
905
986
|
|
|
906
987
|
// Author
|
|
907
988
|
author: resolveAuthorSafe(cwd),
|
|
@@ -959,9 +1040,7 @@ function cmdInitMilestoneOp(cwd, raw, workflow) {
|
|
|
959
1040
|
// Config
|
|
960
1041
|
commit_docs: config.commit_docs,
|
|
961
1042
|
base_branch: config.base_branch,
|
|
962
|
-
branching_strategy:
|
|
963
|
-
phase_branch_template: config.phase_branch_template,
|
|
964
|
-
milestone_branch_template: config.milestone_branch_template,
|
|
1043
|
+
branching_strategy: 'milestone',
|
|
965
1044
|
sync_push: config.sync_push,
|
|
966
1045
|
sync_pull: config.sync_pull,
|
|
967
1046
|
cadence_pull: cadence.pull,
|
|
@@ -1236,6 +1315,222 @@ function cmdInitProgress(cwd, raw) {
|
|
|
1236
1315
|
output(result, raw);
|
|
1237
1316
|
}
|
|
1238
1317
|
|
|
1318
|
+
// ─── cmdInitProgressAll — Product-level dashboard ────────────────────────────
|
|
1319
|
+
|
|
1320
|
+
/**
|
|
1321
|
+
* Parse the product-level STATE.md "Quick Tasks Completed" table and return
|
|
1322
|
+
* the most recent rows (up to `limit`). Returns [] if STATE.md or the table
|
|
1323
|
+
* is missing.
|
|
1324
|
+
*/
|
|
1325
|
+
function parseProductQuickTasks(planningRoot, limit) {
|
|
1326
|
+
const statePath = path.join(planningRoot, 'STATE.md');
|
|
1327
|
+
const content = safeReadFile(statePath);
|
|
1328
|
+
if (!content) return [];
|
|
1329
|
+
|
|
1330
|
+
const lines = content.split('\n');
|
|
1331
|
+
const rows = [];
|
|
1332
|
+
let inTable = false;
|
|
1333
|
+
let headerSeen = false;
|
|
1334
|
+
|
|
1335
|
+
for (const line of lines) {
|
|
1336
|
+
if (!inTable) {
|
|
1337
|
+
if (/^###\s+Quick Tasks Completed/i.test(line)) {
|
|
1338
|
+
inTable = true;
|
|
1339
|
+
}
|
|
1340
|
+
continue;
|
|
1341
|
+
}
|
|
1342
|
+
// Inside the Quick Tasks section
|
|
1343
|
+
if (!line.startsWith('|')) {
|
|
1344
|
+
if (rows.length > 0 || headerSeen) break; // end of table
|
|
1345
|
+
continue;
|
|
1346
|
+
}
|
|
1347
|
+
// It's a table row
|
|
1348
|
+
if (!headerSeen) {
|
|
1349
|
+
if (/^\|\s*#\s*\|\s*Description/i.test(line)) {
|
|
1350
|
+
headerSeen = true;
|
|
1351
|
+
}
|
|
1352
|
+
continue;
|
|
1353
|
+
}
|
|
1354
|
+
// Skip separator row
|
|
1355
|
+
if (/^\|[\s-]+\|/.test(line)) continue;
|
|
1356
|
+
// Data row
|
|
1357
|
+
const cells = line.split('|').map(c => c.trim());
|
|
1358
|
+
// cells[0] is '' before first pipe; real cells are cells[1..n-1]
|
|
1359
|
+
if (cells.length < 6) continue;
|
|
1360
|
+
rows.push({
|
|
1361
|
+
id: cells[1],
|
|
1362
|
+
description: cells[2],
|
|
1363
|
+
date: cells[3],
|
|
1364
|
+
commit: cells[4],
|
|
1365
|
+
status: cells[5],
|
|
1366
|
+
directory: cells[6] || '',
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
// Keep order as in file; take last `limit` (most recent appended last)
|
|
1371
|
+
return rows.slice(-limit);
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
/**
|
|
1375
|
+
* Parse the product-level MILESTONES.md for shipped milestone headers.
|
|
1376
|
+
* Returns at most `limit` milestones in file order (top = most recent).
|
|
1377
|
+
*/
|
|
1378
|
+
function parseProductShippedMilestones(planningRoot, limit) {
|
|
1379
|
+
const mPath = path.join(planningRoot, 'MILESTONES.md');
|
|
1380
|
+
const content = safeReadFile(mPath);
|
|
1381
|
+
if (!content) return [];
|
|
1382
|
+
|
|
1383
|
+
const regex = /^##\s+(v[\d.]+)\s+(.+?)\s+\(Shipped:\s+(\d{4}-\d{2}-\d{2})\)/gm;
|
|
1384
|
+
const entries = [];
|
|
1385
|
+
let m;
|
|
1386
|
+
while ((m = regex.exec(content)) !== null) {
|
|
1387
|
+
entries.push({ version: m[1], name: m[2].trim(), shipped_date: m[3] });
|
|
1388
|
+
if (entries.length >= limit) break;
|
|
1389
|
+
}
|
|
1390
|
+
return entries;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
/**
|
|
1394
|
+
* Parse milestone info from an arbitrary ROADMAP.md file (project-scoped).
|
|
1395
|
+
* Uses the same regexes as core.getMilestoneInfo but reads any path.
|
|
1396
|
+
* Returns { milestone_version, milestone_name } or both nulls if none found.
|
|
1397
|
+
*/
|
|
1398
|
+
function parseProjectMilestone(roadmapPath) {
|
|
1399
|
+
const roadmap = safeReadFile(roadmapPath);
|
|
1400
|
+
if (!roadmap) return { milestone_version: null, milestone_name: null };
|
|
1401
|
+
|
|
1402
|
+
// 🚧 marker: "- 🚧 **v2.1 Belgium** — Phases 24-28 (in progress)"
|
|
1403
|
+
const inProgressMatch = roadmap.match(/🚧\s*\*\*v(\d+\.\d+)\s+([^*]+)\*\*/);
|
|
1404
|
+
if (inProgressMatch) {
|
|
1405
|
+
return { milestone_version: 'v' + inProgressMatch[1], milestone_name: inProgressMatch[2].trim() };
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
// Bullet list: "- v19.0 Git Worktrees -- Phases 124-129 (in progress)"
|
|
1409
|
+
const bulletMatch = roadmap.match(/^- v(\d+\.\d+)\s+(.+?)\s+--\s+Phases\s+\S+\s+\(in progress\)/m);
|
|
1410
|
+
if (bulletMatch) {
|
|
1411
|
+
return { milestone_version: 'v' + bulletMatch[1], milestone_name: bulletMatch[2].trim() };
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
// Heading format (strip shipped <details> blocks)
|
|
1415
|
+
const cleaned = roadmap.replace(/<details>[\s\S]*?<\/details>/gi, '');
|
|
1416
|
+
const headingMatch = cleaned.match(/#{2,3}\s+v(\d+\.\d+)[:\s]+([^\n(]+)/);
|
|
1417
|
+
if (headingMatch) {
|
|
1418
|
+
return { milestone_version: 'v' + headingMatch[1], milestone_name: headingMatch[2].trim() };
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
return { milestone_version: null, milestone_name: null };
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
/**
|
|
1425
|
+
* Count .md files in a directory (non-recursive). Returns 0 on any error
|
|
1426
|
+
* (missing dir, permissions, etc).
|
|
1427
|
+
*/
|
|
1428
|
+
function countMdFiles(dir, excludePredicate) {
|
|
1429
|
+
try {
|
|
1430
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
1431
|
+
let count = 0;
|
|
1432
|
+
for (const e of entries) {
|
|
1433
|
+
if (!e.isFile()) continue;
|
|
1434
|
+
if (!e.name.endsWith('.md')) continue;
|
|
1435
|
+
if (excludePredicate && excludePredicate(e.name)) continue;
|
|
1436
|
+
count++;
|
|
1437
|
+
}
|
|
1438
|
+
return count;
|
|
1439
|
+
} catch {
|
|
1440
|
+
return 0;
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
/**
|
|
1445
|
+
* Count .md files in a flat directory that have a specific frontmatter status.
|
|
1446
|
+
* Used for ideas/todos/jobs that use frontmatter-based status instead of subdirectories.
|
|
1447
|
+
*/
|
|
1448
|
+
function countMdFilesByStatus(dir, status) {
|
|
1449
|
+
try {
|
|
1450
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
1451
|
+
let count = 0;
|
|
1452
|
+
for (const e of entries) {
|
|
1453
|
+
if (!e.isFile()) continue;
|
|
1454
|
+
if (!e.name.endsWith('.md')) continue;
|
|
1455
|
+
if (e.name === 'manifest.json') continue;
|
|
1456
|
+
try {
|
|
1457
|
+
const content = fs.readFileSync(path.join(dir, e.name), 'utf-8');
|
|
1458
|
+
const statusMatch = content.match(/^---\n[\s\S]*?^status:\s*(.+)/m);
|
|
1459
|
+
if (statusMatch && statusMatch[1].trim() === status) count++;
|
|
1460
|
+
} catch { /* skip unreadable files */ }
|
|
1461
|
+
}
|
|
1462
|
+
return count;
|
|
1463
|
+
} catch {
|
|
1464
|
+
return 0;
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
function cmdInitProgressAll(cwd, raw) {
|
|
1469
|
+
const config = loadConfig(cwd);
|
|
1470
|
+
const planningRoot = getPlanningRoot(cwd);
|
|
1471
|
+
|
|
1472
|
+
// listProjectsReadonly handles ghost projects internally via warnings
|
|
1473
|
+
const { projects: allProjects, warnings } = listProjectsReadonly(cwd);
|
|
1474
|
+
|
|
1475
|
+
const activeProjects = allProjects.filter(p => !p.status.toLowerCase().includes('completed'));
|
|
1476
|
+
const completedProjects = allProjects.filter(p => p.status.toLowerCase().includes('completed'));
|
|
1477
|
+
|
|
1478
|
+
// Enrich each active project with milestone info from its ROADMAP.md
|
|
1479
|
+
const enrichedProjects = activeProjects.map(p => {
|
|
1480
|
+
const roadmapPath = path.join(planningRoot, 'projects', p.name, 'ROADMAP.md');
|
|
1481
|
+
const { milestone_version, milestone_name } = parseProjectMilestone(roadmapPath);
|
|
1482
|
+
return {
|
|
1483
|
+
name: p.name,
|
|
1484
|
+
status: p.status,
|
|
1485
|
+
current_phase: p.current_phase,
|
|
1486
|
+
progress: p.progress,
|
|
1487
|
+
repos_touched: p.repos_touched,
|
|
1488
|
+
milestone_version,
|
|
1489
|
+
milestone_name,
|
|
1490
|
+
};
|
|
1491
|
+
});
|
|
1492
|
+
|
|
1493
|
+
// Product-level aggregates
|
|
1494
|
+
const quickTasksRecent = parseProductQuickTasks(planningRoot, 5);
|
|
1495
|
+
const shippedMilestonesRecent = parseProductShippedMilestones(planningRoot, 5);
|
|
1496
|
+
|
|
1497
|
+
const backlog = {
|
|
1498
|
+
todos: countMdFilesByStatus(path.join(planningRoot, 'todos'), 'pending'),
|
|
1499
|
+
ideas: countMdFilesByStatus(path.join(planningRoot, 'ideas'), 'pending'),
|
|
1500
|
+
specs: countMdFiles(path.join(planningRoot, 'specs')),
|
|
1501
|
+
debug_active: countMdFiles(path.join(planningRoot, 'debug'), name => name.includes('resolved')),
|
|
1502
|
+
};
|
|
1503
|
+
|
|
1504
|
+
const result = {
|
|
1505
|
+
planner_model: resolveModelInternal(cwd, 'dgs-planner'),
|
|
1506
|
+
executor_model: resolveModelInternal(cwd, 'dgs-executor'),
|
|
1507
|
+
commit_docs: config.commit_docs,
|
|
1508
|
+
sync_push: config.sync_push,
|
|
1509
|
+
sync_pull: config.sync_pull,
|
|
1510
|
+
cadence_pull: getCadence('progress').pull,
|
|
1511
|
+
cadence_push: getCadence('progress').push,
|
|
1512
|
+
|
|
1513
|
+
product: {
|
|
1514
|
+
product_name: config.product_name || null,
|
|
1515
|
+
active_count: activeProjects.length,
|
|
1516
|
+
completed_count: completedProjects.length,
|
|
1517
|
+
quick_tasks_recent: quickTasksRecent,
|
|
1518
|
+
shipped_milestones_recent: shippedMilestonesRecent,
|
|
1519
|
+
backlog,
|
|
1520
|
+
},
|
|
1521
|
+
|
|
1522
|
+
projects: enrichedProjects,
|
|
1523
|
+
warnings,
|
|
1524
|
+
|
|
1525
|
+
author: resolveAuthorSafe(cwd),
|
|
1526
|
+
dgs_mode: isV2Install(cwd) ? 'v2' : 'v1',
|
|
1527
|
+
planning_root: path.relative(cwd, planningRoot) || '.',
|
|
1528
|
+
};
|
|
1529
|
+
|
|
1530
|
+
applySyncPull(cwd, 'progress', result);
|
|
1531
|
+
output(result, raw);
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1239
1534
|
module.exports = {
|
|
1240
1535
|
cmdInitExecutePhase,
|
|
1241
1536
|
cmdInitPlanPhase,
|
|
@@ -1250,5 +1545,6 @@ module.exports = {
|
|
|
1250
1545
|
cmdInitMilestoneOp,
|
|
1251
1546
|
cmdInitMapCodebase,
|
|
1252
1547
|
cmdInitProgress,
|
|
1548
|
+
cmdInitProgressAll,
|
|
1253
1549
|
applySyncPull,
|
|
1254
1550
|
};
|