devflow-kit 1.0.0 → 1.1.0
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 +30 -0
- package/README.md +13 -6
- package/dist/cli.js +5 -1
- package/dist/commands/ambient.d.ts +18 -0
- package/dist/commands/ambient.js +136 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +97 -10
- package/dist/commands/memory.d.ts +22 -0
- package/dist/commands/memory.js +175 -0
- package/dist/commands/uninstall.js +72 -5
- package/dist/plugins.js +8 -1
- package/dist/utils/post-install.d.ts +12 -0
- package/dist/utils/post-install.js +82 -1
- package/dist/utils/safe-delete-install.d.ts +7 -0
- package/dist/utils/safe-delete-install.js +40 -5
- package/package.json +1 -1
- package/plugins/devflow-ambient/.claude-plugin/plugin.json +7 -0
- package/plugins/devflow-ambient/README.md +49 -0
- package/plugins/devflow-ambient/commands/ambient.md +110 -0
- package/plugins/devflow-ambient/skills/ambient-router/SKILL.md +89 -0
- package/plugins/devflow-ambient/skills/ambient-router/references/skill-catalog.md +64 -0
- 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 +2 -1
- package/plugins/devflow-core-skills/skills/docs-framework/SKILL.md +10 -6
- package/plugins/devflow-core-skills/skills/test-driven-development/SKILL.md +139 -0
- package/plugins/devflow-core-skills/skills/test-driven-development/references/rationalization-prevention.md +111 -0
- package/plugins/devflow-debug/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-implement/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-resolve/.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/scripts/hooks/ambient-prompt.sh +48 -0
- package/scripts/hooks/background-memory-update.sh +49 -8
- package/scripts/hooks/ensure-memory-gitignore.sh +17 -0
- package/scripts/hooks/pre-compact-memory.sh +12 -6
- package/scripts/hooks/session-start-memory.sh +50 -8
- package/scripts/hooks/stop-update-memory.sh +10 -6
- package/shared/skills/ambient-router/SKILL.md +89 -0
- package/shared/skills/ambient-router/references/skill-catalog.md +64 -0
- package/shared/skills/docs-framework/SKILL.md +10 -6
- package/shared/skills/test-driven-development/SKILL.md +139 -0
- package/shared/skills/test-driven-development/references/rationalization-prevention.md +111 -0
- package/src/templates/managed-settings.json +14 -0
|
@@ -9,6 +9,8 @@ import { getInstallationPaths, getClaudeDirectory, getManagedSettingsPath } from
|
|
|
9
9
|
import { getGitRoot } from '../utils/git.js';
|
|
10
10
|
import { isClaudeCliAvailable } from '../utils/cli.js';
|
|
11
11
|
import { DEVFLOW_PLUGINS, getAllSkillNames, LEGACY_SKILL_NAMES } from '../plugins.js';
|
|
12
|
+
import { removeAmbientHook } from './ambient.js';
|
|
13
|
+
import { removeMemoryHooks } from './memory.js';
|
|
12
14
|
import { detectShell, getProfilePath } from '../utils/safe-delete.js';
|
|
13
15
|
import { isAlreadyInstalled, removeFromProfile } from '../utils/safe-delete-install.js';
|
|
14
16
|
import { removeManagedSettings } from '../utils/post-install.js';
|
|
@@ -176,6 +178,21 @@ export const uninstallCommand = new Command('uninstall')
|
|
|
176
178
|
if (!usedCli) {
|
|
177
179
|
if (isSelectiveUninstall) {
|
|
178
180
|
await removeSelectedPlugins(claudeDir, selectedPlugins, verbose);
|
|
181
|
+
// Clean up ambient hook if ambient plugin is being removed
|
|
182
|
+
if (selectedPlugins.some(sp => sp.name === 'devflow-ambient')) {
|
|
183
|
+
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
184
|
+
try {
|
|
185
|
+
const settings = await fs.readFile(settingsPath, 'utf-8');
|
|
186
|
+
const updated = removeAmbientHook(settings);
|
|
187
|
+
if (updated !== settings) {
|
|
188
|
+
await fs.writeFile(settingsPath, updated, 'utf-8');
|
|
189
|
+
if (verbose) {
|
|
190
|
+
p.log.success('Ambient mode hook removed from settings.json');
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
catch { /* settings.json may not exist */ }
|
|
195
|
+
}
|
|
179
196
|
}
|
|
180
197
|
else {
|
|
181
198
|
await removeAllDevFlow(claudeDir, devflowScriptsDir, verbose);
|
|
@@ -221,7 +238,39 @@ export const uninstallCommand = new Command('uninstall')
|
|
|
221
238
|
p.log.info('.docs/ preserved');
|
|
222
239
|
}
|
|
223
240
|
}
|
|
224
|
-
// 2. .
|
|
241
|
+
// 2. .memory/ directory
|
|
242
|
+
const memoryDir = path.join(process.cwd(), '.memory');
|
|
243
|
+
let memoryExist = false;
|
|
244
|
+
try {
|
|
245
|
+
await fs.access(memoryDir);
|
|
246
|
+
memoryExist = true;
|
|
247
|
+
}
|
|
248
|
+
catch { /* .memory doesn't exist */ }
|
|
249
|
+
if (memoryExist) {
|
|
250
|
+
let shouldRemoveMemory = false;
|
|
251
|
+
if (options.keepDocs) {
|
|
252
|
+
shouldRemoveMemory = false;
|
|
253
|
+
}
|
|
254
|
+
else if (process.stdin.isTTY) {
|
|
255
|
+
const removeMemory = await p.confirm({
|
|
256
|
+
message: '.memory/ directory found. Remove working memory files?',
|
|
257
|
+
initialValue: false,
|
|
258
|
+
});
|
|
259
|
+
if (p.isCancel(removeMemory)) {
|
|
260
|
+
p.cancel('Uninstall cancelled.');
|
|
261
|
+
process.exit(0);
|
|
262
|
+
}
|
|
263
|
+
shouldRemoveMemory = removeMemory;
|
|
264
|
+
}
|
|
265
|
+
if (shouldRemoveMemory) {
|
|
266
|
+
await fs.rm(memoryDir, { recursive: true, force: true });
|
|
267
|
+
p.log.success('.memory/ removed');
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
p.log.info('.memory/ preserved');
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
// 4. .claudeignore
|
|
225
274
|
const claudeignorePath = gitRoot
|
|
226
275
|
? path.join(gitRoot, '.claudeignore')
|
|
227
276
|
: path.join(process.cwd(), '.claudeignore');
|
|
@@ -249,12 +298,30 @@ export const uninstallCommand = new Command('uninstall')
|
|
|
249
298
|
p.log.info('.claudeignore preserved (non-interactive mode)');
|
|
250
299
|
}
|
|
251
300
|
}
|
|
252
|
-
//
|
|
301
|
+
// 5. settings.json (DevFlow hooks)
|
|
253
302
|
for (const scope of scopesToUninstall) {
|
|
254
303
|
try {
|
|
255
304
|
const paths = await getInstallationPaths(scope);
|
|
256
305
|
const settingsPath = path.join(paths.claudeDir, 'settings.json');
|
|
257
|
-
|
|
306
|
+
let settingsContent = await fs.readFile(settingsPath, 'utf-8');
|
|
307
|
+
// Always remove ambient hook on full uninstall (idempotent)
|
|
308
|
+
const withoutAmbient = removeAmbientHook(settingsContent);
|
|
309
|
+
if (withoutAmbient !== settingsContent) {
|
|
310
|
+
await fs.writeFile(settingsPath, withoutAmbient, 'utf-8');
|
|
311
|
+
settingsContent = withoutAmbient;
|
|
312
|
+
if (verbose) {
|
|
313
|
+
p.log.success(`Ambient mode hook removed from settings.json (${scope})`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
// Always remove memory hooks on full uninstall (idempotent)
|
|
317
|
+
const withoutMemory = removeMemoryHooks(settingsContent);
|
|
318
|
+
if (withoutMemory !== settingsContent) {
|
|
319
|
+
await fs.writeFile(settingsPath, withoutMemory, 'utf-8');
|
|
320
|
+
settingsContent = withoutMemory;
|
|
321
|
+
if (verbose) {
|
|
322
|
+
p.log.success(`Memory hooks removed from settings.json (${scope})`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
258
325
|
const settings = JSON.parse(settingsContent);
|
|
259
326
|
if (settings.hooks) {
|
|
260
327
|
if (process.stdin.isTTY) {
|
|
@@ -280,7 +347,7 @@ export const uninstallCommand = new Command('uninstall')
|
|
|
280
347
|
// settings.json doesn't exist or can't be parsed — skip
|
|
281
348
|
}
|
|
282
349
|
}
|
|
283
|
-
//
|
|
350
|
+
// 6. Managed settings (security deny list)
|
|
284
351
|
let managedSettingsExist = false;
|
|
285
352
|
try {
|
|
286
353
|
const managedPath = getManagedSettingsPath();
|
|
@@ -309,7 +376,7 @@ export const uninstallCommand = new Command('uninstall')
|
|
|
309
376
|
p.log.info('Managed settings preserved (non-interactive mode)');
|
|
310
377
|
}
|
|
311
378
|
}
|
|
312
|
-
//
|
|
379
|
+
// 7. Safe-delete shell function
|
|
313
380
|
const shell = detectShell();
|
|
314
381
|
const profilePath = getProfilePath(shell);
|
|
315
382
|
if (profilePath && await isAlreadyInstalled(profilePath)) {
|
package/dist/plugins.js
CHANGED
|
@@ -10,7 +10,7 @@ export const DEVFLOW_PLUGINS = [
|
|
|
10
10
|
description: 'Auto-activating quality enforcement (foundation layer)',
|
|
11
11
|
commands: [],
|
|
12
12
|
agents: [],
|
|
13
|
-
skills: ['accessibility', 'core-patterns', 'docs-framework', 'frontend-design', 'git-safety', 'git-workflow', 'github-patterns', 'input-validation', 'react', 'test-patterns', 'typescript'],
|
|
13
|
+
skills: ['accessibility', 'core-patterns', 'docs-framework', 'frontend-design', 'git-safety', 'git-workflow', 'github-patterns', 'input-validation', 'react', 'test-driven-development', 'test-patterns', 'typescript'],
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
16
|
name: 'devflow-specify',
|
|
@@ -54,6 +54,13 @@ export const DEVFLOW_PLUGINS = [
|
|
|
54
54
|
agents: ['simplifier', 'scrutinizer', 'validator'],
|
|
55
55
|
skills: ['self-review', 'core-patterns'],
|
|
56
56
|
},
|
|
57
|
+
{
|
|
58
|
+
name: 'devflow-ambient',
|
|
59
|
+
description: 'Ambient mode — auto-loads relevant skills based on each prompt',
|
|
60
|
+
commands: ['/ambient'],
|
|
61
|
+
agents: [],
|
|
62
|
+
skills: ['ambient-router'],
|
|
63
|
+
},
|
|
57
64
|
{
|
|
58
65
|
name: 'devflow-audit-claude',
|
|
59
66
|
description: 'Audit CLAUDE.md files against Anthropic best practices',
|
|
@@ -65,4 +65,16 @@ export declare function updateGitignore(gitRoot: string, verbose: boolean): Prom
|
|
|
65
65
|
* Create .docs/ directory structure for DevFlow artifacts.
|
|
66
66
|
*/
|
|
67
67
|
export declare function createDocsStructure(verbose: boolean): Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Create .memory/ directory for working memory files.
|
|
70
|
+
* Separate from .docs/ which is for reviews/releases.
|
|
71
|
+
*/
|
|
72
|
+
export declare function createMemoryDir(verbose: boolean, cwd?: string): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* Migrate memory files from .docs/ to .memory/.
|
|
75
|
+
* One-time migration for existing users. Skips if destination exists (no clobber).
|
|
76
|
+
* Also cleans up ephemeral files from .docs/.
|
|
77
|
+
* Returns count of migrated files.
|
|
78
|
+
*/
|
|
79
|
+
export declare function migrateMemoryFiles(verbose: boolean, cwd?: string): Promise<number>;
|
|
68
80
|
//# sourceMappingURL=post-install.d.ts.map
|
|
@@ -386,7 +386,7 @@ export async function installClaudeignore(gitRoot, rootDir, verbose) {
|
|
|
386
386
|
export async function updateGitignore(gitRoot, verbose) {
|
|
387
387
|
try {
|
|
388
388
|
const gitignorePath = path.join(gitRoot, '.gitignore');
|
|
389
|
-
const entriesToAdd = ['.claude/', '.devflow/'];
|
|
389
|
+
const entriesToAdd = ['.claude/', '.devflow/', '.memory/', '.docs/'];
|
|
390
390
|
let gitignoreContent = '';
|
|
391
391
|
try {
|
|
392
392
|
gitignoreContent = await fs.readFile(gitignorePath, 'utf-8');
|
|
@@ -424,4 +424,85 @@ export async function createDocsStructure(verbose) {
|
|
|
424
424
|
}
|
|
425
425
|
catch { /* may already exist */ }
|
|
426
426
|
}
|
|
427
|
+
/**
|
|
428
|
+
* Create .memory/ directory for working memory files.
|
|
429
|
+
* Separate from .docs/ which is for reviews/releases.
|
|
430
|
+
*/
|
|
431
|
+
export async function createMemoryDir(verbose, cwd) {
|
|
432
|
+
const memoryDir = path.join(cwd ?? process.cwd(), '.memory');
|
|
433
|
+
try {
|
|
434
|
+
await fs.mkdir(memoryDir, { recursive: true });
|
|
435
|
+
if (verbose) {
|
|
436
|
+
p.log.success('.memory/ directory ready');
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
catch { /* may already exist */ }
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Migrate memory files from .docs/ to .memory/.
|
|
443
|
+
* One-time migration for existing users. Skips if destination exists (no clobber).
|
|
444
|
+
* Also cleans up ephemeral files from .docs/.
|
|
445
|
+
* Returns count of migrated files.
|
|
446
|
+
*/
|
|
447
|
+
export async function migrateMemoryFiles(verbose, cwd) {
|
|
448
|
+
const root = cwd ?? process.cwd();
|
|
449
|
+
const docsDir = path.join(root, '.docs');
|
|
450
|
+
const memoryDir = path.join(root, '.memory');
|
|
451
|
+
const migrations = [
|
|
452
|
+
{ src: path.join(docsDir, 'WORKING-MEMORY.md'), dest: path.join(memoryDir, 'WORKING-MEMORY.md') },
|
|
453
|
+
{ src: path.join(docsDir, 'patterns.md'), dest: path.join(memoryDir, 'PROJECT-PATTERNS.md') },
|
|
454
|
+
{ src: path.join(docsDir, 'working-memory-backup.json'), dest: path.join(memoryDir, 'backup.json') },
|
|
455
|
+
];
|
|
456
|
+
let migrated = 0;
|
|
457
|
+
for (const { src, dest } of migrations) {
|
|
458
|
+
try {
|
|
459
|
+
await fs.access(src);
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
continue; // Source doesn't exist
|
|
463
|
+
}
|
|
464
|
+
try {
|
|
465
|
+
await fs.access(dest);
|
|
466
|
+
continue; // Destination already exists — no clobber
|
|
467
|
+
}
|
|
468
|
+
catch {
|
|
469
|
+
// Destination doesn't exist — proceed with migration
|
|
470
|
+
}
|
|
471
|
+
try {
|
|
472
|
+
await fs.rename(src, dest);
|
|
473
|
+
migrated++;
|
|
474
|
+
}
|
|
475
|
+
catch {
|
|
476
|
+
// Cross-device or permission error — try copy+delete
|
|
477
|
+
try {
|
|
478
|
+
await fs.copyFile(src, dest);
|
|
479
|
+
await fs.rm(src, { force: true });
|
|
480
|
+
migrated++;
|
|
481
|
+
}
|
|
482
|
+
catch {
|
|
483
|
+
// Migration failed for this file — skip silently
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
// Clean up ephemeral files from .docs/
|
|
488
|
+
const ephemeralFiles = [
|
|
489
|
+
path.join(docsDir, '.working-memory-update.log'),
|
|
490
|
+
path.join(docsDir, '.working-memory-last-trigger'),
|
|
491
|
+
];
|
|
492
|
+
for (const file of ephemeralFiles) {
|
|
493
|
+
try {
|
|
494
|
+
await fs.rm(file, { force: true });
|
|
495
|
+
}
|
|
496
|
+
catch { /* doesn't exist or can't remove */ }
|
|
497
|
+
}
|
|
498
|
+
// Clean up lock directory
|
|
499
|
+
try {
|
|
500
|
+
await fs.rmdir(path.join(docsDir, '.working-memory.lock'));
|
|
501
|
+
}
|
|
502
|
+
catch { /* doesn't exist or not empty */ }
|
|
503
|
+
if (migrated > 0 && verbose) {
|
|
504
|
+
p.log.success(`Migrated ${migrated} memory file(s) from .docs/ to .memory/`);
|
|
505
|
+
}
|
|
506
|
+
return migrated;
|
|
507
|
+
}
|
|
427
508
|
//# sourceMappingURL=post-install.js.map
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { Shell } from './safe-delete.js';
|
|
2
|
+
/** Bump this when the safe-delete block changes. */
|
|
3
|
+
export declare const SAFE_DELETE_BLOCK_VERSION = 2;
|
|
2
4
|
/**
|
|
3
5
|
* Generate the safe-delete shell function block with markers.
|
|
4
6
|
* Returns null for unsupported shells.
|
|
@@ -13,6 +15,11 @@ export declare function isAlreadyInstalled(profilePath: string): Promise<boolean
|
|
|
13
15
|
* Creates parent directories and the file if they don't exist.
|
|
14
16
|
*/
|
|
15
17
|
export declare function installToProfile(profilePath: string, block: string): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Extract the installed safe-delete block version from a profile file.
|
|
20
|
+
* Returns 0 (not installed), 1 (legacy block without version stamp), or N (versioned block).
|
|
21
|
+
*/
|
|
22
|
+
export declare function getInstalledVersion(profilePath: string): Promise<number>;
|
|
16
23
|
/**
|
|
17
24
|
* Remove the safe-delete block from a profile file.
|
|
18
25
|
* Returns true if the block was found and removed, false otherwise.
|
|
@@ -2,6 +2,8 @@ import { promises as fs } from 'fs';
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
const START_MARKER = '# >>> DevFlow safe-delete >>>';
|
|
4
4
|
const END_MARKER = '# <<< DevFlow safe-delete <<<';
|
|
5
|
+
/** Bump this when the safe-delete block changes. */
|
|
6
|
+
export const SAFE_DELETE_BLOCK_VERSION = 2;
|
|
5
7
|
/**
|
|
6
8
|
* Generate the safe-delete shell function block with markers.
|
|
7
9
|
* Returns null for unsupported shells.
|
|
@@ -13,13 +15,18 @@ export function generateSafeDeleteBlock(shell, platform, trashCommand) {
|
|
|
13
15
|
const cmd = trashCommand ?? 'trash';
|
|
14
16
|
return [
|
|
15
17
|
START_MARKER,
|
|
18
|
+
`# v${SAFE_DELETE_BLOCK_VERSION}`,
|
|
16
19
|
`rm() {`,
|
|
17
20
|
` local files=()`,
|
|
18
21
|
` for arg in "$@"; do`,
|
|
19
22
|
` [[ "$arg" =~ ^- ]] || files+=("$arg")`,
|
|
20
23
|
` done`,
|
|
21
|
-
`
|
|
22
|
-
`
|
|
24
|
+
` local existing=()`,
|
|
25
|
+
` for f in "\${files[@]}"; do`,
|
|
26
|
+
` [ -e "$f" ] || [ -L "$f" ] && existing+=("$f")`,
|
|
27
|
+
` done`,
|
|
28
|
+
` if (( \${#existing[@]} > 0 )); then`,
|
|
29
|
+
` ${cmd} "\${existing[@]}"`,
|
|
23
30
|
` fi`,
|
|
24
31
|
`}`,
|
|
25
32
|
`command() {`,
|
|
@@ -36,6 +43,7 @@ export function generateSafeDeleteBlock(shell, platform, trashCommand) {
|
|
|
36
43
|
const cmd = trashCommand ?? 'trash';
|
|
37
44
|
return [
|
|
38
45
|
START_MARKER,
|
|
46
|
+
`# v${SAFE_DELETE_BLOCK_VERSION}`,
|
|
39
47
|
`function rm --description "Safe delete via trash"`,
|
|
40
48
|
` set -l files`,
|
|
41
49
|
` for arg in $argv`,
|
|
@@ -43,8 +51,14 @@ export function generateSafeDeleteBlock(shell, platform, trashCommand) {
|
|
|
43
51
|
` set files $files $arg`,
|
|
44
52
|
` end`,
|
|
45
53
|
` end`,
|
|
46
|
-
`
|
|
47
|
-
`
|
|
54
|
+
` set -l existing`,
|
|
55
|
+
` for f in $files`,
|
|
56
|
+
` if test -e $f; or test -L $f`,
|
|
57
|
+
` set existing $existing $f`,
|
|
58
|
+
` end`,
|
|
59
|
+
` end`,
|
|
60
|
+
` if test (count $existing) -gt 0`,
|
|
61
|
+
` ${cmd} $existing`,
|
|
48
62
|
` end`,
|
|
49
63
|
`end`,
|
|
50
64
|
END_MARKER,
|
|
@@ -54,6 +68,7 @@ export function generateSafeDeleteBlock(shell, platform, trashCommand) {
|
|
|
54
68
|
if (platform === 'win32') {
|
|
55
69
|
return [
|
|
56
70
|
START_MARKER,
|
|
71
|
+
`# v${SAFE_DELETE_BLOCK_VERSION}`,
|
|
57
72
|
`if (Get-Alias rm -ErrorAction SilentlyContinue) {`,
|
|
58
73
|
` Remove-Alias rm -Force -Scope Global`,
|
|
59
74
|
`}`,
|
|
@@ -82,12 +97,14 @@ export function generateSafeDeleteBlock(shell, platform, trashCommand) {
|
|
|
82
97
|
const cmd = trashCommand ?? 'trash';
|
|
83
98
|
return [
|
|
84
99
|
START_MARKER,
|
|
100
|
+
`# v${SAFE_DELETE_BLOCK_VERSION}`,
|
|
85
101
|
`if (Get-Alias rm -ErrorAction SilentlyContinue) {`,
|
|
86
102
|
` Remove-Alias rm -Force -Scope Global`,
|
|
87
103
|
`}`,
|
|
88
104
|
`function rm {`,
|
|
89
105
|
` $files = $args | Where-Object { $_ -notlike '-*' }`,
|
|
90
|
-
`
|
|
106
|
+
` $existing = $files | Where-Object { Test-Path $_ }`,
|
|
107
|
+
` if ($existing) { & ${cmd} @existing }`,
|
|
91
108
|
`}`,
|
|
92
109
|
END_MARKER,
|
|
93
110
|
].join('\n');
|
|
@@ -123,6 +140,24 @@ export async function installToProfile(profilePath, block) {
|
|
|
123
140
|
const content = existing.length > 0 ? existing + separator + block + '\n' : block + '\n';
|
|
124
141
|
await fs.writeFile(profilePath, content, 'utf-8');
|
|
125
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* Extract the installed safe-delete block version from a profile file.
|
|
145
|
+
* Returns 0 (not installed), 1 (legacy block without version stamp), or N (versioned block).
|
|
146
|
+
*/
|
|
147
|
+
export async function getInstalledVersion(profilePath) {
|
|
148
|
+
try {
|
|
149
|
+
const content = await fs.readFile(profilePath, 'utf-8');
|
|
150
|
+
const startIdx = content.indexOf(START_MARKER);
|
|
151
|
+
if (startIdx === -1)
|
|
152
|
+
return 0;
|
|
153
|
+
const afterMarker = content.slice(startIdx + START_MARKER.length);
|
|
154
|
+
const match = afterMarker.match(/^\n# v(\d+)/);
|
|
155
|
+
return match ? parseInt(match[1], 10) : 1;
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return 0;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
126
161
|
/**
|
|
127
162
|
* Remove the safe-delete block from a profile file.
|
|
128
163
|
* Returns true if the block was found and removed, false otherwise.
|
package/package.json
CHANGED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# devflow-ambient
|
|
2
|
+
|
|
3
|
+
Ambient mode — auto-loads relevant skills based on each prompt, no explicit commands needed.
|
|
4
|
+
|
|
5
|
+
## Command
|
|
6
|
+
|
|
7
|
+
### `/ambient`
|
|
8
|
+
|
|
9
|
+
Classify user intent and apply proportional skill enforcement to any prompt.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
/ambient add a login form # BUILD/STANDARD — loads TDD + implementation-patterns
|
|
13
|
+
/ambient fix the auth error # DEBUG/STANDARD — loads test-patterns + core-patterns
|
|
14
|
+
/ambient where is the config? # EXPLORE/QUICK — responds normally, zero overhead
|
|
15
|
+
/ambient refactor the auth system # BUILD/ESCALATE — suggests /implement
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Always-On Mode
|
|
19
|
+
|
|
20
|
+
Enable ambient classification on every prompt without typing `/ambient`:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
devflow ambient --enable # Register UserPromptSubmit hook
|
|
24
|
+
devflow ambient --disable # Remove hook
|
|
25
|
+
devflow ambient --status # Check if enabled
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
When enabled, a `UserPromptSubmit` hook injects a classification preamble before every prompt. Slash commands (`/implement`, `/code-review`, etc.) and short confirmations ("yes", "ok") are skipped automatically.
|
|
29
|
+
|
|
30
|
+
## How It Works
|
|
31
|
+
|
|
32
|
+
1. **Classify intent** — BUILD, DEBUG, REVIEW, PLAN, EXPLORE, or CHAT
|
|
33
|
+
2. **Classify depth** — QUICK (zero overhead), STANDARD (2-3 skills), or ESCALATE (workflow nudge)
|
|
34
|
+
3. **Apply proportionally**:
|
|
35
|
+
- QUICK: respond normally
|
|
36
|
+
- STANDARD: load relevant skills, enforce TDD for BUILD
|
|
37
|
+
- ESCALATE: respond + recommend full workflow command
|
|
38
|
+
|
|
39
|
+
## Depth Tiers
|
|
40
|
+
|
|
41
|
+
| Depth | When | Overhead |
|
|
42
|
+
|-------|------|----------|
|
|
43
|
+
| QUICK | Chat, simple exploration, git/devops ops, single-word confirmations | ~0 tokens |
|
|
44
|
+
| STANDARD | BUILD/DEBUG/REVIEW/PLAN, 1-5 file scope | ~500-1000 tokens (skill reads) |
|
|
45
|
+
| ESCALATE | Multi-file, architectural, system-wide scope | ~0 extra tokens (nudge only) |
|
|
46
|
+
|
|
47
|
+
## Skills
|
|
48
|
+
|
|
49
|
+
- `ambient-router` — Intent + depth classification, skill selection matrix
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Ambient mode — classify intent and auto-load relevant skills for any prompt
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Ambient Command
|
|
6
|
+
|
|
7
|
+
Classify user intent and auto-load relevant skills. No agents spawned — enhances the main session only.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
/ambient <prompt> Classify and respond with skill enforcement
|
|
13
|
+
/ambient Show usage
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Phases
|
|
17
|
+
|
|
18
|
+
### Phase 1: Load Router
|
|
19
|
+
|
|
20
|
+
Read the `ambient-router` skill:
|
|
21
|
+
- `~/.claude/skills/ambient-router/SKILL.md`
|
|
22
|
+
|
|
23
|
+
### Phase 2: Classify
|
|
24
|
+
|
|
25
|
+
Apply the ambient-router classification to `$ARGUMENTS`:
|
|
26
|
+
|
|
27
|
+
1. **Intent:** BUILD | DEBUG | REVIEW | PLAN | EXPLORE | CHAT
|
|
28
|
+
2. **Depth:** QUICK | STANDARD | ESCALATE
|
|
29
|
+
|
|
30
|
+
If no arguments provided, output:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
## Ambient Mode
|
|
34
|
+
|
|
35
|
+
Classify intent and auto-load relevant skills.
|
|
36
|
+
|
|
37
|
+
Usage: /ambient <your prompt>
|
|
38
|
+
|
|
39
|
+
Examples:
|
|
40
|
+
/ambient add a login form → BUILD/STANDARD (loads TDD + implementation-patterns)
|
|
41
|
+
/ambient fix the auth error → DEBUG/STANDARD (loads test-patterns + core-patterns)
|
|
42
|
+
/ambient where is the config? → EXPLORE/QUICK (responds normally)
|
|
43
|
+
/ambient refactor the auth system → BUILD/ESCALATE (suggests /implement)
|
|
44
|
+
|
|
45
|
+
Always-on: devflow ambient --enable
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Then stop.
|
|
49
|
+
|
|
50
|
+
### Phase 3: State Classification
|
|
51
|
+
|
|
52
|
+
- **QUICK:** Skip this phase entirely. Respond directly in Phase 4.
|
|
53
|
+
- **STANDARD:** Output one line: `Ambient: {INTENT}/{DEPTH}. Loading: {skill1}, {skill2}.`
|
|
54
|
+
- **ESCALATE:** Skip — recommendation happens in Phase 4.
|
|
55
|
+
|
|
56
|
+
### Phase 4: Apply
|
|
57
|
+
|
|
58
|
+
**QUICK:**
|
|
59
|
+
Respond to the user's prompt normally. Zero skill loading. Zero overhead.
|
|
60
|
+
|
|
61
|
+
**STANDARD:**
|
|
62
|
+
Read the selected skills based on the ambient-router's skill selection matrix:
|
|
63
|
+
|
|
64
|
+
| Intent | Primary Skills | Secondary (conditional) |
|
|
65
|
+
|--------|---------------|------------------------|
|
|
66
|
+
| BUILD | test-driven-development, implementation-patterns | typescript (.ts), react (.tsx), frontend-design (CSS/UI), input-validation (forms/API), security-patterns (auth/crypto) |
|
|
67
|
+
| DEBUG | test-patterns, core-patterns | git-safety (if git ops) |
|
|
68
|
+
| REVIEW | self-review, core-patterns | test-patterns |
|
|
69
|
+
| PLAN | implementation-patterns | core-patterns |
|
|
70
|
+
|
|
71
|
+
Read up to 3 skills from `~/.claude/skills/{name}/SKILL.md`. Apply their patterns and constraints when responding to the user's prompt.
|
|
72
|
+
|
|
73
|
+
For BUILD intent: enforce RED-GREEN-REFACTOR from test-driven-development. Write failing tests before production code.
|
|
74
|
+
|
|
75
|
+
**ESCALATE:**
|
|
76
|
+
Respond to the user's prompt with your best effort, then append:
|
|
77
|
+
|
|
78
|
+
> This task spans multiple files/systems. Consider `/implement` for full lifecycle management (exploration → planning → implementation → review).
|
|
79
|
+
|
|
80
|
+
## Architecture
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
/ambient <prompt> (main session, no agents)
|
|
84
|
+
│
|
|
85
|
+
├─ Phase 1: Load ambient-router skill
|
|
86
|
+
├─ Phase 2: Classify intent + depth
|
|
87
|
+
├─ Phase 3: State classification (STANDARD only)
|
|
88
|
+
└─ Phase 4: Apply
|
|
89
|
+
├─ QUICK → respond directly
|
|
90
|
+
├─ STANDARD → load 2-3 skills, apply patterns, respond
|
|
91
|
+
└─ ESCALATE → respond + workflow nudge
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Edge Cases
|
|
95
|
+
|
|
96
|
+
| Case | Handling |
|
|
97
|
+
|------|----------|
|
|
98
|
+
| No arguments | Show usage and stop |
|
|
99
|
+
| Single word ("help") | Classify — likely CHAT/QUICK |
|
|
100
|
+
| Prompt references `/implement` etc. | Classify as normal — user chose /ambient intentionally |
|
|
101
|
+
| Mixed intent ("fix and add test") | Use higher-overhead intent (BUILD > DEBUG) |
|
|
102
|
+
| User says "no enforcement" | Respect immediately — treat as QUICK |
|
|
103
|
+
|
|
104
|
+
## Principles
|
|
105
|
+
|
|
106
|
+
1. **No agents** — Ambient enhances the main session, never spawns subagents
|
|
107
|
+
2. **Proportional** — QUICK gets zero overhead, STANDARD gets 2-3 skills, ESCALATE gets a nudge
|
|
108
|
+
3. **Transparent** — State classification for STANDARD/ESCALATE, silent for QUICK
|
|
109
|
+
4. **Respectful** — Never over-classify; when in doubt, go one tier lower
|
|
110
|
+
5. **TDD for BUILD** — STANDARD depth BUILD tasks enforce test-first workflow
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ambient-router
|
|
3
|
+
description: >-
|
|
4
|
+
Classify user intent and response depth for ambient mode. Auto-loads relevant
|
|
5
|
+
skills without explicit command invocation. Used by /ambient command and
|
|
6
|
+
always-on UserPromptSubmit hook.
|
|
7
|
+
user-invocable: false
|
|
8
|
+
allowed-tools: Read, Grep, Glob
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Ambient Router
|
|
12
|
+
|
|
13
|
+
Classify user intent and auto-load relevant skills. Zero overhead for simple requests, skill injection for substantive work, workflow nudges for complex tasks.
|
|
14
|
+
|
|
15
|
+
## Iron Law
|
|
16
|
+
|
|
17
|
+
> **PROPORTIONAL RESPONSE**
|
|
18
|
+
>
|
|
19
|
+
> Match effort to intent. Never apply heavyweight processes to lightweight requests.
|
|
20
|
+
> A chat question gets zero overhead. A 3-file feature gets 2-3 skills. A system
|
|
21
|
+
> refactor gets a nudge toward `/implement`. Misclassification in either direction
|
|
22
|
+
> is a failure.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Step 1: Classify Intent
|
|
27
|
+
|
|
28
|
+
Determine what the user is trying to do from their prompt.
|
|
29
|
+
|
|
30
|
+
| Intent | Signal Words / Patterns | Examples |
|
|
31
|
+
|--------|------------------------|---------|
|
|
32
|
+
| **BUILD** | "add", "create", "implement", "build", "write", "make" | "add a login form", "create an API endpoint" |
|
|
33
|
+
| **DEBUG** | "fix", "bug", "broken", "failing", "error", "why does" | "fix the auth error", "why is this test failing" |
|
|
34
|
+
| **REVIEW** | "check", "look at", "review", "is this ok", "any issues" | "check this function", "any issues with this?" |
|
|
35
|
+
| **PLAN** | "how should", "design", "architecture", "approach", "strategy" | "how should I structure auth?", "what's the approach for caching?" |
|
|
36
|
+
| **EXPLORE** | "what is", "where is", "find", "show me", "explain", "how does" | "where is the config?", "explain this function" |
|
|
37
|
+
| **CHAT** | greetings, meta-questions, confirmations, short responses | "thanks", "yes", "what can you do?" |
|
|
38
|
+
|
|
39
|
+
**Ambiguous prompts:** Default to the lowest-overhead classification. "Update the README" → BUILD/STANDARD. Git operations like "commit this" → QUICK.
|
|
40
|
+
|
|
41
|
+
## Step 2: Classify Depth
|
|
42
|
+
|
|
43
|
+
Determine how much enforcement the prompt warrants.
|
|
44
|
+
|
|
45
|
+
| Depth | Criteria | Action |
|
|
46
|
+
|-------|----------|--------|
|
|
47
|
+
| **QUICK** | CHAT intent. EXPLORE with no analytical depth ("where is X?"). Git/devops operations (commit, push, merge, branch, pr, deploy, reinstall). Single-word continuations. | Respond normally. Zero overhead. Do not state classification. |
|
|
48
|
+
| **STANDARD** | BUILD/DEBUG/REVIEW/PLAN intent (any word count). EXPLORE with analytical depth ("analyze our X", "discuss how Y works"). | Read and apply 2-3 relevant skills from the selection matrix below. State classification briefly. |
|
|
49
|
+
| **ESCALATE** | Multi-file architectural change, system-wide scope, > 5 files. Detailed implementation plan (100+ words with plan structure). | Respond at best effort + recommend: "This looks like it would benefit from `/implement` for full lifecycle management." |
|
|
50
|
+
|
|
51
|
+
## Step 3: Select Skills (STANDARD depth only)
|
|
52
|
+
|
|
53
|
+
Based on classified intent, read the following skills to inform your response.
|
|
54
|
+
|
|
55
|
+
| Intent | Primary Skills | Secondary (if file type matches) |
|
|
56
|
+
|--------|---------------|----------------------------------|
|
|
57
|
+
| **BUILD** | test-driven-development, implementation-patterns | typescript (.ts), react (.tsx/.jsx), frontend-design (CSS/UI), input-validation (forms/API), security-patterns (auth/crypto) |
|
|
58
|
+
| **DEBUG** | test-patterns, core-patterns | git-safety (if git operations involved) |
|
|
59
|
+
| **REVIEW** | self-review, core-patterns | test-patterns |
|
|
60
|
+
| **PLAN** | implementation-patterns | core-patterns |
|
|
61
|
+
|
|
62
|
+
**Excluded from ambient** (review-command-only): review-methodology, complexity-patterns, consistency-patterns, database-patterns, dependencies-patterns, documentation-patterns, regression-patterns, architecture-patterns, accessibility.
|
|
63
|
+
|
|
64
|
+
See `references/skill-catalog.md` for the full skill-to-intent mapping with file pattern triggers.
|
|
65
|
+
|
|
66
|
+
## Step 4: Apply
|
|
67
|
+
|
|
68
|
+
- **QUICK:** Respond directly. No preamble, no classification statement.
|
|
69
|
+
- **STANDARD:** State classification briefly: `Ambient: BUILD/STANDARD. Loading: test-driven-development, implementation-patterns.` Then read the selected skills and apply their patterns to your response. For BUILD intent, enforce RED-GREEN-REFACTOR from test-driven-development.
|
|
70
|
+
- **ESCALATE:** Respond with your best effort, then append: `> This task spans multiple files/systems. Consider \`/implement\` for full lifecycle (exploration → planning → implementation → review).`
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Transparency Rules
|
|
75
|
+
|
|
76
|
+
1. **QUICK → silent.** No classification output.
|
|
77
|
+
2. **STANDARD → brief statement.** One line: intent, depth, skills loaded.
|
|
78
|
+
3. **ESCALATE → recommendation.** Best-effort response + workflow nudge.
|
|
79
|
+
4. **Never lie about classification.** If uncertain, say so.
|
|
80
|
+
5. **Never over-classify.** When in doubt, go one tier lower.
|
|
81
|
+
|
|
82
|
+
## Edge Cases
|
|
83
|
+
|
|
84
|
+
| Case | Handling |
|
|
85
|
+
|------|----------|
|
|
86
|
+
| Mixed intent ("fix this bug and add a test") | Use the higher-overhead intent (BUILD > DEBUG) |
|
|
87
|
+
| Continuation of previous conversation | Inherit previous classification unless prompt clearly shifts |
|
|
88
|
+
| User explicitly requests no enforcement | Respect immediately — classify as QUICK |
|
|
89
|
+
| Prompt references specific DevFlow command | Skip ambient — the command has its own orchestration |
|