@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.
Files changed (177) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +237 -0
  3. package/agents/backward/gtd-accuracy-verifier.md +198 -0
  4. package/agents/backward/gtd-api-doc-writer.md +130 -0
  5. package/agents/backward/gtd-api-extractor.md +128 -0
  6. package/agents/backward/gtd-architecture-analyzer.md +144 -0
  7. package/agents/backward/gtd-capacity-writer.md +123 -0
  8. package/agents/backward/gtd-codebase-mapper.md +274 -0
  9. package/agents/backward/gtd-completeness-auditor.md +129 -0
  10. package/agents/backward/gtd-data-flow-tracer.md +104 -0
  11. package/agents/backward/gtd-dependency-analyzer.md +98 -0
  12. package/agents/backward/gtd-diagram-generator.md +152 -0
  13. package/agents/backward/gtd-hld-writer.md +123 -0
  14. package/agents/backward/gtd-lld-writer.md +126 -0
  15. package/agents/backward/gtd-pattern-detector.md +111 -0
  16. package/agents/backward/gtd-performance-profiler.md +93 -0
  17. package/agents/backward/gtd-runbook-writer.md +126 -0
  18. package/agents/backward/gtd-security-scanner.md +106 -0
  19. package/agents/backward/gtd-sysdesign-writer.md +137 -0
  20. package/agents/backward/gtd-tdd-writer.md +125 -0
  21. package/agents/forward/gtd-code-reviewer.md +130 -0
  22. package/agents/forward/gtd-debugger.md +133 -0
  23. package/agents/forward/gtd-deployer.md +110 -0
  24. package/agents/forward/gtd-executor.md +110 -0
  25. package/agents/forward/gtd-phase-researcher.md +114 -0
  26. package/agents/forward/gtd-plan-checker.md +132 -0
  27. package/agents/forward/gtd-planner.md +136 -0
  28. package/agents/forward/gtd-project-researcher.md +106 -0
  29. package/agents/forward/gtd-research-synthesizer.md +99 -0
  30. package/agents/forward/gtd-roadmapper.md +126 -0
  31. package/agents/forward/gtd-test-runner.md +119 -0
  32. package/agents/forward/gtd-verifier.md +115 -0
  33. package/agents/sync/gtd-alignment-auditor.md +222 -0
  34. package/agents/sync/gtd-drift-detector.md +222 -0
  35. package/agents/sync/gtd-reconciliation-planner.md +194 -0
  36. package/bin/gtd-tools.cjs +89 -0
  37. package/bin/install.js +164 -0
  38. package/commands/gtd/backward/analyze.md +42 -0
  39. package/commands/gtd/backward/create-all.md +32 -0
  40. package/commands/gtd/backward/create-api-docs.md +33 -0
  41. package/commands/gtd/backward/create-capacity.md +33 -0
  42. package/commands/gtd/backward/create-hld.md +33 -0
  43. package/commands/gtd/backward/create-lld.md +33 -0
  44. package/commands/gtd/backward/create-runbook.md +33 -0
  45. package/commands/gtd/backward/create-sysdesign.md +33 -0
  46. package/commands/gtd/backward/create-tdd.md +33 -0
  47. package/commands/gtd/backward/diff.md +22 -0
  48. package/commands/gtd/backward/doc-status.md +24 -0
  49. package/commands/gtd/backward/review-docs.md +22 -0
  50. package/commands/gtd/backward/scan.md +32 -0
  51. package/commands/gtd/backward/update-docs.md +30 -0
  52. package/commands/gtd/backward/verify-docs.md +28 -0
  53. package/commands/gtd/forward/add-phase.md +28 -0
  54. package/commands/gtd/forward/autonomous.md +28 -0
  55. package/commands/gtd/forward/code-review.md +28 -0
  56. package/commands/gtd/forward/complete-milestone.md +28 -0
  57. package/commands/gtd/forward/debug.md +28 -0
  58. package/commands/gtd/forward/discuss-phase.md +29 -0
  59. package/commands/gtd/forward/execute-phase.md +28 -0
  60. package/commands/gtd/forward/fast.md +28 -0
  61. package/commands/gtd/forward/new-milestone.md +28 -0
  62. package/commands/gtd/forward/new-project.md +29 -0
  63. package/commands/gtd/forward/next.md +28 -0
  64. package/commands/gtd/forward/plan-phase.md +29 -0
  65. package/commands/gtd/forward/progress.md +28 -0
  66. package/commands/gtd/forward/quick.md +28 -0
  67. package/commands/gtd/forward/ship.md +28 -0
  68. package/commands/gtd/forward/verify-work.md +28 -0
  69. package/commands/gtd/sync/audit.md +27 -0
  70. package/commands/gtd/sync/drift.md +27 -0
  71. package/commands/gtd/sync/reconcile.md +27 -0
  72. package/commands/gtd/sync/sync.md +27 -0
  73. package/commands/gtd/utility/health.md +53 -0
  74. package/commands/gtd/utility/help.md +61 -0
  75. package/commands/gtd/utility/map-codebase.md +27 -0
  76. package/commands/gtd/utility/settings.md +65 -0
  77. package/commands/gtd/utility/status.md +57 -0
  78. package/contexts/analysis.md +26 -0
  79. package/contexts/execution.md +35 -0
  80. package/contexts/planning.md +33 -0
  81. package/contexts/research.md +26 -0
  82. package/contexts/review.md +27 -0
  83. package/contexts/writing.md +29 -0
  84. package/hooks/gtd-check-update.js +37 -0
  85. package/hooks/gtd-context-monitor.js +32 -0
  86. package/hooks/gtd-prompt-guard.js +35 -0
  87. package/hooks/gtd-statusline.js +32 -0
  88. package/lib/agent-skills.cjs +130 -0
  89. package/lib/analysis.cjs +242 -0
  90. package/lib/config.cjs +255 -0
  91. package/lib/deploy.cjs +222 -0
  92. package/lib/diff-engine.cjs +245 -0
  93. package/lib/docs.cjs +243 -0
  94. package/lib/drift-engine.cjs +202 -0
  95. package/lib/file-ops.cjs +106 -0
  96. package/lib/frontmatter.cjs +100 -0
  97. package/lib/git.cjs +137 -0
  98. package/lib/init.cjs +370 -0
  99. package/lib/installer-core.cjs +197 -0
  100. package/lib/installers/augment.cjs +62 -0
  101. package/lib/installers/claude.cjs +89 -0
  102. package/lib/installers/cline.cjs +96 -0
  103. package/lib/installers/codex.cjs +63 -0
  104. package/lib/installers/copilot.cjs +62 -0
  105. package/lib/installers/cursor.cjs +62 -0
  106. package/lib/installers/gemini.cjs +62 -0
  107. package/lib/installers/opencode.cjs +62 -0
  108. package/lib/installers/windsurf.cjs +62 -0
  109. package/lib/phase.cjs +206 -0
  110. package/lib/roadmap.cjs +156 -0
  111. package/lib/scale-adapter.cjs +192 -0
  112. package/lib/security.cjs +243 -0
  113. package/lib/state.cjs +320 -0
  114. package/lib/template.cjs +218 -0
  115. package/lib/test-runner.cjs +202 -0
  116. package/package.json +76 -0
  117. package/references/agent-contracts.md +157 -0
  118. package/references/analysis-patterns.md +138 -0
  119. package/references/context-budget.md +148 -0
  120. package/references/diagram-conventions.md +88 -0
  121. package/references/document-standards.md +60 -0
  122. package/references/framework-signatures.md +609 -0
  123. package/references/gate-prompts.md +239 -0
  124. package/references/language-analyzers.md +227 -0
  125. package/references/planning-config.md +125 -0
  126. package/references/questioning.md +142 -0
  127. package/references/verification-patterns.md +67 -0
  128. package/templates/backward/api-docs/standard.md +42 -0
  129. package/templates/backward/capacity/standard.md +50 -0
  130. package/templates/backward/formats/compliance-guide.md +45 -0
  131. package/templates/backward/hld/standard.md +62 -0
  132. package/templates/backward/lld/standard.md +63 -0
  133. package/templates/backward/runbook/standard.md +50 -0
  134. package/templates/backward/system-design/standard.md +64 -0
  135. package/templates/backward/tdd/compliance.md +146 -0
  136. package/templates/backward/tdd/enterprise.md +134 -0
  137. package/templates/backward/tdd/standard.md +88 -0
  138. package/templates/backward/tdd/startup.md +51 -0
  139. package/templates/forward/context.md +65 -0
  140. package/templates/forward/phase-prompt.md +109 -0
  141. package/templates/forward/project.md +71 -0
  142. package/templates/forward/requirements.md +74 -0
  143. package/templates/forward/research/ARCHITECTURE.md +118 -0
  144. package/templates/forward/research/FEATURES.md +95 -0
  145. package/templates/forward/research/PITFALLS.md +106 -0
  146. package/templates/forward/research/STACK.md +80 -0
  147. package/templates/forward/research/SUMMARY.md +86 -0
  148. package/templates/forward/roadmap.md +72 -0
  149. package/workflows/backward/analyze-codebase.md +123 -0
  150. package/workflows/backward/create-all.md +53 -0
  151. package/workflows/backward/generate-document.md +182 -0
  152. package/workflows/backward/incremental-update.md +71 -0
  153. package/workflows/backward/review-document.md +102 -0
  154. package/workflows/backward/scan-codebase.md +111 -0
  155. package/workflows/backward/verify-document.md +79 -0
  156. package/workflows/forward/add-phase.md +29 -0
  157. package/workflows/forward/autonomous.md +62 -0
  158. package/workflows/forward/code-review.md +78 -0
  159. package/workflows/forward/complete-milestone.md +45 -0
  160. package/workflows/forward/debug.md +78 -0
  161. package/workflows/forward/deploy-local.md +51 -0
  162. package/workflows/forward/discuss-phase.md +89 -0
  163. package/workflows/forward/execute-phase.md +138 -0
  164. package/workflows/forward/fast.md +64 -0
  165. package/workflows/forward/new-milestone.md +61 -0
  166. package/workflows/forward/new-project.md +126 -0
  167. package/workflows/forward/next.md +49 -0
  168. package/workflows/forward/plan-phase.md +100 -0
  169. package/workflows/forward/progress.md +37 -0
  170. package/workflows/forward/quick.md +65 -0
  171. package/workflows/forward/ship.md +40 -0
  172. package/workflows/forward/test-phase.md +47 -0
  173. package/workflows/forward/verify-work.md +52 -0
  174. package/workflows/sync/audit.md +110 -0
  175. package/workflows/sync/detect-drift.md +122 -0
  176. package/workflows/sync/reconcile.md +113 -0
  177. 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
+ };