@everystack/mcp 0.3.1 → 0.3.3
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/dist/gates/detectors/embedded-data-bundle.d.ts +13 -0
- package/dist/gates/detectors/embedded-data-bundle.d.ts.map +1 -0
- package/dist/gates/detectors/embedded-data-bundle.js +56 -0
- package/dist/gates/detectors/hand-written-migration.d.ts +24 -0
- package/dist/gates/detectors/hand-written-migration.d.ts.map +1 -0
- package/dist/gates/detectors/hand-written-migration.js +54 -0
- package/dist/gates/detectors/secret-in-public-env.d.ts +12 -0
- package/dist/gates/detectors/secret-in-public-env.d.ts.map +1 -0
- package/dist/gates/detectors/secret-in-public-env.js +38 -0
- package/dist/gates/engine.d.ts +28 -0
- package/dist/gates/engine.d.ts.map +1 -0
- package/dist/gates/engine.js +74 -0
- package/dist/gates/registry.d.ts +14 -0
- package/dist/gates/registry.d.ts.map +1 -0
- package/dist/gates/registry.js +21 -0
- package/dist/gates/telemetry.d.ts +47 -0
- package/dist/gates/telemetry.d.ts.map +1 -0
- package/dist/gates/telemetry.js +121 -0
- package/dist/gates/types.d.ts +67 -0
- package/dist/gates/types.d.ts.map +1 -0
- package/dist/gates/types.js +14 -0
- package/dist/governance/cli.d.ts +57 -0
- package/dist/governance/cli.d.ts.map +1 -0
- package/dist/governance/cli.js +169 -0
- package/dist/governance/grounding.d.ts +78 -0
- package/dist/governance/grounding.d.ts.map +1 -0
- package/dist/governance/grounding.js +299 -0
- package/dist/index.cjs +11 -9
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +94 -0
- package/dist/project-claude-md.md +33 -11
- package/dist/prompts/add-feature.d.ts +3 -0
- package/dist/prompts/add-feature.d.ts.map +1 -0
- package/dist/prompts/add-feature.js +154 -0
- package/dist/prompts/claude-md.d.ts +12 -0
- package/dist/prompts/claude-md.d.ts.map +1 -0
- package/dist/prompts/claude-md.js +87 -0
- package/dist/prompts/debug.d.ts +3 -0
- package/dist/prompts/debug.d.ts.map +1 -0
- package/dist/prompts/debug.js +129 -0
- package/dist/prompts/deploy.d.ts +3 -0
- package/dist/prompts/deploy.d.ts.map +1 -0
- package/dist/prompts/deploy.js +118 -0
- package/dist/prompts/design-schema.d.ts +3 -0
- package/dist/prompts/design-schema.d.ts.map +1 -0
- package/dist/prompts/design-schema.js +97 -0
- package/dist/prompts/governance-setup.d.ts +9 -0
- package/dist/prompts/governance-setup.d.ts.map +1 -0
- package/dist/prompts/governance-setup.js +76 -0
- package/dist/prompts/index.d.ts +3 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +20 -0
- package/dist/prompts/new-app.d.ts +3 -0
- package/dist/prompts/new-app.d.ts.map +1 -0
- package/dist/prompts/new-app.js +203 -0
- package/dist/prompts/runbook.d.ts +12 -0
- package/dist/prompts/runbook.d.ts.map +1 -0
- package/dist/prompts/runbook.js +70 -0
- package/dist/prompts/secure.d.ts +3 -0
- package/dist/prompts/secure.d.ts.map +1 -0
- package/dist/prompts/secure.js +219 -0
- package/dist/resources/index.d.ts +12 -0
- package/dist/resources/index.d.ts.map +1 -0
- package/dist/resources/index.js +182 -0
- package/dist/tools/check-environment.d.ts +25 -0
- package/dist/tools/check-environment.d.ts.map +1 -0
- package/dist/tools/check-environment.js +281 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +18 -0
- package/dist/tools/project-status.d.ts +28 -0
- package/dist/tools/project-status.d.ts.map +1 -0
- package/dist/tools/project-status.js +138 -0
- package/dist/tools/project-validate.d.ts +19 -0
- package/dist/tools/project-validate.d.ts.map +1 -0
- package/dist/tools/project-validate.js +323 -0
- package/dist/tools/schema-analyze.d.ts +46 -0
- package/dist/tools/schema-analyze.d.ts.map +1 -0
- package/dist/tools/schema-analyze.js +336 -0
- package/package.json +3 -3
- package/src/gates/detectors/hand-written-migration.ts +23 -10
- package/src/index.ts +1 -1
- package/src/prompts/claude-md.ts +4 -2
- package/src/resources/project-claude-md.md +33 -11
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* governance/cli — the hook-shim entry for `everystack-mcp <subcommand>`.
|
|
3
|
+
*
|
|
4
|
+
* Claude Code hooks pipe their JSON event on stdin to one of these subcommands;
|
|
5
|
+
* the hook config itself carries no logic (the local-agent grounding-gate shape).
|
|
6
|
+
* The handlers are exported so the same logic is unit-testable and so the operator
|
|
7
|
+
* CLI can self-gate on it later — no drift between what the hook blocks and what
|
|
8
|
+
* the gate-to-prod refuses.
|
|
9
|
+
*
|
|
10
|
+
* This module stays LIGHT to import: it pulls only grounding + the pure engine +
|
|
11
|
+
* telemetry. The one heavy dependency (the canonical secret scanner) is lazy-loaded
|
|
12
|
+
* inside its detector behind a cheap pre-filter, so the common tool call is fast.
|
|
13
|
+
*
|
|
14
|
+
* Fail-OPEN on any internal error or unknown input — a governance bug must never
|
|
15
|
+
* brick tool use globally. Only the explicit "ungrounded" / "off-script deny"
|
|
16
|
+
* conditions fail closed.
|
|
17
|
+
*/
|
|
18
|
+
export declare const GOVERNANCE_COMMANDS: readonly ["context", "gate", "mark", "validate", "report"];
|
|
19
|
+
export type GovernanceCommand = (typeof GOVERNANCE_COMMANDS)[number];
|
|
20
|
+
interface HookInput {
|
|
21
|
+
session_id?: string;
|
|
22
|
+
cwd?: string;
|
|
23
|
+
source?: string;
|
|
24
|
+
tool_name?: string;
|
|
25
|
+
tool_input?: {
|
|
26
|
+
file_path?: string;
|
|
27
|
+
content?: string;
|
|
28
|
+
new_string?: string;
|
|
29
|
+
old_string?: string;
|
|
30
|
+
command?: string;
|
|
31
|
+
offset?: number;
|
|
32
|
+
limit?: number;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** SessionStart: the grounding contract text. */
|
|
36
|
+
export declare function handleContext(input: HookInput): string;
|
|
37
|
+
/**
|
|
38
|
+
* PreToolUse: grounding first (the contract must be read), then cheat gates. Returns
|
|
39
|
+
* a deny payload object to emit, or null to allow. Telemetry is recorded for any
|
|
40
|
+
* cheat findings at this boundary (the authoritative "attempt" event).
|
|
41
|
+
*/
|
|
42
|
+
export declare function handleGate(input: HookInput): Promise<unknown | null>;
|
|
43
|
+
/**
|
|
44
|
+
* PostToolUse(Write|Edit): re-scan after the write and surface anything (a backstop
|
|
45
|
+
* for what a pre-write check couldn't see). Non-blocking. Does NOT re-record — the
|
|
46
|
+
* attempt was already counted at the gate — it only surfaces guidance.
|
|
47
|
+
*/
|
|
48
|
+
export declare function handleValidate(input: HookInput): Promise<unknown | null>;
|
|
49
|
+
/** PostToolUse(Read): mark a required-file read toward grounding. */
|
|
50
|
+
export declare function handleMark(input: HookInput): void;
|
|
51
|
+
/**
|
|
52
|
+
* Dispatch a governance subcommand from hook JSON on stdin. `report` reads
|
|
53
|
+
* telemetry instead of stdin and prints a summary. Always fails open.
|
|
54
|
+
*/
|
|
55
|
+
export declare function runGovernanceCli(argv: string[]): Promise<void>;
|
|
56
|
+
export {};
|
|
57
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/governance/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAQH,eAAO,MAAM,mBAAmB,4DAMtB,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAErE,UAAU,SAAS;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE;QACX,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAuCD,iDAAiD;AACjD,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAItD;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAe1E;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAM9E;AAED,qEAAqE;AACrE,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAOjD;AAoBD;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA+BpE"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* governance/cli — the hook-shim entry for `everystack-mcp <subcommand>`.
|
|
3
|
+
*
|
|
4
|
+
* Claude Code hooks pipe their JSON event on stdin to one of these subcommands;
|
|
5
|
+
* the hook config itself carries no logic (the local-agent grounding-gate shape).
|
|
6
|
+
* The handlers are exported so the same logic is unit-testable and so the operator
|
|
7
|
+
* CLI can self-gate on it later — no drift between what the hook blocks and what
|
|
8
|
+
* the gate-to-prod refuses.
|
|
9
|
+
*
|
|
10
|
+
* This module stays LIGHT to import: it pulls only grounding + the pure engine +
|
|
11
|
+
* telemetry. The one heavy dependency (the canonical secret scanner) is lazy-loaded
|
|
12
|
+
* inside its detector behind a cheap pre-filter, so the common tool call is fast.
|
|
13
|
+
*
|
|
14
|
+
* Fail-OPEN on any internal error or unknown input — a governance bug must never
|
|
15
|
+
* brick tool use globally. Only the explicit "ungrounded" / "off-script deny"
|
|
16
|
+
* conditions fail closed.
|
|
17
|
+
*/
|
|
18
|
+
import { context, gate, mark, denyReason } from './grounding.js';
|
|
19
|
+
import { evaluateGates, decide, findingsReason } from '../gates/engine.js';
|
|
20
|
+
import { gatesFor } from '../gates/registry.js';
|
|
21
|
+
import { recordFindings, summarize, renderSummary } from '../gates/telemetry.js';
|
|
22
|
+
export const GOVERNANCE_COMMANDS = [
|
|
23
|
+
'context', // SessionStart: inject the grounding contract
|
|
24
|
+
'gate', // PreToolUse: allow/deny (grounding + cheat gates)
|
|
25
|
+
'mark', // PostToolUse(Read): record a required-file read
|
|
26
|
+
'validate', // PostToolUse(Write|Edit): surface any cheat the write landed
|
|
27
|
+
'report', // summarize the per-session telemetry (the sensor)
|
|
28
|
+
];
|
|
29
|
+
/** Deny payload for PreToolUse. */
|
|
30
|
+
function denyPayload(reason) {
|
|
31
|
+
return {
|
|
32
|
+
hookSpecificOutput: {
|
|
33
|
+
hookEventName: 'PreToolUse',
|
|
34
|
+
permissionDecision: 'deny',
|
|
35
|
+
permissionDecisionReason: reason,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** Non-blocking advisory payload for PostToolUse. */
|
|
40
|
+
function contextPayload(text) {
|
|
41
|
+
return {
|
|
42
|
+
hookSpecificOutput: {
|
|
43
|
+
hookEventName: 'PostToolUse',
|
|
44
|
+
additionalContext: text,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function toToolContext(input) {
|
|
49
|
+
const ti = input.tool_input ?? {};
|
|
50
|
+
return {
|
|
51
|
+
tool: input.tool_name ?? '',
|
|
52
|
+
filePath: ti.file_path,
|
|
53
|
+
// Write carries `content`; Edit carries `new_string` (what is about to land).
|
|
54
|
+
content: ti.content ?? ti.new_string,
|
|
55
|
+
command: ti.command,
|
|
56
|
+
cwd: input.cwd ?? process.cwd(),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Handlers (exported for tests; pure-ish — telemetry is the only side effect)
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
/** SessionStart: the grounding contract text. */
|
|
63
|
+
export function handleContext(input) {
|
|
64
|
+
const sid = input.session_id ?? 'nosession';
|
|
65
|
+
const cwd = input.cwd ?? process.cwd();
|
|
66
|
+
return context(sid, cwd, input.source ?? 'startup');
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* PreToolUse: grounding first (the contract must be read), then cheat gates. Returns
|
|
70
|
+
* a deny payload object to emit, or null to allow. Telemetry is recorded for any
|
|
71
|
+
* cheat findings at this boundary (the authoritative "attempt" event).
|
|
72
|
+
*/
|
|
73
|
+
export async function handleGate(input) {
|
|
74
|
+
const sid = input.session_id ?? 'nosession';
|
|
75
|
+
const cwd = input.cwd ?? process.cwd();
|
|
76
|
+
const tool = input.tool_name ?? '';
|
|
77
|
+
// 1) Grounding: until the contract is read, every non-Read tool is denied.
|
|
78
|
+
const g = gate(sid, cwd, tool);
|
|
79
|
+
if (g.decision === 'deny')
|
|
80
|
+
return denyPayload(denyReason(g.unread));
|
|
81
|
+
// 2) Cheat gates.
|
|
82
|
+
const ctx = toToolContext(input);
|
|
83
|
+
const findings = await evaluateGates(ctx, gatesFor(cwd));
|
|
84
|
+
if (findings.length)
|
|
85
|
+
recordFindings(sid, { cwd, tool }, findings);
|
|
86
|
+
if (decide(findings).decision === 'deny')
|
|
87
|
+
return denyPayload(findingsReason(findings));
|
|
88
|
+
return null; // allow (any warn-tier findings are recorded, not blocked)
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* PostToolUse(Write|Edit): re-scan after the write and surface anything (a backstop
|
|
92
|
+
* for what a pre-write check couldn't see). Non-blocking. Does NOT re-record — the
|
|
93
|
+
* attempt was already counted at the gate — it only surfaces guidance.
|
|
94
|
+
*/
|
|
95
|
+
export async function handleValidate(input) {
|
|
96
|
+
const cwd = input.cwd ?? process.cwd();
|
|
97
|
+
const ctx = toToolContext(input);
|
|
98
|
+
const findings = await evaluateGates(ctx, gatesFor(cwd));
|
|
99
|
+
if (!findings.length)
|
|
100
|
+
return null;
|
|
101
|
+
return contextPayload(findingsReason(findings));
|
|
102
|
+
}
|
|
103
|
+
/** PostToolUse(Read): mark a required-file read toward grounding. */
|
|
104
|
+
export function handleMark(input) {
|
|
105
|
+
const sid = input.session_id ?? 'nosession';
|
|
106
|
+
const cwd = input.cwd ?? process.cwd();
|
|
107
|
+
const ti = input.tool_input ?? {};
|
|
108
|
+
if (!ti.file_path)
|
|
109
|
+
return;
|
|
110
|
+
const partial = ti.offset !== undefined || ti.limit !== undefined;
|
|
111
|
+
mark(sid, cwd, ti.file_path, partial);
|
|
112
|
+
}
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// stdin / dispatch
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
function readStdin() {
|
|
117
|
+
return new Promise((res) => {
|
|
118
|
+
let data = '';
|
|
119
|
+
if (process.stdin.isTTY) {
|
|
120
|
+
res('');
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
process.stdin.setEncoding('utf-8');
|
|
124
|
+
process.stdin.on('data', (c) => (data += c));
|
|
125
|
+
process.stdin.on('end', () => res(data));
|
|
126
|
+
process.stdin.on('error', () => res(data));
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Dispatch a governance subcommand from hook JSON on stdin. `report` reads
|
|
131
|
+
* telemetry instead of stdin and prints a summary. Always fails open.
|
|
132
|
+
*/
|
|
133
|
+
export async function runGovernanceCli(argv) {
|
|
134
|
+
const mode = argv[0];
|
|
135
|
+
if (!mode || !GOVERNANCE_COMMANDS.includes(mode))
|
|
136
|
+
return;
|
|
137
|
+
try {
|
|
138
|
+
if (mode === 'report') {
|
|
139
|
+
process.stdout.write(renderSummary(summarize(argv[1])) + '\n');
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
let input = {};
|
|
143
|
+
try {
|
|
144
|
+
input = JSON.parse((await readStdin()) || '{}');
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return; // fail open on malformed input
|
|
148
|
+
}
|
|
149
|
+
if (mode === 'context') {
|
|
150
|
+
process.stdout.write(handleContext(input) + '\n');
|
|
151
|
+
}
|
|
152
|
+
else if (mode === 'gate') {
|
|
153
|
+
const payload = await handleGate(input);
|
|
154
|
+
if (payload)
|
|
155
|
+
process.stdout.write(JSON.stringify(payload));
|
|
156
|
+
}
|
|
157
|
+
else if (mode === 'mark') {
|
|
158
|
+
handleMark(input);
|
|
159
|
+
}
|
|
160
|
+
else if (mode === 'validate') {
|
|
161
|
+
const payload = await handleValidate(input);
|
|
162
|
+
if (payload)
|
|
163
|
+
process.stdout.write(JSON.stringify(payload));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
/* fail open: stay silent, exit 0 */
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* grounding — enforce reading the project contract before any non-Read tool.
|
|
3
|
+
*
|
|
4
|
+
* Ported from local-agent's grounding gate (the proven implementation; see
|
|
5
|
+
* ~/Sites/local-agent/docs/grounding-gate.md). The contract: the CLAUDE.md files
|
|
6
|
+
* (global + project) and their declared `REQUIRED-READS:` companions define how
|
|
7
|
+
* the build is meant to go — for everystack that includes the Model/Module-first
|
|
8
|
+
* resources a project names. A model does not get to glide past them as scenery.
|
|
9
|
+
* Until the required files are actually Read this session, every tool EXCEPT Read
|
|
10
|
+
* is denied.
|
|
11
|
+
*
|
|
12
|
+
* everystack adaptation: state lives under ~/.everystack/governance/grounding so
|
|
13
|
+
* it never collides with local-agent's own gate (~/.agent/grounding). The pure
|
|
14
|
+
* functions (requiredFiles / gate / mark / context / groundStatus) are exported
|
|
15
|
+
* for governance/cli.ts to drive from hook JSON; the CLI shim lives there.
|
|
16
|
+
*
|
|
17
|
+
* Honest ceiling: this forces the *read* (deliberate, in-context, links followed).
|
|
18
|
+
* It cannot force comprehension. It kills the one specific failure — skipping the
|
|
19
|
+
* files — dead. Nothing more is claimed.
|
|
20
|
+
*
|
|
21
|
+
* Design decisions (from the bash → TS rewrite that proved this out):
|
|
22
|
+
* - Required set is DECLARED, not inferred: the global and project CLAUDE.md are
|
|
23
|
+
* always required, and each names its mandatory companions with an explicit
|
|
24
|
+
* `REQUIRED-READS:` line. (Scraping every Markdown link over-captures incidental
|
|
25
|
+
* prose links, so the contract declares its own reads instead.)
|
|
26
|
+
* - Paths are canonicalised with realpath on BOTH sides, so a symlinked root
|
|
27
|
+
* (/tmp -> /private/tmp) can't silently fail to match.
|
|
28
|
+
* - Partial reads (Read with offset/limit) do NOT count as grounding.
|
|
29
|
+
* - State is one JSON file per session; resets only on startup/clear, never on
|
|
30
|
+
* resume/compact — and `context` re-injects the contract on compact so it
|
|
31
|
+
* re-enters context after summarisation.
|
|
32
|
+
* - Fail-OPEN on any internal error or empty required set (never brick tool use);
|
|
33
|
+
* fail-CLOSED only on the explicit "not yet grounded" condition.
|
|
34
|
+
*/
|
|
35
|
+
export interface RequiredSet {
|
|
36
|
+
/** Canonical paths of required files that exist (the gate can be satisfied by these). */
|
|
37
|
+
present: string[];
|
|
38
|
+
/** Required paths referenced but missing on disk (logged/warned, never block on them). */
|
|
39
|
+
missing: string[];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Resolve the required-reads set for a working directory:
|
|
43
|
+
* global CLAUDE.md + its REQUIRED-READS entries
|
|
44
|
+
* + nearest project CLAUDE.md + its REQUIRED-READS entries.
|
|
45
|
+
* Returns canonical, de-duplicated paths partitioned into present / missing.
|
|
46
|
+
*/
|
|
47
|
+
export declare function requiredFiles(cwd: string): RequiredSet;
|
|
48
|
+
export interface GateResult {
|
|
49
|
+
decision: 'allow' | 'deny';
|
|
50
|
+
unread: string[];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* PreToolUse decision. Always allows `Read`. Allows everything once the session
|
|
54
|
+
* is grounded. Fails open if no required files resolve. Otherwise denies any
|
|
55
|
+
* non-Read tool while required files remain unread.
|
|
56
|
+
*/
|
|
57
|
+
export declare function gate(sessionId: string, cwd: string, toolName: string): GateResult;
|
|
58
|
+
export interface MarkResult {
|
|
59
|
+
grounded: boolean;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* PostToolUse(Read) record. A full read of a required file marks it grounded.
|
|
63
|
+
* Partial reads (offset/limit present) are ignored — a one-line peek is not
|
|
64
|
+
* grounding. Reads of non-required files are ignored.
|
|
65
|
+
*/
|
|
66
|
+
export declare function mark(sessionId: string, cwd: string, filePath: string, partial?: boolean): MarkResult;
|
|
67
|
+
/**
|
|
68
|
+
* SessionStart payload: the required-file list injected into context. Resets
|
|
69
|
+
* per-session state on `startup`/`clear` (fresh grounding required); leaves it
|
|
70
|
+
* intact on `resume`/`compact` but still re-injects the contract so it survives
|
|
71
|
+
* summarisation. Also runs 7-day housekeeping.
|
|
72
|
+
*/
|
|
73
|
+
export declare function context(sessionId: string, cwd: string, source: string): string;
|
|
74
|
+
/** Human-readable grounding status for a session/cwd (the MCP introspection path). */
|
|
75
|
+
export declare function groundStatus(cwd: string, sessionId?: string): string;
|
|
76
|
+
/** The PreToolUse deny reason shown when grounding is incomplete. */
|
|
77
|
+
export declare function denyReason(unread: string[]): string;
|
|
78
|
+
//# sourceMappingURL=grounding.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grounding.d.ts","sourceRoot":"","sources":["../../src/governance/grounding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AA+EH,MAAM,WAAW,WAAW;IAC1B,yFAAyF;IACzF,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,0FAA0F;IAC1F,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAkCtD;AAqED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,OAAO,GAAG,MAAM,CAAC;IAC3B,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,CAgBjF;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,UAAQ,GAAG,UAAU,CAYlG;AAED;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAuB9E;AAED,sFAAsF;AACtF,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAqBpE;AAED,qEAAqE;AACrE,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAQnD"}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* grounding — enforce reading the project contract before any non-Read tool.
|
|
3
|
+
*
|
|
4
|
+
* Ported from local-agent's grounding gate (the proven implementation; see
|
|
5
|
+
* ~/Sites/local-agent/docs/grounding-gate.md). The contract: the CLAUDE.md files
|
|
6
|
+
* (global + project) and their declared `REQUIRED-READS:` companions define how
|
|
7
|
+
* the build is meant to go — for everystack that includes the Model/Module-first
|
|
8
|
+
* resources a project names. A model does not get to glide past them as scenery.
|
|
9
|
+
* Until the required files are actually Read this session, every tool EXCEPT Read
|
|
10
|
+
* is denied.
|
|
11
|
+
*
|
|
12
|
+
* everystack adaptation: state lives under ~/.everystack/governance/grounding so
|
|
13
|
+
* it never collides with local-agent's own gate (~/.agent/grounding). The pure
|
|
14
|
+
* functions (requiredFiles / gate / mark / context / groundStatus) are exported
|
|
15
|
+
* for governance/cli.ts to drive from hook JSON; the CLI shim lives there.
|
|
16
|
+
*
|
|
17
|
+
* Honest ceiling: this forces the *read* (deliberate, in-context, links followed).
|
|
18
|
+
* It cannot force comprehension. It kills the one specific failure — skipping the
|
|
19
|
+
* files — dead. Nothing more is claimed.
|
|
20
|
+
*
|
|
21
|
+
* Design decisions (from the bash → TS rewrite that proved this out):
|
|
22
|
+
* - Required set is DECLARED, not inferred: the global and project CLAUDE.md are
|
|
23
|
+
* always required, and each names its mandatory companions with an explicit
|
|
24
|
+
* `REQUIRED-READS:` line. (Scraping every Markdown link over-captures incidental
|
|
25
|
+
* prose links, so the contract declares its own reads instead.)
|
|
26
|
+
* - Paths are canonicalised with realpath on BOTH sides, so a symlinked root
|
|
27
|
+
* (/tmp -> /private/tmp) can't silently fail to match.
|
|
28
|
+
* - Partial reads (Read with offset/limit) do NOT count as grounding.
|
|
29
|
+
* - State is one JSON file per session; resets only on startup/clear, never on
|
|
30
|
+
* resume/compact — and `context` re-injects the contract on compact so it
|
|
31
|
+
* re-enters context after summarisation.
|
|
32
|
+
* - Fail-OPEN on any internal error or empty required set (never brick tool use);
|
|
33
|
+
* fail-CLOSED only on the explicit "not yet grounded" condition.
|
|
34
|
+
*/
|
|
35
|
+
import { existsSync, realpathSync, readFileSync, readdirSync, statSync, mkdirSync, writeFileSync, unlinkSync, } from 'fs';
|
|
36
|
+
import { join, dirname, resolve, isAbsolute } from 'path';
|
|
37
|
+
import { homedir } from 'os';
|
|
38
|
+
const MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
39
|
+
/** Home dir, overridable via $HOME (hook contexts + tests); falls back to os.homedir(). */
|
|
40
|
+
function home() {
|
|
41
|
+
return process.env.HOME || homedir();
|
|
42
|
+
}
|
|
43
|
+
function globalClaudeMd() {
|
|
44
|
+
return join(home(), '.claude', 'CLAUDE.md');
|
|
45
|
+
}
|
|
46
|
+
function stateDir() {
|
|
47
|
+
return join(home(), '.everystack', 'governance', 'grounding');
|
|
48
|
+
}
|
|
49
|
+
/** Canonical absolute path: resolve symlinks if the file exists, else normalise. */
|
|
50
|
+
function canonical(p) {
|
|
51
|
+
try {
|
|
52
|
+
return realpathSync(p);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return resolve(p);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** Expand a leading `~` (or `~/`) to the home directory. */
|
|
59
|
+
function expandTilde(p) {
|
|
60
|
+
if (p === '~')
|
|
61
|
+
return home();
|
|
62
|
+
if (p.startsWith('~/'))
|
|
63
|
+
return join(home(), p.slice(2));
|
|
64
|
+
return p;
|
|
65
|
+
}
|
|
66
|
+
/** Nearest project CLAUDE.md walking up from cwd, stopping at $HOME or filesystem root. */
|
|
67
|
+
function nearestProjectClaudeMd(cwd) {
|
|
68
|
+
let d = resolve(cwd);
|
|
69
|
+
const stop = resolve(home());
|
|
70
|
+
while (d && d !== '/' && d !== stop) {
|
|
71
|
+
const candidate = join(d, 'CLAUDE.md');
|
|
72
|
+
if (existsSync(candidate))
|
|
73
|
+
return candidate;
|
|
74
|
+
const parent = dirname(d);
|
|
75
|
+
if (parent === d)
|
|
76
|
+
break;
|
|
77
|
+
d = parent;
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
/** Parse an optional `REQUIRED-READS: a.md, b/c.md` line; paths relative to the CLAUDE.md dir. */
|
|
82
|
+
function parseRequiredReads(claudeMdPath) {
|
|
83
|
+
let text;
|
|
84
|
+
try {
|
|
85
|
+
text = readFileSync(claudeMdPath, 'utf-8');
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
const m = text.match(/^[ \t]*REQUIRED-READS:[ \t]*(.+)$/im);
|
|
91
|
+
if (!m)
|
|
92
|
+
return [];
|
|
93
|
+
const base = dirname(claudeMdPath);
|
|
94
|
+
return m[1]
|
|
95
|
+
.split(',')
|
|
96
|
+
.map((s) => s.trim())
|
|
97
|
+
.filter(Boolean)
|
|
98
|
+
.map(expandTilde)
|
|
99
|
+
.map((rel) => (isAbsolute(rel) ? rel : resolve(base, rel)));
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Resolve the required-reads set for a working directory:
|
|
103
|
+
* global CLAUDE.md + its REQUIRED-READS entries
|
|
104
|
+
* + nearest project CLAUDE.md + its REQUIRED-READS entries.
|
|
105
|
+
* Returns canonical, de-duplicated paths partitioned into present / missing.
|
|
106
|
+
*/
|
|
107
|
+
export function requiredFiles(cwd) {
|
|
108
|
+
const candidates = [];
|
|
109
|
+
const gmd = globalClaudeMd();
|
|
110
|
+
if (existsSync(gmd)) {
|
|
111
|
+
candidates.push(gmd);
|
|
112
|
+
candidates.push(...parseRequiredReads(gmd));
|
|
113
|
+
}
|
|
114
|
+
const proj = nearestProjectClaudeMd(cwd);
|
|
115
|
+
if (proj) {
|
|
116
|
+
candidates.push(proj);
|
|
117
|
+
candidates.push(...parseRequiredReads(proj));
|
|
118
|
+
}
|
|
119
|
+
const present = [];
|
|
120
|
+
const missing = [];
|
|
121
|
+
const seen = new Set();
|
|
122
|
+
for (const c of candidates) {
|
|
123
|
+
if (existsSync(c)) {
|
|
124
|
+
const canon = canonical(c);
|
|
125
|
+
if (!seen.has(canon)) {
|
|
126
|
+
seen.add(canon);
|
|
127
|
+
present.push(canon);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
const norm = resolve(c);
|
|
132
|
+
if (!seen.has(norm)) {
|
|
133
|
+
seen.add(norm);
|
|
134
|
+
missing.push(norm);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return { present, missing };
|
|
139
|
+
}
|
|
140
|
+
function safeId(sessionId) {
|
|
141
|
+
return (sessionId || 'nosession').replace(/[^A-Za-z0-9_.-]/g, '_');
|
|
142
|
+
}
|
|
143
|
+
function statePath(sessionId) {
|
|
144
|
+
return join(stateDir(), `${safeId(sessionId)}.json`);
|
|
145
|
+
}
|
|
146
|
+
function loadState(sessionId) {
|
|
147
|
+
try {
|
|
148
|
+
const raw = JSON.parse(readFileSync(statePath(sessionId), 'utf-8'));
|
|
149
|
+
return {
|
|
150
|
+
reads: Array.isArray(raw.reads) ? raw.reads : [],
|
|
151
|
+
grounded: !!raw.grounded,
|
|
152
|
+
updated: typeof raw.updated === 'string' ? raw.updated : '',
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return { reads: [], grounded: false, updated: '' };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function saveState(sessionId, state) {
|
|
160
|
+
mkdirSync(stateDir(), { recursive: true });
|
|
161
|
+
writeFileSync(statePath(sessionId), JSON.stringify(state));
|
|
162
|
+
}
|
|
163
|
+
function resetState(sessionId) {
|
|
164
|
+
try {
|
|
165
|
+
unlinkSync(statePath(sessionId));
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
/* nothing to reset */
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/** Delete session-state files older than 7 days. */
|
|
172
|
+
function prune() {
|
|
173
|
+
let files;
|
|
174
|
+
try {
|
|
175
|
+
files = readdirSync(stateDir());
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const now = Date.now();
|
|
181
|
+
for (const f of files) {
|
|
182
|
+
const p = join(stateDir(), f);
|
|
183
|
+
try {
|
|
184
|
+
if (now - statSync(p).mtimeMs > MAX_AGE_MS)
|
|
185
|
+
unlinkSync(p);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
/* skip */
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* PreToolUse decision. Always allows `Read`. Allows everything once the session
|
|
194
|
+
* is grounded. Fails open if no required files resolve. Otherwise denies any
|
|
195
|
+
* non-Read tool while required files remain unread.
|
|
196
|
+
*/
|
|
197
|
+
export function gate(sessionId, cwd, toolName) {
|
|
198
|
+
if (toolName === 'Read')
|
|
199
|
+
return { decision: 'allow', unread: [] };
|
|
200
|
+
const { present } = requiredFiles(cwd);
|
|
201
|
+
if (present.length === 0)
|
|
202
|
+
return { decision: 'allow', unread: [] }; // nothing to ground on
|
|
203
|
+
const state = loadState(sessionId);
|
|
204
|
+
if (state.grounded)
|
|
205
|
+
return { decision: 'allow', unread: [] };
|
|
206
|
+
const read = new Set(state.reads);
|
|
207
|
+
const unread = present.filter((p) => !read.has(p));
|
|
208
|
+
if (unread.length === 0) {
|
|
209
|
+
saveState(sessionId, { reads: state.reads, grounded: true, updated: new Date().toISOString() });
|
|
210
|
+
return { decision: 'allow', unread: [] };
|
|
211
|
+
}
|
|
212
|
+
return { decision: 'deny', unread };
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* PostToolUse(Read) record. A full read of a required file marks it grounded.
|
|
216
|
+
* Partial reads (offset/limit present) are ignored — a one-line peek is not
|
|
217
|
+
* grounding. Reads of non-required files are ignored.
|
|
218
|
+
*/
|
|
219
|
+
export function mark(sessionId, cwd, filePath, partial = false) {
|
|
220
|
+
if (partial || !filePath)
|
|
221
|
+
return { grounded: loadState(sessionId).grounded };
|
|
222
|
+
const { present } = requiredFiles(cwd);
|
|
223
|
+
const canon = canonical(filePath);
|
|
224
|
+
if (!present.includes(canon))
|
|
225
|
+
return { grounded: loadState(sessionId).grounded };
|
|
226
|
+
const state = loadState(sessionId);
|
|
227
|
+
const reads = state.reads.includes(canon) ? state.reads : [...state.reads, canon];
|
|
228
|
+
const grounded = present.every((p) => reads.includes(p));
|
|
229
|
+
saveState(sessionId, { reads, grounded, updated: new Date().toISOString() });
|
|
230
|
+
return { grounded };
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* SessionStart payload: the required-file list injected into context. Resets
|
|
234
|
+
* per-session state on `startup`/`clear` (fresh grounding required); leaves it
|
|
235
|
+
* intact on `resume`/`compact` but still re-injects the contract so it survives
|
|
236
|
+
* summarisation. Also runs 7-day housekeeping.
|
|
237
|
+
*/
|
|
238
|
+
export function context(sessionId, cwd, source) {
|
|
239
|
+
prune();
|
|
240
|
+
if (source === 'startup' || source === 'clear')
|
|
241
|
+
resetState(sessionId);
|
|
242
|
+
const { present, missing } = requiredFiles(cwd);
|
|
243
|
+
const lines = [];
|
|
244
|
+
lines.push('GROUNDING GATE ACTIVE — this is the contract for how this project is built.');
|
|
245
|
+
lines.push('Before ANY other tool this session you MUST Read these files (Read is the only tool allowed until you have). Read them, act on them, follow their links:');
|
|
246
|
+
if (present.length === 0) {
|
|
247
|
+
lines.push(' (no CLAUDE.md contract found for this directory — gate is inactive)');
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
for (const f of present)
|
|
251
|
+
lines.push(` - ${f}`);
|
|
252
|
+
}
|
|
253
|
+
if (missing.length > 0) {
|
|
254
|
+
lines.push('');
|
|
255
|
+
lines.push('Note — referenced but missing on disk (skipped, not required):');
|
|
256
|
+
for (const f of missing)
|
|
257
|
+
lines.push(` - ${f}`);
|
|
258
|
+
}
|
|
259
|
+
lines.push('');
|
|
260
|
+
lines.push('You do not get the right to ignore them.');
|
|
261
|
+
return lines.join('\n');
|
|
262
|
+
}
|
|
263
|
+
/** Human-readable grounding status for a session/cwd (the MCP introspection path). */
|
|
264
|
+
export function groundStatus(cwd, sessionId) {
|
|
265
|
+
const { present, missing } = requiredFiles(cwd);
|
|
266
|
+
const lines = [];
|
|
267
|
+
lines.push(`Required reads for ${resolve(cwd)}:`);
|
|
268
|
+
if (present.length === 0)
|
|
269
|
+
lines.push(' (none — no CLAUDE.md contract found; gate inactive)');
|
|
270
|
+
if (sessionId) {
|
|
271
|
+
const state = loadState(sessionId);
|
|
272
|
+
const read = new Set(state.reads);
|
|
273
|
+
for (const f of present)
|
|
274
|
+
lines.push(` [${read.has(f) ? 'x' : ' '}] ${f}`);
|
|
275
|
+
lines.push('');
|
|
276
|
+
lines.push(`Grounded: ${state.grounded ? 'yes' : 'no'} (session ${safeId(sessionId)})`);
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
for (const f of present)
|
|
280
|
+
lines.push(` - ${f}`);
|
|
281
|
+
}
|
|
282
|
+
if (missing.length > 0) {
|
|
283
|
+
lines.push('');
|
|
284
|
+
lines.push('Referenced but missing (skipped):');
|
|
285
|
+
for (const f of missing)
|
|
286
|
+
lines.push(` - ${f}`);
|
|
287
|
+
}
|
|
288
|
+
return lines.join('\n');
|
|
289
|
+
}
|
|
290
|
+
/** The PreToolUse deny reason shown when grounding is incomplete. */
|
|
291
|
+
export function denyReason(unread) {
|
|
292
|
+
return [
|
|
293
|
+
'GROUNDING INCOMPLETE — you have not read the contract. Read these files first',
|
|
294
|
+
'(Read is allowed; every other tool is blocked until you have):',
|
|
295
|
+
...unread.map((f) => ` - ${f}`),
|
|
296
|
+
'',
|
|
297
|
+
'This is how this project is built. You do not get to skip it.',
|
|
298
|
+
].join('\n');
|
|
299
|
+
}
|
package/dist/index.cjs
CHANGED
|
@@ -23050,8 +23050,10 @@ function registerClaudeMdPrompt(server) {
|
|
|
23050
23050
|
"",
|
|
23051
23051
|
"Reconcile the existing CLAUDE.md against three things, and propose a concrete diff:",
|
|
23052
23052
|
"1. **Framework drift** \u2014 does it teach superseded ways (e.g. `drizzle-kit generate`,",
|
|
23053
|
-
' hand-written migrations, "pass your Drizzle schema") instead of
|
|
23054
|
-
" (`defineModel` \u2192 `everystack db:
|
|
23053
|
+
' hand-written migrations, a migration journal, "pass your Drizzle schema") instead of',
|
|
23054
|
+
" the migration-authority flow (`defineModel` in db/models/ \u2192 `everystack db:sync` on",
|
|
23055
|
+
" dev; `db:plan` \u2192 `db:apply` on protected stages; `db:check` in CI; data moves in",
|
|
23056
|
+
" db/backfills/ via `db:backfill`)? Suggest the current conventions.",
|
|
23055
23057
|
"2. **Project drift** \u2014 do the Structure / Commands / package list match what is actually",
|
|
23056
23058
|
" installed now (new packages, a new tier, a new models/ dir)? Suggest updates. Also",
|
|
23057
23059
|
" check the operations manual: if docs/RUNBOOK.md is missing, or stale per",
|
|
@@ -23399,7 +23401,7 @@ function renderFinding(f) {
|
|
|
23399
23401
|
// src/gates/detectors/hand-written-migration.ts
|
|
23400
23402
|
var import_path2 = require("path");
|
|
23401
23403
|
var MIGRATION_SQL = /(?:^|\/)(?:drizzle|migrations)\/[^/]+\.sql$/i;
|
|
23402
|
-
var GENERATED_SCHEMA = /
|
|
23404
|
+
var GENERATED_SCHEMA = /schema\.generated\.tsx?$/;
|
|
23403
23405
|
function rel(ctx, p) {
|
|
23404
23406
|
try {
|
|
23405
23407
|
return (0, import_path2.relative)(ctx.cwd, p) || p;
|
|
@@ -23411,15 +23413,15 @@ var handWrittenMigration = {
|
|
|
23411
23413
|
id: "hand-written-migration",
|
|
23412
23414
|
tier: "framework",
|
|
23413
23415
|
severity: "deny",
|
|
23414
|
-
guide: "
|
|
23415
|
-
conform: "everystack db:
|
|
23416
|
-
verify: "everystack db:
|
|
23416
|
+
guide: "Nobody authors migrations. Schema changes have three homes: tables/constraints/authz are declared in db/models/ (edit the Model, then `everystack db:sync` on dev \u2014 protected stages take `db:plan` \u2192 `db:apply`); functions/views/matviews are authored in db/sql/ (deployed by db:reconcile/db:sync); one-shot DATA moves are authored in db/backfills/*.sql (run via db:backfill, never as a schema side effect). No migration file, ever \u2014 and generated artifacts (schema.generated.ts) are never edited.",
|
|
23417
|
+
conform: "edit db/models/ then `everystack db:sync` (dev) or `db:plan` \u2192 `db:apply` (protected) \xB7 db/sql/ for derived objects \xB7 db/backfills/ for data moves",
|
|
23418
|
+
verify: "everystack db:check passes (declared state composes; generated artifacts match regeneration) AND everystack db:fingerprint reports MATCH",
|
|
23417
23419
|
detect(ctx) {
|
|
23418
23420
|
if (ctx.tool !== "Write" && ctx.tool !== "Edit") return null;
|
|
23419
23421
|
const p = ctx.filePath;
|
|
23420
23422
|
if (!p) return null;
|
|
23421
23423
|
if (MIGRATION_SQL.test(p)) return `${rel(ctx, p)} is a SQL migration being written by hand`;
|
|
23422
|
-
if (GENERATED_SCHEMA.test(p)) return `${rel(ctx, p)} is generated from your Models`;
|
|
23424
|
+
if (GENERATED_SCHEMA.test(p)) return `${rel(ctx, p)} is a generated artifact (compiled from your Models \u2014 db:check refuses hand edits)`;
|
|
23423
23425
|
return null;
|
|
23424
23426
|
}
|
|
23425
23427
|
};
|
|
@@ -23705,7 +23707,7 @@ async function runGovernanceCli(argv) {
|
|
|
23705
23707
|
}
|
|
23706
23708
|
|
|
23707
23709
|
// src/index.ts
|
|
23708
|
-
var version2 = (true ? "0.3.
|
|
23710
|
+
var version2 = (true ? "0.3.3" : null) ?? "0.3.0-dev";
|
|
23709
23711
|
var INSTRUCTIONS = [
|
|
23710
23712
|
"You govern how any agent builds everystack \u2014 a self-hosted application stack for Expo apps on AWS.",
|
|
23711
23713
|
"Your job is not only to advise but to keep the build on-script: the architecture the maintainer",
|
|
@@ -23767,7 +23769,7 @@ var INSTRUCTIONS = [
|
|
|
23767
23769
|
"1. Read everystack://core for architecture and conventions.",
|
|
23768
23770
|
"2. Read everystack://security before any deployment or auth guidance.",
|
|
23769
23771
|
"3. Load detail resources on demand when the user asks about specific features.",
|
|
23770
|
-
|
|
23772
|
+
'4. Data lives in PostgreSQL via Models/Modules and is served through the API \u2014 never bundle large computed data into the app. NOBODY AUTHORS MIGRATIONS. Schema work has three homes: tables/constraints/authz are DECLARED in `db/models/` (edit the Model, then `everystack db:sync` moves the dev database to the checkout \u2014 verified by fingerprint); functions/views/matviews are authored in `db/sql/` (deployed by `db:reconcile`/`db:sync`); one-shot DATA moves are authored in `db/backfills/*.sql` (run via `db:backfill`, never as a schema side effect). Protected stages never take a sync: `db:plan` mints a reviewable, fingerprint-pinned edge and `db:apply` verifies at both ends \u2014 the checkout must descend from the commit declaring the target\'s state ("rebase first"), and destructive plans are confirmed, snapshotted, and approver-gated (`db:approvers`). CI runs `everystack db:check` (the merged declared state must compose; generated artifacts must match regeneration byte-for-byte). Per-branch dev databases: `db:template:refresh` + `db:branch`; deployed feature stages fork data with `db:fork`. Reuse `@everystack/ui` components; never put secret values behind `EXPO_PUBLIC_*`.',
|
|
23771
23773
|
'5. When the user wants to start a new project, run check_environment (phase "local" for dev, "deploy" for deployment) to verify prerequisites.',
|
|
23772
23774
|
"6. When the user needs to interact with deployed infrastructure, guide them to use the everystack CLI."
|
|
23773
23775
|
].join("\n");
|