@karthikrajkumar.kannan/get-things-done 1.0.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/LICENSE +21 -0
- package/README.md +237 -0
- package/agents/backward/gtd-accuracy-verifier.md +198 -0
- package/agents/backward/gtd-api-doc-writer.md +130 -0
- package/agents/backward/gtd-api-extractor.md +128 -0
- package/agents/backward/gtd-architecture-analyzer.md +144 -0
- package/agents/backward/gtd-capacity-writer.md +123 -0
- package/agents/backward/gtd-codebase-mapper.md +274 -0
- package/agents/backward/gtd-completeness-auditor.md +129 -0
- package/agents/backward/gtd-data-flow-tracer.md +104 -0
- package/agents/backward/gtd-dependency-analyzer.md +98 -0
- package/agents/backward/gtd-diagram-generator.md +152 -0
- package/agents/backward/gtd-hld-writer.md +123 -0
- package/agents/backward/gtd-lld-writer.md +126 -0
- package/agents/backward/gtd-pattern-detector.md +111 -0
- package/agents/backward/gtd-performance-profiler.md +93 -0
- package/agents/backward/gtd-runbook-writer.md +126 -0
- package/agents/backward/gtd-security-scanner.md +106 -0
- package/agents/backward/gtd-sysdesign-writer.md +137 -0
- package/agents/backward/gtd-tdd-writer.md +125 -0
- package/agents/forward/gtd-code-reviewer.md +130 -0
- package/agents/forward/gtd-debugger.md +133 -0
- package/agents/forward/gtd-deployer.md +110 -0
- package/agents/forward/gtd-executor.md +110 -0
- package/agents/forward/gtd-phase-researcher.md +114 -0
- package/agents/forward/gtd-plan-checker.md +132 -0
- package/agents/forward/gtd-planner.md +136 -0
- package/agents/forward/gtd-project-researcher.md +106 -0
- package/agents/forward/gtd-research-synthesizer.md +99 -0
- package/agents/forward/gtd-roadmapper.md +126 -0
- package/agents/forward/gtd-test-runner.md +119 -0
- package/agents/forward/gtd-verifier.md +115 -0
- package/agents/sync/gtd-alignment-auditor.md +222 -0
- package/agents/sync/gtd-drift-detector.md +222 -0
- package/agents/sync/gtd-reconciliation-planner.md +194 -0
- package/bin/gtd-tools.cjs +89 -0
- package/bin/install.js +164 -0
- package/commands/gtd/backward/analyze.md +42 -0
- package/commands/gtd/backward/create-all.md +32 -0
- package/commands/gtd/backward/create-api-docs.md +33 -0
- package/commands/gtd/backward/create-capacity.md +33 -0
- package/commands/gtd/backward/create-hld.md +33 -0
- package/commands/gtd/backward/create-lld.md +33 -0
- package/commands/gtd/backward/create-runbook.md +33 -0
- package/commands/gtd/backward/create-sysdesign.md +33 -0
- package/commands/gtd/backward/create-tdd.md +33 -0
- package/commands/gtd/backward/diff.md +22 -0
- package/commands/gtd/backward/doc-status.md +24 -0
- package/commands/gtd/backward/review-docs.md +22 -0
- package/commands/gtd/backward/scan.md +32 -0
- package/commands/gtd/backward/update-docs.md +30 -0
- package/commands/gtd/backward/verify-docs.md +28 -0
- package/commands/gtd/forward/add-phase.md +28 -0
- package/commands/gtd/forward/autonomous.md +28 -0
- package/commands/gtd/forward/code-review.md +28 -0
- package/commands/gtd/forward/complete-milestone.md +28 -0
- package/commands/gtd/forward/debug.md +28 -0
- package/commands/gtd/forward/discuss-phase.md +29 -0
- package/commands/gtd/forward/execute-phase.md +28 -0
- package/commands/gtd/forward/fast.md +28 -0
- package/commands/gtd/forward/new-milestone.md +28 -0
- package/commands/gtd/forward/new-project.md +29 -0
- package/commands/gtd/forward/next.md +28 -0
- package/commands/gtd/forward/plan-phase.md +29 -0
- package/commands/gtd/forward/progress.md +28 -0
- package/commands/gtd/forward/quick.md +28 -0
- package/commands/gtd/forward/ship.md +28 -0
- package/commands/gtd/forward/verify-work.md +28 -0
- package/commands/gtd/sync/audit.md +27 -0
- package/commands/gtd/sync/drift.md +27 -0
- package/commands/gtd/sync/reconcile.md +27 -0
- package/commands/gtd/sync/sync.md +27 -0
- package/commands/gtd/utility/health.md +53 -0
- package/commands/gtd/utility/help.md +61 -0
- package/commands/gtd/utility/map-codebase.md +27 -0
- package/commands/gtd/utility/settings.md +65 -0
- package/commands/gtd/utility/status.md +57 -0
- package/contexts/analysis.md +26 -0
- package/contexts/execution.md +35 -0
- package/contexts/planning.md +33 -0
- package/contexts/research.md +26 -0
- package/contexts/review.md +27 -0
- package/contexts/writing.md +29 -0
- package/hooks/gtd-check-update.js +37 -0
- package/hooks/gtd-context-monitor.js +32 -0
- package/hooks/gtd-prompt-guard.js +35 -0
- package/hooks/gtd-statusline.js +32 -0
- package/lib/agent-skills.cjs +130 -0
- package/lib/analysis.cjs +242 -0
- package/lib/config.cjs +255 -0
- package/lib/deploy.cjs +222 -0
- package/lib/diff-engine.cjs +245 -0
- package/lib/docs.cjs +243 -0
- package/lib/drift-engine.cjs +202 -0
- package/lib/file-ops.cjs +106 -0
- package/lib/frontmatter.cjs +100 -0
- package/lib/git.cjs +137 -0
- package/lib/init.cjs +370 -0
- package/lib/installer-core.cjs +197 -0
- package/lib/installers/augment.cjs +62 -0
- package/lib/installers/claude.cjs +89 -0
- package/lib/installers/cline.cjs +96 -0
- package/lib/installers/codex.cjs +63 -0
- package/lib/installers/copilot.cjs +62 -0
- package/lib/installers/cursor.cjs +62 -0
- package/lib/installers/gemini.cjs +62 -0
- package/lib/installers/opencode.cjs +62 -0
- package/lib/installers/windsurf.cjs +62 -0
- package/lib/phase.cjs +206 -0
- package/lib/roadmap.cjs +156 -0
- package/lib/scale-adapter.cjs +192 -0
- package/lib/security.cjs +243 -0
- package/lib/state.cjs +320 -0
- package/lib/template.cjs +218 -0
- package/lib/test-runner.cjs +202 -0
- package/package.json +76 -0
- package/references/agent-contracts.md +157 -0
- package/references/analysis-patterns.md +138 -0
- package/references/context-budget.md +148 -0
- package/references/diagram-conventions.md +88 -0
- package/references/document-standards.md +60 -0
- package/references/framework-signatures.md +609 -0
- package/references/gate-prompts.md +239 -0
- package/references/language-analyzers.md +227 -0
- package/references/planning-config.md +125 -0
- package/references/questioning.md +142 -0
- package/references/verification-patterns.md +67 -0
- package/templates/backward/api-docs/standard.md +42 -0
- package/templates/backward/capacity/standard.md +50 -0
- package/templates/backward/formats/compliance-guide.md +45 -0
- package/templates/backward/hld/standard.md +62 -0
- package/templates/backward/lld/standard.md +63 -0
- package/templates/backward/runbook/standard.md +50 -0
- package/templates/backward/system-design/standard.md +64 -0
- package/templates/backward/tdd/compliance.md +146 -0
- package/templates/backward/tdd/enterprise.md +134 -0
- package/templates/backward/tdd/standard.md +88 -0
- package/templates/backward/tdd/startup.md +51 -0
- package/templates/forward/context.md +65 -0
- package/templates/forward/phase-prompt.md +109 -0
- package/templates/forward/project.md +71 -0
- package/templates/forward/requirements.md +74 -0
- package/templates/forward/research/ARCHITECTURE.md +118 -0
- package/templates/forward/research/FEATURES.md +95 -0
- package/templates/forward/research/PITFALLS.md +106 -0
- package/templates/forward/research/STACK.md +80 -0
- package/templates/forward/research/SUMMARY.md +86 -0
- package/templates/forward/roadmap.md +72 -0
- package/workflows/backward/analyze-codebase.md +123 -0
- package/workflows/backward/create-all.md +53 -0
- package/workflows/backward/generate-document.md +182 -0
- package/workflows/backward/incremental-update.md +71 -0
- package/workflows/backward/review-document.md +102 -0
- package/workflows/backward/scan-codebase.md +111 -0
- package/workflows/backward/verify-document.md +79 -0
- package/workflows/forward/add-phase.md +29 -0
- package/workflows/forward/autonomous.md +62 -0
- package/workflows/forward/code-review.md +78 -0
- package/workflows/forward/complete-milestone.md +45 -0
- package/workflows/forward/debug.md +78 -0
- package/workflows/forward/deploy-local.md +51 -0
- package/workflows/forward/discuss-phase.md +89 -0
- package/workflows/forward/execute-phase.md +138 -0
- package/workflows/forward/fast.md +64 -0
- package/workflows/forward/new-milestone.md +61 -0
- package/workflows/forward/new-project.md +126 -0
- package/workflows/forward/next.md +49 -0
- package/workflows/forward/plan-phase.md +100 -0
- package/workflows/forward/progress.md +37 -0
- package/workflows/forward/quick.md +65 -0
- package/workflows/forward/ship.md +40 -0
- package/workflows/forward/test-phase.md +47 -0
- package/workflows/forward/verify-work.md +52 -0
- package/workflows/sync/audit.md +110 -0
- package/workflows/sync/detect-drift.md +122 -0
- package/workflows/sync/reconcile.md +113 -0
- package/workflows/sync/sync.md +150 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GTD Diff Engine — Change detection, impact mapping, section-level targeting.
|
|
3
|
+
*
|
|
4
|
+
* Maps code changes to affected document sections for incremental updates.
|
|
5
|
+
*
|
|
6
|
+
* @module lib/diff-engine
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const { fileExists } = require('./file-ops.cjs');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* File change → affected analysis dimension mapping.
|
|
17
|
+
* Pattern keys are matched against changed file paths.
|
|
18
|
+
*/
|
|
19
|
+
const IMPACT_MAP = [
|
|
20
|
+
{ pattern: /src\/.*\.(ts|js|py|go|rs|java|rb)$/, dimensions: ['architecture', 'data-flow', 'performance'] },
|
|
21
|
+
{ pattern: /route|controller|handler|endpoint/i, dimensions: ['api'] },
|
|
22
|
+
{ pattern: /model|entity|schema/i, dimensions: ['data-flow', 'architecture'] },
|
|
23
|
+
{ pattern: /auth|security|permission|guard/i, dimensions: ['security'] },
|
|
24
|
+
{ pattern: /middleware|interceptor/i, dimensions: ['security', 'data-flow'] },
|
|
25
|
+
{ pattern: /test|spec|__tests__/i, dimensions: ['architecture'] },
|
|
26
|
+
{ pattern: /package\.json|go\.mod|Cargo\.toml|requirements\.txt|pyproject\.toml/, dimensions: ['dependencies'] },
|
|
27
|
+
{ pattern: /Dockerfile|docker-compose|compose\./i, dimensions: ['architecture', 'performance'] },
|
|
28
|
+
{ pattern: /k8s|kubernetes|helm|chart/i, dimensions: ['architecture', 'performance'] },
|
|
29
|
+
{ pattern: /\.tf$|terraform/i, dimensions: ['architecture'] },
|
|
30
|
+
{ pattern: /\.github\/workflows|\.gitlab-ci|Jenkinsfile/i, dimensions: ['architecture'] },
|
|
31
|
+
{ pattern: /prisma|migration|\.sql$/i, dimensions: ['data-flow', 'dependencies'] },
|
|
32
|
+
{ pattern: /\.env|config\./i, dimensions: ['security'] },
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Analysis dimension → document section mapping.
|
|
37
|
+
*/
|
|
38
|
+
const DIMENSION_TO_SECTION = {
|
|
39
|
+
tdd: {
|
|
40
|
+
architecture: ['architecture-overview', 'component-design', 'deployment'],
|
|
41
|
+
api: ['api-design'],
|
|
42
|
+
'data-flow': ['data-model', 'component-design'],
|
|
43
|
+
dependencies: ['dependencies', 'deployment'],
|
|
44
|
+
security: ['security-design'],
|
|
45
|
+
performance: ['performance'],
|
|
46
|
+
},
|
|
47
|
+
hld: {
|
|
48
|
+
architecture: ['architecture', 'subsystems', 'deployment'],
|
|
49
|
+
api: ['integration-points'],
|
|
50
|
+
'data-flow': ['data-flow', 'subsystems'],
|
|
51
|
+
dependencies: ['deployment', 'cross-cutting'],
|
|
52
|
+
security: ['cross-cutting'],
|
|
53
|
+
performance: ['cross-cutting'],
|
|
54
|
+
},
|
|
55
|
+
lld: {
|
|
56
|
+
architecture: ['module-overview', 'module-specs'],
|
|
57
|
+
api: ['api-specs', 'endpoint-details'],
|
|
58
|
+
'data-flow': ['data-structures', 'query-patterns'],
|
|
59
|
+
dependencies: ['dependency-graph', 'configuration'],
|
|
60
|
+
security: ['error-handling'],
|
|
61
|
+
performance: ['algorithms'],
|
|
62
|
+
},
|
|
63
|
+
'api-docs': {
|
|
64
|
+
api: ['*'], // Full regeneration for API docs
|
|
65
|
+
security: ['authentication'],
|
|
66
|
+
},
|
|
67
|
+
'system-design': {
|
|
68
|
+
architecture: ['architecture', 'components', 'deployment'],
|
|
69
|
+
api: ['api-design'],
|
|
70
|
+
'data-flow': ['data-architecture', 'pipeline'],
|
|
71
|
+
dependencies: ['deployment'],
|
|
72
|
+
security: ['security'],
|
|
73
|
+
performance: ['reliability', 'observability'],
|
|
74
|
+
},
|
|
75
|
+
capacity: {
|
|
76
|
+
dependencies: ['resource-requirements', 'infrastructure'],
|
|
77
|
+
performance: ['performance', 'scaling', 'bottlenecks'],
|
|
78
|
+
architecture: ['system-profile'],
|
|
79
|
+
},
|
|
80
|
+
runbook: {
|
|
81
|
+
architecture: ['service-overview', 'deployment'],
|
|
82
|
+
security: ['access', 'incident-response'],
|
|
83
|
+
dependencies: ['configuration', 'troubleshooting'],
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Classify a changed file by type.
|
|
89
|
+
* @param {string} filePath - Relative file path
|
|
90
|
+
* @returns {string} File type: source, config, infra, test, docs, other
|
|
91
|
+
*/
|
|
92
|
+
function classifyFile(filePath) {
|
|
93
|
+
if (/test|spec|__tests__/i.test(filePath)) return 'test';
|
|
94
|
+
if (/\.md$/i.test(filePath)) return 'docs';
|
|
95
|
+
if (/Dockerfile|docker-compose|\.tf$|k8s|helm|\.github/i.test(filePath)) return 'infra';
|
|
96
|
+
if (/package\.json|go\.mod|Cargo\.toml|\.env|config\./i.test(filePath)) return 'config';
|
|
97
|
+
if (/\.(ts|js|py|go|rs|java|rb|cs|php|swift)$/i.test(filePath)) return 'source';
|
|
98
|
+
return 'other';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Map changed files to affected analysis dimensions.
|
|
103
|
+
* @param {string[]} changedFiles - List of changed file paths
|
|
104
|
+
* @returns {string[]} Unique affected dimension names
|
|
105
|
+
*/
|
|
106
|
+
function mapToDimensions(changedFiles) {
|
|
107
|
+
const dimensions = new Set();
|
|
108
|
+
for (const file of changedFiles) {
|
|
109
|
+
for (const rule of IMPACT_MAP) {
|
|
110
|
+
if (rule.pattern.test(file)) {
|
|
111
|
+
for (const dim of rule.dimensions) dimensions.add(dim);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return Array.from(dimensions);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Map affected dimensions to document sections.
|
|
120
|
+
* @param {string[]} dimensions - Affected dimensions
|
|
121
|
+
* @param {string} docType - Document type
|
|
122
|
+
* @returns {string[]} Affected section names
|
|
123
|
+
*/
|
|
124
|
+
function mapToSections(dimensions, docType) {
|
|
125
|
+
const mapping = DIMENSION_TO_SECTION[docType];
|
|
126
|
+
if (!mapping) return ['*']; // Unknown doc type → regenerate all
|
|
127
|
+
|
|
128
|
+
const sections = new Set();
|
|
129
|
+
for (const dim of dimensions) {
|
|
130
|
+
const secs = mapping[dim] || [];
|
|
131
|
+
for (const s of secs) sections.add(s);
|
|
132
|
+
}
|
|
133
|
+
return Array.from(sections);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Generate a full impact report for changed files.
|
|
138
|
+
* @param {string[]} changedFiles - List of changed file paths
|
|
139
|
+
* @returns {{ changedFiles: number, classification: object, dimensions: string[], documents: Array }}
|
|
140
|
+
*/
|
|
141
|
+
function generateImpactReport(changedFiles) {
|
|
142
|
+
// Classify changes
|
|
143
|
+
const classification = { source: 0, config: 0, infra: 0, test: 0, docs: 0, other: 0 };
|
|
144
|
+
for (const file of changedFiles) {
|
|
145
|
+
const type = classifyFile(file);
|
|
146
|
+
classification[type]++;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Map to dimensions
|
|
150
|
+
const dimensions = mapToDimensions(changedFiles);
|
|
151
|
+
|
|
152
|
+
// Map to document sections
|
|
153
|
+
const docTypes = Object.keys(DIMENSION_TO_SECTION);
|
|
154
|
+
const documents = docTypes.map((docType) => {
|
|
155
|
+
const sections = mapToSections(dimensions, docType);
|
|
156
|
+
const impact = sections.includes('*') ? 'critical' : sections.length > 3 ? 'high' : sections.length > 0 ? 'medium' : 'none';
|
|
157
|
+
return { type: docType, sections, impact };
|
|
158
|
+
}).filter((d) => d.impact !== 'none');
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
changedFiles: changedFiles.length,
|
|
162
|
+
classification,
|
|
163
|
+
dimensions,
|
|
164
|
+
documents,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Extract section boundaries from a Markdown document.
|
|
170
|
+
* @param {string} content - Markdown content
|
|
171
|
+
* @returns {Array<{header: string, level: number, start: number, end: number}>}
|
|
172
|
+
*/
|
|
173
|
+
function extractSections(content) {
|
|
174
|
+
const lines = content.split('\n');
|
|
175
|
+
const sections = [];
|
|
176
|
+
let currentSection = null;
|
|
177
|
+
|
|
178
|
+
for (let i = 0; i < lines.length; i++) {
|
|
179
|
+
const match = lines[i].match(/^(#{1,3})\s+(.+)/);
|
|
180
|
+
if (match) {
|
|
181
|
+
if (currentSection) currentSection.end = i - 1;
|
|
182
|
+
currentSection = {
|
|
183
|
+
header: match[2].trim(),
|
|
184
|
+
level: match[1].length,
|
|
185
|
+
start: i,
|
|
186
|
+
end: lines.length - 1,
|
|
187
|
+
};
|
|
188
|
+
sections.push(currentSection);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return sections;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Replace a section in a document with new content.
|
|
197
|
+
* @param {string} content - Full document content
|
|
198
|
+
* @param {string} sectionHeader - Header text to find
|
|
199
|
+
* @param {string} newSectionContent - New content for the section
|
|
200
|
+
* @returns {string} Updated document
|
|
201
|
+
*/
|
|
202
|
+
function replaceSection(content, sectionHeader, newSectionContent) {
|
|
203
|
+
const sections = extractSections(content);
|
|
204
|
+
const target = sections.find((s) =>
|
|
205
|
+
s.header.toLowerCase().includes(sectionHeader.toLowerCase()),
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
if (!target) return content; // Section not found — return unchanged
|
|
209
|
+
|
|
210
|
+
const lines = content.split('\n');
|
|
211
|
+
const nextSection = sections.find((s) => s.start > target.start && s.level <= target.level);
|
|
212
|
+
const endLine = nextSection ? nextSection.start - 1 : lines.length;
|
|
213
|
+
|
|
214
|
+
const before = lines.slice(0, target.start);
|
|
215
|
+
const after = lines.slice(endLine);
|
|
216
|
+
return [...before, newSectionContent, ...after].join('\n');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// CLI handler
|
|
220
|
+
function run(args) {
|
|
221
|
+
const subcommand = args[0] || 'help';
|
|
222
|
+
|
|
223
|
+
if (subcommand === 'classify' && args[1]) {
|
|
224
|
+
process.stdout.write(JSON.stringify(classifyFile(args[1])));
|
|
225
|
+
} else if (subcommand === 'impact') {
|
|
226
|
+
// Read file list from stdin or args
|
|
227
|
+
const files = args.slice(1);
|
|
228
|
+
process.stdout.write(JSON.stringify(generateImpactReport(files), null, 2));
|
|
229
|
+
} else {
|
|
230
|
+
process.stderr.write('Usage: gtd-tools.cjs diff-engine <classify|impact> [files...]\n');
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
module.exports = {
|
|
236
|
+
IMPACT_MAP,
|
|
237
|
+
DIMENSION_TO_SECTION,
|
|
238
|
+
classifyFile,
|
|
239
|
+
mapToDimensions,
|
|
240
|
+
mapToSections,
|
|
241
|
+
generateImpactReport,
|
|
242
|
+
extractSections,
|
|
243
|
+
replaceSection,
|
|
244
|
+
run,
|
|
245
|
+
};
|
package/lib/docs.cjs
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GTD Document Management — List, status, version, finalize documents.
|
|
3
|
+
*
|
|
4
|
+
* Manages the document lifecycle: draft → review → finalized
|
|
5
|
+
* Tracks versions in history/ directory.
|
|
6
|
+
*
|
|
7
|
+
* @module lib/docs
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const { atomicWrite, ensureDir, fileExists } = require('./file-ops.cjs');
|
|
15
|
+
const { parseFrontmatter } = require('./frontmatter.cjs');
|
|
16
|
+
const { updateDocumentStatus } = require('./state.cjs');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* All recognized document types.
|
|
20
|
+
*/
|
|
21
|
+
const DOCUMENT_TYPES = [
|
|
22
|
+
'tdd',
|
|
23
|
+
'hld',
|
|
24
|
+
'lld',
|
|
25
|
+
'capacity',
|
|
26
|
+
'system-design',
|
|
27
|
+
'api-docs',
|
|
28
|
+
'runbook',
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Mapping of document type to output filename.
|
|
33
|
+
*/
|
|
34
|
+
const DOC_FILENAMES = {
|
|
35
|
+
tdd: 'TDD',
|
|
36
|
+
hld: 'HLD',
|
|
37
|
+
lld: 'LLD',
|
|
38
|
+
capacity: 'CAPACITY-PLAN',
|
|
39
|
+
'system-design': 'SYSTEM-DESIGN',
|
|
40
|
+
'api-docs': 'API-DOCS',
|
|
41
|
+
runbook: 'RUNBOOK',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get the file path for a document.
|
|
46
|
+
*
|
|
47
|
+
* @param {string} docsRoot - .planning/ directory
|
|
48
|
+
* @param {string} docType - Document type
|
|
49
|
+
* @param {boolean} [draft=false] - If true, return draft path
|
|
50
|
+
* @returns {string} Absolute file path
|
|
51
|
+
*/
|
|
52
|
+
function getDocumentPath(docsRoot, docType, draft = false) {
|
|
53
|
+
const filename = DOC_FILENAMES[docType] || docType.toUpperCase();
|
|
54
|
+
if (draft) {
|
|
55
|
+
return path.join(docsRoot, 'drafts', `${filename}-DRAFT.md`);
|
|
56
|
+
}
|
|
57
|
+
return path.join(docsRoot, 'documents', `${filename}.md`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* List all documents with their current status.
|
|
62
|
+
*
|
|
63
|
+
* @param {string} docsRoot - .planning/ directory
|
|
64
|
+
* @returns {Array<{type: string, status: string, version: string|null, path: string|null, draftPath: string|null}>}
|
|
65
|
+
*/
|
|
66
|
+
function listDocuments(docsRoot) {
|
|
67
|
+
return DOCUMENT_TYPES.map((type) => {
|
|
68
|
+
const finalPath = getDocumentPath(docsRoot, type, false);
|
|
69
|
+
const draftPath = getDocumentPath(docsRoot, type, true);
|
|
70
|
+
const hasFinal = fileExists(finalPath);
|
|
71
|
+
const hasDraft = fileExists(draftPath);
|
|
72
|
+
|
|
73
|
+
let status = 'pending';
|
|
74
|
+
let version = null;
|
|
75
|
+
let commit = null;
|
|
76
|
+
|
|
77
|
+
if (hasFinal) {
|
|
78
|
+
const content = fs.readFileSync(finalPath, 'utf8');
|
|
79
|
+
const { frontmatter } = parseFrontmatter(content);
|
|
80
|
+
version = frontmatter.version || '1.0';
|
|
81
|
+
commit = frontmatter.commit || null;
|
|
82
|
+
status = 'finalized';
|
|
83
|
+
} else if (hasDraft) {
|
|
84
|
+
status = 'drafting';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
type,
|
|
89
|
+
status,
|
|
90
|
+
version,
|
|
91
|
+
commit,
|
|
92
|
+
finalPath: hasFinal ? finalPath : null,
|
|
93
|
+
draftPath: hasDraft ? draftPath : null,
|
|
94
|
+
filename: DOC_FILENAMES[type],
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Move a draft document to its final location.
|
|
101
|
+
*
|
|
102
|
+
* @param {string} docsRoot - .planning/ directory
|
|
103
|
+
* @param {string} docType - Document type
|
|
104
|
+
* @returns {{ success: boolean, finalPath: string }}
|
|
105
|
+
*/
|
|
106
|
+
function finalize(docsRoot, docType) {
|
|
107
|
+
const draftPath = getDocumentPath(docsRoot, docType, true);
|
|
108
|
+
const finalPath = getDocumentPath(docsRoot, docType, false);
|
|
109
|
+
|
|
110
|
+
if (!fileExists(draftPath)) {
|
|
111
|
+
throw new Error(`No draft found for ${docType} at ${draftPath}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
ensureDir(path.dirname(finalPath));
|
|
115
|
+
const content = fs.readFileSync(draftPath, 'utf8');
|
|
116
|
+
atomicWrite(finalPath, content);
|
|
117
|
+
|
|
118
|
+
// Remove draft
|
|
119
|
+
try {
|
|
120
|
+
fs.unlinkSync(draftPath);
|
|
121
|
+
} catch (_) {}
|
|
122
|
+
|
|
123
|
+
// Update state
|
|
124
|
+
updateDocumentStatus(docsRoot, docType, {
|
|
125
|
+
status: 'finalized',
|
|
126
|
+
version: extractVersion(content) || '1.0',
|
|
127
|
+
commit: extractCommit(content),
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return { success: true, finalPath };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Archive the current document version to history/ before overwriting.
|
|
135
|
+
*
|
|
136
|
+
* @param {string} docsRoot - .planning/ directory
|
|
137
|
+
* @param {string} docType - Document type
|
|
138
|
+
* @returns {{ archived: boolean, archivePath: string|null }}
|
|
139
|
+
*/
|
|
140
|
+
function archiveVersion(docsRoot, docType) {
|
|
141
|
+
const finalPath = getDocumentPath(docsRoot, docType, false);
|
|
142
|
+
|
|
143
|
+
if (!fileExists(finalPath)) {
|
|
144
|
+
return { archived: false, archivePath: null };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const content = fs.readFileSync(finalPath, 'utf8');
|
|
148
|
+
const { frontmatter } = parseFrontmatter(content);
|
|
149
|
+
const version = frontmatter.version || '1.0';
|
|
150
|
+
const commit = frontmatter.commit || 'unknown';
|
|
151
|
+
const date = new Date().toISOString().split('T')[0];
|
|
152
|
+
|
|
153
|
+
const historyDir = path.join(docsRoot, 'history', DOC_FILENAMES[docType] || docType.toUpperCase());
|
|
154
|
+
ensureDir(historyDir);
|
|
155
|
+
|
|
156
|
+
const archiveName = `v${version}_${commit}_${date}.md`;
|
|
157
|
+
const archivePath = path.join(historyDir, archiveName);
|
|
158
|
+
atomicWrite(archivePath, content);
|
|
159
|
+
|
|
160
|
+
return { archived: true, archivePath };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get metadata from a document file.
|
|
165
|
+
*
|
|
166
|
+
* @param {string} docsRoot - .planning/ directory
|
|
167
|
+
* @param {string} docType - Document type
|
|
168
|
+
* @returns {object|null} Frontmatter metadata or null
|
|
169
|
+
*/
|
|
170
|
+
function getDocumentMetadata(docsRoot, docType) {
|
|
171
|
+
const finalPath = getDocumentPath(docsRoot, docType, false);
|
|
172
|
+
const draftPath = getDocumentPath(docsRoot, docType, true);
|
|
173
|
+
|
|
174
|
+
const filePath = fileExists(finalPath) ? finalPath : fileExists(draftPath) ? draftPath : null;
|
|
175
|
+
if (!filePath) return null;
|
|
176
|
+
|
|
177
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
178
|
+
const { frontmatter } = parseFrontmatter(content);
|
|
179
|
+
return {
|
|
180
|
+
...frontmatter,
|
|
181
|
+
path: filePath,
|
|
182
|
+
isDraft: filePath === draftPath,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Bump the version number of a document.
|
|
188
|
+
*
|
|
189
|
+
* @param {string} currentVersion - Current version string (e.g., "1.0")
|
|
190
|
+
* @param {'major'|'minor'} [bump='minor'] - Bump type
|
|
191
|
+
* @returns {string} New version string
|
|
192
|
+
*/
|
|
193
|
+
function bumpVersion(currentVersion, bump = 'minor') {
|
|
194
|
+
if (!currentVersion) return '1.0';
|
|
195
|
+
const parts = currentVersion.split('.').map(Number);
|
|
196
|
+
if (bump === 'major') {
|
|
197
|
+
return `${parts[0] + 1}.0`;
|
|
198
|
+
}
|
|
199
|
+
return `${parts[0]}.${(parts[1] || 0) + 1}`;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// --- Internal helpers ---
|
|
203
|
+
|
|
204
|
+
function extractVersion(content) {
|
|
205
|
+
const match = content.match(/\*\*Version:\*\*\s*(\S+)/);
|
|
206
|
+
return match ? match[1] : null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function extractCommit(content) {
|
|
210
|
+
const match = content.match(/\*\*Commit:\*\*\s*(\S+)/);
|
|
211
|
+
return match ? match[1] : null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// CLI handler
|
|
215
|
+
function run(args) {
|
|
216
|
+
const docsRoot = path.join(process.cwd(), '.planning');
|
|
217
|
+
const subcommand = args[0] || 'list';
|
|
218
|
+
|
|
219
|
+
if (subcommand === 'list') {
|
|
220
|
+
process.stdout.write(JSON.stringify(listDocuments(docsRoot), null, 2));
|
|
221
|
+
} else if (subcommand === 'status' && args[1]) {
|
|
222
|
+
const meta = getDocumentMetadata(docsRoot, args[1]);
|
|
223
|
+
process.stdout.write(JSON.stringify(meta, null, 2));
|
|
224
|
+
} else if (subcommand === 'finalize' && args[1]) {
|
|
225
|
+
const result = finalize(docsRoot, args[1]);
|
|
226
|
+
process.stdout.write(JSON.stringify(result, null, 2));
|
|
227
|
+
} else {
|
|
228
|
+
process.stderr.write('Usage: gtd-tools.cjs doc <list|status|finalize> [type]\n');
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
module.exports = {
|
|
234
|
+
DOCUMENT_TYPES,
|
|
235
|
+
DOC_FILENAMES,
|
|
236
|
+
getDocumentPath,
|
|
237
|
+
listDocuments,
|
|
238
|
+
finalize,
|
|
239
|
+
archiveVersion,
|
|
240
|
+
getDocumentMetadata,
|
|
241
|
+
bumpVersion,
|
|
242
|
+
run,
|
|
243
|
+
};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GTD Drift Engine — Spec-code drift detection, categorization, reconciliation planning.
|
|
3
|
+
*
|
|
4
|
+
* Drift = difference between what specs/docs SAY and what code DOES.
|
|
5
|
+
* This is GTD's killer differentiator — no other framework detects this.
|
|
6
|
+
*
|
|
7
|
+
* @module lib/drift-engine
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const { fileExists } = require('./file-ops.cjs');
|
|
15
|
+
const { parseFrontmatter } = require('./frontmatter.cjs');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Drift categories.
|
|
19
|
+
*/
|
|
20
|
+
const DRIFT_CATEGORIES = {
|
|
21
|
+
ADDITION: 'Code has something that specs/docs do not mention',
|
|
22
|
+
REMOVAL: 'Specs/docs mention something that code does not have',
|
|
23
|
+
MUTATION: 'Both exist but behavior or structure differs',
|
|
24
|
+
STRUCTURAL: 'Architecture changed (new service, removed layer, different pattern)',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Drift severity levels.
|
|
29
|
+
*/
|
|
30
|
+
const SEVERITY = {
|
|
31
|
+
CRITICAL: 'Fundamental architectural change or security implication',
|
|
32
|
+
MAJOR: 'Significant feature or behavior difference',
|
|
33
|
+
MINOR: 'Small discrepancy, cosmetic or naming difference',
|
|
34
|
+
INFO: 'Informational — code improved beyond spec',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Check if drift detection is needed.
|
|
39
|
+
* @param {string} docsRoot - .planning/ directory
|
|
40
|
+
* @returns {{ needed: boolean, reason: string, lastCheck: string|null }}
|
|
41
|
+
*/
|
|
42
|
+
function isDriftCheckNeeded(docsRoot) {
|
|
43
|
+
const statePath = path.join(docsRoot, 'STATE.md');
|
|
44
|
+
if (!fileExists(statePath)) {
|
|
45
|
+
return { needed: false, reason: 'No state file — nothing to compare' };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const content = fs.readFileSync(statePath, 'utf8');
|
|
49
|
+
const { frontmatter } = parseFrontmatter(content);
|
|
50
|
+
|
|
51
|
+
const lastDriftCheck = frontmatter.sync_last_drift_check || null;
|
|
52
|
+
const lastScanCommit = frontmatter.backward_last_scan_commit || null;
|
|
53
|
+
const forwardStatus = frontmatter.forward_status || 'empty';
|
|
54
|
+
|
|
55
|
+
if (forwardStatus === 'empty' && !lastScanCommit) {
|
|
56
|
+
return { needed: false, reason: 'No forward or backward state — nothing to compare' };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!lastDriftCheck) {
|
|
60
|
+
return { needed: true, reason: 'Drift check has never been run', lastCheck: null };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const { getGitCommit } = require('./git.cjs');
|
|
65
|
+
const currentCommit = getGitCommit(path.dirname(docsRoot));
|
|
66
|
+
if (currentCommit && frontmatter.sync_last_drift_commit !== currentCommit) {
|
|
67
|
+
return { needed: true, reason: 'Code changed since last drift check', lastCheck: lastDriftCheck };
|
|
68
|
+
}
|
|
69
|
+
} catch (_) {}
|
|
70
|
+
|
|
71
|
+
return { needed: false, reason: 'Drift check is current', lastCheck: lastDriftCheck };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Build context for the drift-detector agent.
|
|
76
|
+
* @param {string} docsRoot - .planning/ directory
|
|
77
|
+
* @returns {object} Context for drift detection
|
|
78
|
+
*/
|
|
79
|
+
function buildDriftContext(docsRoot) {
|
|
80
|
+
const context = { hasSpecs: false, hasDocs: false, hasCode: false, specs: {}, docs: {} };
|
|
81
|
+
|
|
82
|
+
// Forward specs
|
|
83
|
+
for (const [key, file] of [['requirements', 'REQUIREMENTS.md'], ['roadmap', 'ROADMAP.md'], ['project', 'PROJECT.md']]) {
|
|
84
|
+
if (fileExists(path.join(docsRoot, file))) {
|
|
85
|
+
context.hasSpecs = true;
|
|
86
|
+
context.specs[key] = path.join(docsRoot, file);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Backward docs
|
|
91
|
+
const docsDir = path.join(docsRoot, 'documents');
|
|
92
|
+
if (fs.existsSync(docsDir)) {
|
|
93
|
+
const docFiles = fs.readdirSync(docsDir).filter((f) => f.endsWith('.md'));
|
|
94
|
+
if (docFiles.length > 0) {
|
|
95
|
+
context.hasDocs = true;
|
|
96
|
+
for (const f of docFiles) {
|
|
97
|
+
context.docs[f.replace('.md', '').toLowerCase()] = path.join(docsDir, f);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (fileExists(path.join(docsRoot, 'CODEBASE-MAP.md'))) context.hasCode = true;
|
|
103
|
+
return context;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Parse a DRIFT-REPORT.md file.
|
|
108
|
+
* @param {string} docsRoot - .planning/ directory
|
|
109
|
+
* @returns {{ exists: boolean, items: Array, summary: object }}
|
|
110
|
+
*/
|
|
111
|
+
function parseDriftReport(docsRoot) {
|
|
112
|
+
const reportPath = path.join(docsRoot, 'DRIFT-REPORT.md');
|
|
113
|
+
if (!fileExists(reportPath)) {
|
|
114
|
+
return { exists: false, items: [], summary: { total: 0, critical: 0, major: 0, minor: 0, info: 0 } };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const content = fs.readFileSync(reportPath, 'utf8');
|
|
118
|
+
const { frontmatter } = parseFrontmatter(content);
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
exists: true,
|
|
122
|
+
items: frontmatter.drift_items ? JSON.parse(frontmatter.drift_items) : [],
|
|
123
|
+
summary: {
|
|
124
|
+
total: frontmatter.total_items || 0,
|
|
125
|
+
critical: frontmatter.critical || 0,
|
|
126
|
+
major: frontmatter.major || 0,
|
|
127
|
+
minor: frontmatter.minor || 0,
|
|
128
|
+
info: frontmatter.info || 0,
|
|
129
|
+
},
|
|
130
|
+
timestamp: frontmatter.timestamp || null,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Reconciliation strategies.
|
|
136
|
+
*/
|
|
137
|
+
const RECONCILIATION_STRATEGIES = {
|
|
138
|
+
'code-wins': 'Update specs and docs to match current code (most common)',
|
|
139
|
+
'spec-wins': 'Generate tasks to update code to match spec',
|
|
140
|
+
interactive: 'Present each drift item to user for individual decision',
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get reconciliation actions for a drift category.
|
|
145
|
+
* @param {string} category - ADDITION, REMOVAL, MUTATION, STRUCTURAL
|
|
146
|
+
* @returns {Array<{action: string, description: string}>}
|
|
147
|
+
*/
|
|
148
|
+
function getReconciliationActions(category) {
|
|
149
|
+
const ACTIONS = {
|
|
150
|
+
ADDITION: [
|
|
151
|
+
{ action: 'add-to-spec', description: 'Add to REQUIREMENTS.md and update ROADMAP.md' },
|
|
152
|
+
{ action: 'remove-code', description: 'Create task to remove the undocumented code' },
|
|
153
|
+
{ action: 'ignore', description: 'Acknowledge and mark as intentional' },
|
|
154
|
+
],
|
|
155
|
+
REMOVAL: [
|
|
156
|
+
{ action: 'implement', description: 'Create task to implement the missing feature' },
|
|
157
|
+
{ action: 'remove-from-spec', description: 'Remove from REQUIREMENTS.md (descoped)' },
|
|
158
|
+
{ action: 'defer', description: 'Move to v2/future scope' },
|
|
159
|
+
],
|
|
160
|
+
MUTATION: [
|
|
161
|
+
{ action: 'update-spec', description: 'Update spec to match code behavior' },
|
|
162
|
+
{ action: 'update-code', description: 'Create task to fix code to match spec' },
|
|
163
|
+
{ action: 'document', description: 'Document the difference as intentional deviation' },
|
|
164
|
+
],
|
|
165
|
+
STRUCTURAL: [
|
|
166
|
+
{ action: 'update-all', description: 'Update all specs, docs, and diagrams' },
|
|
167
|
+
{ action: 'revert', description: 'Create task to revert architectural change' },
|
|
168
|
+
{ action: 'accept', description: 'Accept new architecture, update everything' },
|
|
169
|
+
],
|
|
170
|
+
};
|
|
171
|
+
return ACTIONS[category] || [{ action: 'ignore', description: 'No action needed' }];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// CLI handler
|
|
175
|
+
function run(args) {
|
|
176
|
+
const docsRoot = path.join(process.cwd(), '.planning');
|
|
177
|
+
const subcommand = args[0] || 'check';
|
|
178
|
+
|
|
179
|
+
if (subcommand === 'check') {
|
|
180
|
+
process.stdout.write(JSON.stringify(isDriftCheckNeeded(docsRoot), null, 2));
|
|
181
|
+
} else if (subcommand === 'context') {
|
|
182
|
+
process.stdout.write(JSON.stringify(buildDriftContext(docsRoot), null, 2));
|
|
183
|
+
} else if (subcommand === 'report') {
|
|
184
|
+
process.stdout.write(JSON.stringify(parseDriftReport(docsRoot), null, 2));
|
|
185
|
+
} else if (subcommand === 'actions' && args[1]) {
|
|
186
|
+
process.stdout.write(JSON.stringify(getReconciliationActions(args[1]), null, 2));
|
|
187
|
+
} else {
|
|
188
|
+
process.stderr.write('Usage: gtd-tools.cjs drift <check|context|report|actions> [category]\n');
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
module.exports = {
|
|
194
|
+
DRIFT_CATEGORIES,
|
|
195
|
+
SEVERITY,
|
|
196
|
+
RECONCILIATION_STRATEGIES,
|
|
197
|
+
isDriftCheckNeeded,
|
|
198
|
+
buildDriftContext,
|
|
199
|
+
parseDriftReport,
|
|
200
|
+
getReconciliationActions,
|
|
201
|
+
run,
|
|
202
|
+
};
|