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.
- package/CHANGELOG.md +14 -0
- package/README.md +3 -1
- package/dist/commands/init.js +6 -2
- package/dist/commands/uninstall.d.ts +9 -0
- package/dist/commands/uninstall.js +60 -4
- package/dist/utils/post-install.js +0 -1
- package/package.json +1 -1
- package/plugins/devflow-accessibility/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-ambient/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-ambient/skills/ambient-router/SKILL.md +4 -1
- package/plugins/devflow-audit-claude/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-code-review/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-core-skills/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-core-skills/skills/docs-framework/SKILL.md +0 -1
- package/plugins/devflow-debug/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-frontend-design/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-go/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-implement/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-java/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-python/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-react/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-resolve/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-rust/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-self-review/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-specify/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-typescript/.claude-plugin/plugin.json +1 -1
- package/scripts/hooks/ambient-prompt +5 -4
- package/scripts/hooks/background-memory-update +114 -85
- package/scripts/hooks/session-start-memory +1 -17
- package/shared/skills/ambient-router/SKILL.md +4 -1
- 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
|
|
package/dist/commands/init.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
@@ -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:
|
|
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)
|
|
@@ -25,21 +25,22 @@ if [[ "$PROMPT" == /* ]]; then
|
|
|
25
25
|
fi
|
|
26
26
|
|
|
27
27
|
# Skip single-word confirmations (< 2 words)
|
|
28
|
-
WORD_COUNT=$(
|
|
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=$(
|
|
34
|
+
PROMPT_LOWER=$(printf '%s' "$PROMPT" | tr '[:upper:]' '[:lower:]')
|
|
35
35
|
|
|
36
36
|
# Fast-path: git operations are always QUICK — skip preamble
|
|
37
|
-
if
|
|
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
|
-
|
|
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
|
-
#
|
|
6
|
-
#
|
|
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
|
|
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
|
-
#
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
$
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
$
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
160
|
-
|
|
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
|
-
|
|
163
|
-
|
|
196
|
+
Current working memory:
|
|
197
|
+
${EXISTING_MEMORY:-"(no existing content)"}
|
|
164
198
|
|
|
165
|
-
|
|
166
|
-
<!-- Branch, PR, architectural context, open questions -->
|
|
199
|
+
${EXCHANGE_SECTION}
|
|
167
200
|
|
|
168
|
-
|
|
201
|
+
Git state:
|
|
202
|
+
${GIT_STATE:-"(not a git repo)"}
|
|
169
203
|
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
213
|
+
log "--- Full prompt being passed to claude -p ---"
|
|
214
|
+
log "$PROMPT"
|
|
215
|
+
log "--- End prompt ---"
|
|
176
216
|
|
|
177
|
-
#
|
|
178
|
-
TIMEOUT=120
|
|
217
|
+
# Run fresh claude -p (no --resume, no conversation confusion)
|
|
218
|
+
TIMEOUT=120
|
|
179
219
|
|
|
180
|
-
DEVFLOW_BG_UPDATER=1
|
|
181
|
-
--resume "$SESSION_ID" \
|
|
220
|
+
DEVFLOW_BG_UPDATER=1 "$CLAUDE_BIN" -p \
|
|
182
221
|
--model haiku \
|
|
183
|
-
--
|
|
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
|
-
"$
|
|
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 +
|
|
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:
|
|
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)
|