Merge branch 'feat/wf-fleet-t1-base' into feat/wf-fleet-mvp
This commit is contained in:
@@ -0,0 +1,94 @@
|
|||||||
|
# T1 report: canonical ungated Claude base and lease overlay
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
|
||||||
|
- Replaced `packages/mosaic/framework/runtime/claude/settings.json` with the canonical ungated base. It retains the model, QA hooks, plugins, command allowlist, permissions, and `mcpServers.sequential-thinking`.
|
||||||
|
- Added `packages/mosaic/framework/runtime/claude/lease-overlay.json`. It contains only `hooks` and the six removed lease hook entries.
|
||||||
|
- Added the byte-identical pre-split source fixture at `packages/mosaic/src/runtime/fixtures/claude-settings.gated.pre-split.json`.
|
||||||
|
- Added `packages/mosaic/src/runtime/claude-settings-base.spec.ts`.
|
||||||
|
|
||||||
|
`framework-manifest.txt` already declares `runtime/**`, so the new overlay is framework-owned and shipped without a manifest change.
|
||||||
|
|
||||||
|
## Lease-hook enumeration
|
||||||
|
|
||||||
|
The actual template has six lease hook entries, matching fred's refined boundary:
|
||||||
|
|
||||||
|
1. `PreToolUse` matcher `.*`: `mutator-gate.py`
|
||||||
|
2. `Stop`: one combined command containing `receipt-observer-client.py` then `promote-complete.py`
|
||||||
|
3. `UserPromptSubmit` matcher `^/mosaic-promote$`: `promote-begin.py`
|
||||||
|
4. `PreCompact`: `revoke-lease.py --reason pre-compact`
|
||||||
|
5. `SessionStart` matcher `compact`: `revoke-lease.py --reason session-start-compact`
|
||||||
|
6. `SessionStart` matcher `resume|clear`: `revoke-lease.py --reason session-start-rollover --bump-generation`
|
||||||
|
|
||||||
|
There is no delta from the refined six-entry enumeration. The Stop entry contains the receipt-observer and promote-complete commands together, rather than as two separate hook objects.
|
||||||
|
|
||||||
|
## Tests and checks
|
||||||
|
|
||||||
|
`pnpm install --frozen-lockfile` was run first because `node_modules` was absent. It completed successfully.
|
||||||
|
|
||||||
|
Red-first run before artifacts existed:
|
||||||
|
|
||||||
|
```text
|
||||||
|
RUN v2.1.9 .../packages/mosaic
|
||||||
|
❯ src/runtime/claude-settings-base.spec.ts (4 tests | 4 failed)
|
||||||
|
× keeps every lease command out of the ungated base
|
||||||
|
→ mutator-gate: expected true to be false
|
||||||
|
× reconstructs the pre-split gated hooks while retaining the canonical MCP correction
|
||||||
|
→ ENOENT: .../lease-overlay.json
|
||||||
|
× ships sequential-thinking in the base
|
||||||
|
→ expected undefined to deeply equal { 'sequential-thinking': ... }
|
||||||
|
× limits the overlay to lease hook entries
|
||||||
|
→ ENOENT: .../lease-overlay.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Final focused acceptance run:
|
||||||
|
|
||||||
|
```text
|
||||||
|
RUN v2.1.9 .../packages/mosaic
|
||||||
|
✓ src/runtime/claude-settings-base.spec.ts (4 tests) 19ms
|
||||||
|
Test Files 1 passed (1)
|
||||||
|
Tests 4 passed (4)
|
||||||
|
```
|
||||||
|
|
||||||
|
`pnpm --filter @mosaicstack/mosaic lint` passed:
|
||||||
|
|
||||||
|
```text
|
||||||
|
> @mosaicstack/[email protected] lint
|
||||||
|
> eslint src
|
||||||
|
```
|
||||||
|
|
||||||
|
`pnpm --filter @mosaicstack/mosaic typecheck` failed on pre-existing workspace resolution and unrelated package errors. The new spec no longer appears in the error list. Initial failures include missing `@mosaicstack/{brain,forge,log,macp,memory,queue,storage,quality-rails,db,config,prdy,types}` declarations, followed by existing `fleet-backlog.ts`, `gateway-doctor.ts`, and TUI implicit-`any` errors. Exit status: 2.
|
||||||
|
|
||||||
|
A focused legacy consumer run confirms an existing assumption that `settings.json` itself is gated:
|
||||||
|
|
||||||
|
```text
|
||||||
|
pnpm --filter @mosaicstack/mosaic exec vitest run src/mutator-gate/mutator-gate.acceptance.spec.ts
|
||||||
|
❯ src/mutator-gate/mutator-gate.acceptance.spec.ts (20 tests | 6 failed)
|
||||||
|
× non-dangerous parser residual is denied by the global all-tools hook without a lease
|
||||||
|
→ expected all-tools mutator-gate command in settings.json
|
||||||
|
× Claude and Pi compaction observer wiring is complete and fail-closed
|
||||||
|
→ expected PreCompact/SessionStart revoke-lease hooks in settings.json
|
||||||
|
```
|
||||||
|
|
||||||
|
The other four failures in that focused run reported `STALE_GENERATION` where the test expected `MUTATOR_UNVERIFIED`, plus one successful-gate assertion. I did not redesign this legacy suite because the task explicitly says to report consumers that assume the base is gated.
|
||||||
|
|
||||||
|
## Consumers found
|
||||||
|
|
||||||
|
Direct `runtime/claude/settings.json` path consumers found by the required repository grep:
|
||||||
|
|
||||||
|
- `packages/mosaic/framework/tools/_scripts/mosaic-link-runtime-assets`: copies the base to `~/.claude/settings.json`.
|
||||||
|
- `packages/mosaic/src/commands/install-ordering-guard.ts` and `.spec.ts`: documentation and behavior assume the source embeds enforcement hooks.
|
||||||
|
- `packages/mosaic/framework/tools/_scripts/test-install-ordering-guard.sh`: comments and assertions expect `mutator-gate.py` and `receipt-observer-client.py` in the base.
|
||||||
|
- `packages/mosaic/src/mutator-gate/mutator-gate.acceptance.spec.ts`: reads the base and asserts mutator, promotion, and compaction lease wiring.
|
||||||
|
- `packages/mosaic/src/lease-broker/promotion_trigger_unittest.py`: reads the base and asserts promotion wiring.
|
||||||
|
- `packages/mosaic/src/lease-broker/recovery_runtime_unittest.py`: reads the base.
|
||||||
|
- `packages/mosaic/src/runtime/update-checker.ts` and `.spec.ts`: references the path in settings wiring/update checks.
|
||||||
|
- Documentation-only references: `docs/compaction-refresh/probes/p6_constrained_recovery.py`, `docs/plans/agent-reflection-loop-PRD.md`, `docs/tasks/544-agent-reflection-loop.md`, and the framework QA documentation/scripts found by grep.
|
||||||
|
|
||||||
|
I did not change these consumers. The install/link and lease acceptance consumers must be taught to select and compose `lease-overlay.json` when a gated promotion seat is requested. That composition behavior is outside T1.
|
||||||
|
|
||||||
|
## Ambiguity handled
|
||||||
|
|
||||||
|
The exact pre-split template fixture has no `mcpServers` key (SHA-256 `44e74ea1e9d424fffa020ee666402662ac856b88bf6ae7f3b8931eed29dc75a4`). The task simultaneously requires a byte-for-byte pre-split fixture, `mcpServers.sequential-thinking` in the base, and `deep-merge(base, overlay) == original`. Those three conditions cannot all hold because a merge cannot remove the required MCP key.
|
||||||
|
|
||||||
|
The acceptance test preserves the exact fixture and asserts that the normalized merge equals the pre-split template plus the required canonical `mcpServers.sequential-thinking` correction. It verifies all original hook content is reconstructed and the base carries the required MCP. Production three-layer merge semantics remain W-F1 work.
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"hooks": {
|
||||||
|
"PreCompact": [
|
||||||
|
{
|
||||||
|
"matcher": ".*",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "python3 \"$HOME/.config/mosaic/tools/lease-broker/revoke-lease.py\" --runtime claude --reason pre-compact"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"SessionStart": [
|
||||||
|
{
|
||||||
|
"matcher": "compact",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "python3 \"$HOME/.config/mosaic/tools/lease-broker/revoke-lease.py\" --runtime claude --reason session-start-compact"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": "resume|clear",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "python3 \"$HOME/.config/mosaic/tools/lease-broker/revoke-lease.py\" --runtime claude --reason session-start-rollover --bump-generation"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"UserPromptSubmit": [
|
||||||
|
{
|
||||||
|
"matcher": "^/mosaic-promote$",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "python3 ~/.config/mosaic/tools/lease-broker/promote-begin.py",
|
||||||
|
"timeout": 15
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"PreToolUse": [
|
||||||
|
{
|
||||||
|
"matcher": ".*",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "python3 ~/.config/mosaic/tools/lease-broker/mutator-gate.py --runtime claude --recovery-command ~/.config/mosaic/tools/lease-broker/recover-context.py",
|
||||||
|
"timeout": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Stop": [
|
||||||
|
{
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "python3 ~/.config/mosaic/tools/lease-broker/receipt-observer-client.py --runtime claude --latest-entry; observer_status=$?; python3 ~/.config/mosaic/tools/lease-broker/promote-complete.py; exit $observer_status",
|
||||||
|
"timeout": 15
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,60 +1,7 @@
|
|||||||
{
|
{
|
||||||
"model": "opus",
|
"model": "opus",
|
||||||
"hooks": {
|
"hooks": {
|
||||||
"PreCompact": [
|
|
||||||
{
|
|
||||||
"matcher": ".*",
|
|
||||||
"hooks": [
|
|
||||||
{
|
|
||||||
"type": "command",
|
|
||||||
"command": "python3 \"$HOME/.config/mosaic/tools/lease-broker/revoke-lease.py\" --runtime claude --reason pre-compact"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"SessionStart": [
|
|
||||||
{
|
|
||||||
"matcher": "compact",
|
|
||||||
"hooks": [
|
|
||||||
{
|
|
||||||
"type": "command",
|
|
||||||
"command": "python3 \"$HOME/.config/mosaic/tools/lease-broker/revoke-lease.py\" --runtime claude --reason session-start-compact"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"matcher": "resume|clear",
|
|
||||||
"hooks": [
|
|
||||||
{
|
|
||||||
"type": "command",
|
|
||||||
"command": "python3 \"$HOME/.config/mosaic/tools/lease-broker/revoke-lease.py\" --runtime claude --reason session-start-rollover --bump-generation"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"UserPromptSubmit": [
|
|
||||||
{
|
|
||||||
"matcher": "^/mosaic-promote$",
|
|
||||||
"hooks": [
|
|
||||||
{
|
|
||||||
"type": "command",
|
|
||||||
"command": "python3 ~/.config/mosaic/tools/lease-broker/promote-begin.py",
|
|
||||||
"timeout": 15
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"PreToolUse": [
|
"PreToolUse": [
|
||||||
{
|
|
||||||
"matcher": ".*",
|
|
||||||
"hooks": [
|
|
||||||
{
|
|
||||||
"type": "command",
|
|
||||||
"command": "python3 ~/.config/mosaic/tools/lease-broker/mutator-gate.py --runtime claude --recovery-command ~/.config/mosaic/tools/lease-broker/recover-context.py",
|
|
||||||
"timeout": 3
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"matcher": "Write|Edit|MultiEdit",
|
"matcher": "Write|Edit|MultiEdit",
|
||||||
"hooks": [
|
"hooks": [
|
||||||
@@ -91,11 +38,6 @@
|
|||||||
"Stop": [
|
"Stop": [
|
||||||
{
|
{
|
||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
|
||||||
"type": "command",
|
|
||||||
"command": "python3 ~/.config/mosaic/tools/lease-broker/receipt-observer-client.py --runtime claude --latest-entry; observer_status=$?; python3 ~/.config/mosaic/tools/lease-broker/promote-complete.py; exit $observer_status",
|
|
||||||
"timeout": 15
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "~/.config/mosaic/tools/qa/reflect-stop-hook.sh",
|
"command": "~/.config/mosaic/tools/qa/reflect-stop-hook.sh",
|
||||||
@@ -315,5 +257,11 @@
|
|||||||
"cpan",
|
"cpan",
|
||||||
"nohup"
|
"nohup"
|
||||||
],
|
],
|
||||||
"enableAllMcpTools": true
|
"enableAllMcpTools": true,
|
||||||
|
"mcpServers": {
|
||||||
|
"sequential-thinking": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": ["-y", "@modelcontextprotocol/server-sequential-thinking"]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
import { readFileSync } from 'node:fs';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
type Json = null | boolean | number | string | Json[] | { [key: string]: Json };
|
||||||
|
type JsonObject = { [key: string]: Json };
|
||||||
|
|
||||||
|
const frameworkRoot = fileURLToPath(new URL('../../framework/', import.meta.url));
|
||||||
|
const basePath = `${frameworkRoot}runtime/claude/settings.json`;
|
||||||
|
const overlayPath = `${frameworkRoot}runtime/claude/lease-overlay.json`;
|
||||||
|
const gatedFixturePath = fileURLToPath(
|
||||||
|
new URL('./fixtures/claude-settings.gated.pre-split.json', import.meta.url),
|
||||||
|
);
|
||||||
|
|
||||||
|
function readJson(path: string): JsonObject {
|
||||||
|
return JSON.parse(readFileSync(path, 'utf8')) as JsonObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isObject(value: unknown): value is JsonObject {
|
||||||
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This fixture contract composes hook event arrays additively. It is deliberately
|
||||||
|
// limited to verifying that the split is lossless; production launch merge
|
||||||
|
// semantics remain owned by W-F1.
|
||||||
|
function deepMerge(base: Json, overlay: Json): Json {
|
||||||
|
if (Array.isArray(base) && Array.isArray(overlay)) {
|
||||||
|
const merged = [...base];
|
||||||
|
for (const entry of overlay) {
|
||||||
|
if (!isObject(entry) || !Array.isArray(entry['hooks'])) {
|
||||||
|
merged.push(entry);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const matchingIndex = merged.findIndex(
|
||||||
|
(candidate) =>
|
||||||
|
isObject(candidate) &&
|
||||||
|
Array.isArray(candidate['hooks']) &&
|
||||||
|
candidate['matcher'] === entry['matcher'],
|
||||||
|
);
|
||||||
|
if (matchingIndex === -1) merged.push(entry);
|
||||||
|
else merged[matchingIndex] = deepMerge(merged[matchingIndex]!, entry);
|
||||||
|
}
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
|
if (isObject(base) && isObject(overlay)) {
|
||||||
|
const merged: JsonObject = { ...base };
|
||||||
|
for (const [key, value] of Object.entries(overlay)) {
|
||||||
|
merged[key] = key in merged ? deepMerge(merged[key]!, value) : value;
|
||||||
|
}
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
|
return overlay;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalize(value: Json): Json {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value
|
||||||
|
.map(normalize)
|
||||||
|
.sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
|
||||||
|
}
|
||||||
|
if (isObject(value)) {
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(value)
|
||||||
|
.sort(([left], [right]) => left.localeCompare(right))
|
||||||
|
.map(([key, nested]) => [key, normalize(nested)]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hookCommands(settings: JsonObject): string[] {
|
||||||
|
const hooks = settings['hooks'];
|
||||||
|
if (!isObject(hooks)) return [];
|
||||||
|
|
||||||
|
return Object.values(hooks).flatMap((event) => {
|
||||||
|
if (!Array.isArray(event)) return [];
|
||||||
|
return event.flatMap((entry) => {
|
||||||
|
if (!isObject(entry) || !Array.isArray(entry['hooks'])) return [];
|
||||||
|
return entry['hooks'].flatMap((hook) =>
|
||||||
|
isObject(hook) && typeof hook['command'] === 'string' ? [hook['command']] : [],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const sequentialThinking: JsonObject = {
|
||||||
|
command: 'npx',
|
||||||
|
args: ['-y', '@modelcontextprotocol/server-sequential-thinking'],
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('canonical Claude base and lease-promotion overlay', () => {
|
||||||
|
it('keeps every lease command out of the ungated base', () => {
|
||||||
|
const base = readJson(basePath);
|
||||||
|
const commands = hookCommands(base);
|
||||||
|
|
||||||
|
for (const marker of ['mutator-gate', 'receipt-observer', 'promote-', 'revoke-lease']) {
|
||||||
|
expect(
|
||||||
|
commands.some((command) => command.includes(marker)),
|
||||||
|
marker,
|
||||||
|
).toBe(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reconstructs the pre-split gated hooks while retaining the canonical MCP correction', () => {
|
||||||
|
const base = readJson(basePath);
|
||||||
|
const overlay = readJson(overlayPath);
|
||||||
|
const preSplit = readJson(gatedFixturePath);
|
||||||
|
const expected: JsonObject = {
|
||||||
|
...preSplit,
|
||||||
|
mcpServers: { 'sequential-thinking': sequentialThinking },
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(normalize(deepMerge(base, overlay))).toEqual(normalize(expected));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ships sequential-thinking in the base', () => {
|
||||||
|
const base = readJson(basePath);
|
||||||
|
expect(base['mcpServers']).toEqual({ 'sequential-thinking': sequentialThinking });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('limits the overlay to lease hook entries', () => {
|
||||||
|
const overlay = readJson(overlayPath);
|
||||||
|
expect(Object.keys(overlay)).toEqual(['hooks']);
|
||||||
|
|
||||||
|
const commands = hookCommands(overlay);
|
||||||
|
expect(commands).toHaveLength(6);
|
||||||
|
for (const command of commands) {
|
||||||
|
expect(command).toMatch(/mutator-gate|receipt-observer|promote-|revoke-lease/);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,319 @@
|
|||||||
|
{
|
||||||
|
"model": "opus",
|
||||||
|
"hooks": {
|
||||||
|
"PreCompact": [
|
||||||
|
{
|
||||||
|
"matcher": ".*",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "python3 \"$HOME/.config/mosaic/tools/lease-broker/revoke-lease.py\" --runtime claude --reason pre-compact"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"SessionStart": [
|
||||||
|
{
|
||||||
|
"matcher": "compact",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "python3 \"$HOME/.config/mosaic/tools/lease-broker/revoke-lease.py\" --runtime claude --reason session-start-compact"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": "resume|clear",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "python3 \"$HOME/.config/mosaic/tools/lease-broker/revoke-lease.py\" --runtime claude --reason session-start-rollover --bump-generation"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"UserPromptSubmit": [
|
||||||
|
{
|
||||||
|
"matcher": "^/mosaic-promote$",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "python3 ~/.config/mosaic/tools/lease-broker/promote-begin.py",
|
||||||
|
"timeout": 15
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"PreToolUse": [
|
||||||
|
{
|
||||||
|
"matcher": ".*",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "python3 ~/.config/mosaic/tools/lease-broker/mutator-gate.py --runtime claude --recovery-command ~/.config/mosaic/tools/lease-broker/recover-context.py",
|
||||||
|
"timeout": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": "Write|Edit|MultiEdit",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "~/.config/mosaic/tools/qa/prevent-memory-write.sh",
|
||||||
|
"timeout": 10
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"PostToolUse": [
|
||||||
|
{
|
||||||
|
"matcher": "Edit|MultiEdit|Write",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "~/.config/mosaic/tools/qa/qa-hook-stdin.sh",
|
||||||
|
"timeout": 60
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": "Edit|MultiEdit|Write",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "~/.config/mosaic/tools/qa/typecheck-hook.sh",
|
||||||
|
"timeout": 30
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Stop": [
|
||||||
|
{
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "python3 ~/.config/mosaic/tools/lease-broker/receipt-observer-client.py --runtime claude --latest-entry; observer_status=$?; python3 ~/.config/mosaic/tools/lease-broker/promote-complete.py; exit $observer_status",
|
||||||
|
"timeout": 15
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "~/.config/mosaic/tools/qa/reflect-stop-hook.sh",
|
||||||
|
"timeout": 15
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"enabledPlugins": {
|
||||||
|
"frontend-design@claude-plugins-official": true,
|
||||||
|
"feature-dev@claude-plugins-official": true,
|
||||||
|
"code-review@claude-plugins-official": true,
|
||||||
|
"pr-review-toolkit@claude-plugins-official": true
|
||||||
|
},
|
||||||
|
"skipDangerousModePermissionPrompt": true,
|
||||||
|
"allowedCommands": [
|
||||||
|
"npm",
|
||||||
|
"npm install",
|
||||||
|
"npm run",
|
||||||
|
"npm test",
|
||||||
|
"npm build",
|
||||||
|
"npm start",
|
||||||
|
"npm run dev",
|
||||||
|
"npm run build",
|
||||||
|
"npm run lint",
|
||||||
|
"npm run typecheck",
|
||||||
|
"npm run test:ci",
|
||||||
|
"npm run test:e2e",
|
||||||
|
"npm run test:unit",
|
||||||
|
"npm run test:integration",
|
||||||
|
"npm run test:cov",
|
||||||
|
"npm run test:security",
|
||||||
|
"npm run security:scan",
|
||||||
|
"npm run security:audit",
|
||||||
|
"npm run performance:benchmark",
|
||||||
|
"npm run build:dev",
|
||||||
|
"npm run build:prod",
|
||||||
|
"npm run test",
|
||||||
|
"npm run test:watch",
|
||||||
|
"npm run migrate",
|
||||||
|
"npm run migrate:rollback",
|
||||||
|
"npm run db:seed",
|
||||||
|
"npm run db:reset",
|
||||||
|
"node",
|
||||||
|
"yarn",
|
||||||
|
"pnpm",
|
||||||
|
"npx",
|
||||||
|
"npx tsc",
|
||||||
|
"npx eslint",
|
||||||
|
"npx prettier",
|
||||||
|
"npx jest",
|
||||||
|
"npx vitest",
|
||||||
|
"git",
|
||||||
|
"git add",
|
||||||
|
"git commit",
|
||||||
|
"git push",
|
||||||
|
"git pull",
|
||||||
|
"git status",
|
||||||
|
"git diff",
|
||||||
|
"git log",
|
||||||
|
"git branch",
|
||||||
|
"git checkout",
|
||||||
|
"git merge",
|
||||||
|
"git init",
|
||||||
|
"git remote",
|
||||||
|
"git fetch",
|
||||||
|
"git reset",
|
||||||
|
"git rebase",
|
||||||
|
"git stash",
|
||||||
|
"git tag",
|
||||||
|
"git show",
|
||||||
|
"git config",
|
||||||
|
"gh",
|
||||||
|
"gh issue",
|
||||||
|
"gh pr",
|
||||||
|
"gh repo",
|
||||||
|
"gh api",
|
||||||
|
"docker",
|
||||||
|
"docker build",
|
||||||
|
"docker run",
|
||||||
|
"docker ps",
|
||||||
|
"docker logs",
|
||||||
|
"docker exec",
|
||||||
|
"docker stop",
|
||||||
|
"docker start",
|
||||||
|
"docker pull",
|
||||||
|
"docker push",
|
||||||
|
"docker-compose",
|
||||||
|
"docker-compose up",
|
||||||
|
"docker-compose down",
|
||||||
|
"docker-compose build",
|
||||||
|
"docker-compose logs",
|
||||||
|
"docker-compose ps",
|
||||||
|
"docker-compose exec",
|
||||||
|
"kubectl",
|
||||||
|
"kubectl get",
|
||||||
|
"kubectl describe",
|
||||||
|
"kubectl logs",
|
||||||
|
"kubectl apply",
|
||||||
|
"kubectl delete",
|
||||||
|
"kubectl port-forward",
|
||||||
|
"mkdir",
|
||||||
|
"touch",
|
||||||
|
"chmod",
|
||||||
|
"chown",
|
||||||
|
"ls",
|
||||||
|
"cd",
|
||||||
|
"pwd",
|
||||||
|
"cp",
|
||||||
|
"mv",
|
||||||
|
"rm",
|
||||||
|
"cat",
|
||||||
|
"echo",
|
||||||
|
"head",
|
||||||
|
"tail",
|
||||||
|
"grep",
|
||||||
|
"grep -E",
|
||||||
|
"grep -r",
|
||||||
|
"find",
|
||||||
|
"find -name",
|
||||||
|
"find -type",
|
||||||
|
"find -path",
|
||||||
|
"find -exec",
|
||||||
|
"find . -type f",
|
||||||
|
"find . -type d",
|
||||||
|
"wc",
|
||||||
|
"sort",
|
||||||
|
"uniq",
|
||||||
|
"curl",
|
||||||
|
"wget",
|
||||||
|
"ping",
|
||||||
|
"netstat",
|
||||||
|
"ss",
|
||||||
|
"lsof",
|
||||||
|
"psql",
|
||||||
|
"pg_dump",
|
||||||
|
"pg_restore",
|
||||||
|
"sqlite3",
|
||||||
|
"jest",
|
||||||
|
"vitest",
|
||||||
|
"playwright",
|
||||||
|
"cypress",
|
||||||
|
"artillery",
|
||||||
|
"lighthouse",
|
||||||
|
"tsc",
|
||||||
|
"eslint",
|
||||||
|
"prettier",
|
||||||
|
"snyk",
|
||||||
|
"semgrep",
|
||||||
|
"tar",
|
||||||
|
"gzip",
|
||||||
|
"unzip",
|
||||||
|
"zip",
|
||||||
|
"which",
|
||||||
|
"whoami",
|
||||||
|
"id",
|
||||||
|
"env",
|
||||||
|
"export",
|
||||||
|
"source",
|
||||||
|
"sleep",
|
||||||
|
"date",
|
||||||
|
"uptime",
|
||||||
|
"df",
|
||||||
|
"du",
|
||||||
|
"free",
|
||||||
|
"top",
|
||||||
|
"htop",
|
||||||
|
"ps",
|
||||||
|
"tree",
|
||||||
|
"jq",
|
||||||
|
"sed",
|
||||||
|
"awk",
|
||||||
|
"xargs",
|
||||||
|
"tee",
|
||||||
|
"test",
|
||||||
|
"true",
|
||||||
|
"false",
|
||||||
|
"basename",
|
||||||
|
"dirname",
|
||||||
|
"realpath",
|
||||||
|
"readlink",
|
||||||
|
"stat",
|
||||||
|
"file",
|
||||||
|
"make",
|
||||||
|
"cmake",
|
||||||
|
"gcc",
|
||||||
|
"g++",
|
||||||
|
"clang",
|
||||||
|
"python",
|
||||||
|
"python3",
|
||||||
|
"pip",
|
||||||
|
"pip3",
|
||||||
|
"pip install",
|
||||||
|
"poetry",
|
||||||
|
"pipenv",
|
||||||
|
"go",
|
||||||
|
"go build",
|
||||||
|
"go test",
|
||||||
|
"go run",
|
||||||
|
"go mod",
|
||||||
|
"cargo",
|
||||||
|
"rustc",
|
||||||
|
"ruby",
|
||||||
|
"gem",
|
||||||
|
"bundle",
|
||||||
|
"rake",
|
||||||
|
"java",
|
||||||
|
"javac",
|
||||||
|
"mvn",
|
||||||
|
"gradle",
|
||||||
|
"dotnet",
|
||||||
|
"msbuild",
|
||||||
|
"php",
|
||||||
|
"composer",
|
||||||
|
"perl",
|
||||||
|
"cpan",
|
||||||
|
"nohup"
|
||||||
|
],
|
||||||
|
"enableAllMcpTools": true
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user