deepflow 0.1.59 → 0.1.61

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.
@@ -461,10 +461,10 @@ run_single_spike() {
461
461
  auto_log "Worktree already exists at ${worktree_path}, reusing"
462
462
  else
463
463
  local wt_err
464
- wt_err="$(git worktree add -b "$branch_name" "$worktree_path" HEAD 2>&1)" || {
464
+ wt_err="$(git -C "$PROJECT_ROOT" worktree add -b "$branch_name" "$worktree_path" HEAD 2>&1)" || {
465
465
  auto_log "worktree add -b failed: ${wt_err}"
466
466
  # Branch may already exist from a previous run
467
- wt_err="$(git worktree add "$worktree_path" "$branch_name" 2>&1)" || {
467
+ wt_err="$(git -C "$PROJECT_ROOT" worktree add "$worktree_path" "$branch_name" 2>&1)" || {
468
468
  auto_log "ERROR: failed to create worktree for ${slug}: ${wt_err}"
469
469
  echo "Worktree error for ${slug}: ${wt_err}" >&2
470
470
  return 1
@@ -1232,6 +1232,14 @@ run_spec_cycle() {
1232
1232
  main() {
1233
1233
  parse_flags "$@"
1234
1234
 
1235
+ # Require git repository
1236
+ if ! git -C "$PROJECT_ROOT" rev-parse --git-dir &>/dev/null; then
1237
+ echo "Error: ${PROJECT_ROOT} is not a git repository." >&2
1238
+ echo "deepflow auto requires git for worktree-based parallel spikes." >&2
1239
+ echo "Run: git init && git add . && git commit -m 'initial commit'" >&2
1240
+ exit 1
1241
+ fi
1242
+
1235
1243
  auto_log "deepflow-auto started (parallel=${PARALLEL}, hypotheses=${HYPOTHESES}, max_cycles=${MAX_CYCLES}, continue=${CONTINUE}, fresh=${FRESH})"
1236
1244
 
1237
1245
  echo "deepflow-auto: discovering specs..."
@@ -9,13 +9,14 @@
9
9
 
10
10
  'use strict';
11
11
 
12
+ // Each entry: [canonical name, ...aliases that also satisfy the requirement]
12
13
  const REQUIRED_SECTIONS = [
13
- 'Objective',
14
- 'Requirements',
15
- 'Constraints',
16
- 'Out of Scope',
17
- 'Acceptance Criteria',
18
- 'Technical Notes',
14
+ ['Objective', 'overview', 'goal', 'goals', 'summary'],
15
+ ['Requirements', 'functional requirements'],
16
+ ['Constraints', 'tech constraints', 'technical constraints'],
17
+ ['Out of Scope', 'out of scope (mvp)', 'non-goals', 'exclusions'],
18
+ ['Acceptance Criteria'],
19
+ ['Technical Notes', 'architecture notes', 'architecture', 'tech notes', 'implementation notes'],
19
20
  ];
20
21
 
21
22
  /**
@@ -34,36 +35,42 @@ function validateSpec(content, { mode = 'interactive' } = {}) {
34
35
  const headersFound = [];
35
36
  for (const line of content.split('\n')) {
36
37
  const m = line.match(/^##\s+(.+)/i);
37
- if (m) headersFound.push(m[1].trim());
38
+ if (m) {
39
+ // Strip leading numbering like "1.", "2.", "3." from headers
40
+ const raw = m[1].trim().replace(/^\d+\.\s*/, '');
41
+ headersFound.push(raw);
42
+ }
38
43
  }
39
44
 
40
- for (const section of REQUIRED_SECTIONS) {
41
- const found = headersFound.some(
42
- (h) => h.toLowerCase() === section.toLowerCase()
43
- );
45
+ for (const [canonical, ...aliases] of REQUIRED_SECTIONS) {
46
+ const allNames = [canonical, ...aliases].map((n) => n.toLowerCase());
47
+ const found = headersFound.some((h) => allNames.includes(h.toLowerCase()));
44
48
  if (!found) {
45
- hard.push(`Missing required section: "## ${section}"`);
49
+ // Inline *AC: lines satisfy the Acceptance Criteria requirement
50
+ if (canonical === 'Acceptance Criteria' && /\*AC[:.]/.test(content)) continue;
51
+ hard.push(`Missing required section: "## ${canonical}"`);
46
52
  }
47
53
  }
48
54
 
49
- // ── (b) Requirement lines must have REQ-N: prefix ───────────────────
55
+ // ── (b) Check that requirements have REQ-N identifiers ──────────────
56
+ // Requirements can be formatted as:
57
+ // - List items: "- REQ-1: ..." or "- **REQ-1** — ..."
58
+ // - Paragraphs: "**REQ-1 — Title**"
59
+ // We verify that at least one REQ-N identifier exists in the section.
60
+ // Sub-bullets (detail items) are not flagged.
50
61
  const reqSection = extractSection(content, 'Requirements');
51
62
  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
- }
63
+ const hasReqIds = /REQ-\d+/.test(reqSection);
64
+ if (!hasReqIds) {
65
+ hard.push('Requirements section has no REQ-N identifiers');
62
66
  }
63
67
  }
64
68
 
65
- // ── (c) Acceptance Criteria must use checkbox format ─────────────────
69
+ // ── (c) Acceptance Criteria ────────────────────────────────────────
70
+ // Accept either a dedicated ## Acceptance Criteria section with checkboxes,
71
+ // or inline *AC: lines within the requirements section.
66
72
  const acSection = extractSection(content, 'Acceptance Criteria');
73
+ const hasInlineAC = /\*AC[:.]/.test(content);
67
74
  if (acSection !== null) {
68
75
  const lines = acSection.split('\n');
69
76
  for (const line of lines) {
@@ -74,10 +81,12 @@ function validateSpec(content, { mode = 'interactive' } = {}) {
74
81
  );
75
82
  }
76
83
  }
84
+ } else if (!hasInlineAC) {
85
+ // No dedicated section and no inline ACs — already flagged by missing section check
77
86
  }
78
87
 
79
88
  // ── (d) No duplicate REQ-N IDs ──────────────────────────────────────
80
- const reqIdPattern = /\*{0,2}(REQ-\d+)\*{0,2}\s*:/g;
89
+ const reqIdPattern = /\*{0,2}(REQ-\d+[a-z]?)\*{0,2}\s*(?:[:\u2014]|—)/g;
81
90
  const seenIds = new Map();
82
91
  let match;
83
92
  while ((match = reqIdPattern.exec(content)) !== null) {
@@ -90,16 +99,17 @@ function validateSpec(content, { mode = 'interactive' } = {}) {
90
99
 
91
100
  // ── Advisory checks ──────────────────────────────────────────────────
92
101
 
93
- // (adv-a) Line count > 100
102
+ // (adv-a) Line count > 200
94
103
  const lineCount = content.split('\n').length;
95
- if (lineCount > 100) {
96
- advisory.push(`Spec exceeds 100 lines (${lineCount} lines)`);
104
+ if (lineCount > 200) {
105
+ advisory.push(`Spec exceeds 200 lines (${lineCount} lines)`);
97
106
  }
98
107
 
99
108
  // (adv-b) Orphaned REQ-N IDs not referenced in Acceptance Criteria
109
+ // Skip this check when ACs are inline within requirements
100
110
  if (reqSection !== null && acSection !== null) {
101
111
  const reqIds = [];
102
- const reqLinePattern = /\*{0,2}(REQ-\d+)\*{0,2}\s*:/g;
112
+ const reqLinePattern = /\*{0,2}(REQ-\d+[a-z]?)\*{0,2}\s*[:\u2014-]/g;
103
113
  let reqMatch;
104
114
  while ((reqMatch = reqLinePattern.exec(reqSection)) !== null) {
105
115
  reqIds.push(reqMatch[1]);
@@ -120,9 +130,9 @@ function validateSpec(content, { mode = 'interactive' } = {}) {
120
130
  }
121
131
  }
122
132
 
123
- // (adv-d) More than 12 requirements
124
- if (seenIds.size > 12) {
125
- advisory.push(`Too many requirements (${seenIds.size}, limit 12)`);
133
+ // (adv-d) More than 20 requirements
134
+ if (seenIds.size > 20) {
135
+ advisory.push(`Too many requirements (${seenIds.size}, limit 20)`);
126
136
  }
127
137
 
128
138
  // ── Auto-mode escalation ─────────────────────────────────────────────
@@ -138,15 +148,24 @@ function validateSpec(content, { mode = 'interactive' } = {}) {
138
148
  * Returns null if the section is not found.
139
149
  */
140
150
  function extractSection(content, sectionName) {
151
+ // Find the matching aliases for this section name
152
+ const entry = REQUIRED_SECTIONS.find(
153
+ ([canonical]) => canonical.toLowerCase() === sectionName.toLowerCase()
154
+ );
155
+ const allNames = entry
156
+ ? [entry[0], ...entry.slice(1)].map((n) => n.toLowerCase())
157
+ : [sectionName.toLowerCase()];
158
+
141
159
  const lines = content.split('\n');
142
160
  let capturing = false;
143
161
  const captured = [];
144
162
 
145
163
  for (const line of lines) {
146
- const headerMatch = line.match(/^## \s*(.+)/);
164
+ const headerMatch = line.match(/^##\s+(.+)/);
147
165
  if (headerMatch) {
148
166
  if (capturing) break; // hit the next section
149
- if (headerMatch[1].trim().toLowerCase() === sectionName.toLowerCase()) {
167
+ const normalized = headerMatch[1].trim().replace(/^\d+\.\s*/, '').toLowerCase();
168
+ if (allNames.includes(normalized)) {
150
169
  capturing = true;
151
170
  }
152
171
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepflow",
3
- "version": "0.1.59",
3
+ "version": "0.1.61",
4
4
  "description": "Stay in flow state - lightweight spec-driven task orchestration for Claude Code",
5
5
  "keywords": [
6
6
  "claude",