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/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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepflow",
3
- "version": "0.1.52",
3
+ "version": "0.1.53",
4
4
  "description": "Stay in flow state - lightweight spec-driven task orchestration for Claude Code",
5
5
  "keywords": [
6
6
  "claude",
@@ -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)
@@ -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, create `specs/{name}.md`:
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}