gsd-antigravity-kit 2.0.1 → 2.1.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/.agent/skills/gsd/SKILL.md +26 -4
- package/.agent/skills/gsd/VERSION +1 -1
- package/.agent/skills/gsd/assets/templates/AI-SPEC.md +246 -0
- package/.agent/skills/gsd/assets/templates/DEBUG.md +7 -2
- package/.agent/skills/gsd/assets/templates/config.json +56 -48
- package/.agent/skills/gsd/assets/templates/research.md +40 -0
- package/.agent/skills/gsd/assets/templates/spec.md +307 -0
- package/.agent/skills/gsd/assets/templates/state.md +8 -0
- package/.agent/skills/gsd/bin/gsd-tools.cjs +212 -11
- package/.agent/skills/gsd/bin/help-manifest.json +8 -2
- package/.agent/skills/gsd/bin/hooks/gsd-check-update-worker.js +108 -0
- package/.agent/skills/gsd/bin/hooks/gsd-check-update.js +14 -89
- package/.agent/skills/gsd/bin/hooks/gsd-context-monitor.js +34 -5
- package/.agent/skills/gsd/bin/hooks/gsd-phase-boundary.sh +1 -0
- package/.agent/skills/gsd/bin/hooks/gsd-prompt-guard.js +1 -1
- package/.agent/skills/gsd/bin/hooks/gsd-read-guard.js +6 -1
- package/.agent/skills/gsd/bin/hooks/gsd-session-state.sh +1 -0
- package/.agent/skills/gsd/bin/hooks/gsd-statusline.js +150 -16
- package/.agent/skills/gsd/bin/hooks/gsd-validate-commit.sh +1 -0
- package/.agent/skills/gsd/bin/hooks/gsd-workflow-guard.js +1 -1
- package/.agent/skills/gsd/bin/lib/audit.cjs +757 -0
- package/.agent/skills/gsd/bin/lib/commands.cjs +17 -7
- package/.agent/skills/gsd/bin/lib/config.cjs +66 -20
- package/.agent/skills/gsd/bin/lib/core.cjs +212 -12
- package/.agent/skills/gsd/bin/lib/frontmatter.cjs +6 -8
- package/.agent/skills/gsd/bin/lib/graphify.cjs +494 -0
- package/.agent/skills/gsd/bin/lib/gsd2-import.cjs +511 -0
- package/.agent/skills/gsd/bin/lib/init.cjs +371 -18
- package/.agent/skills/gsd/bin/lib/intel.cjs +9 -30
- package/.agent/skills/gsd/bin/lib/milestone.cjs +18 -17
- package/.agent/skills/gsd/bin/lib/model-profiles.cjs +1 -0
- package/.agent/skills/gsd/bin/lib/phase.cjs +225 -98
- package/.agent/skills/gsd/bin/lib/profile-output.cjs +17 -5
- package/.agent/skills/gsd/bin/lib/roadmap.cjs +12 -5
- package/.agent/skills/gsd/bin/lib/state.cjs +394 -129
- package/.agent/skills/gsd/bin/lib/template.cjs +8 -4
- package/.agent/skills/gsd/bin/lib/uat.cjs +2 -1
- package/.agent/skills/gsd/bin/lib/verify.cjs +111 -42
- package/.agent/skills/gsd/migration_report.md +2 -2
- package/.agent/skills/gsd/references/agents/gsd-advisor-researcher.md +23 -0
- package/.agent/skills/gsd/references/agents/gsd-ai-researcher.md +133 -0
- package/.agent/skills/gsd/references/agents/gsd-code-fixer.md +11 -10
- package/.agent/skills/gsd/references/agents/gsd-code-reviewer.md +2 -2
- package/.agent/skills/gsd/references/agents/gsd-codebase-mapper.md +13 -2
- package/.agent/skills/gsd/references/agents/gsd-debug-session-manager.md +314 -0
- package/.agent/skills/gsd/references/agents/gsd-debugger.md +147 -76
- package/.agent/skills/gsd/references/agents/gsd-doc-verifier.md +1 -1
- package/.agent/skills/gsd/references/agents/gsd-doc-writer.md +615 -602
- package/.agent/skills/gsd/references/agents/gsd-domain-researcher.md +153 -0
- package/.agent/skills/gsd/references/agents/gsd-eval-auditor.md +175 -0
- package/.agent/skills/gsd/references/agents/gsd-eval-planner.md +154 -0
- package/.agent/skills/gsd/references/agents/gsd-executor.md +108 -38
- package/.agent/skills/gsd/references/agents/gsd-framework-selector.md +160 -0
- package/.agent/skills/gsd/references/agents/gsd-integration-checker.md +454 -443
- package/.agent/skills/gsd/references/agents/gsd-intel-updater.md +40 -20
- package/.agent/skills/gsd/references/agents/gsd-nyquist-auditor.md +187 -176
- package/.agent/skills/gsd/references/agents/gsd-pattern-mapper.md +335 -0
- package/.agent/skills/gsd/references/agents/gsd-phase-researcher.md +112 -13
- package/.agent/skills/gsd/references/agents/gsd-plan-checker.md +104 -10
- package/.agent/skills/gsd/references/agents/gsd-planner.md +125 -167
- package/.agent/skills/gsd/references/agents/gsd-project-researcher.md +25 -2
- package/.agent/skills/gsd/references/agents/gsd-research-synthesizer.md +3 -3
- package/.agent/skills/gsd/references/agents/gsd-roadmapper.md +12 -1
- package/.agent/skills/gsd/references/agents/gsd-security-auditor.md +139 -128
- package/.agent/skills/gsd/references/agents/gsd-ui-auditor.md +3 -3
- package/.agent/skills/gsd/references/agents/gsd-ui-checker.md +11 -2
- package/.agent/skills/gsd/references/agents/gsd-ui-researcher.md +27 -4
- package/.agent/skills/gsd/references/agents/gsd-verifier.md +13 -19
- package/.agent/skills/gsd/references/commands/atomic/add-todo.md +2 -2
- package/.agent/skills/gsd/references/commands/atomic/check-todos.md +2 -2
- package/.agent/skills/gsd/references/commands/atomic/cleanup.md +2 -2
- package/.agent/skills/gsd/references/commands/atomic/do.md +2 -2
- package/.agent/skills/gsd/references/commands/atomic/help.md +2 -2
- package/.agent/skills/gsd/references/commands/atomic/join-discord.md +2 -2
- package/.agent/skills/gsd/references/commands/atomic/note.md +2 -2
- package/.agent/skills/gsd/references/commands/atomic/session-report.md +2 -2
- package/.agent/skills/gsd/references/commands/atomic/ship.md +2 -2
- package/.agent/skills/gsd/references/commands/atomic/stats.md +2 -2
- package/.agent/skills/gsd/references/commands/atomic/thread.md +141 -41
- package/.agent/skills/gsd/references/commands/atomic/undo.md +2 -2
- package/.agent/skills/gsd/references/commands/milestone/add-backlog.md +15 -12
- package/.agent/skills/gsd/references/commands/milestone/audit-milestone.md +2 -2
- package/.agent/skills/gsd/references/commands/milestone/complete-milestone.md +2 -2
- package/.agent/skills/gsd/references/commands/milestone/milestone-summary.md +2 -2
- package/.agent/skills/gsd/references/commands/milestone/new-milestone.md +2 -2
- package/.agent/skills/gsd/references/commands/milestone/plan-milestone-gaps.md +2 -2
- package/.agent/skills/gsd/references/commands/milestone/plant-seed.md +2 -2
- package/.agent/skills/gsd/references/commands/milestone/review-backlog.md +4 -4
- package/.agent/skills/gsd/references/commands/misc/ai-integration-phase.md +38 -0
- package/.agent/skills/gsd/references/commands/misc/audit-fix.md +2 -2
- package/.agent/skills/gsd/references/commands/misc/audit-uat.md +2 -2
- package/.agent/skills/gsd/references/commands/misc/eval-review.md +34 -0
- package/.agent/skills/gsd/references/commands/misc/extract_learnings.md +24 -0
- package/.agent/skills/gsd/references/commands/misc/from-gsd2.md +49 -0
- package/.agent/skills/gsd/references/commands/misc/graphify.md +203 -0
- package/.agent/skills/gsd/references/commands/misc/inbox.md +40 -0
- package/.agent/skills/gsd/references/commands/misc/next.md +5 -3
- package/.agent/skills/gsd/references/commands/misc/progress.md +4 -3
- package/.agent/skills/gsd/references/commands/misc/sketch-wrap-up.md +33 -0
- package/.agent/skills/gsd/references/commands/misc/sketch.md +47 -0
- package/.agent/skills/gsd/references/commands/misc/spec-phase.md +64 -0
- package/.agent/skills/gsd/references/commands/misc/spike-wrap-up.md +33 -0
- package/.agent/skills/gsd/references/commands/misc/spike.md +43 -0
- package/.agent/skills/gsd/references/commands/misc/verify-work.md +2 -2
- package/.agent/skills/gsd/references/commands/phase/add-phase.md +2 -2
- package/.agent/skills/gsd/references/commands/phase/add-tests.md +2 -2
- package/.agent/skills/gsd/references/commands/phase/discuss-phase.md +5 -5
- package/.agent/skills/gsd/references/commands/phase/execute-phase.md +4 -4
- package/.agent/skills/gsd/references/commands/phase/insert-phase.md +2 -2
- package/.agent/skills/gsd/references/commands/phase/list-phase-assumptions.md +2 -2
- package/.agent/skills/gsd/references/commands/phase/plan-phase.md +3 -3
- package/.agent/skills/gsd/references/commands/phase/remove-phase.md +2 -2
- package/.agent/skills/gsd/references/commands/phase/research-phase.md +5 -5
- package/.agent/skills/gsd/references/commands/phase/secure-phase.md +2 -2
- package/.agent/skills/gsd/references/commands/phase/ui-phase.md +2 -2
- package/.agent/skills/gsd/references/commands/phase/ui-review.md +2 -2
- package/.agent/skills/gsd/references/commands/phase/validate-phase.md +2 -2
- package/.agent/skills/gsd/references/commands/phase/workstreams.md +9 -9
- package/.agent/skills/gsd/references/commands/project/analyze-dependencies.md +2 -2
- package/.agent/skills/gsd/references/commands/project/explore.md +2 -2
- package/.agent/skills/gsd/references/commands/project/import.md +2 -2
- package/.agent/skills/gsd/references/commands/project/intel.md +10 -10
- package/.agent/skills/gsd/references/commands/project/list-workspaces.md +2 -2
- package/.agent/skills/gsd/references/commands/project/map-codebase.md +2 -2
- package/.agent/skills/gsd/references/commands/project/new-project.md +2 -2
- package/.agent/skills/gsd/references/commands/project/new-workspace.md +2 -2
- package/.agent/skills/gsd/references/commands/project/remove-workspace.md +2 -2
- package/.agent/skills/gsd/references/commands/project/scan.md +2 -2
- package/.agent/skills/gsd/references/commands/system/autonomous.md +4 -3
- package/.agent/skills/gsd/references/commands/system/code-review-fix.md +3 -3
- package/.agent/skills/gsd/references/commands/system/code-review.md +3 -3
- package/.agent/skills/gsd/references/commands/system/debug.md +177 -100
- package/.agent/skills/gsd/references/commands/system/docs-update.md +2 -2
- package/.agent/skills/gsd/references/commands/system/fast.md +2 -2
- package/.agent/skills/gsd/references/commands/system/forensics.md +2 -2
- package/.agent/skills/gsd/references/commands/system/gsd-tools.md +153 -6
- package/.agent/skills/gsd/references/commands/system/health.md +2 -2
- package/.agent/skills/gsd/references/commands/system/manager.md +3 -3
- package/.agent/skills/gsd/references/commands/system/pause-work.md +2 -2
- package/.agent/skills/gsd/references/commands/system/pr-branch.md +2 -2
- package/.agent/skills/gsd/references/commands/system/profile-user.md +2 -2
- package/.agent/skills/gsd/references/commands/system/quick.md +127 -3
- package/.agent/skills/gsd/references/commands/system/reapply-patches.md +45 -6
- package/.agent/skills/gsd/references/commands/system/resume-work.md +2 -2
- package/.agent/skills/gsd/references/commands/system/review.md +6 -4
- package/.agent/skills/gsd/references/commands/system/set-profile.md +3 -3
- package/.agent/skills/gsd/references/commands/system/settings.md +2 -2
- package/.agent/skills/gsd/references/commands/system/update.md +2 -2
- package/.agent/skills/gsd/references/docs/ai-evals.md +156 -0
- package/.agent/skills/gsd/references/docs/ai-frameworks.md +186 -0
- package/.agent/skills/gsd/references/docs/artifact-types.md +18 -0
- package/.agent/skills/gsd/references/docs/autonomous-smart-discuss.md +277 -0
- package/.agent/skills/gsd/references/docs/checkpoints.md +30 -0
- package/.agent/skills/gsd/references/docs/common-bug-patterns.md +49 -49
- package/.agent/skills/gsd/references/docs/continuation-format.md +11 -7
- package/.agent/skills/gsd/references/docs/debugger-philosophy.md +76 -0
- package/.agent/skills/gsd/references/docs/decimal-phase-calculation.md +64 -64
- package/.agent/skills/gsd/references/docs/executor-examples.md +110 -0
- package/.agent/skills/gsd/references/docs/git-integration.md +4 -4
- package/.agent/skills/gsd/references/docs/git-planning-commit.md +40 -38
- package/.agent/skills/gsd/references/docs/ios-scaffold.md +123 -0
- package/.agent/skills/gsd/references/docs/mandatory-initial-read.md +2 -0
- package/.agent/skills/gsd/references/docs/phase-argument-parsing.md +61 -61
- package/.agent/skills/gsd/references/docs/planner-antipatterns.md +89 -0
- package/.agent/skills/gsd/references/docs/planner-revision.md +87 -87
- package/.agent/skills/gsd/references/docs/planner-source-audit.md +73 -0
- package/.agent/skills/gsd/references/docs/planning-config.md +33 -8
- package/.agent/skills/gsd/references/docs/project-skills-discovery.md +19 -0
- package/.agent/skills/gsd/references/docs/sketch-interactivity.md +41 -0
- package/.agent/skills/gsd/references/docs/sketch-theme-system.md +94 -0
- package/.agent/skills/gsd/references/docs/sketch-tooling.md +45 -0
- package/.agent/skills/gsd/references/docs/sketch-variant-patterns.md +81 -0
- package/.agent/skills/gsd/references/docs/tdd.md +67 -0
- package/.agent/skills/gsd/references/docs/universal-anti-patterns.md +5 -0
- package/.agent/skills/gsd/references/docs/workstream-flag.md +11 -11
- package/.agent/skills/gsd/references/mapping.md +1 -1
- package/.agent/skills/gsd/references/workflows/add-phase.md +112 -112
- package/.agent/skills/gsd/references/workflows/add-tests.md +6 -3
- package/.agent/skills/gsd/references/workflows/add-todo.md +5 -3
- package/.agent/skills/gsd/references/workflows/ai-integration-phase.md +284 -0
- package/.agent/skills/gsd/references/workflows/audit-fix.md +157 -157
- package/.agent/skills/gsd/references/workflows/audit-milestone.md +340 -340
- package/.agent/skills/gsd/references/workflows/audit-uat.md +109 -109
- package/.agent/skills/gsd/references/workflows/autonomous.md +20 -288
- package/.agent/skills/gsd/references/workflows/check-todos.md +4 -2
- package/.agent/skills/gsd/references/workflows/cleanup.md +3 -1
- package/.agent/skills/gsd/references/workflows/code-review-fix.md +497 -497
- package/.agent/skills/gsd/references/workflows/code-review.md +515 -515
- package/.agent/skills/gsd/references/workflows/complete-milestone.md +97 -24
- package/.agent/skills/gsd/references/workflows/diagnose-issues.md +238 -238
- package/.agent/skills/gsd/references/workflows/discovery-phase.md +2 -0
- package/.agent/skills/gsd/references/workflows/discuss-phase-assumptions.md +11 -11
- package/.agent/skills/gsd/references/workflows/discuss-phase.md +143 -19
- package/.agent/skills/gsd/references/workflows/do.md +8 -2
- package/.agent/skills/gsd/references/workflows/docs-update.md +5 -3
- package/.agent/skills/gsd/references/workflows/eval-review.md +155 -0
- package/.agent/skills/gsd/references/workflows/execute-phase.md +338 -54
- package/.agent/skills/gsd/references/workflows/execute-plan.md +80 -104
- package/.agent/skills/gsd/references/workflows/explore.md +3 -1
- package/.agent/skills/gsd/references/workflows/extract_learnings.md +232 -0
- package/.agent/skills/gsd/references/workflows/forensics.md +3 -3
- package/.agent/skills/gsd/references/workflows/health.md +2 -2
- package/.agent/skills/gsd/references/workflows/help.md +59 -1
- package/.agent/skills/gsd/references/workflows/import.md +3 -1
- package/.agent/skills/gsd/references/workflows/inbox.md +387 -384
- package/.agent/skills/gsd/references/workflows/insert-phase.md +130 -130
- package/.agent/skills/gsd/references/workflows/list-workspaces.md +56 -56
- package/.agent/skills/gsd/references/workflows/manager.md +5 -3
- package/.agent/skills/gsd/references/workflows/map-codebase.md +19 -5
- package/.agent/skills/gsd/references/workflows/milestone-summary.md +6 -6
- package/.agent/skills/gsd/references/workflows/new-milestone.md +63 -9
- package/.agent/skills/gsd/references/workflows/new-project.md +126 -22
- package/.agent/skills/gsd/references/workflows/new-workspace.md +6 -4
- package/.agent/skills/gsd/references/workflows/next.md +220 -153
- package/.agent/skills/gsd/references/workflows/note.md +2 -0
- package/.agent/skills/gsd/references/workflows/pause-work.md +11 -7
- package/.agent/skills/gsd/references/workflows/plan-milestone-gaps.md +273 -273
- package/.agent/skills/gsd/references/workflows/plan-phase.md +281 -62
- package/.agent/skills/gsd/references/workflows/plant-seed.md +4 -1
- package/.agent/skills/gsd/references/workflows/pr-branch.md +41 -13
- package/.agent/skills/gsd/references/workflows/profile-user.md +15 -13
- package/.agent/skills/gsd/references/workflows/progress.md +133 -21
- package/.agent/skills/gsd/references/workflows/quick.md +67 -27
- package/.agent/skills/gsd/references/workflows/remove-phase.md +155 -155
- package/.agent/skills/gsd/references/workflows/remove-workspace.md +4 -2
- package/.agent/skills/gsd/references/workflows/research-phase.md +3 -3
- package/.agent/skills/gsd/references/workflows/resume-project.md +3 -3
- package/.agent/skills/gsd/references/workflows/review.md +71 -8
- package/.agent/skills/gsd/references/workflows/scan.md +102 -102
- package/.agent/skills/gsd/references/workflows/secure-phase.md +7 -5
- package/.agent/skills/gsd/references/workflows/settings.md +24 -7
- package/.agent/skills/gsd/references/workflows/ship.md +71 -6
- package/.agent/skills/gsd/references/workflows/sketch-wrap-up.md +283 -0
- package/.agent/skills/gsd/references/workflows/sketch.md +263 -0
- package/.agent/skills/gsd/references/workflows/spec-phase.md +262 -0
- package/.agent/skills/gsd/references/workflows/spike-wrap-up.md +273 -0
- package/.agent/skills/gsd/references/workflows/spike.md +270 -0
- package/.agent/skills/gsd/references/workflows/stats.md +60 -60
- package/.agent/skills/gsd/references/workflows/transition.md +671 -671
- package/.agent/skills/gsd/references/workflows/ui-phase.md +33 -12
- package/.agent/skills/gsd/references/workflows/ui-review.md +6 -4
- package/.agent/skills/gsd/references/workflows/undo.md +3 -1
- package/.agent/skills/gsd/references/workflows/update.md +113 -2
- package/.agent/skills/gsd/references/workflows/validate-phase.md +7 -5
- package/.agent/skills/gsd/references/workflows/verify-phase.md +93 -10
- package/.agent/skills/gsd/references/workflows/verify-work.md +50 -10
- package/.agent/skills/gsd-converter/references/mapping.md +1 -1
- package/.agent/skills/gsd-converter/scripts/convert.py +36 -17
- package/.agent/skills/gsd-converter/scripts/regression_test.py +68 -33
- package/README.md +3 -2
- package/package.json +1 -1
|
@@ -0,0 +1,757 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open Artifact Audit — Cross-type unresolved state scanner
|
|
3
|
+
*
|
|
4
|
+
* Scans all .planning/ artifact categories for items with open/unresolved state.
|
|
5
|
+
* Returns structured JSON for workflow consumption.
|
|
6
|
+
* Called by: gsd-tools.cjs audit-open
|
|
7
|
+
* Used by: /gsd-complete-milestone pre-close gate
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const { planningDir, toPosixPath } = require('./core.cjs');
|
|
15
|
+
const { extractFrontmatter } = require('./frontmatter.cjs');
|
|
16
|
+
const { requireSafePath, sanitizeForDisplay } = require('./security.cjs');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Scan .planning/debug/ for open sessions.
|
|
20
|
+
* Open = status NOT in ['resolved', 'complete'].
|
|
21
|
+
* Ignores the resolved/ subdirectory.
|
|
22
|
+
*/
|
|
23
|
+
function scanDebugSessions(planDir) {
|
|
24
|
+
const debugDir = path.join(planDir, 'debug');
|
|
25
|
+
if (!fs.existsSync(debugDir)) return [];
|
|
26
|
+
|
|
27
|
+
const results = [];
|
|
28
|
+
let files;
|
|
29
|
+
try {
|
|
30
|
+
files = fs.readdirSync(debugDir, { withFileTypes: true });
|
|
31
|
+
} catch {
|
|
32
|
+
return [{ scan_error: true }];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for (const entry of files) {
|
|
36
|
+
if (!entry.isFile()) continue;
|
|
37
|
+
if (!entry.name.endsWith('.md')) continue;
|
|
38
|
+
|
|
39
|
+
const filePath = path.join(debugDir, entry.name);
|
|
40
|
+
|
|
41
|
+
let safeFilePath;
|
|
42
|
+
try {
|
|
43
|
+
safeFilePath = requireSafePath(filePath, planDir, 'debug session file', { allowAbsolute: true });
|
|
44
|
+
} catch {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let content;
|
|
49
|
+
try {
|
|
50
|
+
content = fs.readFileSync(safeFilePath, 'utf-8');
|
|
51
|
+
} catch {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const fm = extractFrontmatter(content);
|
|
56
|
+
const status = (fm.status || 'unknown').toLowerCase();
|
|
57
|
+
if (status === 'resolved' || status === 'complete') continue;
|
|
58
|
+
|
|
59
|
+
// Extract hypothesis from "Current Focus" block if parseable
|
|
60
|
+
let hypothesis = '';
|
|
61
|
+
const focusMatch = content.match(/##\s*Current Focus[^\n]*\n([\s\S]*?)(?=\n##\s|$)/i);
|
|
62
|
+
if (focusMatch) {
|
|
63
|
+
const focusText = focusMatch[1].trim().split('\n')[0].trim();
|
|
64
|
+
hypothesis = sanitizeForDisplay(focusText.slice(0, 100));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const slug = path.basename(entry.name, '.md');
|
|
68
|
+
results.push({
|
|
69
|
+
slug: sanitizeForDisplay(slug),
|
|
70
|
+
status: sanitizeForDisplay(status),
|
|
71
|
+
updated: sanitizeForDisplay(String(fm.updated || fm.date || '')),
|
|
72
|
+
hypothesis,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return results;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Scan .planning/quick/ for incomplete tasks.
|
|
81
|
+
* Incomplete if SUMMARY.md missing or status !== 'complete'.
|
|
82
|
+
*/
|
|
83
|
+
function scanQuickTasks(planDir) {
|
|
84
|
+
const quickDir = path.join(planDir, 'quick');
|
|
85
|
+
if (!fs.existsSync(quickDir)) return [];
|
|
86
|
+
|
|
87
|
+
let entries;
|
|
88
|
+
try {
|
|
89
|
+
entries = fs.readdirSync(quickDir, { withFileTypes: true });
|
|
90
|
+
} catch {
|
|
91
|
+
return [{ scan_error: true }];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const results = [];
|
|
95
|
+
for (const entry of entries) {
|
|
96
|
+
if (!entry.isDirectory()) continue;
|
|
97
|
+
|
|
98
|
+
const dirName = entry.name;
|
|
99
|
+
const taskDir = path.join(quickDir, dirName);
|
|
100
|
+
|
|
101
|
+
let safeTaskDir;
|
|
102
|
+
try {
|
|
103
|
+
safeTaskDir = requireSafePath(taskDir, planDir, 'quick task dir', { allowAbsolute: true });
|
|
104
|
+
} catch {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const summaryPath = path.join(safeTaskDir, 'SUMMARY.md');
|
|
109
|
+
|
|
110
|
+
let status = 'missing';
|
|
111
|
+
let description = '';
|
|
112
|
+
|
|
113
|
+
if (fs.existsSync(summaryPath)) {
|
|
114
|
+
let safeSum;
|
|
115
|
+
try {
|
|
116
|
+
safeSum = requireSafePath(summaryPath, planDir, 'quick task summary', { allowAbsolute: true });
|
|
117
|
+
} catch {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
const content = fs.readFileSync(safeSum, 'utf-8');
|
|
122
|
+
const fm = extractFrontmatter(content);
|
|
123
|
+
status = (fm.status || 'unknown').toLowerCase();
|
|
124
|
+
} catch {
|
|
125
|
+
status = 'unreadable';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (status === 'complete') continue;
|
|
130
|
+
|
|
131
|
+
// Parse date and slug from directory name: YYYYMMDD-slug or YYYY-MM-DD-slug
|
|
132
|
+
let date = '';
|
|
133
|
+
let slug = sanitizeForDisplay(dirName);
|
|
134
|
+
const dateMatch = dirName.match(/^(\d{4}-?\d{2}-?\d{2})-(.+)$/);
|
|
135
|
+
if (dateMatch) {
|
|
136
|
+
date = dateMatch[1];
|
|
137
|
+
slug = sanitizeForDisplay(dateMatch[2]);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
results.push({
|
|
141
|
+
slug,
|
|
142
|
+
date,
|
|
143
|
+
status: sanitizeForDisplay(status),
|
|
144
|
+
description,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return results;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Scan .planning/threads/ for open threads.
|
|
153
|
+
* Open if status in ['open', 'in_progress', 'in progress'] (case-insensitive).
|
|
154
|
+
*/
|
|
155
|
+
function scanThreads(planDir) {
|
|
156
|
+
const threadsDir = path.join(planDir, 'threads');
|
|
157
|
+
if (!fs.existsSync(threadsDir)) return [];
|
|
158
|
+
|
|
159
|
+
let files;
|
|
160
|
+
try {
|
|
161
|
+
files = fs.readdirSync(threadsDir, { withFileTypes: true });
|
|
162
|
+
} catch {
|
|
163
|
+
return [{ scan_error: true }];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const openStatuses = new Set(['open', 'in_progress', 'in progress']);
|
|
167
|
+
const results = [];
|
|
168
|
+
|
|
169
|
+
for (const entry of files) {
|
|
170
|
+
if (!entry.isFile()) continue;
|
|
171
|
+
if (!entry.name.endsWith('.md')) continue;
|
|
172
|
+
|
|
173
|
+
const filePath = path.join(threadsDir, entry.name);
|
|
174
|
+
|
|
175
|
+
let safeFilePath;
|
|
176
|
+
try {
|
|
177
|
+
safeFilePath = requireSafePath(filePath, planDir, 'thread file', { allowAbsolute: true });
|
|
178
|
+
} catch {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
let content;
|
|
183
|
+
try {
|
|
184
|
+
content = fs.readFileSync(safeFilePath, 'utf-8');
|
|
185
|
+
} catch {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const fm = extractFrontmatter(content);
|
|
190
|
+
let status = (fm.status || '').toLowerCase().trim();
|
|
191
|
+
|
|
192
|
+
// Fall back to scanning body for ## Status: OPEN / IN PROGRESS
|
|
193
|
+
if (!status) {
|
|
194
|
+
const bodyStatusMatch = content.match(/##\s*Status:\s*(OPEN|IN PROGRESS|IN_PROGRESS)/i);
|
|
195
|
+
if (bodyStatusMatch) {
|
|
196
|
+
status = bodyStatusMatch[1].toLowerCase().replace(/ /g, '_');
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (!openStatuses.has(status)) continue;
|
|
201
|
+
|
|
202
|
+
// Extract title from # Thread: heading or frontmatter title
|
|
203
|
+
let title = sanitizeForDisplay(String(fm.title || ''));
|
|
204
|
+
if (!title) {
|
|
205
|
+
const headingMatch = content.match(/^#\s*Thread:\s*(.+)$/m);
|
|
206
|
+
if (headingMatch) {
|
|
207
|
+
title = sanitizeForDisplay(headingMatch[1].trim().slice(0, 100));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const slug = path.basename(entry.name, '.md');
|
|
212
|
+
results.push({
|
|
213
|
+
slug: sanitizeForDisplay(slug),
|
|
214
|
+
status: sanitizeForDisplay(status),
|
|
215
|
+
updated: sanitizeForDisplay(String(fm.updated || fm.date || '')),
|
|
216
|
+
title,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return results;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Scan .planning/todos/pending/ for pending todos.
|
|
225
|
+
* Returns array of { filename, priority, area, summary }.
|
|
226
|
+
* Display limited to first 5 + count of remainder.
|
|
227
|
+
*/
|
|
228
|
+
function scanTodos(planDir) {
|
|
229
|
+
const pendingDir = path.join(planDir, 'todos', 'pending');
|
|
230
|
+
if (!fs.existsSync(pendingDir)) return [];
|
|
231
|
+
|
|
232
|
+
let files;
|
|
233
|
+
try {
|
|
234
|
+
files = fs.readdirSync(pendingDir, { withFileTypes: true });
|
|
235
|
+
} catch {
|
|
236
|
+
return [{ scan_error: true }];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const mdFiles = files.filter(e => e.isFile() && e.name.endsWith('.md'));
|
|
240
|
+
const results = [];
|
|
241
|
+
|
|
242
|
+
const displayFiles = mdFiles.slice(0, 5);
|
|
243
|
+
for (const entry of displayFiles) {
|
|
244
|
+
const filePath = path.join(pendingDir, entry.name);
|
|
245
|
+
|
|
246
|
+
let safeFilePath;
|
|
247
|
+
try {
|
|
248
|
+
safeFilePath = requireSafePath(filePath, planDir, 'todo file', { allowAbsolute: true });
|
|
249
|
+
} catch {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
let content;
|
|
254
|
+
try {
|
|
255
|
+
content = fs.readFileSync(safeFilePath, 'utf-8');
|
|
256
|
+
} catch {
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const fm = extractFrontmatter(content);
|
|
261
|
+
|
|
262
|
+
// Extract first line of body after frontmatter
|
|
263
|
+
const bodyMatch = content.replace(/^---[\s\S]*?---\n?/, '');
|
|
264
|
+
const firstLine = bodyMatch.trim().split('\n')[0] || '';
|
|
265
|
+
const summary = sanitizeForDisplay(firstLine.slice(0, 100));
|
|
266
|
+
|
|
267
|
+
results.push({
|
|
268
|
+
filename: sanitizeForDisplay(entry.name),
|
|
269
|
+
priority: sanitizeForDisplay(String(fm.priority || '')),
|
|
270
|
+
area: sanitizeForDisplay(String(fm.area || '')),
|
|
271
|
+
summary,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (mdFiles.length > 5) {
|
|
276
|
+
results.push({ _remainder_count: mdFiles.length - 5 });
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return results;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Scan .planning/seeds/SEED-*.md for unimplemented seeds.
|
|
284
|
+
* Unimplemented if status in ['dormant', 'active', 'triggered'].
|
|
285
|
+
*/
|
|
286
|
+
function scanSeeds(planDir) {
|
|
287
|
+
const seedsDir = path.join(planDir, 'seeds');
|
|
288
|
+
if (!fs.existsSync(seedsDir)) return [];
|
|
289
|
+
|
|
290
|
+
let files;
|
|
291
|
+
try {
|
|
292
|
+
files = fs.readdirSync(seedsDir, { withFileTypes: true });
|
|
293
|
+
} catch {
|
|
294
|
+
return [{ scan_error: true }];
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const unimplementedStatuses = new Set(['dormant', 'active', 'triggered']);
|
|
298
|
+
const results = [];
|
|
299
|
+
|
|
300
|
+
for (const entry of files) {
|
|
301
|
+
if (!entry.isFile()) continue;
|
|
302
|
+
if (!entry.name.startsWith('SEED-') || !entry.name.endsWith('.md')) continue;
|
|
303
|
+
|
|
304
|
+
const filePath = path.join(seedsDir, entry.name);
|
|
305
|
+
|
|
306
|
+
let safeFilePath;
|
|
307
|
+
try {
|
|
308
|
+
safeFilePath = requireSafePath(filePath, planDir, 'seed file', { allowAbsolute: true });
|
|
309
|
+
} catch {
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
let content;
|
|
314
|
+
try {
|
|
315
|
+
content = fs.readFileSync(safeFilePath, 'utf-8');
|
|
316
|
+
} catch {
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const fm = extractFrontmatter(content);
|
|
321
|
+
const status = (fm.status || 'dormant').toLowerCase();
|
|
322
|
+
|
|
323
|
+
if (!unimplementedStatuses.has(status)) continue;
|
|
324
|
+
|
|
325
|
+
// Extract seed_id from filename or frontmatter
|
|
326
|
+
const seedIdMatch = entry.name.match(/^(SEED-[\w-]+)\.md$/);
|
|
327
|
+
const seed_id = seedIdMatch ? seedIdMatch[1] : path.basename(entry.name, '.md');
|
|
328
|
+
const slug = sanitizeForDisplay(seed_id.replace(/^SEED-/, ''));
|
|
329
|
+
|
|
330
|
+
let title = sanitizeForDisplay(String(fm.title || ''));
|
|
331
|
+
if (!title) {
|
|
332
|
+
const headingMatch = content.match(/^#\s*(.+)$/m);
|
|
333
|
+
if (headingMatch) title = sanitizeForDisplay(headingMatch[1].trim().slice(0, 100));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
results.push({
|
|
337
|
+
seed_id: sanitizeForDisplay(seed_id),
|
|
338
|
+
slug,
|
|
339
|
+
status: sanitizeForDisplay(status),
|
|
340
|
+
title,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return results;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Scan .planning/phases for UAT gaps (UAT files with status != 'complete').
|
|
349
|
+
*/
|
|
350
|
+
function scanUatGaps(planDir) {
|
|
351
|
+
const phasesDir = path.join(planDir, 'phases');
|
|
352
|
+
if (!fs.existsSync(phasesDir)) return [];
|
|
353
|
+
|
|
354
|
+
let dirs;
|
|
355
|
+
try {
|
|
356
|
+
dirs = fs.readdirSync(phasesDir, { withFileTypes: true })
|
|
357
|
+
.filter(e => e.isDirectory())
|
|
358
|
+
.map(e => e.name)
|
|
359
|
+
.sort();
|
|
360
|
+
} catch {
|
|
361
|
+
return [{ scan_error: true }];
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const results = [];
|
|
365
|
+
|
|
366
|
+
for (const dir of dirs) {
|
|
367
|
+
const phaseDir = path.join(phasesDir, dir);
|
|
368
|
+
const phaseMatch = dir.match(/^(\d+[A-Z]?(?:\.\d+)*)/i);
|
|
369
|
+
const phaseNum = phaseMatch ? phaseMatch[1] : dir;
|
|
370
|
+
|
|
371
|
+
let files;
|
|
372
|
+
try {
|
|
373
|
+
files = fs.readdirSync(phaseDir);
|
|
374
|
+
} catch {
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
for (const file of files.filter(f => f.includes('-UAT') && f.endsWith('.md'))) {
|
|
379
|
+
const filePath = path.join(phaseDir, file);
|
|
380
|
+
|
|
381
|
+
let safeFilePath;
|
|
382
|
+
try {
|
|
383
|
+
safeFilePath = requireSafePath(filePath, planDir, 'UAT file', { allowAbsolute: true });
|
|
384
|
+
} catch {
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
let content;
|
|
389
|
+
try {
|
|
390
|
+
content = fs.readFileSync(safeFilePath, 'utf-8');
|
|
391
|
+
} catch {
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const fm = extractFrontmatter(content);
|
|
396
|
+
const status = (fm.status || 'unknown').toLowerCase();
|
|
397
|
+
|
|
398
|
+
if (status === 'complete') continue;
|
|
399
|
+
|
|
400
|
+
// Count open scenarios
|
|
401
|
+
const pendingMatches = (content.match(/result:\s*(?:pending|\[pending\])/gi) || []).length;
|
|
402
|
+
|
|
403
|
+
results.push({
|
|
404
|
+
phase: sanitizeForDisplay(phaseNum),
|
|
405
|
+
file: sanitizeForDisplay(file),
|
|
406
|
+
status: sanitizeForDisplay(status),
|
|
407
|
+
open_scenario_count: pendingMatches,
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return results;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Scan .planning/phases for VERIFICATION gaps.
|
|
417
|
+
*/
|
|
418
|
+
function scanVerificationGaps(planDir) {
|
|
419
|
+
const phasesDir = path.join(planDir, 'phases');
|
|
420
|
+
if (!fs.existsSync(phasesDir)) return [];
|
|
421
|
+
|
|
422
|
+
let dirs;
|
|
423
|
+
try {
|
|
424
|
+
dirs = fs.readdirSync(phasesDir, { withFileTypes: true })
|
|
425
|
+
.filter(e => e.isDirectory())
|
|
426
|
+
.map(e => e.name)
|
|
427
|
+
.sort();
|
|
428
|
+
} catch {
|
|
429
|
+
return [{ scan_error: true }];
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const results = [];
|
|
433
|
+
|
|
434
|
+
for (const dir of dirs) {
|
|
435
|
+
const phaseDir = path.join(phasesDir, dir);
|
|
436
|
+
const phaseMatch = dir.match(/^(\d+[A-Z]?(?:\.\d+)*)/i);
|
|
437
|
+
const phaseNum = phaseMatch ? phaseMatch[1] : dir;
|
|
438
|
+
|
|
439
|
+
let files;
|
|
440
|
+
try {
|
|
441
|
+
files = fs.readdirSync(phaseDir);
|
|
442
|
+
} catch {
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
for (const file of files.filter(f => f.includes('-VERIFICATION') && f.endsWith('.md'))) {
|
|
447
|
+
const filePath = path.join(phaseDir, file);
|
|
448
|
+
|
|
449
|
+
let safeFilePath;
|
|
450
|
+
try {
|
|
451
|
+
safeFilePath = requireSafePath(filePath, planDir, 'VERIFICATION file', { allowAbsolute: true });
|
|
452
|
+
} catch {
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
let content;
|
|
457
|
+
try {
|
|
458
|
+
content = fs.readFileSync(safeFilePath, 'utf-8');
|
|
459
|
+
} catch {
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const fm = extractFrontmatter(content);
|
|
464
|
+
const status = (fm.status || 'unknown').toLowerCase();
|
|
465
|
+
|
|
466
|
+
if (status !== 'gaps_found' && status !== 'human_needed') continue;
|
|
467
|
+
|
|
468
|
+
results.push({
|
|
469
|
+
phase: sanitizeForDisplay(phaseNum),
|
|
470
|
+
file: sanitizeForDisplay(file),
|
|
471
|
+
status: sanitizeForDisplay(status),
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return results;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Scan .planning/phases for CONTEXT files with open_questions.
|
|
481
|
+
*/
|
|
482
|
+
function scanContextQuestions(planDir) {
|
|
483
|
+
const phasesDir = path.join(planDir, 'phases');
|
|
484
|
+
if (!fs.existsSync(phasesDir)) return [];
|
|
485
|
+
|
|
486
|
+
let dirs;
|
|
487
|
+
try {
|
|
488
|
+
dirs = fs.readdirSync(phasesDir, { withFileTypes: true })
|
|
489
|
+
.filter(e => e.isDirectory())
|
|
490
|
+
.map(e => e.name)
|
|
491
|
+
.sort();
|
|
492
|
+
} catch {
|
|
493
|
+
return [{ scan_error: true }];
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const results = [];
|
|
497
|
+
|
|
498
|
+
for (const dir of dirs) {
|
|
499
|
+
const phaseDir = path.join(phasesDir, dir);
|
|
500
|
+
const phaseMatch = dir.match(/^(\d+[A-Z]?(?:\.\d+)*)/i);
|
|
501
|
+
const phaseNum = phaseMatch ? phaseMatch[1] : dir;
|
|
502
|
+
|
|
503
|
+
let files;
|
|
504
|
+
try {
|
|
505
|
+
files = fs.readdirSync(phaseDir);
|
|
506
|
+
} catch {
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
for (const file of files.filter(f => f.includes('-CONTEXT') && f.endsWith('.md'))) {
|
|
511
|
+
const filePath = path.join(phaseDir, file);
|
|
512
|
+
|
|
513
|
+
let safeFilePath;
|
|
514
|
+
try {
|
|
515
|
+
safeFilePath = requireSafePath(filePath, planDir, 'CONTEXT file', { allowAbsolute: true });
|
|
516
|
+
} catch {
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
let content;
|
|
521
|
+
try {
|
|
522
|
+
content = fs.readFileSync(safeFilePath, 'utf-8');
|
|
523
|
+
} catch {
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const fm = extractFrontmatter(content);
|
|
528
|
+
|
|
529
|
+
// Check frontmatter open_questions field
|
|
530
|
+
let questions = [];
|
|
531
|
+
if (fm.open_questions) {
|
|
532
|
+
if (Array.isArray(fm.open_questions) && fm.open_questions.length > 0) {
|
|
533
|
+
questions = fm.open_questions.map(q => sanitizeForDisplay(String(q).slice(0, 200)));
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Also check for ## Open Questions section in body
|
|
538
|
+
if (questions.length === 0) {
|
|
539
|
+
const oqMatch = content.match(/##\s*Open Questions[^\n]*\n([\s\S]*?)(?=\n##\s|$)/i);
|
|
540
|
+
if (oqMatch) {
|
|
541
|
+
const oqBody = oqMatch[1].trim();
|
|
542
|
+
if (oqBody && oqBody.length > 0 && !/^\s*none\s*$/i.test(oqBody)) {
|
|
543
|
+
const items = oqBody.split('\n')
|
|
544
|
+
.map(l => l.trim())
|
|
545
|
+
.filter(l => l && l !== '-' && l !== '*')
|
|
546
|
+
.filter(l => /^[-*\d]/.test(l) || l.includes('?'));
|
|
547
|
+
questions = items.slice(0, 3).map(q => sanitizeForDisplay(q.slice(0, 200)));
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
if (questions.length === 0) continue;
|
|
553
|
+
|
|
554
|
+
results.push({
|
|
555
|
+
phase: sanitizeForDisplay(phaseNum),
|
|
556
|
+
file: sanitizeForDisplay(file),
|
|
557
|
+
question_count: questions.length,
|
|
558
|
+
questions: questions.slice(0, 3),
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
return results;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Main audit function. Scans all .planning/ artifact categories.
|
|
568
|
+
*
|
|
569
|
+
* @param {string} cwd - Project root directory
|
|
570
|
+
* @returns {object} Structured audit result
|
|
571
|
+
*/
|
|
572
|
+
function auditOpenArtifacts(cwd) {
|
|
573
|
+
const planDir = planningDir(cwd);
|
|
574
|
+
|
|
575
|
+
const debugSessions = (() => {
|
|
576
|
+
try { return scanDebugSessions(planDir); } catch { return [{ scan_error: true }]; }
|
|
577
|
+
})();
|
|
578
|
+
|
|
579
|
+
const quickTasks = (() => {
|
|
580
|
+
try { return scanQuickTasks(planDir); } catch { return [{ scan_error: true }]; }
|
|
581
|
+
})();
|
|
582
|
+
|
|
583
|
+
const threads = (() => {
|
|
584
|
+
try { return scanThreads(planDir); } catch { return [{ scan_error: true }]; }
|
|
585
|
+
})();
|
|
586
|
+
|
|
587
|
+
const todos = (() => {
|
|
588
|
+
try { return scanTodos(planDir); } catch { return [{ scan_error: true }]; }
|
|
589
|
+
})();
|
|
590
|
+
|
|
591
|
+
const seeds = (() => {
|
|
592
|
+
try { return scanSeeds(planDir); } catch { return [{ scan_error: true }]; }
|
|
593
|
+
})();
|
|
594
|
+
|
|
595
|
+
const uatGaps = (() => {
|
|
596
|
+
try { return scanUatGaps(planDir); } catch { return [{ scan_error: true }]; }
|
|
597
|
+
})();
|
|
598
|
+
|
|
599
|
+
const verificationGaps = (() => {
|
|
600
|
+
try { return scanVerificationGaps(planDir); } catch { return [{ scan_error: true }]; }
|
|
601
|
+
})();
|
|
602
|
+
|
|
603
|
+
const contextQuestions = (() => {
|
|
604
|
+
try { return scanContextQuestions(planDir); } catch { return [{ scan_error: true }]; }
|
|
605
|
+
})();
|
|
606
|
+
|
|
607
|
+
// Count real items (not scan_error sentinels)
|
|
608
|
+
const countReal = arr => arr.filter(i => !i.scan_error && !i._remainder_count).length;
|
|
609
|
+
|
|
610
|
+
const counts = {
|
|
611
|
+
debug_sessions: countReal(debugSessions),
|
|
612
|
+
quick_tasks: countReal(quickTasks),
|
|
613
|
+
threads: countReal(threads),
|
|
614
|
+
todos: countReal(todos),
|
|
615
|
+
seeds: countReal(seeds),
|
|
616
|
+
uat_gaps: countReal(uatGaps),
|
|
617
|
+
verification_gaps: countReal(verificationGaps),
|
|
618
|
+
context_questions: countReal(contextQuestions),
|
|
619
|
+
};
|
|
620
|
+
counts.total = Object.values(counts).reduce((s, n) => s + n, 0);
|
|
621
|
+
|
|
622
|
+
return {
|
|
623
|
+
scanned_at: new Date().toISOString(),
|
|
624
|
+
has_open_items: counts.total > 0,
|
|
625
|
+
counts,
|
|
626
|
+
items: {
|
|
627
|
+
debug_sessions: debugSessions,
|
|
628
|
+
quick_tasks: quickTasks,
|
|
629
|
+
threads,
|
|
630
|
+
todos,
|
|
631
|
+
seeds,
|
|
632
|
+
uat_gaps: uatGaps,
|
|
633
|
+
verification_gaps: verificationGaps,
|
|
634
|
+
context_questions: contextQuestions,
|
|
635
|
+
},
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Format the audit result as a human-readable report.
|
|
641
|
+
*
|
|
642
|
+
* @param {object} auditResult - Result from auditOpenArtifacts()
|
|
643
|
+
* @returns {string} Formatted report
|
|
644
|
+
*/
|
|
645
|
+
function formatAuditReport(auditResult) {
|
|
646
|
+
const { counts, items, has_open_items } = auditResult;
|
|
647
|
+
const lines = [];
|
|
648
|
+
const hr = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━';
|
|
649
|
+
|
|
650
|
+
lines.push(hr);
|
|
651
|
+
lines.push(' Milestone Close: Open Artifact Audit');
|
|
652
|
+
lines.push(hr);
|
|
653
|
+
|
|
654
|
+
if (!has_open_items) {
|
|
655
|
+
lines.push('');
|
|
656
|
+
lines.push(' All artifact types clear. Safe to proceed.');
|
|
657
|
+
lines.push('');
|
|
658
|
+
lines.push(hr);
|
|
659
|
+
return lines.join('\n');
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// Debug sessions (blocking quality — red)
|
|
663
|
+
if (counts.debug_sessions > 0) {
|
|
664
|
+
lines.push('');
|
|
665
|
+
lines.push(`🔴 Debug Sessions (${counts.debug_sessions} open)`);
|
|
666
|
+
for (const item of items.debug_sessions.filter(i => !i.scan_error)) {
|
|
667
|
+
const hyp = item.hypothesis ? ` — ${item.hypothesis}` : '';
|
|
668
|
+
lines.push(` • ${item.slug} [${item.status}]${hyp}`);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// UAT gaps (blocking quality — red)
|
|
673
|
+
if (counts.uat_gaps > 0) {
|
|
674
|
+
lines.push('');
|
|
675
|
+
lines.push(`🔴 UAT Gaps (${counts.uat_gaps} phases with incomplete UAT)`);
|
|
676
|
+
for (const item of items.uat_gaps.filter(i => !i.scan_error)) {
|
|
677
|
+
lines.push(` • Phase ${item.phase}: ${item.file} [${item.status}] — ${item.open_scenario_count} pending scenarios`);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// Verification gaps (blocking quality — red)
|
|
682
|
+
if (counts.verification_gaps > 0) {
|
|
683
|
+
lines.push('');
|
|
684
|
+
lines.push(`🔴 Verification Gaps (${counts.verification_gaps} unresolved)`);
|
|
685
|
+
for (const item of items.verification_gaps.filter(i => !i.scan_error)) {
|
|
686
|
+
lines.push(` • Phase ${item.phase}: ${item.file} [${item.status}]`);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// Quick tasks (incomplete work — yellow)
|
|
691
|
+
if (counts.quick_tasks > 0) {
|
|
692
|
+
lines.push('');
|
|
693
|
+
lines.push(`🟡 Quick Tasks (${counts.quick_tasks} incomplete)`);
|
|
694
|
+
for (const item of items.quick_tasks.filter(i => !i.scan_error)) {
|
|
695
|
+
const d = item.date ? ` (${item.date})` : '';
|
|
696
|
+
lines.push(` • ${item.slug}${d} [${item.status}]`);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// Todos (incomplete work — yellow)
|
|
701
|
+
if (counts.todos > 0) {
|
|
702
|
+
const realTodos = items.todos.filter(i => !i.scan_error && !i._remainder_count);
|
|
703
|
+
const remainder = items.todos.find(i => i._remainder_count);
|
|
704
|
+
lines.push('');
|
|
705
|
+
lines.push(`🟡 Pending Todos (${counts.todos} pending)`);
|
|
706
|
+
for (const item of realTodos) {
|
|
707
|
+
const area = item.area ? ` [${item.area}]` : '';
|
|
708
|
+
const pri = item.priority ? ` (${item.priority})` : '';
|
|
709
|
+
lines.push(` • ${item.filename}${area}${pri}`);
|
|
710
|
+
if (item.summary) lines.push(` ${item.summary}`);
|
|
711
|
+
}
|
|
712
|
+
if (remainder) {
|
|
713
|
+
lines.push(` ... and ${remainder._remainder_count} more`);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Threads (deferred decisions — blue)
|
|
718
|
+
if (counts.threads > 0) {
|
|
719
|
+
lines.push('');
|
|
720
|
+
lines.push(`🔵 Open Threads (${counts.threads} active)`);
|
|
721
|
+
for (const item of items.threads.filter(i => !i.scan_error)) {
|
|
722
|
+
const title = item.title ? ` — ${item.title}` : '';
|
|
723
|
+
lines.push(` • ${item.slug} [${item.status}]${title}`);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Seeds (deferred decisions — blue)
|
|
728
|
+
if (counts.seeds > 0) {
|
|
729
|
+
lines.push('');
|
|
730
|
+
lines.push(`🔵 Unimplemented Seeds (${counts.seeds} pending)`);
|
|
731
|
+
for (const item of items.seeds.filter(i => !i.scan_error)) {
|
|
732
|
+
const title = item.title ? ` — ${item.title}` : '';
|
|
733
|
+
lines.push(` • ${item.seed_id} [${item.status}]${title}`);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// Context questions (deferred decisions — blue)
|
|
738
|
+
if (counts.context_questions > 0) {
|
|
739
|
+
lines.push('');
|
|
740
|
+
lines.push(`🔵 CONTEXT Open Questions (${counts.context_questions} phases with open questions)`);
|
|
741
|
+
for (const item of items.context_questions.filter(i => !i.scan_error)) {
|
|
742
|
+
lines.push(` • Phase ${item.phase}: ${item.file} (${item.question_count} question${item.question_count !== 1 ? 's' : ''})`);
|
|
743
|
+
for (const q of item.questions) {
|
|
744
|
+
lines.push(` - ${q}`);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
lines.push('');
|
|
750
|
+
lines.push(hr);
|
|
751
|
+
lines.push(` ${counts.total} item${counts.total !== 1 ? 's' : ''} require decisions before close.`);
|
|
752
|
+
lines.push(hr);
|
|
753
|
+
|
|
754
|
+
return lines.join('\n');
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
module.exports = { auditOpenArtifacts, formatAuditReport };
|