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.
@@ -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.0.0
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 + Last Change** — record learnings, refresh `CLAUDE.md` | `domain-updater` |
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.0.0
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
- | < 60s | active | Counts for collision detection. PreToolUse may **block** Edit/Write. |
30
- | 60s – 30min | idle | Surfaced as a warning in `systemMessage`. Edits are **not** blocked. |
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.0.0
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: <60s active, 60s-30min idle, >30min stale, >24h removed.
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 = 60 * 1000;
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 = 50;
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
- const readBytes = Math.min(size, 64 * 1024);
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
- let rel = p;
367
- if (p.startsWith(projectDir + '/')) rel = p.slice(projectDir.length + 1);
368
- out.push(rel);
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.0.0
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 < 60s) AND touched the same
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 (60s — 5min).
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 60s of peer inactivity (their heartbeat will go IDLE and the hook will downgrade to a warning).`;
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
- const touches = tailFileTouches(stateDir);
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