deepflow 0.1.52 → 0.1.53
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/README.md +140 -55
- package/bin/deepflow-auto.sh +1248 -0
- package/bin/install.js +21 -0
- package/hooks/df-spec-lint.js +197 -0
- package/package.json +1 -1
- package/src/commands/df/plan.md +4 -0
- package/src/commands/df/spec.md +7 -1
package/bin/install.js
CHANGED
|
@@ -8,6 +8,18 @@ const fs = require('fs');
|
|
|
8
8
|
const path = require('path');
|
|
9
9
|
const os = require('os');
|
|
10
10
|
const readline = require('readline');
|
|
11
|
+
const { execFileSync } = require('child_process');
|
|
12
|
+
|
|
13
|
+
// Subcommand routing: `deepflow auto [...]` -> bin/deepflow-auto.sh
|
|
14
|
+
if (process.argv[2] === 'auto') {
|
|
15
|
+
const scriptPath = path.join(__dirname, 'deepflow-auto.sh');
|
|
16
|
+
try {
|
|
17
|
+
execFileSync('bash', [scriptPath, ...process.argv.slice(3)], { stdio: 'inherit' });
|
|
18
|
+
} catch (e) {
|
|
19
|
+
process.exit(e.status || 1);
|
|
20
|
+
}
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
11
23
|
|
|
12
24
|
// Colors
|
|
13
25
|
const c = {
|
|
@@ -118,6 +130,13 @@ async function main() {
|
|
|
118
130
|
}
|
|
119
131
|
}
|
|
120
132
|
|
|
133
|
+
// Ensure deepflow-auto.sh is executable
|
|
134
|
+
const autoScript = path.join(PACKAGE_DIR, 'bin', 'deepflow-auto.sh');
|
|
135
|
+
if (fs.existsSync(autoScript)) {
|
|
136
|
+
fs.chmodSync(autoScript, 0o755);
|
|
137
|
+
log('deepflow-auto.sh marked executable');
|
|
138
|
+
}
|
|
139
|
+
|
|
121
140
|
// Get version from package.json (single source of truth)
|
|
122
141
|
const packageJson = require(path.join(PACKAGE_DIR, 'package.json'));
|
|
123
142
|
const installedVersion = packageJson.version;
|
|
@@ -154,9 +173,11 @@ async function main() {
|
|
|
154
173
|
console.log(' commands/df/ — /df:discover, /df:debate, /df:spec, /df:plan, /df:execute, /df:verify, /df:note, /df:resume, /df:update');
|
|
155
174
|
console.log(' skills/ — gap-discovery, atomic-commits, code-completeness');
|
|
156
175
|
console.log(' agents/ — reasoner');
|
|
176
|
+
console.log(' bin/ — deepflow auto (autonomous overnight execution)');
|
|
157
177
|
if (level === 'global') {
|
|
158
178
|
console.log(' hooks/ — statusline, update checker');
|
|
159
179
|
}
|
|
180
|
+
console.log(' hooks/df-spec-* — spec validation (auto-enforced by /df:spec and /df:plan)');
|
|
160
181
|
console.log(' env/ — ENABLE_LSP_TOOL (code navigation via goToDefinition, findReferences, workspaceSymbol)');
|
|
161
182
|
console.log('');
|
|
162
183
|
if (level === 'project') {
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* deepflow spec linter
|
|
4
|
+
* Validates spec files against hard invariants and advisory checks.
|
|
5
|
+
*
|
|
6
|
+
* Usage (CLI): node df-spec-lint.js <spec-file.md>
|
|
7
|
+
* Usage (module): const { validateSpec } = require('./df-spec-lint');
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const REQUIRED_SECTIONS = [
|
|
13
|
+
'Objective',
|
|
14
|
+
'Requirements',
|
|
15
|
+
'Constraints',
|
|
16
|
+
'Out of Scope',
|
|
17
|
+
'Acceptance Criteria',
|
|
18
|
+
'Technical Notes',
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Validate a spec's content against hard invariants and advisory checks.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} content - The raw markdown content of the spec file.
|
|
25
|
+
* @param {object} opts
|
|
26
|
+
* @param {'interactive'|'auto'} opts.mode
|
|
27
|
+
* @returns {{ hard: string[], advisory: string[] }}
|
|
28
|
+
*/
|
|
29
|
+
function validateSpec(content, { mode = 'interactive' } = {}) {
|
|
30
|
+
const hard = [];
|
|
31
|
+
const advisory = [];
|
|
32
|
+
|
|
33
|
+
// ── (a) Required sections ────────────────────────────────────────────
|
|
34
|
+
const headersFound = [];
|
|
35
|
+
for (const line of content.split('\n')) {
|
|
36
|
+
const m = line.match(/^##\s+(.+)/i);
|
|
37
|
+
if (m) headersFound.push(m[1].trim());
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
for (const section of REQUIRED_SECTIONS) {
|
|
41
|
+
const found = headersFound.some(
|
|
42
|
+
(h) => h.toLowerCase() === section.toLowerCase()
|
|
43
|
+
);
|
|
44
|
+
if (!found) {
|
|
45
|
+
hard.push(`Missing required section: "## ${section}"`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ── (b) Requirement lines must have REQ-N: prefix ───────────────────
|
|
50
|
+
const reqSection = extractSection(content, 'Requirements');
|
|
51
|
+
if (reqSection !== null) {
|
|
52
|
+
const lines = reqSection.split('\n');
|
|
53
|
+
for (const line of lines) {
|
|
54
|
+
// Only consider list items (lines starting with - or *)
|
|
55
|
+
if (!/^\s*[-*]\s+/.test(line)) continue;
|
|
56
|
+
// Must match REQ-\d+: with optional bold markers
|
|
57
|
+
if (!/^\s*[-*]\s*\*{0,2}(REQ-\d+)\*{0,2}\s*:/.test(line)) {
|
|
58
|
+
hard.push(
|
|
59
|
+
`Requirement line missing REQ-N: prefix: "${line.trim()}"`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ── (c) Acceptance Criteria must use checkbox format ─────────────────
|
|
66
|
+
const acSection = extractSection(content, 'Acceptance Criteria');
|
|
67
|
+
if (acSection !== null) {
|
|
68
|
+
const lines = acSection.split('\n');
|
|
69
|
+
for (const line of lines) {
|
|
70
|
+
if (!/^\s*[-*]\s+/.test(line)) continue;
|
|
71
|
+
if (!/^\s*- \[ \]/.test(line)) {
|
|
72
|
+
hard.push(
|
|
73
|
+
`Acceptance Criteria line missing "- [ ]" checkbox: "${line.trim()}"`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ── (d) No duplicate REQ-N IDs ──────────────────────────────────────
|
|
80
|
+
const reqIdPattern = /\*{0,2}(REQ-\d+)\*{0,2}\s*:/g;
|
|
81
|
+
const seenIds = new Map();
|
|
82
|
+
let match;
|
|
83
|
+
while ((match = reqIdPattern.exec(content)) !== null) {
|
|
84
|
+
const id = match[1];
|
|
85
|
+
if (seenIds.has(id)) {
|
|
86
|
+
hard.push(`Duplicate requirement ID: ${id}`);
|
|
87
|
+
}
|
|
88
|
+
seenIds.set(id, true);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ── Advisory checks ──────────────────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
// (adv-a) Line count > 100
|
|
94
|
+
const lineCount = content.split('\n').length;
|
|
95
|
+
if (lineCount > 100) {
|
|
96
|
+
advisory.push(`Spec exceeds 100 lines (${lineCount} lines)`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// (adv-b) Orphaned REQ-N IDs not referenced in Acceptance Criteria
|
|
100
|
+
if (reqSection !== null && acSection !== null) {
|
|
101
|
+
const reqIds = [];
|
|
102
|
+
const reqLinePattern = /\*{0,2}(REQ-\d+)\*{0,2}\s*:/g;
|
|
103
|
+
let reqMatch;
|
|
104
|
+
while ((reqMatch = reqLinePattern.exec(reqSection)) !== null) {
|
|
105
|
+
reqIds.push(reqMatch[1]);
|
|
106
|
+
}
|
|
107
|
+
for (const id of reqIds) {
|
|
108
|
+
if (!acSection.includes(id)) {
|
|
109
|
+
advisory.push(`Orphaned requirement: ${id} not found in Acceptance Criteria`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// (adv-c) Technical Notes section > 10 lines
|
|
115
|
+
const techNotes = extractSection(content, 'Technical Notes');
|
|
116
|
+
if (techNotes !== null) {
|
|
117
|
+
const techLines = techNotes.split('\n').filter((l) => l.trim().length > 0);
|
|
118
|
+
if (techLines.length > 10) {
|
|
119
|
+
advisory.push(`Technical Notes section too long (${techLines.length} non-empty lines, limit 10)`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// (adv-d) More than 12 requirements
|
|
124
|
+
if (seenIds.size > 12) {
|
|
125
|
+
advisory.push(`Too many requirements (${seenIds.size}, limit 12)`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ── Auto-mode escalation ─────────────────────────────────────────────
|
|
129
|
+
if (mode === 'auto') {
|
|
130
|
+
hard.push(...advisory.splice(0, advisory.length));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return { hard, advisory };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Extract the content of a named ## section (up to the next ## or EOF).
|
|
138
|
+
* Returns null if the section is not found.
|
|
139
|
+
*/
|
|
140
|
+
function extractSection(content, sectionName) {
|
|
141
|
+
const lines = content.split('\n');
|
|
142
|
+
let capturing = false;
|
|
143
|
+
const captured = [];
|
|
144
|
+
|
|
145
|
+
for (const line of lines) {
|
|
146
|
+
const headerMatch = line.match(/^## \s*(.+)/);
|
|
147
|
+
if (headerMatch) {
|
|
148
|
+
if (capturing) break; // hit the next section
|
|
149
|
+
if (headerMatch[1].trim().toLowerCase() === sectionName.toLowerCase()) {
|
|
150
|
+
capturing = true;
|
|
151
|
+
}
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (capturing) {
|
|
155
|
+
captured.push(line);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return capturing ? captured.join('\n') : null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ── CLI entry point ──────────────────────────────────────────────────────
|
|
163
|
+
if (require.main === module) {
|
|
164
|
+
const fs = require('fs');
|
|
165
|
+
|
|
166
|
+
const filePath = process.argv[2];
|
|
167
|
+
if (!filePath) {
|
|
168
|
+
console.error('Usage: df-spec-lint.js <spec-file.md>');
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
173
|
+
const mode = process.argv.includes('--auto') ? 'auto' : 'interactive';
|
|
174
|
+
const result = validateSpec(content, { mode });
|
|
175
|
+
|
|
176
|
+
if (result.hard.length > 0) {
|
|
177
|
+
console.error('HARD invariant failures:');
|
|
178
|
+
for (const msg of result.hard) {
|
|
179
|
+
console.error(` [FAIL] ${msg}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (result.advisory.length > 0) {
|
|
184
|
+
console.warn('Advisory warnings:');
|
|
185
|
+
for (const msg of result.advisory) {
|
|
186
|
+
console.warn(` [WARN] ${msg}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (result.hard.length === 0 && result.advisory.length === 0) {
|
|
191
|
+
console.log('All checks passed.');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
process.exit(result.hard.length > 0 ? 1 : 0);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
module.exports = { validateSpec };
|
package/package.json
CHANGED
package/src/commands/df/plan.md
CHANGED
|
@@ -36,6 +36,8 @@ Load:
|
|
|
36
36
|
Determine source_dir from config or default to src/
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
Run `validateSpec` on each loaded spec. Hard failures → skip that spec entirely and emit an error line. Advisory warnings → include them in plan output.
|
|
40
|
+
|
|
39
41
|
If no new specs: report counts, suggest `/df:execute`.
|
|
40
42
|
|
|
41
43
|
### 2. CHECK PAST EXPERIMENTS (SPIKE-FIRST)
|
|
@@ -98,6 +100,8 @@ Scale agent count based on codebase size:
|
|
|
98
100
|
|
|
99
101
|
Spawn `Task(subagent_type="reasoner", model="opus")`. Reasoner maps each requirement to DONE / PARTIAL / MISSING / CONFLICT. Flag spec gaps; don't silently assume.
|
|
100
102
|
|
|
103
|
+
Check spec health: verify REQ-AC alignment, requirement clarity, and completeness. Note any issues (orphan ACs, vague requirements) in plan output.
|
|
104
|
+
|
|
101
105
|
**Priority order:** Dependencies → Impact → Risk
|
|
102
106
|
|
|
103
107
|
### 6. GENERATE SPIKE TASKS (IF NEEDED)
|
package/src/commands/df/spec.md
CHANGED
|
@@ -86,10 +86,16 @@ The reasoner will:
|
|
|
86
86
|
- Identify constraints from existing architecture
|
|
87
87
|
- Suggest requirements based on patterns found
|
|
88
88
|
- Flag potential conflicts with existing code
|
|
89
|
+
- Verify every REQ-N has at least one corresponding Acceptance Criterion; flag any uncovered requirements
|
|
90
|
+
- Identify and flag vague or untestable requirements before finalizing (e.g., "should be fast" without a metric)
|
|
89
91
|
|
|
90
92
|
### 4. GENERATE SPEC
|
|
91
93
|
|
|
92
|
-
Once gaps covered and context gathered,
|
|
94
|
+
Once gaps covered and context gathered, run `validateSpec` on the generated content **before** writing the file.
|
|
95
|
+
- **Hard failure:** Do NOT write the file. Show errors to the user with actionable fix suggestions and re-synthesize.
|
|
96
|
+
- **Advisory warnings:** Write the file but display the warnings to the user after confirmation.
|
|
97
|
+
|
|
98
|
+
Create `specs/{name}.md`:
|
|
93
99
|
|
|
94
100
|
```markdown
|
|
95
101
|
# {Name}
|