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.
- package/LICENSE +21 -0
- package/README.md +313 -0
- package/bin/devflow.js +2 -0
- package/dist/cli.js +52 -0
- package/dist/commands/add.js +137 -0
- package/dist/commands/doctor.js +246 -0
- package/dist/commands/explain.js +204 -0
- package/dist/commands/init.js +91 -0
- package/dist/constants.js +120 -0
- package/dist/install.js +176 -0
- package/dist/plugins.js +184 -0
- package/dist/types.js +2 -0
- package/package.json +51 -0
- package/templates/adapters/claude/.claude/commands/build.md +15 -0
- package/templates/adapters/claude/.claude/commands/commit.md +74 -0
- package/templates/adapters/claude/.claude/commands/plan.md +15 -0
- package/templates/adapters/claude/.claude/commands/review.md +16 -0
- package/templates/adapters/claude/.claude/commands/tests.md +14 -0
- package/templates/adapters/claude/.claude/commands/verify.md +16 -0
- package/templates/adapters/codex/README.md +14 -0
- package/templates/adapters/cursor/.cursor/commands/build.md +15 -0
- package/templates/adapters/cursor/.cursor/commands/plan.md +15 -0
- package/templates/adapters/cursor/.cursor/commands/review.md +16 -0
- package/templates/adapters/cursor/.cursor/commands/tests.md +14 -0
- package/templates/adapters/cursor/.cursor/commands/verify.md +16 -0
- package/templates/adapters/cursor/.cursor/rules/typescript.md +15 -0
- package/templates/adapters/gemini/README.md +16 -0
- package/templates/adapters/generic/README.md +14 -0
- package/templates/core/.devflow/workflows.yml +21 -0
- package/templates/core/AGENTS.md +44 -0
- package/templates/core/DEVFLOW.md +138 -0
- package/templates/prompts/build.txt +13 -0
- package/templates/prompts/plan.txt +13 -0
- package/templates/prompts/review.txt +14 -0
- package/templates/prompts/tests.txt +18 -0
- package/templates/prompts/verify.txt +15 -0
package/dist/install.js
ADDED
|
@@ -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
|
+
}
|
package/dist/plugins.js
ADDED
|
@@ -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
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.
|