cc-devflow 4.1.2 → 4.1.4

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/CHANGELOG.md CHANGED
@@ -7,6 +7,72 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [4.1.4] - 2026-02-08
11
+
12
+ ### 🧭 Workspace Worktree Session Recovery & Release Flow Clarity
13
+
14
+ v4.1.4 strengthens workspace-to-worktree continuity so developers can resume a requirement reliably across sessions, and clarifies flow init/release semantics in command and skill docs.
15
+
16
+ #### Added
17
+
18
+ - **Workspace switch script**
19
+ - Added `.claude/scripts/flow-workspace-switch.sh`
20
+ - Supports `REQ/BUG` pointer updates, journal logging, `--print-cd`, and sourced `--cd` switching
21
+
22
+ - **Scripts architecture map**
23
+ - Added `.claude/scripts/CLAUDE.md` as L2 directory map for script responsibilities
24
+
25
+ #### Changed
26
+
27
+ - **Workspace start behavior**
28
+ - `.claude/scripts/flow-workspace-start.sh` now accepts `REQ-XXX/BUG-XXX` override
29
+ - Added `--switch` and sourced `--cd` behavior for faster worktree handoff
30
+ - Added BUG status file compatibility (`devflow/bugs/*/status.json`)
31
+
32
+ - **Requirement initialization linkage**
33
+ - `.claude/scripts/create-requirement.sh` now syncs `devflow/workspace/{developer}/.current-req` when workspace exists
34
+
35
+ - **Flow docs and skills alignment**
36
+ - Updated flow init/release/workspace command docs and workflow skills to reflect:
37
+ - default worktree naming semantics
38
+ - merge semantics by release strategy
39
+ - workspace continuity expectations
40
+
41
+ #### Benefits
42
+
43
+ - ✅ New session recovery can target the intended REQ/BUG directly
44
+ - ✅ Worktree switching is faster and less error-prone via generated `cd` commands
45
+ - ✅ Flow release semantics are clearer when deciding between PR-only vs merge paths
46
+
47
+ ## [4.1.3] - 2026-02-08
48
+
49
+ ### 🔧 Flow Quality Default Path + AGENTS.md Safe Emit
50
+
51
+ v4.1.3 aligns release gates with `/flow-quality` as the default path and improves compiler emitters to preserve user-owned AGENTS memory content.
52
+
53
+ #### Fixed
54
+
55
+ - **Flow quality/release gate consistency**
56
+ - `/flow-quality` quick mode now generates minimal `TEST_REPORT.md` and `SECURITY_REPORT.md`
57
+ - Release entry gate accepts `quality_complete` (with `qa_complete` backward compatibility)
58
+ - Flow docs/scripts updated to recommend `/flow-quality` by default, with `--full` as enhanced review mode
59
+
60
+ - **AGENTS.md overwrite prevention in compiler emitters**
61
+ - Added managed block upsert mechanism in base emitter
62
+ - Codex/Antigravity emitters now write compact index blocks instead of appending full agent/rule bodies
63
+ - Existing user memory content in `AGENTS.md` is preserved
64
+
65
+ #### Added
66
+
67
+ - **Regression coverage for managed blocks**
68
+ - Tests for AGENTS managed block generation, idempotence, and preservation behavior
69
+
70
+ #### Benefits
71
+
72
+ - ✅ `flow-quality` becomes a true default path to `flow-release` without requiring `--full`
73
+ - ✅ Compiler output no longer clobbers user-maintained AGENTS memory sections
74
+ - ✅ Backward compatibility with legacy QA status remains intact
75
+
10
76
  ## [4.1.2] - 2026-02-07
11
77
 
12
78
  ### 🔧 Adapt Compiler Migration Reliability Fixes
@@ -74,6 +74,8 @@ describe('compile() regression', () => {
74
74
  });
75
75
 
