devflow-kit 1.6.0 → 1.6.1

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.
Files changed (31) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +3 -1
  3. package/dist/commands/init.js +6 -2
  4. package/dist/commands/uninstall.d.ts +9 -0
  5. package/dist/commands/uninstall.js +60 -4
  6. package/dist/utils/post-install.js +0 -1
  7. package/package.json +1 -1
  8. package/plugins/devflow-accessibility/.claude-plugin/plugin.json +1 -1
  9. package/plugins/devflow-ambient/.claude-plugin/plugin.json +1 -1
  10. package/plugins/devflow-ambient/skills/ambient-router/SKILL.md +4 -1
  11. package/plugins/devflow-audit-claude/.claude-plugin/plugin.json +1 -1
  12. package/plugins/devflow-code-review/.claude-plugin/plugin.json +1 -1
  13. package/plugins/devflow-core-skills/.claude-plugin/plugin.json +1 -1
  14. package/plugins/devflow-core-skills/skills/docs-framework/SKILL.md +0 -1
  15. package/plugins/devflow-debug/.claude-plugin/plugin.json +1 -1
  16. package/plugins/devflow-frontend-design/.claude-plugin/plugin.json +1 -1
  17. package/plugins/devflow-go/.claude-plugin/plugin.json +1 -1
  18. package/plugins/devflow-implement/.claude-plugin/plugin.json +1 -1
  19. package/plugins/devflow-java/.claude-plugin/plugin.json +1 -1
  20. package/plugins/devflow-python/.claude-plugin/plugin.json +1 -1
  21. package/plugins/devflow-react/.claude-plugin/plugin.json +1 -1
  22. package/plugins/devflow-resolve/.claude-plugin/plugin.json +1 -1
  23. package/plugins/devflow-rust/.claude-plugin/plugin.json +1 -1
  24. package/plugins/devflow-self-review/.claude-plugin/plugin.json +1 -1
  25. package/plugins/devflow-specify/.claude-plugin/plugin.json +1 -1
  26. package/plugins/devflow-typescript/.claude-plugin/plugin.json +1 -1
  27. package/scripts/hooks/ambient-prompt +5 -4
  28. package/scripts/hooks/background-memory-update +114 -85
  29. package/scripts/hooks/session-start-memory +1 -17
  30. package/shared/skills/ambient-router/SKILL.md +4 -1
  31. package/shared/skills/docs-framework/SKILL.md +0 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,19 @@ All notable changes to DevFlow will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.6.1] - 2026-03-20
9
+
10
+ ### Added
11
+ - **`--dry-run` flag** for `devflow uninstall` — preview removal plan without deleting anything
12
+
13
+ ### Fixed
14
+ - **Ambient skill loading** — removed `allowed-tools` restriction from ambient-router so skills actually load via the Skill tool
15
+ - **Ambient hook preamble** — explicit Skill tool instruction ensures models invoke skills rather than responding directly
16
+ - **Init wizard** — hide `devflow-ambient` from plugin multiselect (auto-included via ambient prompt)
17
+ - **Working memory** — replaced broken `--resume` with transcript-based background updater
18
+
19
+ ---
20
+
8
21
  ## [1.6.0] - 2026-03-19
9
22
 
10
23
  ### Added
@@ -902,6 +915,7 @@ devflow init
902
915
  ---
903
916
 
904
917
  [Unreleased]: https://github.com/dean0x/devflow/compare/v1.4.0...HEAD
918
+ [1.6.1]: https://github.com/dean0x/devflow/compare/v1.6.0...v1.6.1
905
919
  [1.6.0]: https://github.com/dean0x/devflow/compare/v1.5.0...v1.6.0
906
920
  [1.5.0]: https://github.com/dean0x/devflow/compare/v1.4.0...v1.5.0
907
921
  [1.4.0]: https://github.com/dean0x/devflow/compare/v1.3.3...v1.4.0
