@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
package/lib/file-ops.cjs
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GTD File Operations — Atomic writes, directory utilities, project root detection.
|
|
3
|
+
* @module lib/file-ops
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Write content to a file atomically.
|
|
13
|
+
* Writes to a temp file first, then renames (atomic on same filesystem).
|
|
14
|
+
* Prevents corruption if the process crashes mid-write.
|
|
15
|
+
*
|
|
16
|
+
* @param {string} targetPath - Absolute path to write to
|
|
17
|
+
* @param {string} content - File content
|
|
18
|
+
*/
|
|
19
|
+
function atomicWrite(targetPath, content) {
|
|
20
|
+
const tmpPath = targetPath + '.tmp.' + process.pid;
|
|
21
|
+
try {
|
|
22
|
+
const dir = path.dirname(targetPath);
|
|
23
|
+
if (!fs.existsSync(dir)) {
|
|
24
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
fs.writeFileSync(tmpPath, content, 'utf8');
|
|
27
|
+
fs.renameSync(tmpPath, targetPath);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
try {
|
|
30
|
+
fs.unlinkSync(tmpPath);
|
|
31
|
+
} catch (_) {
|
|
32
|
+
// Ignore cleanup errors
|
|
33
|
+
}
|
|
34
|
+
throw err;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Ensure a directory exists (recursive mkdir).
|
|
40
|
+
* @param {string} dirPath - Directory path to create
|
|
41
|
+
*/
|
|
42
|
+
function ensureDir(dirPath) {
|
|
43
|
+
if (!fs.existsSync(dirPath)) {
|
|
44
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Find the project root by walking up the directory tree.
|
|
50
|
+
* Looks for: .planning/, .git/, package.json
|
|
51
|
+
*
|
|
52
|
+
* @param {string} [startDir=process.cwd()] - Starting directory
|
|
53
|
+
* @returns {string|null} Project root path or null
|
|
54
|
+
*/
|
|
55
|
+
function findProjectRoot(startDir) {
|
|
56
|
+
let dir = startDir || process.cwd();
|
|
57
|
+
const root = path.parse(dir).root;
|
|
58
|
+
|
|
59
|
+
while (dir !== root) {
|
|
60
|
+
if (
|
|
61
|
+
fs.existsSync(path.join(dir, '.planning')) ||
|
|
62
|
+
fs.existsSync(path.join(dir, '.git')) ||
|
|
63
|
+
fs.existsSync(path.join(dir, 'package.json'))
|
|
64
|
+
) {
|
|
65
|
+
return dir;
|
|
66
|
+
}
|
|
67
|
+
dir = path.dirname(dir);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Check if a file exists.
|
|
75
|
+
* @param {string} filePath - Path to check
|
|
76
|
+
* @returns {boolean}
|
|
77
|
+
*/
|
|
78
|
+
function fileExists(filePath) {
|
|
79
|
+
try {
|
|
80
|
+
return fs.statSync(filePath).isFile();
|
|
81
|
+
} catch (_) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Read a file, returning a default value if it doesn't exist.
|
|
88
|
+
* @param {string} filePath - Path to read
|
|
89
|
+
* @param {string} defaultValue - Default if file missing
|
|
90
|
+
* @returns {string}
|
|
91
|
+
*/
|
|
92
|
+
function readFileOr(filePath, defaultValue) {
|
|
93
|
+
try {
|
|
94
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
95
|
+
} catch (_) {
|
|
96
|
+
return defaultValue;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
module.exports = {
|
|
101
|
+
atomicWrite,
|
|
102
|
+
ensureDir,
|
|
103
|
+
findProjectRoot,
|
|
104
|
+
fileExists,
|
|
105
|
+
readFileOr,
|
|
106
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GTD Frontmatter Parser — YAML frontmatter between --- delimiters.
|
|
3
|
+
* @module lib/frontmatter
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const FRONTMATTER_REGEX = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Parse YAML frontmatter from a Markdown string.
|
|
12
|
+
* Supports simple key: value pairs and basic types (string, number, boolean, null).
|
|
13
|
+
*
|
|
14
|
+
* @param {string} content - Markdown content with optional frontmatter
|
|
15
|
+
* @returns {{ frontmatter: object, body: string }}
|
|
16
|
+
*/
|
|
17
|
+
function parseFrontmatter(content) {
|
|
18
|
+
if (!content || typeof content !== 'string') {
|
|
19
|
+
return { frontmatter: {}, body: content || '' };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const match = content.match(FRONTMATTER_REGEX);
|
|
23
|
+
if (!match) {
|
|
24
|
+
return { frontmatter: {}, body: content };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const yamlBlock = match[1];
|
|
28
|
+
const body = match[2];
|
|
29
|
+
const frontmatter = parseSimpleYaml(yamlBlock);
|
|
30
|
+
|
|
31
|
+
return { frontmatter, body };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Serialize frontmatter and body back to a Markdown string.
|
|
36
|
+
*
|
|
37
|
+
* @param {object} frontmatter - Key-value pairs
|
|
38
|
+
* @param {string} body - Markdown body
|
|
39
|
+
* @returns {string}
|
|
40
|
+
*/
|
|
41
|
+
function serializeFrontmatter(frontmatter, body) {
|
|
42
|
+
if (!frontmatter || Object.keys(frontmatter).length === 0) {
|
|
43
|
+
return body || '';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const yamlLines = Object.entries(frontmatter).map(([key, value]) => {
|
|
47
|
+
if (value === null || value === undefined) return `${key}: null`;
|
|
48
|
+
if (typeof value === 'boolean') return `${key}: ${value}`;
|
|
49
|
+
if (typeof value === 'number') return `${key}: ${value}`;
|
|
50
|
+
if (typeof value === 'string' && value.includes(':')) return `${key}: "${value}"`;
|
|
51
|
+
return `${key}: ${value}`;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return `---\n${yamlLines.join('\n')}\n---\n${body || ''}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Parse simple YAML (flat key-value pairs only).
|
|
59
|
+
* Handles: strings, numbers, booleans, null, quoted strings.
|
|
60
|
+
*
|
|
61
|
+
* @param {string} yaml - YAML block content
|
|
62
|
+
* @returns {object}
|
|
63
|
+
*/
|
|
64
|
+
function parseSimpleYaml(yaml) {
|
|
65
|
+
const result = {};
|
|
66
|
+
const lines = yaml.split('\n');
|
|
67
|
+
|
|
68
|
+
for (const line of lines) {
|
|
69
|
+
const trimmed = line.trim();
|
|
70
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
71
|
+
|
|
72
|
+
const colonIndex = trimmed.indexOf(':');
|
|
73
|
+
if (colonIndex === -1) continue;
|
|
74
|
+
|
|
75
|
+
const key = trimmed.slice(0, colonIndex).trim();
|
|
76
|
+
let value = trimmed.slice(colonIndex + 1).trim();
|
|
77
|
+
|
|
78
|
+
// Remove quotes
|
|
79
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
80
|
+
value = value.slice(1, -1);
|
|
81
|
+
} else if (value === 'true') {
|
|
82
|
+
value = true;
|
|
83
|
+
} else if (value === 'false') {
|
|
84
|
+
value = false;
|
|
85
|
+
} else if (value === 'null' || value === '~' || value === '') {
|
|
86
|
+
value = null;
|
|
87
|
+
} else if (!isNaN(Number(value)) && value !== '') {
|
|
88
|
+
value = Number(value);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
result[key] = value;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = {
|
|
98
|
+
parseFrontmatter,
|
|
99
|
+
serializeFrontmatter,
|
|
100
|
+
};
|
package/lib/git.cjs
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GTD Git Integration — Git operations for state tracking and change detection.
|
|
3
|
+
* @module lib/git
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const { execSync } = require('child_process');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Execute a git command and return stdout trimmed.
|
|
13
|
+
* Returns null on any error (not a git repo, git not installed, etc.)
|
|
14
|
+
*
|
|
15
|
+
* @param {string} command - Git command (without 'git' prefix)
|
|
16
|
+
* @param {string} cwd - Working directory
|
|
17
|
+
* @returns {string|null}
|
|
18
|
+
*/
|
|
19
|
+
function git(command, cwd) {
|
|
20
|
+
try {
|
|
21
|
+
return execSync(`git ${command}`, {
|
|
22
|
+
cwd,
|
|
23
|
+
encoding: 'utf8',
|
|
24
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
25
|
+
timeout: 10000,
|
|
26
|
+
}).trim();
|
|
27
|
+
} catch (_) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get the current short commit hash.
|
|
34
|
+
* @param {string} projectRoot - Project root directory
|
|
35
|
+
* @returns {string|null} 7-character commit hash or null
|
|
36
|
+
*/
|
|
37
|
+
function getGitCommit(projectRoot) {
|
|
38
|
+
return git('rev-parse --short HEAD', projectRoot);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get the full commit hash.
|
|
43
|
+
* @param {string} projectRoot - Project root directory
|
|
44
|
+
* @returns {string|null} Full commit hash or null
|
|
45
|
+
*/
|
|
46
|
+
function getGitCommitFull(projectRoot) {
|
|
47
|
+
return git('rev-parse HEAD', projectRoot);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get the current branch name.
|
|
52
|
+
* @param {string} projectRoot - Project root directory
|
|
53
|
+
* @returns {string|null} Branch name or null
|
|
54
|
+
*/
|
|
55
|
+
function getGitBranch(projectRoot) {
|
|
56
|
+
return git('rev-parse --abbrev-ref HEAD', projectRoot);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Check if there are uncommitted changes (staged or unstaged).
|
|
61
|
+
* @param {string} projectRoot - Project root directory
|
|
62
|
+
* @returns {boolean}
|
|
63
|
+
*/
|
|
64
|
+
function hasUncommittedChanges(projectRoot) {
|
|
65
|
+
const status = git('status --porcelain', projectRoot);
|
|
66
|
+
return status !== null && status.length > 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get the list of files changed between two commits.
|
|
71
|
+
* @param {string} projectRoot - Project root directory
|
|
72
|
+
* @param {string} fromCommit - Starting commit hash
|
|
73
|
+
* @param {string} [toCommit='HEAD'] - Ending commit hash
|
|
74
|
+
* @returns {string[]} Array of changed file paths (relative to project root)
|
|
75
|
+
*/
|
|
76
|
+
function getChangedFiles(projectRoot, fromCommit, toCommit = 'HEAD') {
|
|
77
|
+
const output = git(`diff --name-only ${fromCommit}..${toCommit}`, projectRoot);
|
|
78
|
+
if (!output) return [];
|
|
79
|
+
return output.split('\n').filter((line) => line.length > 0);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get recent commit messages for context.
|
|
84
|
+
* @param {string} projectRoot - Project root directory
|
|
85
|
+
* @param {number} [count=10] - Number of commits to retrieve
|
|
86
|
+
* @returns {Array<{hash: string, message: string}>}
|
|
87
|
+
*/
|
|
88
|
+
function getRecentCommits(projectRoot, count = 10) {
|
|
89
|
+
const output = git(`log --oneline -${count}`, projectRoot);
|
|
90
|
+
if (!output) return [];
|
|
91
|
+
return output.split('\n').filter(Boolean).map((line) => {
|
|
92
|
+
const spaceIndex = line.indexOf(' ');
|
|
93
|
+
return {
|
|
94
|
+
hash: line.slice(0, spaceIndex),
|
|
95
|
+
message: line.slice(spaceIndex + 1),
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Check if a directory is inside a git repository.
|
|
102
|
+
* @param {string} dir - Directory to check
|
|
103
|
+
* @returns {boolean}
|
|
104
|
+
*/
|
|
105
|
+
function isGitRepo(dir) {
|
|
106
|
+
return git('rev-parse --is-inside-work-tree', dir) === 'true';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get the git root directory.
|
|
111
|
+
* @param {string} dir - Directory to start from
|
|
112
|
+
* @returns {string|null} Git root path or null
|
|
113
|
+
*/
|
|
114
|
+
function getGitRoot(dir) {
|
|
115
|
+
return git('rev-parse --show-toplevel', dir);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Get the remote origin URL.
|
|
120
|
+
* @param {string} projectRoot - Project root directory
|
|
121
|
+
* @returns {string|null}
|
|
122
|
+
*/
|
|
123
|
+
function getRemoteUrl(projectRoot) {
|
|
124
|
+
return git('remote get-url origin', projectRoot);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
module.exports = {
|
|
128
|
+
getGitCommit,
|
|
129
|
+
getGitCommitFull,
|
|
130
|
+
getGitBranch,
|
|
131
|
+
hasUncommittedChanges,
|
|
132
|
+
getChangedFiles,
|
|
133
|
+
getRecentCommits,
|
|
134
|
+
isGitRepo,
|
|
135
|
+
getGitRoot,
|
|
136
|
+
getRemoteUrl,
|
|
137
|
+
};
|
package/lib/init.cjs
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GTD Init Module — Assembles workflow context for agent orchestration.
|
|
3
|
+
*
|
|
4
|
+
* Each workflow type receives a tailored context payload containing only
|
|
5
|
+
* the data it needs. This keeps agent prompts focused and avoids
|
|
6
|
+
* loading unnecessary context into limited token windows.
|
|
7
|
+
*
|
|
8
|
+
* @module lib/init
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
const { findProjectRoot, fileExists, readFileOr, ensureDir } = require('./file-ops.cjs');
|
|
17
|
+
const { loadConfig } = require('./config.cjs');
|
|
18
|
+
const { loadState } = require('./state.cjs');
|
|
19
|
+
const { getAnalysisStatus, getCodebaseMapStatus } = require('./analysis.cjs');
|
|
20
|
+
const {
|
|
21
|
+
getGitCommit,
|
|
22
|
+
getGitBranch,
|
|
23
|
+
hasUncommittedChanges,
|
|
24
|
+
isGitRepo,
|
|
25
|
+
getRemoteUrl,
|
|
26
|
+
} = require('./git.cjs');
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Workflow → required context dimensions mapping.
|
|
30
|
+
* Each workflow only gets the context slices it needs.
|
|
31
|
+
*/
|
|
32
|
+
const WORKFLOW_CONTEXT = {
|
|
33
|
+
// Backward workflows
|
|
34
|
+
'scan-codebase': ['config', 'state', 'git'],
|
|
35
|
+
'analyze-codebase': ['config', 'state', 'git', 'codebase_map', 'analysis_status'],
|
|
36
|
+
'generate-document': ['config', 'state', 'git', 'codebase_map', 'analysis_status', 'doc_status'],
|
|
37
|
+
'create-all': ['config', 'state', 'git', 'codebase_map', 'analysis_status', 'doc_status'],
|
|
38
|
+
'verify-document': ['config', 'state', 'git', 'doc_status'],
|
|
39
|
+
'review-document': ['config', 'state', 'doc_status'],
|
|
40
|
+
'incremental-update': ['config', 'state', 'git', 'analysis_status', 'doc_status', 'git_diff'],
|
|
41
|
+
|
|
42
|
+
// Forward workflows
|
|
43
|
+
'new-project': ['config', 'state', 'git', 'codebase_map'],
|
|
44
|
+
'discuss-phase': ['config', 'state', 'git', 'roadmap', 'phase_context'],
|
|
45
|
+
'plan-phase': ['config', 'state', 'git', 'roadmap', 'phase_context', 'research'],
|
|
46
|
+
'execute-phase': ['config', 'state', 'git', 'roadmap', 'phase_context', 'plans'],
|
|
47
|
+
'verify-work': ['config', 'state', 'git', 'roadmap', 'phase_context', 'plans', 'summaries'],
|
|
48
|
+
'deploy-local': ['config', 'state', 'git', 'codebase_map'],
|
|
49
|
+
'test-phase': ['config', 'state', 'git', 'phase_context'],
|
|
50
|
+
ship: ['config', 'state', 'git'],
|
|
51
|
+
|
|
52
|
+
// Sync workflows
|
|
53
|
+
'detect-drift': ['config', 'state', 'git', 'analysis_status', 'doc_status', 'roadmap'],
|
|
54
|
+
reconcile: ['config', 'state', 'git', 'drift_report'],
|
|
55
|
+
sync: ['config', 'state', 'git', 'drift_report'],
|
|
56
|
+
audit: ['config', 'state', 'git', 'analysis_status', 'doc_status', 'roadmap'],
|
|
57
|
+
|
|
58
|
+
// Utility workflows
|
|
59
|
+
help: ['config', 'state'],
|
|
60
|
+
status: ['config', 'state', 'analysis_status', 'doc_status'],
|
|
61
|
+
settings: ['config'],
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Main init function — assembles context for a workflow.
|
|
66
|
+
*
|
|
67
|
+
* @param {string[]} args - [workflowName, ...workflowArgs]
|
|
68
|
+
* @returns {void} Writes JSON to stdout
|
|
69
|
+
*/
|
|
70
|
+
function init(args) {
|
|
71
|
+
const workflowName = args[0];
|
|
72
|
+
const workflowArgs = args.slice(1);
|
|
73
|
+
|
|
74
|
+
if (!workflowName) {
|
|
75
|
+
process.stderr.write('Usage: gtd-tools.cjs init <workflow> [args...]\n');
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const projectRoot = findProjectRoot(process.cwd()) || process.cwd();
|
|
80
|
+
const docsRoot = path.join(projectRoot, '.planning');
|
|
81
|
+
|
|
82
|
+
// Base context — always present
|
|
83
|
+
const context = {
|
|
84
|
+
project_root: projectRoot,
|
|
85
|
+
docs_root: docsRoot,
|
|
86
|
+
workflow: workflowName,
|
|
87
|
+
args: parseWorkflowArgs(workflowName, workflowArgs),
|
|
88
|
+
timestamp: new Date().toISOString(),
|
|
89
|
+
gtd_version: getGtdVersion(),
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// Determine which context slices this workflow needs
|
|
93
|
+
const needed = WORKFLOW_CONTEXT[workflowName] || ['config', 'state', 'git'];
|
|
94
|
+
|
|
95
|
+
// Load each needed context slice
|
|
96
|
+
if (needed.includes('config')) {
|
|
97
|
+
context.config = loadConfig(docsRoot);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (needed.includes('state')) {
|
|
101
|
+
context.state = loadState(docsRoot);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (needed.includes('git')) {
|
|
105
|
+
context.git = buildGitContext(projectRoot);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (needed.includes('codebase_map')) {
|
|
109
|
+
context.codebase_map = getCodebaseMapStatus(docsRoot);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (needed.includes('analysis_status')) {
|
|
113
|
+
context.analysis_status = getAnalysisStatus(docsRoot);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (needed.includes('doc_status')) {
|
|
117
|
+
const state = context.state || loadState(docsRoot);
|
|
118
|
+
context.documents = state.backward.documents;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (needed.includes('roadmap')) {
|
|
122
|
+
context.roadmap = loadArtifact(docsRoot, 'ROADMAP.md');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (needed.includes('phase_context')) {
|
|
126
|
+
context.phase = loadPhaseContext(docsRoot, context.args);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (needed.includes('plans')) {
|
|
130
|
+
context.plans = loadPhasePlans(docsRoot, context.args);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (needed.includes('research')) {
|
|
134
|
+
context.research = loadResearchSummary(docsRoot);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (needed.includes('drift_report')) {
|
|
138
|
+
context.drift_report = loadArtifact(docsRoot, 'DRIFT-REPORT.md');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Resolve model tiers for this workflow
|
|
142
|
+
if (context.config) {
|
|
143
|
+
context.models = resolveModelsForWorkflow(workflowName, context.config);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Output context
|
|
147
|
+
outputContext(context);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Build git context object.
|
|
152
|
+
*/
|
|
153
|
+
function buildGitContext(projectRoot) {
|
|
154
|
+
if (!isGitRepo(projectRoot)) {
|
|
155
|
+
return { available: false };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
available: true,
|
|
160
|
+
commit: getGitCommit(projectRoot),
|
|
161
|
+
branch: getGitBranch(projectRoot),
|
|
162
|
+
has_changes: hasUncommittedChanges(projectRoot),
|
|
163
|
+
remote_url: getRemoteUrl(projectRoot),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Parse workflow-specific arguments.
|
|
169
|
+
*/
|
|
170
|
+
function parseWorkflowArgs(workflowName, args) {
|
|
171
|
+
const parsed = { raw: args };
|
|
172
|
+
|
|
173
|
+
// Extract common flags
|
|
174
|
+
for (let i = 0; i < args.length; i++) {
|
|
175
|
+
const arg = args[i];
|
|
176
|
+
|
|
177
|
+
if (arg === '--auto' || arg === '-a') parsed.auto = true;
|
|
178
|
+
else if (arg === '--force' || arg === '-f') parsed.force = true;
|
|
179
|
+
else if (arg === '--parallel') parsed.parallel = true;
|
|
180
|
+
else if (arg === '--deep') parsed.deep = true;
|
|
181
|
+
else if (arg === '--format' && args[i + 1]) { parsed.format = args[++i]; }
|
|
182
|
+
else if (arg === '--since' && args[i + 1]) { parsed.since = args[++i]; }
|
|
183
|
+
else if (arg === '--focus' && args[i + 1]) { parsed.focus = args[++i]; }
|
|
184
|
+
else if (arg === '--module' && args[i + 1]) { parsed.module = args[++i]; }
|
|
185
|
+
else if (arg === '--wave' && args[i + 1]) { parsed.wave = parseInt(args[++i], 10); }
|
|
186
|
+
else if (arg === '--strategy' && args[i + 1]) { parsed.strategy = args[++i]; }
|
|
187
|
+
else if (arg === '--direction' && args[i + 1]) { parsed.direction = args[++i]; }
|
|
188
|
+
else if (arg === '--include-tests') parsed.include_tests = true;
|
|
189
|
+
else if (!arg.startsWith('-') && !parsed.positional) {
|
|
190
|
+
parsed.positional = arg;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Workflow-specific positional arg interpretation
|
|
195
|
+
if (parsed.positional) {
|
|
196
|
+
if (['generate-document', 'verify-document', 'review-document'].includes(workflowName)) {
|
|
197
|
+
parsed.doc_type = parsed.positional;
|
|
198
|
+
} else if (
|
|
199
|
+
['discuss-phase', 'plan-phase', 'execute-phase', 'verify-work', 'test-phase'].includes(
|
|
200
|
+
workflowName,
|
|
201
|
+
)
|
|
202
|
+
) {
|
|
203
|
+
parsed.phase_number = parseInt(parsed.positional, 10);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return parsed;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Load a planning artifact as a string (or null if missing).
|
|
212
|
+
*/
|
|
213
|
+
function loadArtifact(docsRoot, filename) {
|
|
214
|
+
const filepath = path.join(docsRoot, filename);
|
|
215
|
+
if (!fileExists(filepath)) return null;
|
|
216
|
+
try {
|
|
217
|
+
return fs.readFileSync(filepath, 'utf8');
|
|
218
|
+
} catch (_) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Load phase-specific context files.
|
|
225
|
+
*/
|
|
226
|
+
function loadPhaseContext(docsRoot, args) {
|
|
227
|
+
const phaseNum = args.phase_number;
|
|
228
|
+
if (!phaseNum) return null;
|
|
229
|
+
|
|
230
|
+
const phasesDir = path.join(docsRoot, 'phases');
|
|
231
|
+
if (!fs.existsSync(phasesDir)) return { phase_number: phaseNum, exists: false };
|
|
232
|
+
|
|
233
|
+
// Find the phase directory (zero-padded: 01-, 02-, etc.)
|
|
234
|
+
const padded = String(phaseNum).padStart(2, '0');
|
|
235
|
+
const entries = fs.existsSync(phasesDir) ? fs.readdirSync(phasesDir) : [];
|
|
236
|
+
const phaseDir = entries.find((e) => e.startsWith(padded + '-'));
|
|
237
|
+
|
|
238
|
+
if (!phaseDir) return { phase_number: phaseNum, exists: false };
|
|
239
|
+
|
|
240
|
+
const fullPhaseDir = path.join(phasesDir, phaseDir);
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
phase_number: phaseNum,
|
|
244
|
+
exists: true,
|
|
245
|
+
directory: fullPhaseDir,
|
|
246
|
+
context_file: loadArtifact(fullPhaseDir, `${padded}-CONTEXT.md`),
|
|
247
|
+
research_file: loadArtifact(fullPhaseDir, `${padded}-RESEARCH.md`),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Load plan files from a phase directory.
|
|
253
|
+
*/
|
|
254
|
+
function loadPhasePlans(docsRoot, args) {
|
|
255
|
+
const phaseNum = args.phase_number;
|
|
256
|
+
if (!phaseNum) return null;
|
|
257
|
+
|
|
258
|
+
const padded = String(phaseNum).padStart(2, '0');
|
|
259
|
+
const phasesDir = path.join(docsRoot, 'phases');
|
|
260
|
+
if (!fs.existsSync(phasesDir)) return [];
|
|
261
|
+
|
|
262
|
+
const entries = fs.readdirSync(phasesDir);
|
|
263
|
+
const phaseDir = entries.find((e) => e.startsWith(padded + '-'));
|
|
264
|
+
if (!phaseDir) return [];
|
|
265
|
+
|
|
266
|
+
const fullPhaseDir = path.join(phasesDir, phaseDir);
|
|
267
|
+
const allFiles = fs.readdirSync(fullPhaseDir);
|
|
268
|
+
const planFiles = allFiles.filter((f) => f.includes('-PLAN') && f.endsWith('.md'));
|
|
269
|
+
|
|
270
|
+
return planFiles.map((f) => ({
|
|
271
|
+
filename: f,
|
|
272
|
+
path: path.join(fullPhaseDir, f),
|
|
273
|
+
}));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Load research summary if available.
|
|
278
|
+
*/
|
|
279
|
+
function loadResearchSummary(docsRoot) {
|
|
280
|
+
const researchDir = path.join(docsRoot, 'research');
|
|
281
|
+
if (!fs.existsSync(researchDir)) return null;
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
summary: loadArtifact(researchDir, 'SUMMARY.md'),
|
|
285
|
+
stack: loadArtifact(researchDir, 'STACK.md'),
|
|
286
|
+
features: loadArtifact(researchDir, 'FEATURES.md'),
|
|
287
|
+
architecture: loadArtifact(researchDir, 'ARCHITECTURE.md'),
|
|
288
|
+
pitfalls: loadArtifact(researchDir, 'PITFALLS.md'),
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Resolve model tiers for agents used in this workflow.
|
|
294
|
+
*/
|
|
295
|
+
function resolveModelsForWorkflow(workflowName, config) {
|
|
296
|
+
const models = config.models || {};
|
|
297
|
+
const backwardWorkflows = [
|
|
298
|
+
'scan-codebase',
|
|
299
|
+
'analyze-codebase',
|
|
300
|
+
'generate-document',
|
|
301
|
+
'create-all',
|
|
302
|
+
'verify-document',
|
|
303
|
+
'review-document',
|
|
304
|
+
'incremental-update',
|
|
305
|
+
];
|
|
306
|
+
const forwardWorkflows = [
|
|
307
|
+
'new-project',
|
|
308
|
+
'discuss-phase',
|
|
309
|
+
'plan-phase',
|
|
310
|
+
'execute-phase',
|
|
311
|
+
'verify-work',
|
|
312
|
+
'deploy-local',
|
|
313
|
+
'test-phase',
|
|
314
|
+
'ship',
|
|
315
|
+
];
|
|
316
|
+
|
|
317
|
+
if (backwardWorkflows.includes(workflowName)) {
|
|
318
|
+
return {
|
|
319
|
+
analyzer: models.analyzer || 'sonnet',
|
|
320
|
+
writer: models.writer || 'sonnet',
|
|
321
|
+
verifier: models.verifier || 'haiku',
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (forwardWorkflows.includes(workflowName)) {
|
|
326
|
+
return {
|
|
327
|
+
researcher: models.researcher || 'sonnet',
|
|
328
|
+
planner: models.planner || 'sonnet',
|
|
329
|
+
executor: models.executor || 'sonnet',
|
|
330
|
+
verifier: models.verifier || 'haiku',
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return models;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Output context as JSON, using @file: for large payloads.
|
|
339
|
+
*/
|
|
340
|
+
function outputContext(context) {
|
|
341
|
+
const json = JSON.stringify(context, null, 2);
|
|
342
|
+
|
|
343
|
+
if (json.length > 50000) {
|
|
344
|
+
// Write to temp file to avoid overwhelming stdout
|
|
345
|
+
const tmpDir = path.join(os.tmpdir(), 'gtd');
|
|
346
|
+
ensureDir(tmpDir);
|
|
347
|
+
const tmpFile = path.join(tmpDir, `init-${Date.now()}.json`);
|
|
348
|
+
fs.writeFileSync(tmpFile, json, 'utf8');
|
|
349
|
+
process.stdout.write(`@file:${tmpFile}`);
|
|
350
|
+
} else {
|
|
351
|
+
process.stdout.write(json);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Get the GTD package version.
|
|
357
|
+
*/
|
|
358
|
+
function getGtdVersion() {
|
|
359
|
+
try {
|
|
360
|
+
const pkg = require('../package.json');
|
|
361
|
+
return pkg.version;
|
|
362
|
+
} catch (_) {
|
|
363
|
+
return 'unknown';
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
module.exports = init;
|
|
368
|
+
module.exports.buildGitContext = buildGitContext;
|
|
369
|
+
module.exports.parseWorkflowArgs = parseWorkflowArgs;
|
|
370
|
+
module.exports.WORKFLOW_CONTEXT = WORKFLOW_CONTEXT;
|