76
76
  test('should compile nested commands and emit skills for codex', async () => {
77
+ fs.writeFileSync('AGENTS.md', '# User Memory\n\nKeep this content.\n');
78
+
77
79
  const result = await compile({
78
80
  sourceDir: '.claude/commands',
79
81
  skillsDir: '.claude/skills',
@@ -90,5 +92,12 @@ describe('compile() regression', () => {
90
92
  expect(fs.existsSync('.codex/skills/flow-dev/SKILL.md')).toBe(true);
91
93
  expect(fs.existsSync('.codex/skills/orchestrator/SKILL.md')).toBe(true);
92
94
  expect(fs.existsSync('AGENTS.md')).toBe(true);
95
+
96
+ const agentsContent = fs.readFileSync('AGENTS.md', 'utf8');
97
+ expect(agentsContent).toContain('# User Memory');
98
+ expect(agentsContent).toContain('<!-- cc-devflow:codex-agents:start -->');
99
+ expect(agentsContent).toContain('<!-- cc-devflow:codex-rules:start -->');
100
+ expect(agentsContent).not.toContain('# planner');
101
+ expect(agentsContent).not.toContain('# rule');
93
102
  });
94
103
  });
@@ -213,9 +213,10 @@ describe('CodexEmitter Multi-Module', () => {
213
213
  expect(content).toContain('## Required Context');
214
214
  });
215
215
 
216
- test('emitAgents merges to AGENTS.md', async () => {
216
+ test('emitAgents writes compact managed block and preserves existing content', async () => {
217
217
  const sourceDir = path.join(tempDir, '.claude', 'agents');
218
218
  const targetPath = path.join(tempDir, 'AGENTS.md');
219
+ fs.writeFileSync(targetPath, '# User Memory\n\nDo not overwrite this.\n');
219
220
 
220
221
  const results = await emitter.emitAgents(sourceDir, targetPath);
221
222
 
@@ -223,25 +224,32 @@ describe('CodexEmitter Multi-Module', () => {
223
224
  expect(fs.existsSync(targetPath)).toBe(true);
224
225
 
225
226
  const content = fs.readFileSync(targetPath, 'utf8');
226
- expect(content).toContain('# Agents');
227
- expect(content).toContain('## test-agent');
227
+ expect(content).toContain('# User Memory');
228
+ expect(content).toContain('<!-- cc-devflow:codex-agents:start -->');
229
+ expect(content).toContain('## CC-DevFlow Agents');
230
+ expect(content).toContain('- Count: 1');
231
+ expect(content).not.toContain('This agent is for testing.');
228
232
  });
229
233
 
230
- test('emitRules appends to AGENTS.md', async () => {
234
+ test('emitRules writes compact managed block and remains idempotent', async () => {
231
235
  const sourceDir = path.join(tempDir, '.claude', 'rules');
232
236
  const targetPath = path.join(tempDir, 'AGENTS.md');
233
237
 
234
- // 先创建 AGENTS.md
235
- fs.writeFileSync(targetPath, '# Agents\n\nExisting content.\n');
238
+ fs.writeFileSync(targetPath, '# User Memory\n\nKeep me.\n');
236
239
 
237
- const results = await emitter.emitRules(sourceDir, targetPath);
240
+ const first = await emitter.emitRules(sourceDir, targetPath);
241
+ const second = await emitter.emitRules(sourceDir, targetPath);
238
242
 
239
- expect(results.length).toBe(1);
243
+ expect(first.length).toBe(1);
244
+ expect(second.length).toBe(1);
240
245
 
241
246
  const content = fs.readFileSync(targetPath, 'utf8');
242
- expect(content).toContain('# Agents');
243
- expect(content).toContain('## Rules');
244
- expect(content).toContain('### test-rule');
247
+ expect(content).toContain('# User Memory');
248
+ expect(content).toContain('<!-- cc-devflow:codex-rules:start -->');
249
+ expect(content).toContain('## CC-DevFlow Rules');
250
+ expect(content).toContain('- Count: 1');
251
+ expect(content).not.toContain('This rule is for testing.');
252
+ expect(content.split('<!-- cc-devflow:codex-rules:start -->').length - 1).toBe(1);
245
253
  });
246
254
  });
247
255
 
@@ -428,6 +436,24 @@ describe('AntigravityEmitter Multi-Module', () => {
428
436
  const targetPath = path.join(targetDir, 'test-rule.md');
429
437
  expect(fs.existsSync(targetPath)).toBe(true);
430
438
  });
439
+
440
+ test('emitAgents writes compact managed block and preserves existing content', async () => {
441
+ const sourceDir = path.join(tempDir, '.claude', 'agents');
442
+ const targetPath = path.join(tempDir, 'AGENTS.md');
443
+ fs.writeFileSync(targetPath, '# User Memory\n\nKeep this section.\n');
444
+
445
+ const results = await emitter.emitAgents(sourceDir, targetPath);
446
+
447
+ expect(results.length).toBe(1);
448
+ expect(fs.existsSync(targetPath)).toBe(true);
449
+
450
+ const content = fs.readFileSync(targetPath, 'utf8');
451
+ expect(content).toContain('# User Memory');
452
+ expect(content).toContain('<!-- cc-devflow:antigravity-agents:start -->');
453
+ expect(content).toContain('## CC-DevFlow Agents');
454
+ expect(content).toContain('- Count: 1');
455
+ expect(content).not.toContain('This agent is for testing.');
456
+ });
431
457
  });
432
458
 
433
459
  // ============================================================
@@ -9,7 +9,7 @@
9
9
  * 输出格式:
10
10
  * - Commands: Markdown + YAML frontmatter -> .agent/workflows/
11
11
  * - Skills: SKILL.md (YAML frontmatter) -> .agent/skills/
12
- * - Agents: 合并到 AGENTS.md
12
+ * - Agents: 索引摘要写入 AGENTS.md(保留用户内容)
13
13
  * - Rules: Markdown -> .agent/rules/
14
14
  *
15
15
  * 限制: 单文件 <= 12,000 字符,超限时自动拆分
@@ -319,10 +319,29 @@ class AntigravityEmitter extends BaseEmitter {
319
319
 
320
320
  /**
321
321
  * 编译 Agents 模块
322
- * .claude/agents/[name].md -> AGENTS.md (合并)
322
+ * .claude/agents/[name].md -> AGENTS.md (短索引,受管块)
323
323
  */
324
324
  async emitAgents(sourceDir, targetPath) {
325
- return this._defaultAgentsEmit(sourceDir, targetPath);
325
+ const results = [];
326
+ const agentNames = await this.readMarkdownEntryNames(sourceDir);
327
+
328
+ if (agentNames.length === 0) {
329
+ return results;
330
+ }
331
+
332
+ const summary = [
333
+ '## CC-DevFlow Agents',
334
+ '',
335
+ '- Scope: `.claude/agents/*.md`',
336
+ `- Count: ${agentNames.length}`,
337
+ `- Entries: ${this.formatCompactList(agentNames)}`,
338
+ '- Policy: Keep AGENTS.md concise; detailed agent instructions stay in source files.'
339
+ ].join('\n');
340
+
341
+ const result = await this.upsertManagedBlock(targetPath, 'antigravity-agents', summary);
342
+ results.push({ ...result, count: agentNames.length });
343
+
344
+ return results;
326
345
  }
327
346
 
328
347
  /**
@@ -23,6 +23,7 @@ const crypto = require('crypto');
23
23
  // SECURITY CONFIGURATION (FINDING-003)
24
24
  // ============================================================
25
25
  const MAX_OUTPUT_SIZE = 2 * 1024 * 1024; // 2MB limit
26
+ const MANAGED_BLOCK_PREFIX = 'cc-devflow';
26
27
 
27
28
  // ============================================================
28
29
  // MODULE_TYPES - 支持的模块类型
@@ -284,6 +285,83 @@ class BaseEmitter {
284
285
  hashContent(content) {
285
286
  return crypto.createHash('sha256').update(content).digest('hex');
286
287
  }
288
+
289
+ /**
290
+ * 读取目录下的 Markdown 文件名(去掉 .md)
291
+ */
292
+ async readMarkdownEntryNames(sourceDir) {
293
+ if (!fs.existsSync(sourceDir)) {
294
+ return [];
295
+ }
296
+
297
+ const entries = await fs.promises.readdir(sourceDir, { withFileTypes: true });
298
+
299
+ return entries
300
+ .filter(entry => entry.isFile() && entry.name.endsWith('.md'))
301
+ .map(entry => entry.name.replace(/\.md$/, ''))
302
+ .sort((a, b) => a.localeCompare(b));
303
+ }
304
+
305
+ /**
306
+ * 格式化短列表,避免全局记忆文件过长
307
+ */
308
+ formatCompactList(items, maxItems = 12) {
309
+ if (!items || items.length === 0) {
310
+ return '`(none)`';
311
+ }
312
+
313
+ const visible = items.slice(0, maxItems).map(item => `\`${item}\``);
314
+ const hiddenCount = items.length - visible.length;
315
+
316
+ if (hiddenCount > 0) {
317
+ visible.push(`... (+${hiddenCount} more)`);
318
+ }
319
+
320
+ return visible.join(', ');
321
+ }
322
+
323
+ /**
324
+ * 受管块写入:仅替换指定区块,保留用户原有内容
325
+ */
326
+ async upsertManagedBlock(targetPath, blockId, body) {
327
+ const startMarker = `<!-- ${MANAGED_BLOCK_PREFIX}:${blockId}:start -->`;
328
+ const endMarker = `<!-- ${MANAGED_BLOCK_PREFIX}:${blockId}:end -->`;
329
+ const managedBlock = `${startMarker}\n${body.trim()}\n${endMarker}`;
330
+
331
+ let existing = '';
332
+ if (fs.existsSync(targetPath)) {
333
+ existing = await fs.promises.readFile(targetPath, 'utf8');
334
+ }
335
+
336
+ const merged = this._replaceManagedBlock(existing, startMarker, endMarker, managedBlock);
337
+ return this.emitToPath(targetPath, merged);
338
+ }
339
+
340
+ _replaceManagedBlock(existing, startMarker, endMarker, managedBlock) {
341
+ const startIndex = existing.indexOf(startMarker);
342
+ const endIndex = existing.indexOf(endMarker);
343
+
344
+ if (startIndex !== -1 && endIndex !== -1 && endIndex > startIndex) {
345
+ const before = existing.slice(0, startIndex).trimEnd();
346
+ const after = existing.slice(endIndex + endMarker.length).trimStart();
347
+
348
+ return this._joinBlocks(before, managedBlock, after);
349
+ }
350
+
351
+ if (!existing.trim()) {
352
+ return `${managedBlock}\n`;
353
+ }
354
+
355
+ return `${existing.trimEnd()}\n\n${managedBlock}\n`;
356
+ }
357
+
358
+ _joinBlocks(...blocks) {
359
+ const validBlocks = blocks.filter(block => block && block.trim().length > 0);
360
+ if (validBlocks.length === 0) {
361
+ return '';
362
+ }
363
+ return `${validBlocks.join('\n\n')}\n`;
364
+ }
287
365
  }
288
366
 
289
367
  module.exports = BaseEmitter;
@@ -9,8 +9,8 @@
9
9
  * 输出格式:
10
10
  * - Commands: Markdown + YAML frontmatter → .codex/prompts/
11
11
  * - Skills: SKILL.md (YAML frontmatter) → .codex/skills/
12
- * - Agents: 合并到 AGENTS.md
13
- * - Rules: 合并到 AGENTS.md
12
+ * - Agents: 索引摘要写入 AGENTS.md(保留用户内容)
13
+ * - Rules: 索引摘要写入 AGENTS.md(保留用户内容)
14
14
  *
15
15
  * v2.0: 支持多模块编译
16
16
  */
@@ -194,51 +194,54 @@ class CodexEmitter extends BaseEmitter {
194
194
 
195
195
  /**
196
196
  * 编译 Agents 模块
197
- * .claude/agents/[name].md -> AGENTS.md (合并)
197
+ * .claude/agents/[name].md -> AGENTS.md (短索引,受管块)
198
198
  */
199
199
  async emitAgents(sourceDir, targetPath) {
200
- return this._defaultAgentsEmit(sourceDir, targetPath);
200
+ const results = [];
201
+ const agentNames = await this.readMarkdownEntryNames(sourceDir);
202
+
203
+ if (agentNames.length === 0) {
204
+ return results;
205
+ }
206
+
207
+ const summary = [
208
+ '## CC-DevFlow Agents',
209
+ '',
210
+ '- Scope: `.claude/agents/*.md`',
211
+ `- Count: ${agentNames.length}`,
212
+ `- Entries: ${this.formatCompactList(agentNames)}`,
213
+ '- Policy: Keep AGENTS.md concise as global memory; full agent specs stay in source files.'
214
+ ].join('\n');
215
+
216
+ const result = await this.upsertManagedBlock(targetPath, 'codex-agents', summary);
217
+ results.push({ ...result, count: agentNames.length });
218
+
219
+ return results;
201
220
  }
202
221
 
203
222
  /**
204
223
  * 编译 Rules 模块
205
- * .claude/rules/[name].md -> AGENTS.md (追加)
224
+ * .claude/rules/[name].md -> AGENTS.md (短索引,受管块)
206
225
  */
207
226
  async emitRules(sourceDir, targetPath) {
208
227
  const results = [];
228
+ const ruleNames = await this.readMarkdownEntryNames(sourceDir);
209
229
 
210
- if (!fs.existsSync(sourceDir)) {
230
+ if (ruleNames.length === 0) {
211
231
  return results;
212
232
  }
213
233
 
214
- const entries = await fs.promises.readdir(sourceDir, { withFileTypes: true });
215
- const sections = [];
216
-
217
- for (const entry of entries) {
218
- if (!entry.isFile() || !entry.name.endsWith('.md')) {
219
- continue;
220
- }
221
-
222
- const filePath = path.join(sourceDir, entry.name);
223
- const content = await fs.promises.readFile(filePath, 'utf8');
224
- const ruleName = entry.name.replace('.md', '');
225
-
226
- sections.push(`### ${ruleName}\n\n${content}`);
227
- }
228
-
229
- if (sections.length > 0) {
230
- // 追加到现有 AGENTS.md
231
- let existingContent = '';
232
- if (fs.existsSync(targetPath)) {
233
- existingContent = await fs.promises.readFile(targetPath, 'utf8');
234
- }
235
-
236
- const rulesSection = `\n\n## Rules\n\n${sections.join('\n\n---\n\n')}`;
237
- const merged = existingContent + rulesSection;
238
-
239
- const result = await this.emitToPath(targetPath, merged);
240
- results.push(result);
241
- }
234
+ const summary = [
235
+ '## CC-DevFlow Rules',
236
+ '',
237
+ '- Scope: `.claude/rules/*.md`',
238
+ `- Count: ${ruleNames.length}`,
239
+ `- Entries: ${this.formatCompactList(ruleNames)}`,
240
+ '- Policy: AGENTS.md stores only memory-level constraints, not full rule bodies.'
241
+ ].join('\n');
242
+
243
+ const result = await this.upsertManagedBlock(targetPath, 'codex-rules', summary);
244
+ results.push({ ...result, count: ruleNames.length });
242
245
 
243
246
  return results;
244
247
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-devflow",
3
- "version": "4.1.2",
3
+ "version": "4.1.4",
4
4
  "description": "DevFlow CLI tool",
5
5
  "main": "bin/cc-devflow.js",
6
6
  "bin": {