package/README.md CHANGED
@@ -197,7 +197,6 @@ DevFlow creates project documentation in `.docs/` and working memory in `.memory
197
197
 
198
198
  .memory/
199
199
  ├── WORKING-MEMORY.md # Auto-maintained by Stop hook
200
- ├── PROJECT-PATTERNS.md # Accumulated patterns across sessions
201
200
  ├── backup.json # Pre-compact git state snapshot
202
201
  └── knowledge/
203
202
  ├── decisions.md # Architectural decisions (ADR-NNN, append-only)
@@ -255,7 +254,10 @@ Session context is saved and restored automatically via Working Memory hooks —
255
254
  | Option | Description |
256
255
  |--------|-------------|
257
256
  | `--scope <user\|local>` | Uninstall scope (default: user) |
257
+ | `--plugin <names>` | Comma-separated plugin names to uninstall selectively |
258
258
  | `--keep-docs` | Preserve .docs/ directory |
259
+ | `--dry-run` | Show what would be removed without deleting anything |
260
+ | `--verbose` | Show detailed uninstall output |
259
261
 
260
262
  ## Building from Source
261
263
 
@@ -121,14 +121,14 @@ export const initCommand = new Command('init')
121
121
  }
122
122
  else if (process.stdin.isTTY) {
123
123
  const choices = DEVFLOW_PLUGINS
124
- .filter(pl => pl.name !== 'devflow-core-skills')
124
+ .filter(pl => pl.name !== 'devflow-core-skills' && pl.name !== 'devflow-ambient')
125
125
  .map(pl => ({
126
126
  value: pl.name,
127
127
  label: pl.name.replace('devflow-', ''),
128
128
  hint: pl.description + (pl.optional ? ' (optional)' : ''),
129
129
  }));
130
130
  const preSelected = DEVFLOW_PLUGINS
131
- .filter(pl => !pl.optional && pl.name !== 'devflow-core-skills')
131
+ .filter(pl => !pl.optional && pl.name !== 'devflow-core-skills' && pl.name !== 'devflow-ambient')
132
132
  .map(pl => pl.name);
133
133
  const pluginSelection = await p.multiselect({
134
134
  message: 'Select plugins to install',
@@ -284,6 +284,10 @@ export const initCommand = new Command('init')
284
284
  if (pluginsToInstall.length > 0 && coreSkillsPlugin && !pluginsToInstall.includes(coreSkillsPlugin)) {
285
285
  pluginsToInstall = [coreSkillsPlugin, ...pluginsToInstall];
286
286
  }
287
+ const ambientPlugin = DEVFLOW_PLUGINS.find(p => p.name === 'devflow-ambient');
288
+ if (ambientEnabled && ambientPlugin && !pluginsToInstall.includes(ambientPlugin)) {
289
+ pluginsToInstall.push(ambientPlugin);
290
+ }
287
291
  const { skillsMap, agentsMap } = buildAssetMaps(pluginsToInstall);
288
292
  // Install: try native CLI first, fall back to file copy
289
293
  const cliAvailable = isClaudeCliAvailable();
@@ -9,5 +9,14 @@ export declare function computeAssetsToRemove(selectedPlugins: PluginDefinition[
9
9
  agents: string[];
10
10
  commands: string[];
11
11
  };
12
+ /**
13
+ * Format a dry-run plan showing what would be removed.
14
+ * Pure function — no I/O, fully testable.
15
+ */
16
+ export declare function formatDryRunPlan(assets: {
17
+ skills: string[];
18
+ agents: string[];
19
+ commands: string[];
20
+ }, extras?: string[]): string;
12
21
  export declare const uninstallCommand: Command;
13
22
  //# sourceMappingURL=uninstall.d.ts.map
@@ -2,7 +2,7 @@ import { Command } from 'commander';
2
2
  import { promises as fs } from 'fs';
3
3
  import * as path from 'path';
4
4
  import { fileURLToPath } from 'url';
5
- import { execSync } from 'child_process';
5
+ import { execFileSync } from 'child_process';
6
6
  import * as p from '@clack/prompts';
7
7
  import color from 'picocolors';
8
8
  import { getInstallationPaths, getClaudeDirectory, getDevFlowDirectory, getManagedSettingsPath } from '../utils/paths.js';
@@ -46,13 +46,37 @@ export function computeAssetsToRemove(selectedPlugins, allPlugins) {
46
46
  }
47
47
  return { skills, agents, commands };
48
48
  }
49
+ /**
50
+ * Format a dry-run plan showing what would be removed.
51
+ * Pure function — no I/O, fully testable.
52
+ */
53
+ export function formatDryRunPlan(assets, extras) {
54
+ const skills = [...new Set(assets.skills)];
55
+ const agents = [...new Set(assets.agents)];
56
+ const commands = [...new Set(assets.commands)];
57
+ const hasAssets = skills.length > 0 || agents.length > 0 || commands.length > 0;
58
+ const hasExtras = extras && extras.length > 0;
59
+ if (!hasAssets && !hasExtras) {
60
+ return 'Nothing to remove.';
61
+ }
62
+ const lines = [];
63
+ if (skills.length > 0)
64
+ lines.push(`Skills (${skills.length}): ${skills.join(', ')}`);
65
+ if (agents.length > 0)
66
+ lines.push(`Agents (${agents.length}): ${agents.join(', ')}`);
67
+ if (commands.length > 0)
68
+ lines.push(`Commands (${commands.length}): ${commands.join(', ')}`);
69
+ if (hasExtras)
70
+ lines.push(`Extras: ${extras.join(', ')}`);
71
+ return lines.join('\n');
72
+ }
49
73
  /**
50
74
  * Uninstall plugin using Claude CLI
51
75
  */
52
76
  function uninstallPluginViaCli(scope) {
53
77
  try {
54
78
  const cliScope = scope === 'local' ? 'project' : 'user';
55
- execSync(`claude plugin uninstall devflow --scope ${cliScope}`, { stdio: 'inherit' });
79
+ execFileSync('claude', ['plugin', 'uninstall', 'devflow', '--scope', cliScope], { stdio: 'inherit' });
56
80
  return true;
57
81
  }
58
82
  catch {
@@ -77,8 +101,10 @@ export const uninstallCommand = new Command('uninstall')
77
101
  .option('--scope <type>', 'Uninstall from specific scope only (default: auto-detect all)', /^(user|local)$/i)
78
102
  .option('--plugin <names>', 'Uninstall specific plugin(s), comma-separated (e.g., implement,review)')
79
103
  .option('--verbose', 'Show detailed uninstall output')
104
+ .option('--dry-run', 'Show what would be removed without actually removing anything')
80
105
  .action(async (options) => {
81
- p.intro(color.bgRed(color.white(' Uninstalling DevFlow ')));
106
+ const dryRun = options.dryRun ?? false;
107
+ p.intro(color.bgRed(color.white(dryRun ? ' DevFlow Uninstall (dry run) ' : ' Uninstalling DevFlow ')));
82
108
  const verbose = options.verbose ?? false;
83
109
  // Parse plugin selection
84
110
  let selectedPluginNames = [];
@@ -121,7 +147,7 @@ export const uninstallCommand = new Command('uninstall')
121
147
  p.log.info('Checked user scope (~/.claude/) and local scope (git-root/.claude/)');
122
148
  process.exit(1);
123
149
  }
124
- if (scopesToUninstall.length > 1) {
150
+ if (scopesToUninstall.length > 1 && !dryRun) {
125
151
  if (process.stdin.isTTY) {
126
152
  const scopeChoice = await p.select({
127
153
  message: 'Found DevFlow in multiple scopes. Uninstall from:',
@@ -144,6 +170,36 @@ export const uninstallCommand = new Command('uninstall')
144
170
  }
145
171
  }
146
172
  }
173
+ // === DRY RUN: show plan and exit ===
174
+ if (dryRun) {
175
+ p.log.info(`Scope(s): ${scopesToUninstall.join(', ')} (dry-run shows all detected scopes)`);
176
+ const assets = isSelectiveUninstall
177
+ ? computeAssetsToRemove(selectedPlugins, DEVFLOW_PLUGINS)
178
+ : computeAssetsToRemove(DEVFLOW_PLUGINS, DEVFLOW_PLUGINS);
179
+ // Detect extras that would be cleaned up (full uninstall only)
180
+ const extras = [];
181
+ if (!isSelectiveUninstall) {
182
+ const docsDir = path.join(process.cwd(), '.docs');
183
+ const memoryDir = path.join(process.cwd(), '.memory');
184
+ try {
185
+ await fs.access(docsDir);
186
+ extras.push('.docs/');
187
+ }
188
+ catch { /* noop */ }
189
+ try {
190
+ await fs.access(memoryDir);
191
+ extras.push('.memory/');
192
+ }
193
+ catch { /* noop */ }
194
+ extras.push('hooks in settings.json', 'scripts in ~/.devflow/');
195
+ }
196
+ const plan = formatDryRunPlan(assets, extras.length > 0 ? extras : undefined);
197
+ for (const line of plan.split('\n')) {
198
+ p.log.info(line);
199
+ }
200
+ p.outro(color.dim('No changes made (dry run)'));
201
+ return;
202
+ }
147
203
  const cliAvailable = isClaudeCliAvailable();
148
204
  // Uninstall from each scope
149
205
  for (const scope of scopesToUninstall) {
@@ -448,7 +448,6 @@ export async function migrateMemoryFiles(verbose, cwd) {
448
448
  const memoryDir = path.join(root, '.memory');
449
449
  const migrations = [
450
450
  { src: path.join(docsDir, 'WORKING-MEMORY.md'), dest: path.join(memoryDir, 'WORKING-MEMORY.md') },
451
- { src: path.join(docsDir, 'patterns.md'), dest: path.join(memoryDir, 'PROJECT-PATTERNS.md') },
452
451
  { src: path.join(docsDir, 'working-memory-backup.json'), dest: path.join(memoryDir, 'backup.json') },
453
452
  ];
454
453
  let migrated = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devflow-kit",
3
- "version": "1.6.0",
3
+ "version": "1.6.1",
4
4
  "description": "Agentic Development Toolkit for Claude Code - Enhance AI-assisted development with intelligent commands and workflows",
5
5
  "type": "module",
6
6
  "bin": {
@@ -4,7 +4,7 @@
4
4
  "author": {
5
5
  "name": "Dean0x"
6
6
  },
7
- "version": "1.6.0",
7
+ "version": "1.6.1",
8
8
  "homepage": "https://github.com/dean0x/devflow",
9
9
  "repository": "https://github.com/dean0x/devflow",
10
10
  "license": "MIT",
@@ -4,7 +4,7 @@
4
4
  "author": {
5
5
  "name": "Dean0x"
6
6
  },
7
- "version": "1.6.0",
7
+ "version": "1.6.1",
8
8
  "homepage": "https://github.com/dean0x/devflow",
9
9
  "repository": "https://github.com/dean0x/devflow",
10
10
  "license": "MIT",
@@ -2,7 +2,7 @@
2
2
  name: ambient-router
3
3
  description: This skill should be used when classifying user intent for ambient mode, auto-loading relevant skills without explicit command invocation. Used by the always-on UserPromptSubmit hook.
4
4
  user-invocable: false
5
- allowed-tools: Read, Grep, Glob
5
+ # No allowed-tools: orchestrator requires unrestricted access (Skill, Agent, Edit, Write, Bash)
6
6
  ---
7
7
 
8
8
  # Ambient Router
@@ -89,6 +89,9 @@ When classification is GUIDED or ORCHESTRATED, skill loading is NON-NEGOTIABLE.
89
89
  Do not rationalize skipping skills. Do not respond without loading them first.
90
90
  BLOCKING REQUIREMENT: Invoke each selected skill using the Skill tool before proceeding.
91
91
  For IMPLEMENT intent, enforce TDD: write the failing test before ANY production code.
92
+ NOTE: Skills loaded in the main session via ambient mode are reference patterns only —
93
+ their allowed-tools metadata does NOT restrict your tool access. You retain full access
94
+ to all tools (Edit, Write, Bash, Agent, etc.) for implementation work.
92
95
  </IMPORTANT>
93
96
 
94
97
  - **QUICK:** Respond directly. No preamble, no classification statement.
@@ -4,7 +4,7 @@
4
4
  "author": {
5
5
  "name": "Dean0x"
6
6
  },
7
- "version": "1.6.0",
7
+ "version": "1.6.1",
8
8
  "homepage": "https://github.com/dean0x/devflow",
9
9
  "repository": "https://github.com/dean0x/devflow",
10
10
  "license": "MIT",
@@ -4,7 +4,7 @@
4
4
  "author": {
5
5
  "name": "Dean0x"
6
6
  },
7
- "version": "1.6.0",
7
+ "version": "1.6.1",
8
8
  "homepage": "https://github.com/dean0x/devflow",
9
9
  "repository": "https://github.com/dean0x/devflow",
10
10
  "license": "MIT",
@@ -4,7 +4,7 @@
4
4
  "author": {
5
5
  "name": "Dean0x"
6
6
  },
7
- "version": "1.6.0",
7
+ "version": "1.6.1",
8
8
  "homepage": "https://github.com/dean0x/devflow",
9
9
  "repository": "https://github.com/dean0x/devflow",
10
10
  "license": "MIT",
@@ -38,7 +38,6 @@ All generated documentation lives under `.docs/` in the project root:
38
38
 
39
39
  .memory/
40
40
  ├── WORKING-MEMORY.md # Auto-maintained by Stop hook (overwritten)
41
- ├── PROJECT-PATTERNS.md # Accumulated patterns (merged across sessions)
42
41
  ├── backup.json # Pre-compact git state snapshot
43
42
  └── knowledge/
44
43
  ├── decisions.md # Architectural decisions (ADR-NNN format)
@@ -4,7 +4,7 @@
4
4
  "author": {
5
5
  "name": "Dean0x"
6
6
  },
7
- "version": "1.6.0",
7
+ "version": "1.6.1",
8
8
  "homepage": "https://github.com/dean0x/devflow",
9
9
  "repository": "https://github.com/dean0x/devflow",
10
10
  "license": "MIT",
@@ -4,7 +4,7 @@
4
4
  "author": {
5
5
  "name": "Dean0x"
6
6
  },
7
- "version": "1.6.0",
7
+ "version": "1.6.1",
8
8
  "homepage": "https://github.com/dean0x/devflow",
9
9
  "repository": "https://github.com/dean0x/devflow",
10
10
  "license": "MIT",
@@ -4,7 +4,7 @@
4
4
  "author": {
5
5
  "name": "Dean0x"
6
6
  },
7
- "version": "1.6.0",
7
+ "version": "1.6.1",
8
8
  "homepage": "https://github.com/dean0x/devflow",
9
9
  "repository": "https://github.com/dean0x/devflow",
10
10
  "license": "MIT",
@@ -4,7 +4,7 @@
4
4
  "author": {
5
5
  "name": "Dean0x"
6
6
  },
7
- "version": "1.6.0",
7
+ "version": "1.6.1",
8
8
  "homepage": "https://github.com/dean0x/devflow",
9
9
  "repository": "https://github.com/dean0x/devflow",
10
10
  "license": "MIT",
@@ -4,7 +4,7 @@
4
4
  "author": {
5
5
  "name": "Dean0x"
6
6
  },
7
- "version": "1.6.0",
7
+ "version": "1.6.1",
8
8
  "homepage": "https://github.com/dean0x/devflow",
9
9
  "repository": "https://github.com/dean0x/devflow",
10
10
  "license": "MIT",
@@ -4,7 +4,7 @@
4
4
  "author": {
5
5
  "name": "Dean0x"
6
6
  },
7
- "version": "1.6.0",
7
+ "version": "1.6.1",
8
8
  "homepage": "https://github.com/dean0x/devflow",
9
9
  "repository": "https://github.com/dean0x/devflow",
10
10
  "license": "MIT",
@@ -4,7 +4,7 @@
4
4
  "author": {
5
5
  "name": "Dean0x"
6
6
  },
7
- "version": "1.6.0",
7
+ "version": "1.6.1",
8
8
  "homepage": "https://github.com/dean0x/devflow",
9
9
  "repository": "https://github.com/dean0x/devflow",
10
10
  "license": "MIT",
@@ -4,7 +4,7 @@
4
4
  "author": {
5
5
  "name": "Dean0x"
6
6
  },
7
- "version": "1.6.0",
7
+ "version": "1.6.1",
8
8
  "homepage": "https://github.com/dean0x/devflow",
9
9
  "repository": "https://github.com/dean0x/devflow",
10
10
  "license": "MIT",
@@ -4,7 +4,7 @@
4
4
  "author": {
5
5
  "name": "Dean0x"
6
6
  },
7
- "version": "1.6.0",
7
+ "version": "1.6.1",
8
8
  "homepage": "https://github.com/dean0x/devflow",
9
9
  "repository": "https://github.com/dean0x/devflow",
10
10
  "license": "MIT",
@@ -4,7 +4,7 @@
4
4
  "author": {
5
5
  "name": "Dean0x"
6
6
  },
7
- "version": "1.6.0",
7
+ "version": "1.6.1",
8
8
  "homepage": "https://github.com/dean0x/devflow",
9
9
  "repository": "https://github.com/dean0x/devflow",
10
10
  "license": "MIT",
@@ -4,7 +4,7 @@
4
4
  "author": {
5
5
  "name": "Dean0x"
6
6
  },
7
- "version": "1.6.0",
7
+ "version": "1.6.1",
8
8
  "homepage": "https://github.com/dean0x/devflow",
9
9
  "repository": "https://github.com/dean0x/devflow",
10
10
  "license": "MIT",
@@ -4,7 +4,7 @@
4
4
  "author": {
5
5
  "name": "Dean0x"
6
6
  },
7
- "version": "1.6.0",
7
+ "version": "1.6.1",
8
8
  "homepage": "https://github.com/dean0x/devflow",
9
9
  "repository": "https://github.com/dean0x/devflow",
10
10
  "license": "MIT",
@@ -25,21 +25,22 @@ if [[ "$PROMPT" == /* ]]; then
25
25
  fi
26
26
 
27
27
  # Skip single-word confirmations (< 2 words)
28
- WORD_COUNT=$(echo "$PROMPT" | wc -w | tr -d ' ')
28
+ WORD_COUNT=$(printf '%s' "$PROMPT" | wc -w | tr -d ' ')
29
29
  if [ "$WORD_COUNT" -lt 2 ]; then
30
30
  exit 0
31
31
  fi
32
32
 
33
33
  # Normalize to lowercase for matching
34
- PROMPT_LOWER=$(echo "$PROMPT" | tr '[:upper:]' '[:lower:]')
34
+ PROMPT_LOWER=$(printf '%s' "$PROMPT" | tr '[:upper:]' '[:lower:]')
35
35
 
36
36
  # Fast-path: git operations are always QUICK — skip preamble
37
- if echo "$PROMPT_LOWER" | grep -qE '^(commit|push|pull|merge|rebase|cherry-pick|squash|tag|stash)|create (a )?pr|open (a )?pr'; then
37
+ if printf '%s' "$PROMPT_LOWER" | grep -qE '^(commit|push|pull|merge|rebase|cherry-pick|squash|tag|stash)|create (a )?pr|open (a )?pr'; then
38
38
  exit 0
39
39
  fi
40
40
 
41
41
  # Inject classification preamble
42
- PREAMBLE="AMBIENT MODE ACTIVE: Before responding, silently classify this prompt using the ambient-router skill already in your session context. If QUICK, respond normally without stating classification."
42
+ # SYNC: must match tests/integration/helpers.ts AMBIENT_PREAMBLE
43
+ PREAMBLE="AMBIENT MODE ACTIVE: Before responding, silently classify this prompt using the ambient-router skill already in your session context. If QUICK, respond normally without stating classification. If GUIDED or ORCHESTRATED, you MUST load the selected skills using the Skill tool before proceeding."
43
44
 
44
45
  jq -n --arg ctx "$PREAMBLE" '{
45
46
  "hookSpecificOutput": {
@@ -2,8 +2,9 @@
2
2
 
3
3
  # Background Working Memory Updater
4
4
  # Called by stop-update-memory as a detached background process.
5
- # Resumes the parent session headlessly to update .memory/WORKING-MEMORY.md.
6
- # On failure: logs error, does nothing (no fallback).
5
+ # Reads the last turn from the session transcript, then uses a fresh `claude -p`
6
+ # invocation to update .memory/WORKING-MEMORY.md.
7
+ # On failure: logs error, does nothing (stale memory is better than fake data).
7
8
 
8
9
  set -e
9
10
 
@@ -29,7 +30,7 @@ rotate_log() {
29
30
 
30
31
  # --- Stale Lock Recovery ---
31
32
 
32
- # Portable mtime in epoch seconds (same pattern as stop-update-memory:35-39)
33
+ # Portable mtime in epoch seconds
33
34
  get_mtime() {
34
35
  if stat --version &>/dev/null 2>&1; then
35
36
  stat -c %Y "$1"
@@ -72,11 +73,65 @@ cleanup() {
72
73
  }
73
74
  trap cleanup EXIT
74
75
 
76
+ # --- Transcript Extraction ---
77
+
78
+ extract_last_turn() {
79
+ # Compute transcript path: Claude Code stores transcripts at
80
+ # ~/.claude/projects/{cwd-with-slashes-replaced-by-hyphens}/{session_id}.jsonl
81
+ local encoded_cwd
82
+ encoded_cwd=$(echo "$CWD" | sed 's|^/||' | tr '/' '-')
83
+ local transcript="$HOME/.claude/projects/-${encoded_cwd}/${SESSION_ID}.jsonl"
84
+
85
+ if [ ! -f "$transcript" ]; then
86
+ log "Transcript not found at $transcript"
87
+ return 1
88
+ fi
89
+
90
+ # Extract last user and assistant text from JSONL
91
+ # Each line is a JSON object with "type" field
92
+ local last_user last_assistant
93
+
94
+ last_user=$(grep '"type":"user"' "$transcript" 2>/dev/null \
95
+ | tail -3 \
96
+ | jq -r '
97
+ if .message.content then
98
+ [.message.content[] | select(.type == "text") | .text] | join("\n")
99
+ else ""
100
+ end
101
+ ' 2>/dev/null \
102
+ | tail -1)
103
+
104
+ last_assistant=$(grep '"type":"assistant"' "$transcript" 2>/dev/null \
105
+ | tail -3 \
106
+ | jq -r '
107
+ if .message.content then
108
+ [.message.content[] | select(.type == "text") | .text] | join("\n")
109
+ else ""
110
+ end
111
+ ' 2>/dev/null \
112
+ | tail -1)
113
+
114
+ # Truncate to ~4000 chars total to keep token cost low
115
+ if [ ${#last_user} -gt 2000 ]; then
116
+ last_user="${last_user:0:2000}... [truncated]"
117
+ fi
118
+ if [ ${#last_assistant} -gt 2000 ]; then
119
+ last_assistant="${last_assistant:0:2000}... [truncated]"
120
+ fi
121
+
122
+ if [ -z "$last_user" ] && [ -z "$last_assistant" ]; then
123
+ log "No text content found in transcript"
124
+ return 1
125
+ fi
126
+
127
+ LAST_USER_TEXT="$last_user"
128
+ LAST_ASSISTANT_TEXT="$last_assistant"
129
+ return 0
130
+ }
131
+
75
132
  # --- Main ---
76
133
 
77
- # Wait for parent session to flush transcript.
78
- # 3s provides ~6-10x margin over typical flush times.
79
- # If --resume shows stale transcripts, bump to 5s.
134
+ # Wait for parent session to flush transcript
80
135
  sleep 3
81
136
 
82
137
  log "Starting update for session $SESSION_ID"
@@ -87,7 +142,6 @@ break_stale_lock
87
142
  # Acquire lock (other sessions may be updating concurrently)
88
143
  if ! acquire_lock; then
89
144
  log "Lock timeout after 90s — skipping update for session $SESSION_ID"
90
- # Don't clean up lock we don't own
91
145
  trap - EXIT
92
146
  exit 0
93
147
  fi
@@ -102,97 +156,72 @@ if [ -f "$MEMORY_FILE" ]; then
102
156
  PRE_UPDATE_MTIME=$(get_mtime "$MEMORY_FILE")
103
157
  fi
104
158
 
105
- # Build instruction
106
- if [ -n "$EXISTING_MEMORY" ]; then
107
- PATTERNS_INSTRUCTION=""
108
- PATTERNS_FILE="$CWD/.memory/PROJECT-PATTERNS.md"
109
- EXISTING_PATTERNS=""
110
- if [ -f "$PATTERNS_FILE" ]; then
111
- EXISTING_PATTERNS=$(cat "$PATTERNS_FILE")
112
- PATTERNS_INSTRUCTION="
113
-
114
- Also update $PATTERNS_FILE by APPENDING any new recurring patterns discovered during this session. Do NOT overwrite existing entries — only add new ones. Skip if no new patterns were observed. Format each entry as: - **Pattern name**: Brief description (discovered: YYYY-MM-DD). Keep patterns.md under 40 entries. When approaching the limit, consolidate related patterns into broader entries rather than adding duplicates.
115
-
116
- Existing patterns:
117
- $EXISTING_PATTERNS"
118
- else
119
- PATTERNS_INSTRUCTION="
120
-
121
- If recurring patterns were observed during this session (coding conventions, architectural decisions, team preferences, tooling quirks), create $PATTERNS_FILE with entries formatted as: - **Pattern name**: Brief description (discovered: YYYY-MM-DD). Only create this file if genuine patterns were observed — do not fabricate entries."
159
+ # Gather git state (always available, used as fallback too)
160
+ GIT_STATE=""
161
+ if cd "$CWD" 2>/dev/null && git rev-parse --git-dir >/dev/null 2>&1; then
162
+ GIT_STATUS=$(git status --short 2>/dev/null | head -20)
163
+ GIT_LOG=$(git log --oneline -5 2>/dev/null)
164
+ GIT_DIFF=$(git diff --stat HEAD 2>/dev/null | tail -10)
165
+ GIT_STATE="Branch: $(git branch --show-current 2>/dev/null || echo 'unknown')
166
+ Recent commits:
167
+ ${GIT_LOG}
168
+ Changed files:
169
+ ${GIT_STATUS}
170
+ Diff summary:
171
+ ${GIT_DIFF}"
122
172
  fi
123
173
 
124
- INSTRUCTION="First, Read the file $MEMORY_FILE to satisfy Claude Code's read-before-write requirement. Then update it with working memory from this session. The file already has content — possibly from a concurrent session that just wrote it moments ago. Merge this session's context with the existing content to produce a single unified working memory snapshot. Both this session and the existing content represent fresh, concurrent work — integrate both fully. Working memory captures what's active now, not a changelog. Deduplicate overlapping information. Keep under 120 lines total. Use the same structure: ## Now, ## Progress, ## Decisions, ## Modified Files, ## Context, ## Session Log.
125
-
126
- ## Progress tracks Done (completed items), Remaining (next steps), and Blockers (if any). Keep each sub-list to 1-3 items. This section reflects current work state, not historical logs.
127
-
128
- ## Decisions entries must include date and status. Format: - **[Decision]** — [rationale] (YYYY-MM-DD) [ACTIVE|SUPERSEDED]. Mark superseded decisions rather than deleting them.${PATTERNS_INSTRUCTION}
129
-
130
- Existing content:
131
- $EXISTING_MEMORY"
174
+ # Extract last turn from transcript (or fall back to git-only)
175
+ LAST_USER_TEXT=""
176
+ LAST_ASSISTANT_TEXT=""
177
+ EXCHANGE_SECTION=""
178
+
179
+ if extract_last_turn; then
180
+ log "--- Extracted user text (${#LAST_USER_TEXT} chars) ---"
181
+ log "$LAST_USER_TEXT"
182
+ log "--- Extracted assistant text (${#LAST_ASSISTANT_TEXT} chars) ---"
183
+ log "$LAST_ASSISTANT_TEXT"
184
+ log "--- End transcript extraction ---"
185
+ EXCHANGE_SECTION="Last exchange:
186
+ User: ${LAST_USER_TEXT}
187
+ Assistant: ${LAST_ASSISTANT_TEXT}"
132
188
  else
133
- PATTERNS_INSTRUCTION=""
134
- PATTERNS_FILE="$CWD/.memory/PROJECT-PATTERNS.md"
135
- if [ -f "$PATTERNS_FILE" ]; then
136
- EXISTING_PATTERNS=$(cat "$PATTERNS_FILE")
137
- PATTERNS_INSTRUCTION="
138
-
139
- Also update $PATTERNS_FILE by APPENDING any new recurring patterns discovered during this session. Do NOT overwrite existing entries — only add new ones. Skip if no new patterns were observed. Format each entry as: - **Pattern name**: Brief description (discovered: YYYY-MM-DD). Keep patterns.md under 40 entries. When approaching the limit, consolidate related patterns into broader entries rather than adding duplicates.
140
-
141
- Existing patterns:
142
- $EXISTING_PATTERNS"
143
- else
144
- PATTERNS_INSTRUCTION="
145
-
146
- If recurring patterns were observed during this session (coding conventions, architectural decisions, team preferences, tooling quirks), create $PATTERNS_FILE with entries formatted as: - **Pattern name**: Brief description (discovered: YYYY-MM-DD). Only create this file if genuine patterns were observed — do not fabricate entries."
147
- fi
148
-
149
- INSTRUCTION="First, Read the file $MEMORY_FILE if it exists (to satisfy Claude Code's read-before-write requirement). Then create it with working memory from this session. Keep under 120 lines. Use this structure:
150
-
151
- # Working Memory
152
-
153
- ## Now
154
- <!-- Current focus, status, blockers (1-3 bullets) -->
155
-
156
- ## Progress
157
- <!-- Done: completed items (1-3). Remaining: next steps (1-3). Blockers: if any. -->
189
+ log "Falling back to git-state-only context"
190
+ EXCHANGE_SECTION="(Session transcript not available — using git state only)"
191
+ fi
158
192
 
159
- ## Decisions
160
- <!-- Format: - **[Decision]**[rationale] (YYYY-MM-DD) [ACTIVE|SUPERSEDED] -->
193
+ # Build prompt for fresh claude -p invocation
194
+ PROMPT="You are a working memory updater. Your ONLY job is to update the file at ${MEMORY_FILE} using the Write tool. Do it immediately do not ask questions or explain.
161
195
 
162
- ## Modified Files
163
- <!-- File paths only, most recent first -->
196
+ Current working memory:
197
+ ${EXISTING_MEMORY:-"(no existing content)"}
164
198
 
165
- ## Context
166
- <!-- Branch, PR, architectural context, open questions -->
199
+ ${EXCHANGE_SECTION}
167
200
 
168
- ## Session Log
201
+ Git state:
202
+ ${GIT_STATE:-"(not a git repo)"}
169
203
 
170
- ### Today
171
- <!-- Chronological summary of work done today (2-5 bullets) -->
204
+ Instructions:
205
+ - Use the Write tool to update ${MEMORY_FILE} immediately
206
+ - Keep under 120 lines
207
+ - Use sections: ## Now, ## Progress, ## Decisions, ## Modified Files, ## Context, ## Session Log
208
+ - Integrate new information with existing content
209
+ - Deduplicate overlapping information
210
+ - ## Progress tracks Done (completed), Remaining (next steps), Blockers (if any)
211
+ - ## Decisions entries: format as - **[Decision]** — [rationale] (YYYY-MM-DD) [ACTIVE|SUPERSEDED]"
172
212
 
173
- ### This Week
174
- <!-- Broader multi-day context if relevant -->${PATTERNS_INSTRUCTION}"
175
- fi
213
+ log "--- Full prompt being passed to claude -p ---"
214
+ log "$PROMPT"
215
+ log "--- End prompt ---"
176
216
 
177
- # Resume session headlessly to perform the update
178
- TIMEOUT=120 # Normal runtime 30-60s; 2x margin
217
+ # Run fresh claude -p (no --resume, no conversation confusion)
218
+ TIMEOUT=120
179
219
 
180
- DEVFLOW_BG_UPDATER=1 env -u CLAUDECODE "$CLAUDE_BIN" -p \
181
- --resume "$SESSION_ID" \
220
+ DEVFLOW_BG_UPDATER=1 "$CLAUDE_BIN" -p \
182
221
  --model haiku \
183
- --tools "Read,Write,Bash" \
184
- --allowedTools \
185
- "Read($CWD/.memory/WORKING-MEMORY.md)" \
186
- "Read($CWD/.memory/PROJECT-PATTERNS.md)" \
187
- "Write($CWD/.memory/WORKING-MEMORY.md)" \
188
- "Write($CWD/.memory/PROJECT-PATTERNS.md)" \
189
- "Bash(git status:*)" \
190
- "Bash(git log:*)" \
191
- "Bash(git diff:*)" \
192
- "Bash(git branch:*)" \
193
- --no-session-persistence \
222
+ --dangerously-skip-permissions \
194
223
  --output-format text \
195
- "$INSTRUCTION" \
224
+ "$PROMPT" \
196
225
  >> "$LOG_FILE" 2>&1 &
197
226
  CLAUDE_PID=$!
198
227
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  # SessionStart Hook
4
4
  # Injects working memory AND ambient skill content as additionalContext.
5
- # Memory: restores .memory/WORKING-MEMORY.md + patterns + git state + compact recovery.
5
+ # Memory: restores .memory/WORKING-MEMORY.md + git state + compact recovery.
6
6
  # Ambient: injects ambient-router SKILL.md so Claude has it in context (no Read call needed).
7
7
  # Either section can fire independently — ambient works even without memory files.
8
8
 
@@ -27,13 +27,6 @@ MEMORY_FILE="$CWD/.memory/WORKING-MEMORY.md"
27
27
  if [ -f "$MEMORY_FILE" ]; then
28
28
  MEMORY_CONTENT=$(cat "$MEMORY_FILE")
29
29
 
30
- # Read accumulated patterns if they exist
31
- PATTERNS_FILE="$CWD/.memory/PROJECT-PATTERNS.md"
32
- PATTERNS_CONTENT=""
33
- if [ -f "$PATTERNS_FILE" ]; then
34
- PATTERNS_CONTENT=$(cat "$PATTERNS_FILE")
35
- fi
36
-
37
30
  # Compute staleness warning
38
31
  if stat --version &>/dev/null 2>&1; then
39
32
  FILE_MTIME=$(stat -c %Y "$MEMORY_FILE")
@@ -91,15 +84,6 @@ $BACKUP_MEMORY
91
84
 
92
85
  ${MEMORY_CONTENT}"
93
86
 
94
- # Insert accumulated patterns between working memory and git state
95
- if [ -n "$PATTERNS_CONTENT" ]; then
96
- CONTEXT="${CONTEXT}
97
-
98
- --- PROJECT PATTERNS (accumulated) ---
99
-
100
- ${PATTERNS_CONTENT}"
101
- fi
102
-
103
87
  CONTEXT="${CONTEXT}
104
88
 
105
89
  --- CURRENT GIT STATE ---
@@ -2,7 +2,7 @@
2
2
  name: ambient-router
3
3
  description: This skill should be used when classifying user intent for ambient mode, auto-loading relevant skills without explicit command invocation. Used by the always-on UserPromptSubmit hook.
4
4
  user-invocable: false
5
- allowed-tools: Read, Grep, Glob
5
+ # No allowed-tools: orchestrator requires unrestricted access (Skill, Agent, Edit, Write, Bash)
6
6
  ---
7
7
 
8
8
  # Ambient Router
@@ -89,6 +89,9 @@ When classification is GUIDED or ORCHESTRATED, skill loading is NON-NEGOTIABLE.
89
89
  Do not rationalize skipping skills. Do not respond without loading them first.
90
90
  BLOCKING REQUIREMENT: Invoke each selected skill using the Skill tool before proceeding.
91
91
  For IMPLEMENT intent, enforce TDD: write the failing test before ANY production code.
92
+ NOTE: Skills loaded in the main session via ambient mode are reference patterns only —
93
+ their allowed-tools metadata does NOT restrict your tool access. You retain full access
94
+ to all tools (Edit, Write, Bash, Agent, etc.) for implementation work.
92
95
  </IMPORTANT>
93
96
 
94
97
  - **QUICK:** Respond directly. No preamble, no classification statement.
@@ -38,7 +38,6 @@ All generated documentation lives under `.docs/` in the project root:
38
38
 
39
39
  .memory/
40
40
  ├── WORKING-MEMORY.md # Auto-maintained by Stop hook (overwritten)
41
- ├── PROJECT-PATTERNS.md # Accumulated patterns (merged across sessions)
42
41
  ├── backup.json # Pre-compact git state snapshot
43
42
  └── knowledge/
44
43
  ├── decisions.md # Architectural decisions (ADR-NNN format)