devflow-agent-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +313 -0
  3. package/bin/devflow.js +2 -0
  4. package/dist/cli.js +52 -0
  5. package/dist/commands/add.js +137 -0
  6. package/dist/commands/doctor.js +246 -0
  7. package/dist/commands/explain.js +204 -0
  8. package/dist/commands/init.js +91 -0
  9. package/dist/constants.js +120 -0
  10. package/dist/install.js +176 -0
  11. package/dist/plugins.js +184 -0
  12. package/dist/types.js +2 -0
  13. package/package.json +51 -0
  14. package/templates/adapters/claude/.claude/commands/build.md +15 -0
  15. package/templates/adapters/claude/.claude/commands/commit.md +74 -0
  16. package/templates/adapters/claude/.claude/commands/plan.md +15 -0
  17. package/templates/adapters/claude/.claude/commands/review.md +16 -0
  18. package/templates/adapters/claude/.claude/commands/tests.md +14 -0
  19. package/templates/adapters/claude/.claude/commands/verify.md +16 -0
  20. package/templates/adapters/codex/README.md +14 -0
  21. package/templates/adapters/cursor/.cursor/commands/build.md +15 -0
  22. package/templates/adapters/cursor/.cursor/commands/plan.md +15 -0
  23. package/templates/adapters/cursor/.cursor/commands/review.md +16 -0
  24. package/templates/adapters/cursor/.cursor/commands/tests.md +14 -0
  25. package/templates/adapters/cursor/.cursor/commands/verify.md +16 -0
  26. package/templates/adapters/cursor/.cursor/rules/typescript.md +15 -0
  27. package/templates/adapters/gemini/README.md +16 -0
  28. package/templates/adapters/generic/README.md +14 -0
  29. package/templates/core/.devflow/workflows.yml +21 -0
  30. package/templates/core/AGENTS.md +44 -0
  31. package/templates/core/DEVFLOW.md +138 -0
  32. package/templates/prompts/build.txt +13 -0
  33. package/templates/prompts/plan.txt +13 -0
  34. package/templates/prompts/review.txt +14 -0
  35. package/templates/prompts/tests.txt +18 -0
  36. package/templates/prompts/verify.txt +15 -0
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.isManaged = void 0;
37
+ exports.collectFiles = collectFiles;
38
+ exports.buildInstallList = buildInstallList;
39
+ exports.parseAdapterList = parseAdapterList;
40
+ exports.resolveTarget = resolveTarget;
41
+ exports.exists = exists;
42
+ exports.copyItem = copyItem;
43
+ exports.hasAnyPath = hasAnyPath;
44
+ exports.validate = validate;
45
+ exports.detectDefaultAdapters = detectDefaultAdapters;
46
+ exports.resolveAdapters = resolveAdapters;
47
+ const fs = __importStar(require("fs"));
48
+ const path = __importStar(require("path"));
49
+ const constants_1 = require("./constants");
50
+ Object.defineProperty(exports, "isManaged", { enumerable: true, get: function () { return constants_1.isManaged; } });
51
+ function collectFiles(dir, base = '') {
52
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
53
+ const files = [];
54
+ for (const entry of entries) {
55
+ const rel = base ? path.join(base, entry.name) : entry.name;
56
+ if (entry.isDirectory()) {
57
+ files.push(...collectFiles(path.join(dir, entry.name), rel));
58
+ }
59
+ else {
60
+ files.push(rel);
61
+ }
62
+ }
63
+ return files;
64
+ }
65
+ // Returns [{ src: absolutePath, dest: relPathInTarget }].
66
+ // core/ root files (AGENTS.md, DEVFLOW.md) go to target root.
67
+ // prompts/ goes to target/devflow/prompts/ (namespaced to avoid clashes).
68
+ // Each adapter's srcDir is mapped to its destDir.
69
+ function buildInstallList(adapters) {
70
+ const sources = [
71
+ { srcDir: path.join(constants_1.TEMPLATES_ROOT, 'core'), destPrefix: '' },
72
+ { srcDir: path.join(constants_1.TEMPLATES_ROOT, 'prompts'), destPrefix: path.join('devflow', 'prompts') },
73
+ ];
74
+ for (const adapter of adapters) {
75
+ const { srcDir, destDir } = constants_1.ADAPTER_CONFIG[adapter];
76
+ sources.push({ srcDir: path.join(constants_1.TEMPLATES_ROOT, srcDir), destPrefix: destDir });
77
+ }
78
+ const items = [];
79
+ for (const { srcDir, destPrefix } of sources) {
80
+ if (!fs.existsSync(srcDir))
81
+ continue;
82
+ for (const rel of collectFiles(srcDir)) {
83
+ const dest = destPrefix ? path.join(destPrefix, rel) : rel;
84
+ items.push({ src: path.join(srcDir, rel), dest });
85
+ }
86
+ }
87
+ return items;
88
+ }
89
+ function parseAdapterList(raw) {
90
+ if (!raw)
91
+ return [];
92
+ const adapters = raw
93
+ .split(',')
94
+ .map((tool) => tool.trim().toLowerCase())
95
+ .filter(Boolean);
96
+ if (adapters.includes('all') && adapters.length > 1) {
97
+ console.error('Error: "all" cannot be combined with other adapters.');
98
+ process.exit(1);
99
+ }
100
+ if (adapters.includes('none') && adapters.length > 1) {
101
+ console.error('Error: "none" cannot be combined with other adapters.');
102
+ process.exit(1);
103
+ }
104
+ if (adapters.length === 1 && adapters[0] === 'all') {
105
+ return constants_1.ALL_ADAPTERS;
106
+ }
107
+ if (adapters.length === 1 && adapters[0] === 'none') {
108
+ return [];
109
+ }
110
+ const invalid = adapters.filter((adapter) => !Object.prototype.hasOwnProperty.call(constants_1.ADAPTER_CONFIG, adapter));
111
+ if (invalid.length > 0) {
112
+ console.error(`Error: unknown adapter(s): ${invalid.join(', ')}`);
113
+ console.error(`Valid values: ${constants_1.ALL_ADAPTERS.join(', ')}, none, all`);
114
+ process.exit(1);
115
+ }
116
+ return [...new Set(adapters)];
117
+ }
118
+ function resolveTarget(raw) {
119
+ const resolved = path.resolve(raw);
120
+ if (!fs.existsSync(resolved)) {
121
+ console.error(`Error: target path does not exist: ${resolved}`);
122
+ process.exit(1);
123
+ }
124
+ if (!fs.statSync(resolved).isDirectory()) {
125
+ console.error(`Error: target is not a directory: ${resolved}`);
126
+ process.exit(1);
127
+ }
128
+ return resolved;
129
+ }
130
+ function exists(dest, targetDir) {
131
+ return fs.existsSync(path.join(targetDir, dest));
132
+ }
133
+ function copyItem(item, targetDir) {
134
+ const destPath = path.join(targetDir, item.dest);
135
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
136
+ fs.copyFileSync(item.src, destPath);
137
+ }
138
+ function hasAnyPath(targetDir, paths) {
139
+ return paths.some((rel) => exists(rel, targetDir));
140
+ }
141
+ // Checks core files (always) + one key file per selected adapter.
142
+ function validate(adapters, targetDir) {
143
+ const failures = [];
144
+ for (const file of constants_1.CORE_KEY_FILES) {
145
+ if (!exists(file, targetDir)) {
146
+ failures.push(`${file} (from core)`);
147
+ }
148
+ }
149
+ for (const adapter of adapters) {
150
+ const { keyFile } = constants_1.ADAPTER_CONFIG[adapter];
151
+ if (keyFile && !exists(keyFile, targetDir)) {
152
+ failures.push(`${keyFile} (${adapter})`);
153
+ }
154
+ }
155
+ return failures;
156
+ }
157
+ function detectDefaultAdapters(targetDir) {
158
+ if (fs.existsSync(path.join(targetDir, constants_1.DEFAULT_CURSOR_MARKER))) {
159
+ return ['cursor'];
160
+ }
161
+ if (fs.existsSync(path.join(targetDir, constants_1.DEFAULT_CLAUDE_MARKER))) {
162
+ return ['claude'];
163
+ }
164
+ return ['generic'];
165
+ }
166
+ function resolveAdapters(options, targetDir) {
167
+ const adapterArg = options.adapters ?? options.adapter ?? options.tool;
168
+ if (options.adapters && (options.adapter || options.tool)) {
169
+ console.error('Error: use either --adapter/--tool or --adapters, not both.');
170
+ process.exit(1);
171
+ }
172
+ if (!adapterArg) {
173
+ return detectDefaultAdapters(targetDir);
174
+ }
175
+ return parseAdapterList(adapterArg);
176
+ }
@@ -0,0 +1,184 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.readPluginsFile = readPluginsFile;
37
+ exports.writePluginsFile = writePluginsFile;
38
+ exports.isNpmPackage = isNpmPackage;
39
+ exports.resolvePluginSource = resolvePluginSource;
40
+ exports.loadPluginManifest = loadPluginManifest;
41
+ exports.resolvePluginPaths = resolvePluginPaths;
42
+ exports.buildPluginInstallList = buildPluginInstallList;
43
+ const fs = __importStar(require("fs"));
44
+ const path = __importStar(require("path"));
45
+ const os = __importStar(require("os"));
46
+ const child_process_1 = require("child_process");
47
+ const constants_1 = require("./constants");
48
+ const install_1 = require("./install");
49
+ // ─── plugins.yml I/O ────────────────────────────────────────────────────────
50
+ function readPluginsFile(targetDir) {
51
+ const filePath = path.join(targetDir, constants_1.PLUGINS_FILE);
52
+ if (!fs.existsSync(filePath)) {
53
+ return { plugins: [] };
54
+ }
55
+ const raw = fs.readFileSync(filePath, 'utf8').trim();
56
+ if (!raw)
57
+ return { plugins: [] };
58
+ // Minimal YAML parser for our simple format (no dependency required)
59
+ const plugins = [];
60
+ let current = null;
61
+ for (const line of raw.split('\n')) {
62
+ const trimmed = line.trim();
63
+ if (trimmed === 'plugins:')
64
+ continue;
65
+ if (trimmed.startsWith('- name:')) {
66
+ if (current)
67
+ plugins.push(current);
68
+ current = { name: trimmed.replace('- name:', '').trim() };
69
+ }
70
+ else if (current && trimmed.startsWith('source:')) {
71
+ current.source = trimmed.replace('source:', '').trim();
72
+ }
73
+ else if (current && trimmed.startsWith('version:')) {
74
+ current.version = trimmed.replace('version:', '').trim();
75
+ }
76
+ else if (current && trimmed.startsWith('destDir:')) {
77
+ current.destDir = trimmed.replace('destDir:', '').trim();
78
+ }
79
+ else if (current && trimmed.startsWith('keyFile:')) {
80
+ current.keyFile = trimmed.replace('keyFile:', '').trim();
81
+ }
82
+ }
83
+ if (current)
84
+ plugins.push(current);
85
+ return { plugins };
86
+ }
87
+ function writePluginsFile(targetDir, data) {
88
+ const filePath = path.join(targetDir, constants_1.PLUGINS_FILE);
89
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
90
+ const lines = ['plugins:'];
91
+ for (const p of data.plugins) {
92
+ lines.push(` - name: ${p.name}`);
93
+ lines.push(` source: ${p.source}`);
94
+ lines.push(` version: ${p.version}`);
95
+ lines.push(` destDir: ${p.destDir}`);
96
+ lines.push(` keyFile: ${p.keyFile}`);
97
+ }
98
+ fs.writeFileSync(filePath, lines.join('\n') + '\n', 'utf8');
99
+ }
100
+ // ─── source resolution ───────────────────────────────────────────────────────
101
+ function isNpmPackage(source) {
102
+ // Not a path — doesn't start with . / ~
103
+ return !source.startsWith('.') && !source.startsWith('/') && !source.startsWith('~');
104
+ }
105
+ function resolvePluginSource(source) {
106
+ if (!isNpmPackage(source)) {
107
+ // Local path
108
+ const resolved = path.resolve(source);
109
+ if (!fs.existsSync(resolved)) {
110
+ console.error(`Error: plugin source does not exist: ${resolved}`);
111
+ process.exit(1);
112
+ }
113
+ return { pluginDir: resolved, cleanup: null };
114
+ }
115
+ // npm package — install into temp dir
116
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'devflow-plugin-'));
117
+ try {
118
+ console.log(` … fetching ${source} from npm`);
119
+ (0, child_process_1.execSync)(`npm install --prefix "${tmp}" "${source}" --no-save --silent`, { stdio: 'pipe' });
120
+ }
121
+ catch (err) {
122
+ fs.rmSync(tmp, { recursive: true, force: true });
123
+ const msg = err instanceof Error ? err.message : String(err);
124
+ console.error(`Error: failed to install npm package "${source}".\n${msg}`);
125
+ process.exit(1);
126
+ }
127
+ // Resolve the package name (strip scope for directory lookup)
128
+ const pkgName = source.split('@').filter(Boolean)[0];
129
+ const pluginDir = path.join(tmp, 'node_modules', pkgName);
130
+ if (!fs.existsSync(pluginDir)) {
131
+ fs.rmSync(tmp, { recursive: true, force: true });
132
+ console.error(`Error: could not locate package "${pkgName}" after install.`);
133
+ process.exit(1);
134
+ }
135
+ return {
136
+ pluginDir,
137
+ cleanup: () => fs.rmSync(tmp, { recursive: true, force: true }),
138
+ };
139
+ }
140
+ // ─── manifest loading ────────────────────────────────────────────────────────
141
+ function loadPluginManifest(pluginDir) {
142
+ const manifestPath = path.join(pluginDir, 'devflow-plugin.json');
143
+ if (!fs.existsSync(manifestPath)) {
144
+ console.error(`Error: no devflow-plugin.json found in ${pluginDir}`);
145
+ console.error(' Every Devflow plugin must have a devflow-plugin.json manifest.');
146
+ process.exit(1);
147
+ }
148
+ let manifest;
149
+ try {
150
+ manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
151
+ }
152
+ catch {
153
+ console.error(`Error: devflow-plugin.json in ${pluginDir} is not valid JSON.`);
154
+ process.exit(1);
155
+ }
156
+ if (!manifest.name || typeof manifest.name !== 'string') {
157
+ console.error('Error: devflow-plugin.json must have a "name" string field.');
158
+ process.exit(1);
159
+ }
160
+ if (!manifest.version || typeof manifest.version !== 'string') {
161
+ console.error('Error: devflow-plugin.json must have a "version" string field.');
162
+ process.exit(1);
163
+ }
164
+ return manifest;
165
+ }
166
+ // ─── install helpers ─────────────────────────────────────────────────────────
167
+ function resolvePluginPaths(manifest) {
168
+ const destDir = manifest.destDir ?? path.join('.devflow', 'plugins', manifest.name);
169
+ const keyFile = manifest.keyFile ?? path.join(destDir, 'README.md');
170
+ return { destDir, keyFile };
171
+ }
172
+ function buildPluginInstallList(pluginDir) {
173
+ const templatesDir = path.join(pluginDir, 'templates');
174
+ if (!fs.existsSync(templatesDir)) {
175
+ console.error(`Error: plugin has no templates/ directory in ${pluginDir}`);
176
+ process.exit(1);
177
+ }
178
+ // templates/ mirrors the project root layout — each file's relative path
179
+ // is its destination path in the target project.
180
+ return (0, install_1.collectFiles)(templatesDir).map((rel) => ({
181
+ src: path.join(templatesDir, rel),
182
+ dest: rel,
183
+ }));
184
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "devflow-agent-cli",
3
+ "version": "0.1.0",
4
+ "description": "Portable AI development workflow kit with a universal core and optional adapters",
5
+ "main": "dist/cli.js",
6
+ "bin": {
7
+ "devflow": "bin/devflow.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "dev": "tsc && node dist/cli.js",
12
+ "test:ci-smoke": "node scripts/ci-smoke-test.mjs",
13
+ "test:doctor": "node scripts/doctor-test.mjs",
14
+ "test:explain": "node scripts/explain-test.mjs",
15
+ "test:unit": "node scripts/unit-test.mjs",
16
+ "validate": "node scripts/validate-templates.mjs",
17
+ "test": "tsc && node scripts/unit-test.mjs && node scripts/validate-templates.mjs && node scripts/smoke-test.mjs && node scripts/doctor-test.mjs && node scripts/explain-test.mjs"
18
+ },
19
+ "files": [
20
+ "bin",
21
+ "dist",
22
+ "templates",
23
+ "README.md",
24
+ "LICENSE"
25
+ ],
26
+ "keywords": [
27
+ "cursor",
28
+ "claude",
29
+ "codex",
30
+ "gemini",
31
+ "anthropic",
32
+ "ai",
33
+ "workflow",
34
+ "cli",
35
+ "agents",
36
+ "prompts",
37
+ "developer-tools"
38
+ ],
39
+ "author": "mayordomoespejo",
40
+ "license": "MIT",
41
+ "engines": {
42
+ "node": ">=18"
43
+ },
44
+ "dependencies": {
45
+ "commander": "^12.0.0"
46
+ },
47
+ "devDependencies": {
48
+ "@types/node": "^22.0.0",
49
+ "typescript": "^5.0.0"
50
+ }
51
+ }
@@ -0,0 +1,15 @@
1
+ # Devflow Build
2
+
3
+ Implement the agreed plan for this task.
4
+
5
+ If the user provided inline command text, treat it as the task description and any accepted plan details. Otherwise use the current conversation context.
6
+
7
+ Requirements:
8
+
9
+ 1. Make the smallest working change that satisfies the plan
10
+ 2. Modify existing code before adding new abstractions
11
+ 3. Keep functions small and explicit
12
+ 4. Preserve existing behavior outside the requested change
13
+ 5. Note any follow-up risks or tradeoffs if they remain
14
+
15
+ Return the implementation and briefly explain what changed.
@@ -0,0 +1,74 @@
1
+ # Devflow Commit
2
+
3
+ Propose a commit (or commits) for the current changes. Always propose before executing — never commit automatically.
4
+
5
+ If the user provided inline text, treat it as a hint about scope or message style.
6
+
7
+ ---
8
+
9
+ ## Step 1 — Detect changes
10
+
11
+ Run: `git status --porcelain`
12
+
13
+ If there are no changes: respond "No changes to commit." and stop.
14
+
15
+ ## Step 2 — Scan for secrets
16
+
17
+ Run `git diff` and `git diff --staged`. Before proposing anything, scan the output for obvious secret patterns:
18
+
19
+ - `API_KEY=`, `SECRET=`, `TOKEN=`, `PASSWORD=`
20
+ - `BEGIN PRIVATE KEY`, `BEGIN RSA PRIVATE KEY`
21
+ - `Authorization: Bearer <long-string>`
22
+ - Random 32+ character strings in sensitive variable names
23
+
24
+ If a likely secret is found:
25
+ - **Stop. Do not continue with the commit proposal.**
26
+ - Warn the user: describe the exact file and line.
27
+ - Suggest: remove, rotate, use an env var, add the file to `.gitignore`.
28
+
29
+ ## Step 3 — Analyze and propose
30
+
31
+ Read the full diff. Group logically related changes.
32
+
33
+ - **1 commit** if the changes form a single coherent unit.
34
+ - **2–4 commits** if there are clearly separable concerns (implementation / tests / docs / config).
35
+
36
+ Use Conventional Commits format:
37
+
38
+ ```
39
+ <type>(<scope>): <subject>
40
+ ```
41
+
42
+ Types: `feat` | `fix` | `refactor` | `test` | `docs` | `chore` | `perf` | `ci`
43
+ Subject: imperative mood, lowercase, no period, max 72 characters.
44
+
45
+ ## Step 4 — Present the proposal
46
+
47
+ Show each commit clearly:
48
+
49
+ ```
50
+ Commit 1
51
+ Message: feat(auth): add OAuth2 token refresh
52
+ Files: src/auth/token.ts, src/auth/refresh.ts
53
+ Command:
54
+ git add src/auth/token.ts src/auth/refresh.ts
55
+ git commit -m "feat(auth): add OAuth2 token refresh"
56
+ ```
57
+
58
+ Do not execute any git commands yet.
59
+
60
+ ## Step 5 — Ask for confirmation
61
+
62
+ Ask: **"Execute these git commands? (y/n)"**
63
+
64
+ - **y / yes / sí** → run the exact commands shown.
65
+ - **n / no** → do not execute. Leave the proposal available to copy.
66
+
67
+ ## Always
68
+
69
+ - Never run `git push`.
70
+ - Never run `git commit --amend` or `git rebase` unless explicitly requested.
71
+ - Never use `--no-verify`.
72
+ - Never use `git add -A` or `git add .` unless the user explicitly requests it.
73
+ - Stage only the specific files listed in each commit's proposal.
74
+ - If a commit hook fails, report the error and stop.
@@ -0,0 +1,15 @@
1
+ # Devflow Plan
2
+
3
+ Create a step-by-step implementation plan for this task.
4
+
5
+ If the user provided inline command text, treat it as the task description. Otherwise use the active conversation context.
6
+
7
+ The response must include:
8
+
9
+ 1. What needs to be built or changed
10
+ 2. Which files will be affected
11
+ 3. Functions, components, or types to create or modify
12
+ 4. Tests that should pass when done
13
+ 5. Edge cases and risks to address
14
+
15
+ Do not write code yet.
@@ -0,0 +1,16 @@
1
+ # Devflow Review
2
+
3
+ Review the current implementation as a senior engineer.
4
+
5
+ If the user provided inline command text, treat it as additional focus for the review.
6
+
7
+ Check for:
8
+
9
+ - Bugs and logic errors
10
+ - Unhandled edge cases
11
+ - Security issues
12
+ - Duplicated logic
13
+ - Unnecessary complexity
14
+ - Material performance issues
15
+
16
+ List concrete findings first and suggest fixes.
@@ -0,0 +1,14 @@
1
+ # Devflow Tests
2
+
3
+ Generate tests for the current implementation.
4
+
5
+ If the user provided inline command text, treat it as extra scope. Otherwise use the current conversation context and repository state.
6
+
7
+ Cover:
8
+
9
+ - The happy path
10
+ - Edge cases from planning
11
+ - Error states and invalid input
12
+ - Boundary conditions
13
+
14
+ Prefer higher-value tests with minimal mocking.
@@ -0,0 +1,16 @@
1
+ # Devflow Verify
2
+
3
+ Verify the implementation against the agreed plan before it is considered done.
4
+
5
+ If the user provided inline command text, treat it as additional context.
6
+
7
+ Check:
8
+
9
+ 1. Compilation or runtime correctness
10
+ 2. Tests passing
11
+ 3. Planned edge cases handled
12
+ 4. No unnecessary duplication
13
+ 5. Simplicity of the solution
14
+ 6. No exposed sensitive data
15
+
16
+ If issues remain, list them clearly. Otherwise confirm it is ready to ship.
@@ -0,0 +1,14 @@
1
+ # Codex Adapter
2
+
3
+ Devflow does not install a Codex-specific adapter configuration in this step.
4
+
5
+ Reason:
6
+
7
+ - the core workflow already lives in `AGENTS.md`, `DEVFLOW.md`, and `devflow/prompts/`
8
+ - this step avoids inventing extra Codex integration files without a clear contract
9
+
10
+ Use Devflow with Codex by:
11
+
12
+ 1. Installing the core files
13
+ 2. Running Codex from the project root so it can read `AGENTS.md`
14
+ 3. Reusing the prompts in `devflow/prompts/` when you want explicit PLAN, BUILD, TEST, REVIEW, or VERIFY phases
@@ -0,0 +1,15 @@
1
+ # Devflow Build
2
+
3
+ Implement the agreed plan for this task.
4
+
5
+ If the user provided inline command text, treat it as the task description and any accepted plan details. Otherwise use the current conversation context.
6
+
7
+ Requirements:
8
+
9
+ 1. Make the smallest working change that satisfies the plan
10
+ 2. Modify existing code before adding new abstractions
11
+ 3. Keep functions small and explicit
12
+ 4. Preserve existing behavior outside the requested change
13
+ 5. Note any follow-up risks or tradeoffs if they remain
14
+
15
+ Return the implementation and briefly explain what changed.
@@ -0,0 +1,15 @@
1
+ # Devflow Plan
2
+
3
+ Create a step-by-step implementation plan for this task.
4
+
5
+ If the user provided inline command text, treat it as the task description. Otherwise use the active conversation context.
6
+
7
+ The response must include:
8
+
9
+ 1. What needs to be built or changed
10
+ 2. Which files will be affected
11
+ 3. Functions, components, or types to create or modify
12
+ 4. Tests that should pass when done
13
+ 5. Edge cases and risks to address
14
+
15
+ Do not write code yet.
@@ -0,0 +1,16 @@
1
+ # Devflow Review
2
+
3
+ Review the current implementation as a senior engineer.
4
+
5
+ If the user provided inline command text, treat it as additional focus for the review.
6
+
7
+ Check for:
8
+
9
+ - Bugs and logic errors
10
+ - Unhandled edge cases
11
+ - Security issues
12
+ - Duplicated logic
13
+ - Unnecessary complexity
14
+ - Material performance issues
15
+
16
+ List concrete findings first and suggest fixes.
@@ -0,0 +1,14 @@
1
+ # Devflow Tests
2
+
3
+ Generate tests for the current implementation.
4
+
5
+ If the user provided inline command text, treat it as extra scope. Otherwise use the current conversation context and repository state.
6
+
7
+ Cover:
8
+
9
+ - The happy path
10
+ - Edge cases from planning
11
+ - Error states and invalid input
12
+ - Boundary conditions
13
+
14
+ Prefer higher-value tests with minimal mocking.
@@ -0,0 +1,16 @@
1
+ # Devflow Verify
2
+
3
+ Verify the implementation against the agreed plan before it is considered done.
4
+
5
+ If the user provided inline command text, treat it as additional context.
6
+
7
+ Check:
8
+
9
+ 1. Compilation or runtime correctness
10
+ 2. Tests passing
11
+ 3. Planned edge cases handled
12
+ 4. No unnecessary duplication
13
+ 5. Simplicity of the solution
14
+ 6. No exposed sensitive data
15
+
16
+ If issues remain, list them clearly. Otherwise confirm it is ready to ship.