start-vibing-stacks 2.25.2 → 2.28.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/README.md +2 -2
- package/dist/migrate.d.ts +3 -1
- package/dist/migrate.js +23 -1
- package/dist/setup.js +7 -0
- package/package.json +1 -1
- package/stacks/_shared/agents/claude-md-compactor.md +38 -4
- package/stacks/_shared/agents/commit-manager.md +100 -36
- package/stacks/_shared/agents/documenter.md +11 -6
- package/stacks/_shared/agents/domain-updater.md +107 -42
- package/stacks/_shared/commands/commit-mine.md +78 -0
- package/stacks/_shared/commands/feature.md +4 -2
- package/stacks/_shared/commands/fix.md +6 -4
- package/stacks/_shared/hooks/_state.README.md +2 -2
- package/stacks/_shared/hooks/_state.ts +25 -9
- package/stacks/_shared/hooks/pre-tool-use.ts +6 -5
- package/stacks/_shared/hooks/scope.ts +478 -0
- package/stacks/_shared/hooks/session-start.ts +3 -2
- package/stacks/_shared/hooks/stop-validator.ts +79 -14
- package/stacks/_shared/hooks/user-prompt-submit.ts +12 -4
- package/stacks/_shared/skills/git-workflow/SKILL.md +1 -1
- package/stacks/_shared/skills/hook-development/SKILL.md +5 -1
- package/stacks/_shared/skills/multi-instance-coordination/SKILL.md +5 -5
- package/templates/CLAUDE-default.md +8 -4
- package/templates/CLAUDE-nodejs.md +14 -10
- package/templates/CLAUDE-php.md +14 -10
- package/templates/CLAUDE-python.md +23 -10
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: commit-mine
|
|
3
|
+
description: Commit ONLY this Claude session's edited files (multi-instance safe). Replaces raw `git add . && git commit` to prevent your commit from bundling a peer session's uncommitted changes.
|
|
4
|
+
version: 1.1.0
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /commit-mine — Per-Instance Commit
|
|
8
|
+
|
|
9
|
+
**Why this exists:** when two or more Claude sessions run in the same repo, `git add .` /
|
|
10
|
+
`git add -A` / `git add -u` pick up files modified by peer sessions and bundle them into
|
|
11
|
+
your commit. The peer then loses attribution and the commit becomes impossible to revert
|
|
12
|
+
cleanly. This command stages and commits ONLY the files your session actually edited.
|
|
13
|
+
|
|
14
|
+
**Source of truth:** `.claude/state/sessions/<your-id>.json#filesTouched`, maintained by
|
|
15
|
+
`post-tool-use.ts` (one entry per successful Edit / Write / MultiEdit / NotebookEdit).
|
|
16
|
+
|
|
17
|
+
**Recommended (single atomic command):**
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx tsx "$CLAUDE_PROJECT_DIR/.claude/hooks/scope.ts" commit "<message>" [--push]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This commits ONLY your session's files via `git commit -o -- <files>` — it commits
|
|
24
|
+
exactly your paths regardless of what a peer has staged, so it can never bundle their
|
|
25
|
+
work, and it never runs a global `git reset`. It REFUSES if a peer touched any of your
|
|
26
|
+
files in the last 5 min unless you pass `--include-conflicted`.
|
|
27
|
+
|
|
28
|
+
**Step-by-step (when you want to review first):**
|
|
29
|
+
|
|
30
|
+
| # | Step | Tool |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| 1 | **Inspect scope** — list SAFE / CONFLICTED / NOT-YOURS / STAGED | `npx tsx "$CLAUDE_PROJECT_DIR/.claude/hooks/scope.ts" status` |
|
|
33
|
+
| 2 | **Resolve conflicts** if any (peer touched same file in last 5 min) | `npx tsx "$CLAUDE_PROJECT_DIR/.claude/hooks/peers.ts" notify <id> "msg"` |
|
|
34
|
+
| 3 | **Review** the diff of your files | `npx tsx "$CLAUDE_PROJECT_DIR/.claude/hooks/scope.ts" diff` |
|
|
35
|
+
| 4 | **Commit** — atomic, only your files | `npx tsx "$CLAUDE_PROJECT_DIR/.claude/hooks/scope.ts" commit "<message>"` |
|
|
36
|
+
| 5 | **Push** (optional) | add `--push` to step 4, or `git push` |
|
|
37
|
+
|
|
38
|
+
> `scope stage` still exists for manual index inspection, but it is best-effort in a
|
|
39
|
+
> shared worktree (a peer's staged files can linger). Prefer `scope commit` — it is the
|
|
40
|
+
> only path that is atomic against a concurrent peer.
|
|
41
|
+
|
|
42
|
+
## Forbidden patterns (NEVER do these in a multi-instance project)
|
|
43
|
+
|
|
44
|
+
| Command | Why forbidden |
|
|
45
|
+
|---|---|
|
|
46
|
+
| `git add .` | Pulls in every dirty file in the worktree, including a peer's |
|
|
47
|
+
| `git add -A` | Same — adds all tracked AND untracked |
|
|
48
|
+
| `git add -u` | Adds every tracked modification, including a peer's |
|
|
49
|
+
| `git commit -am "..."` | Implicit `-a` = `git add -u`, same problem |
|
|
50
|
+
|
|
51
|
+
If you intentionally want to commit a peer's file (e.g. you're closing their session for them
|
|
52
|
+
because they crashed), use `scope stage --include-conflicted` and document WHY in the commit body.
|
|
53
|
+
|
|
54
|
+
## Exit codes (when scripting around it)
|
|
55
|
+
|
|
56
|
+
| Code | Meaning |
|
|
57
|
+
|---|---|
|
|
58
|
+
| `0` | Staged / committed cleanly |
|
|
59
|
+
| `1` | Argument or state error (no session, no dirty files in scope, git failed) |
|
|
60
|
+
| `2` | Conflict refusal — peer touched a file in the last 5 min; pass `--include-conflicted` or coordinate |
|
|
61
|
+
|
|
62
|
+
## What gets staged, precisely
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
session.filesTouched ∩ git status (dirty + untracked) \ peer-touched-in-last-5min
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Files in `filesTouched` that are no longer dirty (you reverted the change) are skipped.
|
|
69
|
+
Files dirty in the worktree but never touched by your session (a peer's edits, or your own
|
|
70
|
+
manual edits not via Claude tools) are skipped — they appear in `status` under NOT-YOURS.
|
|
71
|
+
|
|
72
|
+
## Where this fits in the broader workflow
|
|
73
|
+
|
|
74
|
+
- `/fix` step 7 ("Commit") and `/feature` workflows that previously implied `git add .`
|
|
75
|
+
should now route through `/commit-mine` whenever `peers.ts list` shows ≥ 1 peer.
|
|
76
|
+
- The `commit-manager` agent (if used) MUST commit via `scope commit` (never `git add -A`)
|
|
77
|
+
in a multi-instance project.
|
|
78
|
+
- See `CLAUDE.md` NRY: "Instance N's commit bundling instance M's uncommitted files".
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: feature
|
|
3
3
|
description: Start a new feature with the full workflow (research → plan → implement → test → security → quality → commit → docs).
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# /feature — Start New Feature
|
|
@@ -20,6 +20,8 @@ Execute in order — do not skip steps. Steps 5–6 have **VETO power** over com
|
|
|
20
20
|
| 6 | **Quality** — typecheck → lint → test → build | `quality-gate` |
|
|
21
21
|
| 7 | **Commit** — gate-aware, diff-driven message | `commit-manager` |
|
|
22
22
|
| 8 | **Map** — files + commits → domains | `documenter` |
|
|
23
|
-
| 9 | **Wisdom +
|
|
23
|
+
| 9 | **Wisdom + Recent Changes** — record learnings, PREPEND new `### YYYY-MM-DD · branch · vX.Y.Z` entry to `CLAUDE.md` `## Recent Changes` (append-only LIFO, cap 10) | `domain-updater` |
|
|
24
24
|
|
|
25
25
|
If `security-auditor` returns CRITICAL/HIGH/MEDIUM, fix before re-running step 5. Steps 8–9 run AFTER push and never before.
|
|
26
|
+
|
|
27
|
+
**Multi-instance note:** `commit-manager` v3.0.0 stages only THIS session's files via `scope.ts` when `.claude/state/` is present — peers' uncommitted files are never bundled into your commit. For a manual / interactive equivalent of Step 7, use `/commit-mine` (skip `/feature` from Step 7 onward).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: fix
|
|
3
3
|
description: Fix a bug — reproduce, isolate, minimal fix, regression test, security check, commit, record wisdom.
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# /fix — Fix Bug
|
|
@@ -16,8 +16,10 @@ Always read `.claude/config/active-project.json` first.
|
|
|
16
16
|
| 4 | **Verify** — re-run the regression test + the wider suite | `tester` |
|
|
17
17
|
| 5 | **Security** — only if the bug touched auth / input / secrets / SSRF surface | `security-auditor` (VETO) |
|
|
18
18
|
| 6 | **Quality gate** — typecheck → lint → test → build | `quality-gate` |
|
|
19
|
-
| 7 | **Commit** — `fix(scope): <subject>`, diff-driven body | `commit-manager` |
|
|
20
|
-
| 8 | **Map** — files + commit → domain | `documenter` |
|
|
21
|
-
| 9 | **Wisdom** — append to domain `## Problems & Solutions` (symptom + root cause + fix + prevention + skill ref) | `domain-updater` |
|
|
19
|
+
| 7 | **Commit** — `fix(scope): <subject>`, diff-driven body, per-instance scoped staging | `commit-manager` v3.0.0 |
|
|
20
|
+
| 8 | **Map** — files + commit → domain | `documenter` v3.0.0 |
|
|
21
|
+
| 9 | **Wisdom + Recent Changes entry** — append to domain `## Problems & Solutions` (symptom + root cause + fix + prevention + skill ref) AND PREPEND `### YYYY-MM-DD · branch · fix-tag` to `CLAUDE.md` `## Recent Changes` | `domain-updater` v3.0.0 |
|
|
22
22
|
|
|
23
23
|
`domain-updater` deduplicates by symptom/root-cause. If the same bug recurred, it appends a "Recurrence" note instead of a duplicate entry.
|
|
24
|
+
|
|
25
|
+
**Multi-instance note:** Step 7 stages only THIS session's files via `scope.ts` when `.claude/state/` is present (peers' uncommitted files are never bundled). For a manual / interactive Step 7 (no chain auto-trigger), use `/commit-mine` instead.
|
|
@@ -26,8 +26,8 @@ When two or more Claude Code instances run in the same project folder, the hooks
|
|
|
26
26
|
|
|
27
27
|
| Last activity | State | Effect |
|
|
28
28
|
| ------------- | -------- | --------------------------------------------------------------------------- |
|
|
29
|
-
| <
|
|
30
|
-
|
|
|
29
|
+
| < 180s | active | Counts for collision detection. PreToolUse may **block** Edit/Write. |
|
|
30
|
+
| 180s – 30min | idle | Surfaced as a warning in `systemMessage`. Edits are **not** blocked. |
|
|
31
31
|
| > 30min | stale | Auto-archived on the next sweep. |
|
|
32
32
|
| > 24h | removed | Deleted entirely. |
|
|
33
33
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @sv-version: 1.
|
|
1
|
+
// @sv-version: 1.1.0
|
|
2
2
|
/**
|
|
3
3
|
* Multi-Instance Coordination — Shared State Library
|
|
4
4
|
*
|
|
@@ -16,7 +16,13 @@
|
|
|
16
16
|
* Design rules:
|
|
17
17
|
* - All writes are atomic (.tmp + rename) where the file is read by peers.
|
|
18
18
|
* - Every read tolerates corruption (try/catch) — hooks must NEVER block Claude.
|
|
19
|
-
* - Heartbeat thresholds: <
|
|
19
|
+
* - Heartbeat thresholds: <180s active, 180s-30min idle, >30min stale, >24h removed.
|
|
20
|
+
*
|
|
21
|
+
* v1.1.0: ACTIVE window 60s→180s (agent think/generate time between tool calls
|
|
22
|
+
* routinely exceeds 60s — a 60s window mis-classifies busy peers as IDLE);
|
|
23
|
+
* FILES_TOUCHED_CAP 50→200 (large sessions were dropping early edits, which then
|
|
24
|
+
* showed up as orphaned dirty files a peer could not attribute); path
|
|
25
|
+
* normalization in extractTargetFiles is now repo-root-stable.
|
|
20
26
|
*/
|
|
21
27
|
|
|
22
28
|
import {
|
|
@@ -33,18 +39,18 @@ import {
|
|
|
33
39
|
readSync,
|
|
34
40
|
closeSync,
|
|
35
41
|
} from 'fs';
|
|
36
|
-
import { join, basename } from 'path';
|
|
42
|
+
import { join, basename, resolve, relative, isAbsolute } from 'path';
|
|
37
43
|
import { randomBytes } from 'crypto';
|
|
38
44
|
import { spawnSync } from 'child_process';
|
|
39
45
|
|
|
40
|
-
export const ACTIVE_MS =
|
|
46
|
+
export const ACTIVE_MS = 180 * 1000;
|
|
41
47
|
export const IDLE_MS = 30 * 60 * 1000;
|
|
42
48
|
export const STALE_MS = 24 * 60 * 60 * 1000;
|
|
43
49
|
export const COLLISION_WINDOW_MS = 5 * 60 * 1000;
|
|
44
50
|
|
|
45
51
|
export const TOUCHES_ROTATE_THRESHOLD = 1000;
|
|
46
52
|
export const TOUCHES_TAIL_LINES = 200;
|
|
47
|
-
export const FILES_TOUCHED_CAP =
|
|
53
|
+
export const FILES_TOUCHED_CAP = 200;
|
|
48
54
|
|
|
49
55
|
export interface SessionRecord {
|
|
50
56
|
sessionId: string;
|
|
@@ -238,7 +244,9 @@ export function tailFileTouches(stateDir: string, lines = TOUCHES_TAIL_LINES): F
|
|
|
238
244
|
if (!existsSync(path)) return [];
|
|
239
245
|
try {
|
|
240
246
|
const size = statSync(path).size;
|
|
241
|
-
|
|
247
|
+
// 512KB tail comfortably holds ≳1000 touch records; collision decisions read
|
|
248
|
+
// up to 1000 lines (see callers), so the byte window must not truncate them.
|
|
249
|
+
const readBytes = Math.min(size, 512 * 1024);
|
|
242
250
|
const fd = openSync(path, 'r');
|
|
243
251
|
const buf = Buffer.alloc(readBytes);
|
|
244
252
|
readSync(fd, buf, 0, readBytes, size - readBytes);
|
|
@@ -361,11 +369,19 @@ export function classifyAge(ageMsec: number): 'active' | 'idle' | 'stale' {
|
|
|
361
369
|
export function extractTargetFiles(toolName: string, toolInput: any, projectDir: string): string[] {
|
|
362
370
|
if (!toolInput || typeof toolInput !== 'object') return [];
|
|
363
371
|
const out: string[] = [];
|
|
372
|
+
// Normalize every path to the SAME shape `git status --porcelain` emits:
|
|
373
|
+
// forward-slash, relative to the project (≈ repo) root. Tools may hand us an
|
|
374
|
+
// absolute path, a path relative to a subdirectory cwd, or already-relative.
|
|
375
|
+
// A mismatch here is the #1 cause of "I edited this file but scope.ts says it
|
|
376
|
+
// is NOT MINE" — the string simply fails to equal the git porcelain path.
|
|
364
377
|
const push = (p: any) => {
|
|
365
378
|
if (typeof p !== 'string' || !p) return;
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
379
|
+
const abs = isAbsolute(p) ? p : resolve(projectDir, p);
|
|
380
|
+
let rel = relative(projectDir, abs);
|
|
381
|
+
// Outside the project tree (rare): keep the original so we never crash; it
|
|
382
|
+
// simply will not match a git path, which is the correct, safe outcome.
|
|
383
|
+
if (!rel || rel.startsWith('..')) rel = p;
|
|
384
|
+
out.push(rel.split('\\').join('/'));
|
|
369
385
|
};
|
|
370
386
|
if (toolName === 'Edit' || toolName === 'Write') {
|
|
371
387
|
push(toolInput.file_path || toolInput.path);
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// @sv-version: 1.
|
|
2
|
+
// @sv-version: 1.1.0
|
|
3
3
|
/**
|
|
4
4
|
* PreToolUse Hook — Multi-Instance Coordination
|
|
5
5
|
*
|
|
6
6
|
* Wired with matcher `Edit|Write|MultiEdit|NotebookEdit`. Reads the file-touches
|
|
7
7
|
* log + active peer sessions and decides:
|
|
8
8
|
*
|
|
9
|
-
* - BLOCK if a peer is currently ACTIVE (heartbeat <
|
|
9
|
+
* - BLOCK if a peer is currently ACTIVE (heartbeat < 180s) AND touched the same
|
|
10
10
|
* file within the last 5 minutes. The reason explains how to recover.
|
|
11
11
|
* - WARN (approve + systemMessage) if a peer touched the file recently but is
|
|
12
|
-
* only IDLE (
|
|
12
|
+
* only IDLE (180s — 5min).
|
|
13
13
|
* - APPROVE silently otherwise.
|
|
14
14
|
*
|
|
15
15
|
* Hook input:
|
|
@@ -100,7 +100,7 @@ function evaluate(
|
|
|
100
100
|
` 1. Run \`npx tsx .claude/hooks/peers.ts list\` to see who is active.\n` +
|
|
101
101
|
` 2. Notify them: \`npx tsx .claude/hooks/peers.ts notify <id-prefix> "I need to edit <file>, can you commit/stash?"\`\n` +
|
|
102
102
|
` 3. Wait for them to commit, then retry — or have them call \`peers.ts cleanup\` if you confirm they are no longer editing.\n` +
|
|
103
|
-
` 4. If you must override: re-run the same Edit after
|
|
103
|
+
` 4. If you must override: re-run the same Edit after 180s of peer inactivity (their heartbeat will go IDLE and the hook will downgrade to a warning).`;
|
|
104
104
|
return { block: true, reason };
|
|
105
105
|
}
|
|
106
106
|
|
|
@@ -141,7 +141,8 @@ async function main(): Promise<void> {
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
const peers = listPeerSessions(stateDir, sessionId || null);
|
|
144
|
-
|
|
144
|
+
// Read a wide tail so a peer's collision touch is not missed under heavy load.
|
|
145
|
+
const touches = tailFileTouches(stateDir, 1000);
|
|
145
146
|
|
|
146
147
|
const verdict = evaluate(targetFiles, peers, touches, sessionId || '');
|
|
147
148
|
|