create-claude-cabinet 0.48.0 → 0.50.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/lib/CLAUDE.md +22 -0
- package/lib/cli.js +100 -5
- package/lib/metadata.js +20 -0
- package/package.json +1 -1
- package/templates/CLAUDE.md +270 -37
- package/templates/cabinet/watchtower-contracts.md +85 -0
- package/templates/cabinet/worktree-invocation-contract.md +52 -10
- package/templates/engagement/__tests__/transport-token.test.mjs +103 -0
- package/templates/engagement/engagement-transport.mjs +35 -1
- package/templates/engagement/pib-db-patches/pib-db-lib.mjs +53 -9
- package/templates/mux/__tests__/mux-fail-loud.fixture.sh +4 -5
- package/templates/mux/__tests__/worktree-lifecycle.fixture.sh +214 -0
- package/templates/mux/__tests__/worktree-lifecycle.test.mjs +44 -0
- package/templates/mux/__tests__/worktree-status-clean.fixture.sh +245 -0
- package/templates/mux/__tests__/worktree-status-clean.test.mjs +89 -0
- package/templates/mux/bin/mux +44 -28
- package/templates/mux/config/worktree-cleanup.sh +57 -12
- package/templates/mux/config/worktree-dirty-check.sh +72 -2
- package/templates/mux/config/worktree-session-health.sh +401 -17
- package/templates/scripts/__tests__/assessment-recalibration.test.mjs +387 -0
- package/templates/scripts/__tests__/batch-disposition.test.mjs +21 -1
- package/templates/scripts/__tests__/branch-diverged-reconcile.test.mjs +374 -0
- package/templates/scripts/__tests__/completion-review-reconcile.test.mjs +173 -0
- package/templates/scripts/__tests__/cross-ring-reader.test.mjs +12 -4
- package/templates/scripts/__tests__/detector-registry.test.mjs +152 -0
- package/templates/scripts/__tests__/draft-surfacing.test.mjs +433 -0
- package/templates/scripts/__tests__/generated-state-classification.test.mjs +228 -0
- package/templates/scripts/__tests__/mirror-parity.test.mjs +54 -0
- package/templates/scripts/__tests__/qa-handoff-aging.e2e.test.mjs +3 -0
- package/templates/scripts/__tests__/qa-handoff-gate.test.mjs +8 -1
- package/templates/scripts/__tests__/qa-handoff-merge-state.test.mjs +69 -20
- package/templates/scripts/__tests__/qa-handoff-merged-by-construction.test.mjs +310 -0
- package/templates/scripts/__tests__/resolve-cli.test.mjs +6 -1
- package/templates/scripts/__tests__/resolve-project-slug.test.mjs +186 -0
- package/templates/scripts/__tests__/ring3-attribution.test.mjs +267 -0
- package/templates/scripts/__tests__/ring3-completion-filter.test.mjs +176 -0
- package/templates/scripts/__tests__/ring3-coverage-debt.test.mjs +377 -0
- package/templates/scripts/__tests__/ring3-last-session-pointer.test.mjs +80 -0
- package/templates/scripts/__tests__/routine-dispatch.test.mjs +2 -0
- package/templates/scripts/__tests__/session-ack.test.mjs +61 -0
- package/templates/scripts/audit-coherence-check.mjs +6 -2
- package/templates/scripts/load-triage-history.js +23 -2
- package/templates/scripts/merge-findings.js +9 -2
- package/templates/scripts/pib-db-lib.mjs +55 -7
- package/templates/scripts/watchtower-build-context.mjs +24 -2
- package/templates/scripts/watchtower-cross-ring-reader.mjs +20 -3
- package/templates/scripts/watchtower-inbox-assessment.mjs +186 -35
- package/templates/scripts/watchtower-lib.mjs +268 -5
- package/templates/scripts/watchtower-queue.mjs +469 -4
- package/templates/scripts/watchtower-ring1.mjs +490 -69
- package/templates/scripts/watchtower-ring2.mjs +175 -3
- package/templates/scripts/watchtower-ring3-close.mjs +519 -69
- package/templates/skills/audit/SKILL.md +6 -3
- package/templates/skills/cabinet-anthropic-insider/SKILL.md +29 -8
- package/templates/skills/cabinet-process-therapist/SKILL.md +45 -3
- package/templates/skills/collab-client/SKILL.md +13 -7
- package/templates/skills/collab-consultant/SKILL.md +9 -4
- package/templates/skills/execute/SKILL.md +60 -33
- package/templates/skills/inbox/SKILL.md +29 -1
- package/templates/skills/qa-handoff/SKILL.md +112 -48
- package/templates/skills/session-handoff/SKILL.md +6 -8
- package/templates/watchtower/config.json.template +2 -1
package/lib/CLAUDE.md
CHANGED
|
@@ -33,6 +33,16 @@ No build step; CommonJS.
|
|
|
33
33
|
it, `expandFrozenClosure`, and the hoisted top-level `manifestPath`
|
|
34
34
|
are exported + unit-tested. `generateAgentWrappers()` (see root
|
|
35
35
|
§ Generated artifacts) also lives here.
|
|
36
|
+
Also home of `seedAdvisoriesGitignore()` (act:c008862c,
|
|
37
|
+
grp:wt-noise-immunity), run in the install body: on FRESH installs
|
|
38
|
+
only (no prior `.ccrc.json`) it seeds a `.gitignore` entry for
|
|
39
|
+
`.claude/cabinet/advisories-state.json` (machine-written runtime
|
|
40
|
+
state — tracked, it churns every commit and reads as authored work in
|
|
41
|
+
mux worktrees) unless some rule already ignores it (`git
|
|
42
|
+
check-ignore`); existing installs are left untouched — untracking a
|
|
43
|
+
live consumer repo is operator-gated — but a TRACKED copy is reported
|
|
44
|
+
loudly so the consumer walk surfaces it. No-git and dry-run aware,
|
|
45
|
+
injectable exec, exported for tests.
|
|
36
46
|
- `lib/modules.js` — the `MODULES` manifest: every module → the template
|
|
37
47
|
paths it copies into `.claude/` (architecture-0005; extracted from
|
|
38
48
|
`cli.js` in act:e82baead). `cli.js` requires + re-exports it for
|
|
@@ -90,6 +100,18 @@ No build step; CommonJS.
|
|
|
90
100
|
(act:c1013967): the `.ccrc.json` `frozenModules` accessors for the
|
|
91
101
|
freeze-module mechanism (a non-array reads as empty — shape-drift
|
|
92
102
|
defensive; `create()` preserves the field via `...existing`).
|
|
103
|
+
Also exposes `readMcpOptOut` (act:b770c323): the `.ccrc.json`
|
|
104
|
+
`mcpOptOut` accessor — the CC-shipped MCP server keys this consumer
|
|
105
|
+
declines (flow's `no-pib-db.md` is the live specimen; its validator
|
|
106
|
+
requires the pib-db registration absent while the engagement→
|
|
107
|
+
work-tracking closure keeps shipping the files). Same defensive shape
|
|
108
|
+
as `readFrozenModules`; the key survives reinstalls via `create()`'s
|
|
109
|
+
`...existing` spread, so a consumer sets it once by hand. Honored by
|
|
110
|
+
`mergeMcpServers` in `lib/cli.js` (opted-out keys are never added and
|
|
111
|
+
are REMOVED if present — self-heal toward declared intent; removal is
|
|
112
|
+
scoped to keys CC ships, so hand-added servers are never touched;
|
|
113
|
+
nothing-to-contribute installs don't rewrite `.mcp.json`). Tests in
|
|
114
|
+
`test/mcp-opt-out/`.
|
|
93
115
|
`normalize()` guards shape drift (non-object top level reads as
|
|
94
116
|
'no metadata').
|
|
95
117
|
- `lib/settings-merge.js` — merges CC hooks into `.claude/settings.json`.
|
package/lib/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ const os = require('os');
|
|
|
5
5
|
const crypto = require('crypto');
|
|
6
6
|
const { copyTemplates, recordSkip, isProjectOwnedSeed, classifyFileOwnership } = require('./copy');
|
|
7
7
|
const { mergeSettings, healUserSettings, mergeWatchtowerHooks, mergeMuxHooks, mergeBashCompressHooks, mergeCcDevHooks, mergeSkillOverrides } = require('./settings-merge');
|
|
8
|
-
const { create: createMetadata, read: readMetadata, readFrozenModules, setFrozenModules } = require('./metadata');
|
|
8
|
+
const { create: createMetadata, read: readMetadata, readFrozenModules, setFrozenModules, readMcpOptOut } = require('./metadata');
|
|
9
9
|
const { setupDb, shouldSkipInit, readBundledSchemaVersion } = require('./db-setup');
|
|
10
10
|
const { setupVerifyRuntime } = require('./verify-setup');
|
|
11
11
|
const { setupSiteAuditRuntime } = require('./site-audit-setup');
|
|
@@ -551,6 +551,35 @@ function computeRemovals({ existingManifest, allManifest, selectedModules, templ
|
|
|
551
551
|
return { toRemove, kept };
|
|
552
552
|
}
|
|
553
553
|
|
|
554
|
+
// Merge CC-shipped MCP server entries into a consumer's .mcp.json mcpServers
|
|
555
|
+
// object, honoring the .ccrc.json `mcpOptOut` list (act:b770c323). Pure and
|
|
556
|
+
// mutating-in-place on `existing` (the caller serializes the parent object):
|
|
557
|
+
// - incoming keys NOT opted out are set/overwritten (the prior Object.assign
|
|
558
|
+
// behavior) and reported in `added`;
|
|
559
|
+
// - incoming keys the consumer opted out are never added (`skipped`);
|
|
560
|
+
// - an opted-out key already present in `existing` is DELETED (`removed`) —
|
|
561
|
+
// self-heal toward the declared intent, but ONLY for keys CC itself ships
|
|
562
|
+
// (in `incoming`), so a consumer's hand-added servers are never touched.
|
|
563
|
+
function mergeMcpServers(existing, incoming = {}, optOut = []) {
|
|
564
|
+
const optedOut = new Set(optOut);
|
|
565
|
+
const added = [];
|
|
566
|
+
const removed = [];
|
|
567
|
+
const skipped = [];
|
|
568
|
+
for (const [key, value] of Object.entries(incoming)) {
|
|
569
|
+
if (optedOut.has(key)) {
|
|
570
|
+
skipped.push(key);
|
|
571
|
+
if (Object.prototype.hasOwnProperty.call(existing, key)) {
|
|
572
|
+
delete existing[key];
|
|
573
|
+
removed.push(key);
|
|
574
|
+
}
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
existing[key] = value;
|
|
578
|
+
added.push(key);
|
|
579
|
+
}
|
|
580
|
+
return { added, removed, skipped };
|
|
581
|
+
}
|
|
582
|
+
|
|
554
583
|
// Signals that a directory contains a real project (not just empty)
|
|
555
584
|
const PROJECT_SIGNALS = [
|
|
556
585
|
'package.json', 'Cargo.toml', 'requirements.txt', 'pyproject.toml',
|
|
@@ -690,6 +719,42 @@ function printHelp() {
|
|
|
690
719
|
`);
|
|
691
720
|
}
|
|
692
721
|
|
|
722
|
+
// --- advisories-state gitignore seeding (act:c008862c) ---
|
|
723
|
+
// .claude/cabinet/advisories-state.json is machine-written runtime state
|
|
724
|
+
// (the advisory pass rewrites it), so tracked it churns every commit and
|
|
725
|
+
// reads as authored work in mux worktrees. FRESH installs (no prior
|
|
726
|
+
// .ccrc.json) seed a .gitignore entry so it never enters git; existing
|
|
727
|
+
// installs are left untouched — untracking a live consumer repo is
|
|
728
|
+
// operator-gated — but a TRACKED copy is reported so the consumer walk
|
|
729
|
+
// surfaces it. Pure and injectable for tests.
|
|
730
|
+
const ADVISORIES_STATE_PATH = '.claude/cabinet/advisories-state.json';
|
|
731
|
+
function seedAdvisoriesGitignore(projectDir, { fresh, dryRun, exec } = {}) {
|
|
732
|
+
const run = exec || ((cmd) => require('child_process').execSync(cmd, { cwd: projectDir, stdio: 'pipe' }));
|
|
733
|
+
try {
|
|
734
|
+
run(`git ls-files --error-unmatch ${ADVISORIES_STATE_PATH}`);
|
|
735
|
+
return { status: 'tracked-reported' };
|
|
736
|
+
} catch { /* not tracked — fall through */ }
|
|
737
|
+
if (!fresh) return { status: 'skipped-existing-install' };
|
|
738
|
+
try {
|
|
739
|
+
run('git rev-parse --git-dir');
|
|
740
|
+
} catch {
|
|
741
|
+
return { status: 'skipped-no-git' };
|
|
742
|
+
}
|
|
743
|
+
try {
|
|
744
|
+
// Already ignored by ANY rule (exact line or a covering glob like this
|
|
745
|
+
// repo's `.claude/cabinet/*`) — nothing to add.
|
|
746
|
+
run(`git check-ignore -q ${ADVISORIES_STATE_PATH}`);
|
|
747
|
+
return { status: 'already-present' };
|
|
748
|
+
} catch { /* not ignored — seed below */ }
|
|
749
|
+
const giPath = path.join(projectDir, '.gitignore');
|
|
750
|
+
let content = '';
|
|
751
|
+
try { content = fs.readFileSync(giPath, 'utf8'); } catch { /* no .gitignore yet */ }
|
|
752
|
+
if (dryRun) return { status: 'would-seed' };
|
|
753
|
+
const nl = content && !content.endsWith('\n') ? '\n' : '';
|
|
754
|
+
fs.appendFileSync(giPath, `${nl}# machine-written advisory-dismissal state (CC)\n${ADVISORIES_STATE_PATH}\n`);
|
|
755
|
+
return { status: 'seeded' };
|
|
756
|
+
}
|
|
757
|
+
|
|
693
758
|
async function run() {
|
|
694
759
|
const flags = parseArgs(process.argv);
|
|
695
760
|
|
|
@@ -1406,9 +1471,22 @@ async function run() {
|
|
|
1406
1471
|
existing = {};
|
|
1407
1472
|
}
|
|
1408
1473
|
if (!existing.mcpServers) existing.mcpServers = {};
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1474
|
+
const optOut = readMcpOptOut(projectDir);
|
|
1475
|
+
const { added, removed, skipped } = mergeMcpServers(existing.mcpServers, mcpConfig.mcpServers, optOut);
|
|
1476
|
+
// Only write when this merge actually contributed or healed something —
|
|
1477
|
+
// an all-opted-out install must not reformat a consumer's .mcp.json.
|
|
1478
|
+
if (added.length > 0 || removed.length > 0) {
|
|
1479
|
+
fs.writeFileSync(mcpJsonPath, JSON.stringify(existing, null, 2) + '\n');
|
|
1480
|
+
}
|
|
1481
|
+
if (added.length > 0) {
|
|
1482
|
+
console.log(` 🔌 Merged ${added.join(', ')} MCP server into .mcp.json`);
|
|
1483
|
+
}
|
|
1484
|
+
if (skipped.length > 0) {
|
|
1485
|
+
console.log(` ⏭ MCP registration declined by .ccrc.json mcpOptOut: ${skipped.join(', ')}`);
|
|
1486
|
+
}
|
|
1487
|
+
if (removed.length > 0) {
|
|
1488
|
+
console.log(` 🧹 Removed opted-out MCP entr${removed.length === 1 ? 'y' : 'ies'} from .mcp.json: ${removed.join(', ')}`);
|
|
1489
|
+
}
|
|
1412
1490
|
}
|
|
1413
1491
|
} catch (err) {
|
|
1414
1492
|
console.log(` ⚠ MCP config merge failed: ${err.message}`);
|
|
@@ -1632,6 +1710,23 @@ async function run() {
|
|
|
1632
1710
|
}
|
|
1633
1711
|
}
|
|
1634
1712
|
|
|
1713
|
+
// --- Seed advisories-state gitignore (fresh installs only; act:c008862c) ---
|
|
1714
|
+
try {
|
|
1715
|
+
const seedResult = seedAdvisoriesGitignore(projectDir, {
|
|
1716
|
+
fresh: dirState !== 'existing-install',
|
|
1717
|
+
dryRun: !!flags.dryRun,
|
|
1718
|
+
});
|
|
1719
|
+
if (seedResult.status === 'seeded') {
|
|
1720
|
+
console.log(' 🙈 Gitignored .claude/cabinet/advisories-state.json (machine-written state)');
|
|
1721
|
+
} else if (seedResult.status === 'would-seed') {
|
|
1722
|
+
console.log(' [dry run] Would gitignore .claude/cabinet/advisories-state.json');
|
|
1723
|
+
} else if (seedResult.status === 'tracked-reported') {
|
|
1724
|
+
console.log(' ℹ .claude/cabinet/advisories-state.json is TRACKED in this repo — left untouched. Untracking (git rm --cached) is an operator call; tracked, it churns every commit.');
|
|
1725
|
+
}
|
|
1726
|
+
} catch (err) {
|
|
1727
|
+
console.log(` ⚠ advisories-state gitignore seeding skipped: ${err.message}`);
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1635
1730
|
// --- Write metadata ---
|
|
1636
1731
|
if (!flags.dryRun) {
|
|
1637
1732
|
createMetadata(projectDir, {
|
|
@@ -1725,4 +1820,4 @@ async function run() {
|
|
|
1725
1820
|
console.log('');
|
|
1726
1821
|
}
|
|
1727
1822
|
|
|
1728
|
-
module.exports = { run, MODULES, generateAgentWrappers, generateSkillIndex, expandFrozenClosure, computeRemovals, manifestPath };
|
|
1823
|
+
module.exports = { run, MODULES, generateAgentWrappers, generateSkillIndex, expandFrozenClosure, computeRemovals, mergeMcpServers, manifestPath, seedAdvisoriesGitignore };
|
package/lib/metadata.js
CHANGED
|
@@ -111,6 +111,25 @@ function setFrozenModules(projectDir, mods) {
|
|
|
111
111
|
return normalized;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Read the project's MCP opt-out list from `.ccrc.json` (`mcpOptOut`) — the
|
|
116
|
+
* CC-shipped MCP server keys this consumer declines (act:b770c323; the live
|
|
117
|
+
* specimen is flow's no-pib-db.md: engagement→work-tracking closure ships the
|
|
118
|
+
* pib-db files, but the consumer's own validator requires the MCP registration
|
|
119
|
+
* absent). Honored by the `.mcp.json` merge in lib/cli.js: opted-out keys are
|
|
120
|
+
* never added and, when present from a prior install, are removed (self-heal
|
|
121
|
+
* toward the declared intent). Same defensive shape as `readFrozenModules`:
|
|
122
|
+
* non-array reads as "no opt-outs"; a malformed `.ccrc.json` still throws in
|
|
123
|
+
* `read()` and halts the install. The key survives reinstalls via `create()`'s
|
|
124
|
+
* `...existing` spread — no threading required.
|
|
125
|
+
*/
|
|
126
|
+
function readMcpOptOut(projectDir) {
|
|
127
|
+
const m = read(projectDir);
|
|
128
|
+
const raw = m && m.mcpOptOut;
|
|
129
|
+
if (!Array.isArray(raw)) return [];
|
|
130
|
+
return [...new Set(raw.filter((x) => typeof x === 'string'))];
|
|
131
|
+
}
|
|
132
|
+
|
|
114
133
|
module.exports = {
|
|
115
134
|
read,
|
|
116
135
|
write,
|
|
@@ -120,4 +139,5 @@ module.exports = {
|
|
|
120
139
|
METADATA_FILE,
|
|
121
140
|
readFrozenModules,
|
|
122
141
|
setFrozenModules,
|
|
142
|
+
readMcpOptOut,
|
|
123
143
|
};
|
package/package.json
CHANGED
package/templates/CLAUDE.md
CHANGED
|
@@ -15,15 +15,58 @@ consumes the registry stays in cli.js).
|
|
|
15
15
|
## mux config (`templates/mux/config/`)
|
|
16
16
|
|
|
17
17
|
- `worktree-session-health.sh` — self-healing worktree health check (4
|
|
18
|
-
checks, auto-fix, cache + tmux indicator)
|
|
18
|
+
checks, auto-fix, cache + tmux indicator); the single home for the
|
|
19
|
+
worktree LANE LIFECYCLE (act:dc80380c): PROVISION (a per-project
|
|
20
|
+
`.mux-worktree-provision` file declares gitignored env files copied
|
|
21
|
+
main → worktree at creation and freshness-checked on later health
|
|
22
|
+
runs — a locally-modified worktree copy is never overwritten; reports
|
|
23
|
+
name paths/hashes/mtimes only, never file CONTENTS), `--preflight`
|
|
24
|
+
(pre-spawn source check: every declared provision file exists in MAIN
|
|
25
|
+
and every declared time-boxed `check:` command passes; exit 1 gates
|
|
26
|
+
parallel lane spawns — surfaced as `mux worktree preflight`), and
|
|
27
|
+
`--reap` (removal-time docker container reap: exact
|
|
28
|
+
compose-project-label match on the worktree dir name, containers only
|
|
29
|
+
NEVER volumes, always exits 0 — a failed reap is loud but never blocks
|
|
30
|
+
removal; every removal path delegates here). Also owns the worktree
|
|
31
|
+
MERGE-OR-LOSE noise immunity (act:c008862c): `rebuild_worktree_excludes`
|
|
32
|
+
makes per-worktree excludes LIVE via `extensions.worktreeConfig` + a
|
|
33
|
+
worktree-scoped `core.excludesFile` (git never consults a linked
|
|
34
|
+
worktree's `worktrees/<name>/info/exclude` — the old rules were
|
|
35
|
+
silently dead), copying the user's own excludesFile patterns in FIRST
|
|
36
|
+
so the authored-record negations win, then hiding untracked identity
|
|
37
|
+
symlinks (root-anchored; only while actually a symlink into main) and
|
|
38
|
+
machine-written generated state (names kept in step with
|
|
39
|
+
`GENERATED_STATE_PATTERNS` in watchtower-lib and `GENERATED_STATE_RE`
|
|
40
|
+
in worktree-dirty-check.sh — the merge tail diffs the lists);
|
|
41
|
+
`hide_identity_symlink_typechange` sets git's skip-worktree bit on
|
|
42
|
+
TRACKED `.mcp.json`/`.claudeignore` mux symlinks (typechange churn is
|
|
43
|
+
exclude-immune) — granted only while the symlink resolves into main,
|
|
44
|
+
cleared whenever a real file returns.
|
|
19
45
|
- `worktree-health-popup.sh` — tmux popup for health details (triggered by
|
|
20
46
|
double-clicking a `·wt` tab).
|
|
21
47
|
- `worktree-dirty-check.sh` — single shared worktree dirty-detection
|
|
22
48
|
(fail-DIRTY on any error, `--untracked-files=all`; the authored-record
|
|
23
49
|
set is derived from the worktree's index — any top-level `.claude/`
|
|
24
50
|
entry holding tracked files — with the static plans/methodology pair as
|
|
25
|
-
fallback floor)
|
|
26
|
-
|
|
51
|
+
fallback floor). act:c008862c taught it the noise/work boundary:
|
|
52
|
+
untracked identity symlinks (`?? .mcp.json`/`.claudeignore`/`pib.db`)
|
|
53
|
+
are churn ONLY while the path is a live symlink — a real file there is
|
|
54
|
+
real work — and a skip-worktree'd path that no longer holds a symlink
|
|
55
|
+
counts DIRTY (a real edit may hide behind the bit; a clean-verdict
|
|
56
|
+
deletion must never race the health heal); untracked generated state
|
|
57
|
+
(`GENERATED_STATE_RE`: advisories-state.json, checklist-stats.json,
|
|
58
|
+
`.claude/verification/`, `e2e/.verify-progress.jsonl` — the shell twin
|
|
59
|
+
of watchtower-lib's `GENERATED_STATE_PATTERNS`, list-diffed at the
|
|
60
|
+
program merge tail) is subtracted from BOTH counting passes, so a
|
|
61
|
+
fully-merged worktree no longer reads "MERGE OR LOSE" over runtime
|
|
62
|
+
residue while a tracked-and-modified copy still counts dirty.
|
|
63
|
+
Delegated to by both `bin/mux` and the pane-exited
|
|
64
|
+
`worktree-cleanup.sh` — whose dirty-close inbox filing mirrors ring1's
|
|
65
|
+
earned-urgency register split (`urgent` only with unmerged commits,
|
|
66
|
+
`normal` for uncommitted-only, `evidence.unmerged` carried) and whose
|
|
67
|
+
clean-close removal reaps lane containers via `--reap`, routing an
|
|
68
|
+
incomplete reap to the desk's sticky notes, never silence;
|
|
69
|
+
sandboxed fixture suite in `templates/mux/__tests__/`,
|
|
27
70
|
which also holds the `mux-fail-loud` suite (bin/mux fail-loud worktree
|
|
28
71
|
creation + resume reuse, run against a private tmux server), the
|
|
29
72
|
`claude-carveout` suite (tracked `.claude/` = authored: never frozen with
|
|
@@ -161,7 +204,22 @@ consumes the registry stays in cli.js).
|
|
|
161
204
|
- `watchtower-contracts.md` — shared contracts for all watchtower components
|
|
162
205
|
(atomic writes, no-index, session-ID join, schema versioning, project-state
|
|
163
206
|
section ownership — Ring 3 owns `## Last Session` once authored — and
|
|
164
|
-
deferred schemas).
|
|
207
|
+
deferred schemas). Also home of "Detector Symmetry" (act:4d11fd53,
|
|
208
|
+
grp:wt-noise-immunity): every ring-filed inbox category declares its
|
|
209
|
+
mechanical retraction condition + filing exclusions, or an explicit
|
|
210
|
+
exemption — encoded in `INBOX_DETECTOR_REGISTRY` (watchtower-lib) and
|
|
211
|
+
enforced by a structural filing-site test; machine retractions resolve
|
|
212
|
+
through `autoReconcileItem` (`resolution_type: 'auto-reconciled'` +
|
|
213
|
+
`evidence.actor`, excluded from engagement buckets); reconcilers fail
|
|
214
|
+
toward KEEPING (retract only on positive evidence — fid-not-found
|
|
215
|
+
keeps); the long-lived branch exclusion list
|
|
216
|
+
(`defaults.long_lived_branches`, staging/production exact-match +
|
|
217
|
+
`backup/*` glob, consumer-overridable) gates FILING only, never the
|
|
218
|
+
attention surfaces; and the standing-debt aggregation contract
|
|
219
|
+
(act:e888dd63) — a standing condition files ONE item per (project,
|
|
220
|
+
debt-class), APPENDS evidence with `first_seen` = oldest and a
|
|
221
|
+
session-count urgency escalation, and post-resolution recurrence files
|
|
222
|
+
FRESH, never a refile loop.
|
|
165
223
|
|
|
166
224
|
## Workflows (`templates/workflows/`)
|
|
167
225
|
|
|
@@ -249,7 +307,39 @@ consumes the registry stays in cli.js).
|
|
|
249
307
|
`checkMemoryIntegrity` delegates to it (the old filename-substring scan
|
|
250
308
|
predated both mechanisms and flagged 203 false orphans while the validator
|
|
251
309
|
passed green). Tests in `__tests__/bsql-loader-verify.test.mjs` +
|
|
252
|
-
`__tests__/memory-reachability.test.mjs`.
|
|
310
|
+
`__tests__/memory-reachability.test.mjs`. Lane A of
|
|
311
|
+
grp:wt-noise-immunity added the reconciliation substrate:
|
|
312
|
+
`autoReconcileItem` — the ONE resolution path for machine (ring)
|
|
313
|
+
retractions: resolves through the real queue with `resolution_type:
|
|
314
|
+
'auto-reconciled'`, then stamps `evidence.actor` + caller evidence
|
|
315
|
+
naming WHY (action fid / branch); returns null when the item is no
|
|
316
|
+
longer pending (a human got there first); a failed evidence stamp is
|
|
317
|
+
logged and tolerated — the typed resolution already landed atomically.
|
|
318
|
+
`INBOX_DETECTOR_REGISTRY` — the detector-symmetry ledger mapping every
|
|
319
|
+
ring-filed inbox category to `{reconciler}` or `{exempt}` (exactly
|
|
320
|
+
one), enforced by the structural filing-site test
|
|
321
|
+
`__tests__/detector-registry.test.mjs`: it scans `category:` literals
|
|
322
|
+
at createItem sites across ring1 + ring2 + ring3-close (read-only
|
|
323
|
+
source scan) and fails on an unregistered category or a non-literal
|
|
324
|
+
category value inside a filing owner (act:4d11fd53).
|
|
325
|
+
`GENERATED_STATE_PATTERNS`/`generatedStateIsDisposable` — the JS SSOT
|
|
326
|
+
for machine-written generated state (UNTRACKED `??` lines only:
|
|
327
|
+
`.claude/verification/`, advisories-state.json, checklist-stats.json,
|
|
328
|
+
`e2e/.verify-progress.jsonl` — a TRACKED file of the same name is
|
|
329
|
+
authored by definition), folded into `claudeChurnIsDisposable` for
|
|
330
|
+
ring1's `countRealUncommitted` and ring3's Phase 2a dirty filter; the
|
|
331
|
+
mux shell twin (`GENERATED_STATE_RE` in worktree-dirty-check.sh) is
|
|
332
|
+
list-diffed at the program merge tail (act:c008862c). And
|
|
333
|
+
`resolveProjectFromTranscriptSlug` — the slug-parsing fallback beside
|
|
334
|
+
`resolveProjectIdentity` for CLEANED-UP mux worktrees (act:29001b07,
|
|
335
|
+
filesystem resolution has nothing left to resolve): decodes the
|
|
336
|
+
transcript dir slug's `--mux-worktrees-` tail through mux's own
|
|
337
|
+
`~/.config/mux/projects.json` (longest match on a `-` boundary;
|
|
338
|
+
same-length tie = ambiguous → null), hands the MAIN-checkout path to
|
|
339
|
+
the canonical resolver, and returns null on EVERY failure — the caller
|
|
340
|
+
files `project_unresolved: true`, never a cwd guess. The export NAME
|
|
341
|
+
is a cross-lane contract: ring3-close feature-detects it on its
|
|
342
|
+
namespace import.
|
|
253
343
|
- `watchtower-ring3-close.mjs` — Ring 3 session-close transcript processing:
|
|
254
344
|
the knowledge-extraction lenses + the dedup that gates whether an
|
|
255
345
|
extraction is filed. The **recall-fix program** (audit act:7d6a4b3b;
|
|
@@ -275,7 +365,36 @@ consumes the registry stays in cli.js).
|
|
|
275
365
|
suppression sites appends a `recordSuppression` ledger line. Tests in
|
|
276
366
|
`__tests__/ring3-dedup`, `ring3-memory-titles`, `ring3-novelty-rescue`,
|
|
277
367
|
`ring3-recent-slice`, `ring3-chunk-merge`, `suppression-ledger`,
|
|
278
|
-
`ring3-close-lenses`.
|
|
368
|
+
`ring3-close-lenses`. Lane B of grp:wt-noise-immunity rebuilt the
|
|
369
|
+
filing quality. Attribution (act:29001b07): BOTH entry points — live
|
|
370
|
+
session close and manual reprocess — share ONE `resolveSessionProject`
|
|
371
|
+
call that attributes the session by its TRANSCRIPT SLUG, never the
|
|
372
|
+
runner's cwd (302 of 510 reprocess-recovered extractions were
|
|
373
|
+
confidently misattributed to the runner's project before this): cwd is
|
|
374
|
+
trusted only when it AGREES with the transcript's project-dir slug
|
|
375
|
+
(`transcriptSlugFromPath`/`normalizeSlugKey`); a distrusted cwd falls
|
|
376
|
+
to the lib's `resolveProjectFromTranscriptSlug` (feature-detected by
|
|
377
|
+
name on the namespace import) and then to `project_unresolved: true` —
|
|
378
|
+
inbox filing proceeds, but no state-file or thread key is minted from
|
|
379
|
+
a raw slug. Phase 2c's `completionReviewEmitGuard` adds the
|
|
380
|
+
create-vs-complete filter (act:9eebbac4): an action whose db `created`
|
|
381
|
+
date falls inside the session's day window (+1 for past-midnight
|
|
382
|
+
spill) with confidence below `high` is not a completion candidate (an
|
|
383
|
+
action born this session whose only evidence is its own creation);
|
|
384
|
+
mechanical (db row, the same prepared statement as the status
|
|
385
|
+
re-check) and FAIL-OPEN in every gap — no sessionDate, an unparseable
|
|
386
|
+
sessionDate (windowEnd is a required conjunct), a missing `created`
|
|
387
|
+
column, or a thrown lookup all emit. coverage-warning is now a
|
|
388
|
+
STANDING-DEBT aggregation (act:e888dd63): ONE pending item per
|
|
389
|
+
(project, debt-class — `COVERAGE_DEBT_CLASS`) whose evidence APPENDS
|
|
390
|
+
per session (session-id union, so a reprocess replay can't inflate
|
|
391
|
+
counts; per-path counts; `first_seen` = oldest evidence date;
|
|
392
|
+
`filed_at` bumped so the expiry clock measures the LAST recurrence),
|
|
393
|
+
urgency escalating with session count (`coverageDebtUrgency`: normal
|
|
394
|
+
at 5, urgent at 15); resolving closes the accumulated view and a
|
|
395
|
+
persisting debt refiles FRESH next session. The `## Last Session`
|
|
396
|
+
block names its authoritative per-session record (`appendRecordPointer`
|
|
397
|
+
→ `<slug>/sessions/<date>-<sessionId>.md`; act:8c076580).
|
|
279
398
|
- `watchtower-queue.mjs` — inbox queue CRUD library (atomic temp+rename,
|
|
280
399
|
no-index convention); also home of the staff-QA recipient gate
|
|
281
400
|
(2026-06-11): every terminal exit on a `qa-handoff` item is structural —
|
|
@@ -297,28 +416,45 @@ consumes the registry stays in cli.js).
|
|
|
297
416
|
required, and a structural refusal of GATED_CATEGORIES items (today:
|
|
298
417
|
qa-handoff) — gated items never batch, each exits only through its own
|
|
299
418
|
gate; `routine` is dispatched but NOT gated, so stale routines are legal
|
|
300
|
-
batch fodder (
|
|
419
|
+
batch fodder (19-test hermetic suite in `__tests__/batch-disposition.test.mjs`).
|
|
301
420
|
Inbox bulk triage splits sign-off from judgment via `isHighConfidenceSignoff`/
|
|
302
421
|
`partitionForBatchSignoff` — high-confidence pre-drafted extractions
|
|
303
422
|
auto-group into one batch sign-off while judgment items stay per-item
|
|
304
423
|
(act:7f1e4d91). qa-handoff items carry a first-class `evidence.merge_state`
|
|
305
424
|
(act:971570c4): `'merged'` (default — filed after the worktree branch
|
|
306
|
-
merged) or `'merge-pending'` (filed before the merge, when it's gated
|
|
307
|
-
|
|
308
|
-
`createItem` rejects a present-but-illegal token at the boundary
|
|
309
|
-
can't read as `'merged'`) and canonicalizes a legal one (additive —
|
|
310
|
-
stays absent, readers default to `'merged'`). A `createItem`
|
|
425
|
+
merged) or `'merge-pending'` (filed before the merge, ONLY when it's gated
|
|
426
|
+
on something external). `normalizeMergeState` + `QA_MERGE_STATES` are
|
|
427
|
+
exported; `createItem` rejects a present-but-illegal token at the boundary
|
|
428
|
+
(a typo can't read as `'merged'`) and canonicalizes a legal one (additive —
|
|
429
|
+
absent stays absent, readers default to `'merged'`). A `createItem`
|
|
311
430
|
MERGE-COHERENCE guard (act:e859b3a3) also rejects a `'merged'` handoff
|
|
312
431
|
whose `merged_into`/`merged_commit` free-texts a not-yet-merged marker
|
|
313
|
-
(`PENDING`/`unmerged`/…) —
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
`
|
|
321
|
-
`
|
|
432
|
+
(`PENDING`/`unmerged`/…) — pure string check, fenced to qa-handoff. On top
|
|
433
|
+
of it, the MERGED-BY-CONSTRUCTION guards (act:3d1ac2b7) make the merge
|
|
434
|
+
claim verified, not asserted — a two-layer defense with the drain's Step 0
|
|
435
|
+
live re-check: a `'merged'` handoff requires a sha-shaped `merged_commit`
|
|
436
|
+
(omission was the trivial bypass) which `verifyMergedAncestry` checks
|
|
437
|
+
against git (queue-local origin/HEAD→main/master ref resolution, no fetch,
|
|
438
|
+
execFile args, 5s timeout; REJECT only on positive refutation, file with a
|
|
439
|
+
visible `evidence.ancestry_verified: 'unverifiable'` stamp when git can't
|
|
440
|
+
answer); a `'merge-pending'` handoff requires a non-empty
|
|
441
|
+
`evidence.merge_gate` naming the external blocker (rejection messages
|
|
442
|
+
teach the merge-first close-out, self-correcting older skill text); and
|
|
443
|
+
the ONE legal merge-pending→merged flip is `markHandoffMerged` — same
|
|
444
|
+
verification, writes the deferred stage-2 dispatch descriptor directly
|
|
445
|
+
into the mux queue dir (fs-only, desk token shape-guarded, in-flight
|
|
446
|
+
aware), records `evidence.stage2_dispatch` so a failed dispatch retries
|
|
447
|
+
by state — while `annotateItemEvidence` REFUSES `merge_state` patches on
|
|
448
|
+
qa-handoffs (the side door is closed). The recipient gate is unchanged: a
|
|
449
|
+
merge-pending handoff resolves only AFTER `markHandoffMerged` records the
|
|
450
|
+
verified merge, then through a stamped `qa_verdict` cited against the
|
|
451
|
+
post-merge commit. The skill defers stage-2 dispatch and the pickup
|
|
452
|
+
prompt says "merge then QA" when merge-pending (`buildQaPickupPrompt` is
|
|
453
|
+
the library sibling of the skill's merged-path prompt — pared to the gate
|
|
454
|
+
essentials, not byte-parity). 32-test hermetic suite in
|
|
455
|
+
`templates/scripts/__tests__/qa-handoff-gate.test.mjs` + 20-test
|
|
456
|
+
`qa-handoff-merge-state.test.mjs` + 19-test
|
|
457
|
+
`qa-handoff-merged-by-construction.test.mjs` (real temp git repos). Also home of the operator RESOLVE CLI
|
|
322
458
|
(act:00030e1d): `resolve <id> --verdict-file <path.json>` (`parseResolveArgs`
|
|
323
459
|
+ `runResolveCli` + an isMain guard) reads the FULL `resolveItem` params
|
|
324
460
|
object `{resolution, resolution_type, resolution_notes, qa_verdict}` from a
|
|
@@ -328,6 +464,25 @@ consumes the registry stays in cli.js).
|
|
|
328
464
|
drain. Fail-loud exit policy: 2 = malformed invocation, 1 = can't-proceed
|
|
329
465
|
(item left pending), 0 = resolved; never leaks the absolute queue path.
|
|
330
466
|
qa-drain's Step 2 is the consumer; 23 tests in `__tests__/resolve-cli.test.mjs`.
|
|
467
|
+
Also home of the surfacing-intelligence apparatus (act:00051dca,
|
|
468
|
+
grp:wt-noise-immunity): `extractCitedActFids` (regex-validated `act:`
|
|
469
|
+
fids from draft text, first-appearance order, capped at 50 — the
|
|
470
|
+
matches are the ONLY values that ever reach a pib-db query, bound as
|
|
471
|
+
parameters); the fold recipe — `foldTokens` (lowercase alnum runs,
|
|
472
|
+
length ≥3, stopworded) + `unigramJaccard` with
|
|
473
|
+
`FOLD_SIMILARITY_THRESHOLD = 0.22` and the `FOLD_MIN_TOKENS = 3`
|
|
474
|
+
short-text floor (calibrated 2026-07-12 against the live pairs a full
|
|
475
|
+
510-draft human read surfaced), `proposeFolds` annotating BOTH sides
|
|
476
|
+
of a pair (reciprocity is the self-validating criterion); and
|
|
477
|
+
`annotateItemEvidence` — the additive evidence-only write path
|
|
478
|
+
(fresh read from disk, PENDING-only fence, deep-equal skip so
|
|
479
|
+
idempotent re-sweeps never rewrite). `isHighConfidenceSignoff` DEMOTES
|
|
480
|
+
on `evidence.freshness.overtaken` or a non-empty
|
|
481
|
+
`evidence.possible_duplicate_of` — the item drops out of batch
|
|
482
|
+
sign-off into individual review, never auto-dismissed. Every
|
|
483
|
+
caller-supplied id → path join routes through `assertLegalItemId`
|
|
484
|
+
(`itemPath` id-shape guard — persisted annotation id arrays flow back
|
|
485
|
+
in as lookups).
|
|
331
486
|
- `watchtower-routines.mjs` — routine dispatch engine (act:c2a55c08):
|
|
332
487
|
projects declare interactive routines in watchtower config (`{name,
|
|
333
488
|
trigger, script}`; triggers: time-of-day, interval, path-nonempty,
|
|
@@ -370,8 +525,17 @@ consumes the registry stays in cli.js).
|
|
|
370
525
|
(git-dir == git-common-dir; worktree windows stay silent) and built from
|
|
371
526
|
the shared `buildPickupPrompt` so the resolve-on-run wording is
|
|
372
527
|
single-sourced; qa-handoff items are excluded by the `category: 'routine'`
|
|
373
|
-
filter (operator-initiated by design).
|
|
374
|
-
|
|
528
|
+
filter (operator-initiated by design). Also OPENS every injection with a
|
|
529
|
+
session-start acknowledgment directive (act:058d00f0,
|
|
530
|
+
`renderSessionAckSection`, `PRIORITY_NEVER`): a short instruction telling
|
|
531
|
+
the session to begin its first reply with ONE plain-English line
|
|
532
|
+
confirming the context loaded — positive confirmation the hook chain
|
|
533
|
+
works (the injection is invisible in the terminal, so a silent session
|
|
534
|
+
was indistinguishable from a broken hook). Prompt-layer convention only;
|
|
535
|
+
a frontier-model warning still precedes it, resume re-injections skip it;
|
|
536
|
+
tests in `__tests__/session-ack.test.mjs`. Exports
|
|
537
|
+
`renderMissedRoutineSection`/`renderSessionAckSection`/`isMainCheckout`
|
|
538
|
+
for tests.
|
|
375
539
|
- `watchtower-snapshot.mjs` — mid-session awareness polling (act:a8057f87):
|
|
376
540
|
the sibling primitive behind `/catch-up`. The SessionStart hook captures a
|
|
377
541
|
per-session BASELINE snapshot (keyed by the stdin `.session_id`, written to
|
|
@@ -416,7 +580,9 @@ consumes the registry stays in cli.js).
|
|
|
416
580
|
configured project, Ring 1's parsed state-file sections + git-attention
|
|
417
581
|
facts, Ring 3's recent sessions + thread cursor timelines, and the inbox's
|
|
418
582
|
pending counts + RESOLUTION HISTORY (`resolution_mix` — engaged vs
|
|
419
|
-
discarded vs
|
|
583
|
+
discarded vs machine vs untyped; `machine` = `MACHINE_TYPES`
|
|
584
|
+
auto-reconciled ring retractions, excluded from `ENGAGED_TYPES` so cron
|
|
585
|
+
activity never reads as operator engagement). The script
|
|
420
586
|
GATHERS; the consumer synthesizes — no convergence scoring
|
|
421
587
|
(pattern-intelligence-first). Read-only projection (no write path; hermetic
|
|
422
588
|
fs-snapshot test), a sibling of `watchtower-narrative-corpus.mjs`, touches
|
|
@@ -475,7 +641,25 @@ consumes the registry stays in cli.js).
|
|
|
475
641
|
drill-down) / `--window <N>` — answer-first, verbs + counts, NEVER
|
|
476
642
|
decimals. Consumers: /inbox Step 2a (answer-first verdict before the
|
|
477
643
|
walk/bulk/summary choice, healthy projects suppressed) and /briefing Step
|
|
478
|
-
3b (the "backlog rot" weighable candidate).
|
|
644
|
+
3b (the "backlog rot" weighable candidate). act:ab4927eb
|
|
645
|
+
(grp:wt-noise-immunity) sharpened the verdict:
|
|
646
|
+
`deriveAssessmentDetail` returns `{state, state_reason}` and two
|
|
647
|
+
ABSOLUTE not-consumed arms override a healthy keep ratio — a pile ≥50
|
|
648
|
+
pending (`deep-pile`) or an oldest tail ≥21d on a pile ≥10
|
|
649
|
+
(`old-tail`) can never read `keeping` (the 2026-07-12 morning
|
|
650
|
+
regression: 884 pending / 29d oldest reported all-keeping); rendered
|
|
651
|
+
wordings are state_reason-honest. `worked_recent` and the keep-ratio
|
|
652
|
+
event window EXCLUDE machine acts (`bucketResolution` 'machine' /
|
|
653
|
+
`MACHINE_TYPES`, re-exported from the cross-ring reader; surfaced as a
|
|
654
|
+
`machine_resolutions` count) so cron retractions can't suppress the
|
|
655
|
+
not-consumed verdict. Backlog rot handles AGGREGATED standing-debt
|
|
656
|
+
items: age = `evidence.first_seen` (falls back to filed_at) and weight
|
|
657
|
+
= the accumulated session count (`evidence.session_ids` — the live
|
|
658
|
+
producer spelling — /`session_count`/`sessions`), so one 49-session
|
|
659
|
+
debt can trip `MIN_PILE_FOR_ROT` alone; raw item counts ride beside
|
|
660
|
+
the weighted ones. `assessRingsAlive` also reads Ring 2's
|
|
661
|
+
`draft-annotations-health.json` sidecar (its named reader) —
|
|
662
|
+
informational only, never flips `all_ok`. 12 hermetic tests in
|
|
479
663
|
`__tests__/inbox-assessment.test.mjs`.
|
|
480
664
|
- `watchtower-migrate-keys.mjs` — one-time migration re-keying inbox items,
|
|
481
665
|
thread memberships, and state dirs filed under pre-resolver
|
|
@@ -723,13 +907,48 @@ retained for the human-readable line only. Compares against `origin/<main>`
|
|
|
723
907
|
structured `state/git-attention.json` sidecar that
|
|
724
908
|
`watchtower-build-context.mjs` re-verifies against live git before relaying
|
|
725
909
|
any banner — so a stale "MERGE OR LOSE" line can't survive a merge that
|
|
726
|
-
already happened; act:6f36cbe2, act:a136b362
|
|
910
|
+
already happened; act:6f36cbe2, act:a136b362. Since grp:wt-noise-immunity
|
|
911
|
+
Ring 1's tick also RETRACTS what the rings filed — every machine
|
|
912
|
+
retraction routes through `autoReconcileItem` (watchtower-lib;
|
|
913
|
+
`'auto-reconciled'` + `evidence.actor`, excluded from engagement buckets
|
|
914
|
+
portfolio-wide): `autoResolveBranchDivergedItems` retracts
|
|
915
|
+
branch-diverged items whose branch merged, is verifiably gone
|
|
916
|
+
(membership in a successful branch listing — a failed git call retracts
|
|
917
|
+
nothing), or sits on `defaults.long_lived_branches` (staging/production
|
|
918
|
+
exact + `backup/*` glob) — an exclusion that gates FILING ONLY, so an
|
|
919
|
+
excluded branch stays visible on all four attention surfaces (attention
|
|
920
|
+
line, git-attention sidecar, summary, Standing Issues; act:98649971);
|
|
921
|
+
`autoReconcileCompletionReviews` resolves completion-review items whose
|
|
922
|
+
action fid is verifiably CLOSED in its own project's pib-db (opened
|
|
923
|
+
read-only; fid-not-found KEEPS the item — act:9eebbac4);
|
|
924
|
+
worktree-unmerged filing splits an earned-urgency register
|
|
925
|
+
(act:c008862c): `urgent` only with real unmerged commits, `normal` for
|
|
926
|
+
uncommitted-files-only (`evidence.unmerged`), with REGISTER-AWARE dedup
|
|
927
|
+
— a register change supersedes + refiles under the current register, and
|
|
928
|
+
escalation is damped (a soft→urgent flip needs corroborating commits
|
|
929
|
+
ahead, since `hasUnmergedContent` fails toward true on git blips);
|
|
930
|
+
`countRealUncommitted` excludes generated runtime state via the
|
|
931
|
+
watchtower-lib SSOT. CP3 hardening: untracked filenames pass as execFile
|
|
932
|
+
args (never shell-interpolated), every branch-name path gates on
|
|
933
|
+
`isSafeRefName`, and the detector census fails loudly on a non-literal
|
|
934
|
+
`category:` inside a filing owner), Ring 2 (Claude intelligence,
|
|
727
935
|
fast 5-min + slow 30-min tiers; the slow tier also runs `recallCanary`
|
|
728
936
|
(act:6354a9db) — reads + prunes `state/suppression-ledger.jsonl`, writes
|
|
729
937
|
`state/recall-canary.json`, an over-suppression-only trend detector gated on
|
|
730
938
|
`defaults.recall_canary`; `buildRecallCanary`/`readLedgerRecords`/`withinDays`
|
|
731
|
-
exported
|
|
732
|
-
|
|
939
|
+
exported — and `runDraftAnnotationSweep` (act:00051dca; gated on
|
|
940
|
+
`defaults.draft_annotations`, default ON): annotates pending
|
|
941
|
+
knowledge-extraction drafts with freshness (cited `act:` fids positively
|
|
942
|
+
CLOSED in the item's own project pib-db — parameter-bound lookups via
|
|
943
|
+
`openPibDb`, now `timeout: 5000` for lock parity with Ring 3) and fold
|
|
944
|
+
proposals (the watchtower-queue recipe), demoting them out of batch
|
|
945
|
+
sign-off, never dismissing; unreadable dbs and out-of-config drafts are
|
|
946
|
+
counted LOUDLY (`skipped_projects`/`unscanned_items`) and every run
|
|
947
|
+
writes the positive-confirmation sidecar
|
|
948
|
+
`state/draft-annotations-health.json`, so "no annotations" is
|
|
949
|
+
distinguishable from "never ran"), Ring 3 (session close, transcript
|
|
950
|
+
processing; fires session-close routines as Phase 2j2; runs the standing
|
|
951
|
+
session advisors as Phase 2m — see
|
|
733
952
|
below; runs three session-close extraction lenses (act:4ff2cfb3) — Phase 2n
|
|
734
953
|
raised-but-unhandled (loose ends the session raised but neither did nor filed
|
|
735
954
|
→ inbox category `raised-unhandled`), Phase 2o skill-candidate (a manual
|
|
@@ -805,7 +1024,9 @@ step — then push it to the desk's main window via `mux qa dispatch`; usually
|
|
|
805
1024
|
filed right after the merge — an unmerged named branch now PROMPTS merge-now
|
|
806
1025
|
(Claude merges → `merged`) vs keep-gated, never a silent `merge-pending`
|
|
807
1026
|
default (act:350e7373); merge-pending is opt-in, filed BEFORE the merge only
|
|
808
|
-
when the merge is gated
|
|
1027
|
+
when the merge is gated on a named external blocker (`evidence.merge_gate`,
|
|
1028
|
+
required at the boundary; act:3d1ac2b7), in which case stage-2 dispatch
|
|
1029
|
+
defers until `markHandoffMerged` records the verified flip — see the
|
|
809
1030
|
watchtower-queue `merge_state` entry above; stages 1–2 of the QA-handoff
|
|
810
1031
|
protocol, `.claude/plans/qa-handoff-protocol.md`). Stage 3 — the drain — is
|
|
811
1032
|
enforced by the staff-QA recipient gate in `watchtower-queue.mjs`: qa-handoff
|
|
@@ -874,7 +1095,14 @@ shared fail-DIRTY `worktree-dirty-check.sh`; carve-out-aware `mux worktree
|
|
|
874
1095
|
refresh` preserves uncommitted edits to ANY tracked `.claude/` file — tracked
|
|
875
1096
|
= authored, only gitignored infra is disposable, classification
|
|
876
1097
|
single-sourced in `worktree-session-health.sh` and guarded by the
|
|
877
|
-
`claude-carveout` test suite),
|
|
1098
|
+
`claude-carveout` test suite), worktree lane lifecycle (act:dc80380c:
|
|
1099
|
+
per-project `.mux-worktree-provision` env-file provisioning at spawn,
|
|
1100
|
+
`mux worktree preflight` pre-spawn source check, docker container reap
|
|
1101
|
+
on every removal path — all single-sourced in
|
|
1102
|
+
`worktree-session-health.sh`), live per-worktree excludes + skip-worktree
|
|
1103
|
+
identity-churn suppression so a fully-merged worktree never reads
|
|
1104
|
+
"MERGE OR LOSE" over mux/runtime residue (act:c008862c), trail logging,
|
|
1105
|
+
sticky notes, global DX
|
|
878
1106
|
captures, portal color-switching, durable tmux bindings (`mux.tmux.conf`
|
|
879
1107
|
sourced via `~/.tmux.conf`), clipboard copy with hard-wrap removal
|
|
880
1108
|
(`unwrap-copy.py`), screenshot-to-clipboard launchd watcher
|
|
@@ -950,10 +1178,15 @@ MAIN checkout for breadcrumbs/report, so worktree invocations land artifacts
|
|
|
950
1178
|
durably). `checkpoint-protocol.md` documents three CP modes (Interactive,
|
|
951
1179
|
Advisory, Full) and is the shared contract between `/execute` and
|
|
952
1180
|
`/execute-group`. `/execute` is worktree-aware (act:1067adee): its Step 0
|
|
953
|
-
reads the worktree-invocation contract and syncs main *into* the worktree
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
1181
|
+
reads the worktree-invocation contract and syncs main *into* the worktree
|
|
1182
|
+
(the merge OUT happens once, at close-out); breadcrumb writes resolve to
|
|
1183
|
+
`$MAIN/.claude/verification/`, closing the lost-on-cleanup gap
|
|
1184
|
+
(act:d4767af6); and its Step 8 worktree close-out is MERGE-FIRST
|
|
1185
|
+
(act:3d1ac2b7): re-sync + re-gate, merge + push via `/qa-handoff`'s
|
|
1186
|
+
merge-now recipe (no prompt — merge-now is the default; every failure HALTs
|
|
1187
|
+
loudly, the action stays open), then file a `merged` qa-handoff that
|
|
1188
|
+
dispatches unconditionally. Done is marked only after merge + filing
|
|
1189
|
+
succeed. `merge-pending` is reserved for an explicitly gated merge
|
|
1190
|
+
(`evidence.merge_gate` required). Without watchtower the close-out still
|
|
1191
|
+
merges, then degrades loudly (never silently) — the no-handoff note names
|
|
1192
|
+
what to QA manually. On the main checkout all of this no-ops.
|