continuous-improvement 3.17.0 → 3.18.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/.claude-plugin/marketplace.json +1 -1
- package/README.md +164 -99
- package/bin/audit-actions.mjs +433 -0
- package/bin/check-command-count.mjs +114 -0
- package/bin/portfolio-health.mjs +298 -0
- package/commands/reconcile.md +34 -7
- package/hooks/gateguard.mjs +137 -3
- package/lib/gateguard-state.mjs +5 -1
- package/package.json +12 -6
- package/plugins/beginner.json +1 -1
- package/plugins/continuous-improvement/.claude-plugin/marketplace.json +1 -1
- package/plugins/continuous-improvement/.claude-plugin/plugin.json +1 -1
- package/plugins/continuous-improvement/commands/reconcile.md +34 -7
- package/plugins/continuous-improvement/hooks/gateguard.mjs +137 -3
- package/plugins/continuous-improvement/lib/gateguard-state.mjs +5 -1
- package/plugins/continuous-improvement/skills/README.md +1 -1
- package/plugins/continuous-improvement/skills/gateguard/SKILL.md +10 -0
- package/plugins/continuous-improvement/skills/reconcile/SKILL.md +52 -4
- package/plugins/expert.json +1 -1
- package/skills/gateguard.md +10 -0
- package/skills/reconcile.md +52 -4
- package/templates/actions_security_checklist.md +39 -0
- package/templates/experiment_template.md +38 -0
- package/templates/portfolio_event.schema.json +69 -0
- package/templates/release_receipt_template.md +37 -0
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: reconcile
|
|
3
|
-
description: Establish git ground truth (branch, status, stashes, worktrees, ahead/behind) before any mutation, halt on protected or destructive operations,
|
|
3
|
+
description: Establish git ground truth (branch, status, stashes, worktrees, ahead/behind) before any mutation, halt on protected or destructive operations, then carry the known-good state through a single-concern commit, a push, an open PR, and — after the PR merges — a fast-forward of the default branch. Enforces Law 1 (Research Before Executing).
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# /reconcile — Ground
|
|
6
|
+
# /reconcile — Ground Truth, Then Commit, Push, and Open the PR
|
|
7
7
|
|
|
8
|
-
Read the repo's real state before acting on it: a branch that shifted, a push that did not land, or another session mid-merge will burn a whole session if you assume instead of check.
|
|
8
|
+
Read the repo's real state before acting on it: a branch that shifted, a push that did not land, or another session mid-merge will burn a whole session if you assume instead of check. Once the state is known, `/reconcile` carries the work through to an open PR and back to an up-to-date default branch.
|
|
9
9
|
|
|
10
10
|
## What it does
|
|
11
11
|
|
|
12
|
-
Snapshots the full git state in one pass, detects a concurrent writer, classifies the upstream relationship, then acts only on the known state — stopping at every operation that is hard to reverse. Backed by the `reconcile` skill.
|
|
12
|
+
Snapshots the full git state in one pass, detects a concurrent writer, classifies the upstream relationship, then acts only on the known state — stopping at every operation that is hard to reverse. When work is ready, it stages by filename, commits one concern, pushes a feature branch, verifies the push landed, and opens a PR. After a human merges, it fast-forwards the default branch and checks it out. Backed by the `reconcile` skill.
|
|
13
13
|
|
|
14
14
|
## Establish ground truth
|
|
15
15
|
|
|
16
16
|
```
|
|
17
17
|
git branch --show-current
|
|
18
18
|
git status --porcelain=v1 # but trust git diff --stat for real drift (autocrlf)
|
|
19
|
-
git rev-list --left-right --count @{u}...HEAD # behind / ahead
|
|
19
|
+
git rev-list --left-right --count '@{u}...HEAD' # behind / ahead (quote the ref — bare @{u} trips the Bash parser)
|
|
20
20
|
git stash list
|
|
21
21
|
git worktree list
|
|
22
22
|
ls .git/MERGE_HEAD .git/rebase-merge .git/rebase-apply 2>/dev/null # in-progress op = another actor; do not race
|
|
@@ -31,7 +31,22 @@ behind -> git pull --ff-only
|
|
|
31
31
|
diverged -> rebase/merge deliberately; never blind --force
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
STOP for authorization before: pushing to a protected branch (this repo = feature branch + PR, never direct push to main), `--force` / `--force-with-lease`, `reset --hard`, `clean -fd`, or removing a dirty worktree. Never stage with `git add -A` on a Windows autocrlf tree (it commits phantom line-ending-only changes) — stage by explicit filename.
|
|
34
|
+
STOP for authorization before: pushing to a protected branch (this repo = feature branch + PR, never direct push to main), merging the PR you opened, `--force` / `--force-with-lease`, `reset --hard`, `clean -fd`, force-deleting a branch (`branch -D`), or removing a dirty worktree. Never stage with `git add -A` on a Windows autocrlf tree (it commits phantom line-ending-only changes) — stage by explicit filename.
|
|
35
|
+
|
|
36
|
+
## Commit and open the PR (self-contained)
|
|
37
|
+
|
|
38
|
+
Reimplements the commit → push → PR tail inline, so it works with no companion plugin installed:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
git switch main && git pull --ff-only origin main # branch from a fresh base
|
|
42
|
+
git switch -c <type>/<slug> # only if not already on a feature branch
|
|
43
|
+
git add path/one path/two # stage by name, one concern
|
|
44
|
+
git commit -m "feat(scope): <observable outcome>" # single-line -m; never a multi-line here-doc on Windows
|
|
45
|
+
git push -u origin <type>/<slug>
|
|
46
|
+
gh pr create --fill --base main # open one PR, then STOP — the merge is a human decision
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`/reconcile` never merges the PR, never uses `--admin` / `--force` / `--no-verify`, never auto-merges on green CI, and never deploys.
|
|
35
50
|
|
|
36
51
|
## Verify the push landed
|
|
37
52
|
|
|
@@ -39,9 +54,21 @@ STOP for authorization before: pushing to a protected branch (this repo = featur
|
|
|
39
54
|
git ls-remote origin refs/heads/<branch> # remote tip must equal local HEAD, else it did not land
|
|
40
55
|
```
|
|
41
56
|
|
|
57
|
+
## Sync the default branch after the PR merges
|
|
58
|
+
|
|
59
|
+
"Latest work on main" is true only once the PR merges, and on a protected branch that merge is a human action. After it lands:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
git switch main # or master
|
|
63
|
+
git pull --ff-only origin main # fast-forward only; if it will not ff, main diverged — re-survey, do not force
|
|
64
|
+
git rev-parse HEAD # confirm this equals the squash-merge SHA
|
|
65
|
+
git branch -d <type>/<slug> # delete the merged feature branch (safe -d, never -D)
|
|
66
|
+
```
|
|
67
|
+
|
|
42
68
|
## Pairs with
|
|
43
69
|
|
|
44
70
|
- **`reconcile`** skill — the discipline this command runs.
|
|
45
71
|
- **`gateguard`** / **`safety-guard`** — runtime + destructive-op guardrails.
|
|
46
72
|
- **`recall`** — recall whether the same git op failed here before.
|
|
47
|
-
- **`audit`** — the loop that often produces the fix
|
|
73
|
+
- **`audit`** — the loop that often produces the fix `/reconcile` then ships.
|
|
74
|
+
- **`/ship`** — the TDD-gated single-defect variant; `commit-commands:commit-push-pr` is the external-plugin equivalent of the commit → PR tail.
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
import { readFileSync } from "node:fs";
|
|
36
36
|
import { dirname, join } from "node:path";
|
|
37
37
|
import { fileURLToPath } from "node:url";
|
|
38
|
-
import { MAX_CLEARED_FILES, canonicalizeFileKey, isCapReached, isFileCleared, loadState, markFileCleared, resolveSessionDir, saveState, } from "../lib/gateguard-state.mjs";
|
|
38
|
+
import { MAX_CLEARED_FILES, canonicalizeFileKey, canonicalizeProjectRoot, isCapReached, isFileCleared, loadState, markFileCleared, resolveProjectRoot, resolveSessionDir, saveState, } from "../lib/gateguard-state.mjs";
|
|
39
39
|
const TOOL_ROUTE = {
|
|
40
40
|
Read: "allow",
|
|
41
41
|
Grep: "allow",
|
|
@@ -69,8 +69,20 @@ const DESTRUCTIVE_PATTERNS = [
|
|
|
69
69
|
"Remove-Item -Recurse",
|
|
70
70
|
"Remove-Item -Force",
|
|
71
71
|
];
|
|
72
|
+
// Flags whose VALUE is human prose (a commit message, a PR body) or a filename —
|
|
73
|
+
// never a command to execute. Their contents must not trip the destructive scan:
|
|
74
|
+
// `git commit -m "drop the stale format helper"` and `gh pr create --body "…"`
|
|
75
|
+
// were stranding finished work on their own wording. `-c` is deliberately
|
|
76
|
+
// EXCLUDED — `bash -c "rm -rf /"` carries a real command and must still gate.
|
|
77
|
+
const MESSAGE_FLAG_RE = /(^|\s)(-m|--message|-F|--file|--body|--body-file|--title|--notes|-C|--reuse-message)(=|\s+)('[^']*'|"[^"]*"|\S+)/g;
|
|
78
|
+
// Blank the value of every message/body flag so only executable command syntax
|
|
79
|
+
// remains for the destructive-pattern scan. The flag itself is preserved so a
|
|
80
|
+
// flag like `-F` never accidentally merges with its neighbours.
|
|
81
|
+
function stripMessageArgs(command) {
|
|
82
|
+
return command.replace(MESSAGE_FLAG_RE, (_match, lead, flag) => `${lead}${flag} `);
|
|
83
|
+
}
|
|
72
84
|
function isDestructiveBash(command) {
|
|
73
|
-
const lower = command.toLowerCase();
|
|
85
|
+
const lower = stripMessageArgs(command).toLowerCase();
|
|
74
86
|
return DESTRUCTIVE_PATTERNS.some((p) => lower.includes(p.toLowerCase()));
|
|
75
87
|
}
|
|
76
88
|
function classifyTool(toolName, toolInput) {
|
|
@@ -95,6 +107,60 @@ function extractFilePaths(toolInput) {
|
|
|
95
107
|
return [toolInput.command];
|
|
96
108
|
return [];
|
|
97
109
|
}
|
|
110
|
+
// --- Path exclusions -------------------------------------------------------
|
|
111
|
+
// Opt-in: skip the fact-forcing gate for low-risk paths a user edits
|
|
112
|
+
// constantly (an LLM-maintained prose wiki, a generated scratch dir). Set the
|
|
113
|
+
// CI_GATEGUARD_EXCLUDE env var to a comma-separated list of path substrings;
|
|
114
|
+
// each is matched case-insensitively against the forward-slash-normalized file
|
|
115
|
+
// path. Unset/empty (the default) changes nothing — every mutating file call is
|
|
116
|
+
// gated exactly as before. A call whose targets mix excluded and non-excluded
|
|
117
|
+
// paths still gates the non-excluded ones.
|
|
118
|
+
const EXCLUDE_FRAGMENTS = String(process.env.CI_GATEGUARD_EXCLUDE ?? "")
|
|
119
|
+
.split(",")
|
|
120
|
+
.map((fragment) => fragment.trim().replace(/\\/g, "/").toLowerCase())
|
|
121
|
+
.filter((fragment) => fragment !== "");
|
|
122
|
+
function isExcludedPath(filePath) {
|
|
123
|
+
if (EXCLUDE_FRAGMENTS.length === 0 || typeof filePath !== "string" || filePath === "") {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
const normalized = filePath.replace(/\\/g, "/").toLowerCase();
|
|
127
|
+
return EXCLUDE_FRAGMENTS.some((fragment) => normalized.includes(fragment));
|
|
128
|
+
}
|
|
129
|
+
// --- Target lock (opt-in) --------------------------------------------------
|
|
130
|
+
// A fact-list can't catch a wrong-repo / wrong-worktree write — you can present
|
|
131
|
+
// perfect facts about the wrong file. CI_GATEGUARD_TARGET_LOCK=block denies a
|
|
132
|
+
// mutating call whose ABSOLUTE target canonicalizes outside the session project
|
|
133
|
+
// root. Default (unset) checks nothing, so existing sessions — including
|
|
134
|
+
// legitimate out-of-root edits to ~/.claude or /tmp — are unaffected. This is
|
|
135
|
+
// the warn-first rollout: ship non-enforcing, flip to block per session.
|
|
136
|
+
const TARGET_LOCK_ON = String(process.env.CI_GATEGUARD_TARGET_LOCK ?? "").toLowerCase() === "block";
|
|
137
|
+
// Relative paths resolve under cwd (= the project root) and always pass; only an
|
|
138
|
+
// absolute path into a different tree can be out-of-root. Drive-letter (d:/,
|
|
139
|
+
// D:\), POSIX-absolute (/x), and UNC (\\host) forms all count as absolute.
|
|
140
|
+
function isAbsolutePathString(p) {
|
|
141
|
+
return /^[A-Za-z]:[\\/]/.test(p) || p.startsWith("/") || p.startsWith("\\\\");
|
|
142
|
+
}
|
|
143
|
+
function isTargetOutsideRoot(filePath, projectRoot) {
|
|
144
|
+
if (!isAbsolutePathString(filePath))
|
|
145
|
+
return false;
|
|
146
|
+
const root = canonicalizeProjectRoot(projectRoot);
|
|
147
|
+
if (root === "global" || root === "")
|
|
148
|
+
return false; // no known root — do not guess
|
|
149
|
+
const target = canonicalizeFileKey(filePath);
|
|
150
|
+
return target !== root && !target.startsWith(`${root}/`);
|
|
151
|
+
}
|
|
152
|
+
function buildTargetLockReason(strayPath, projectRoot) {
|
|
153
|
+
return [
|
|
154
|
+
`Target is outside the session project root — refusing a possible wrong-repo / wrong-worktree write.`,
|
|
155
|
+
"",
|
|
156
|
+
` Target: ${strayPath.replace(/\\/g, "/")}`,
|
|
157
|
+
` Session root: ${canonicalizeProjectRoot(projectRoot)}`,
|
|
158
|
+
"",
|
|
159
|
+
"If this is intentional, confirm you are in the right worktree (cwd / CLAUDE_PROJECT_DIR),",
|
|
160
|
+
"or unset CI_GATEGUARD_TARGET_LOCK for this session. Target lock is opt-in; it fires only",
|
|
161
|
+
"when CI_GATEGUARD_TARGET_LOCK=block.",
|
|
162
|
+
].join("\n");
|
|
163
|
+
}
|
|
98
164
|
// The call site only reads this inside the block branch, where at least one
|
|
99
165
|
// path is uncleared; an all-cleared batch returns "" and is never consumed.
|
|
100
166
|
function firstUnclearedFilePath(toolInput, state) {
|
|
@@ -144,6 +210,47 @@ function buildMutatingFileReason(toolName, filePaths, stateFilePath) {
|
|
|
144
210
|
" `_gateguard_facts_presented: true`; Claude Code's strict schema rejects that, so use A or B.)",
|
|
145
211
|
].join("\n");
|
|
146
212
|
}
|
|
213
|
+
function findUnquotedBraceRef(command) {
|
|
214
|
+
let quote = null;
|
|
215
|
+
for (let i = 0; i < command.length; i++) {
|
|
216
|
+
const ch = command[i];
|
|
217
|
+
if (quote) {
|
|
218
|
+
if (ch === quote)
|
|
219
|
+
quote = null;
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (ch === '"' || ch === "'") {
|
|
223
|
+
quote = ch;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if (ch === "@" && command[i + 1] === "{") {
|
|
227
|
+
// Expand to the whitespace-delimited word that carries this @{ ref, then
|
|
228
|
+
// single-quote that whole word in the suggested fix.
|
|
229
|
+
let wordStart = i;
|
|
230
|
+
while (wordStart > 0 && !/\s/.test(command[wordStart - 1]))
|
|
231
|
+
wordStart--;
|
|
232
|
+
let wordEnd = i;
|
|
233
|
+
while (wordEnd < command.length && !/\s/.test(command[wordEnd]))
|
|
234
|
+
wordEnd++;
|
|
235
|
+
const word = command.slice(wordStart, wordEnd);
|
|
236
|
+
const braceEnd = command.indexOf("}", i);
|
|
237
|
+
const ref = braceEnd === -1 ? command.slice(i, wordEnd) : command.slice(i, braceEnd + 1);
|
|
238
|
+
const fixed = `${command.slice(0, wordStart)}'${word}'${command.slice(wordEnd)}`;
|
|
239
|
+
return { ref, fixed };
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
function buildBraceRefReason(hit) {
|
|
245
|
+
return [
|
|
246
|
+
`Unquoted git ref ${hit.ref} — Claude Code's Bash parser trips on the braces and blocks this`,
|
|
247
|
+
"post-hoc, costing a retry. Quote the ref and run the SAME command:",
|
|
248
|
+
"",
|
|
249
|
+
` ${hit.fixed}`,
|
|
250
|
+
"",
|
|
251
|
+
"Single quotes stop the shell from touching the braces; git reads the ref as-is.",
|
|
252
|
+
].join("\n");
|
|
253
|
+
}
|
|
147
254
|
function buildDestructiveBashReason(command) {
|
|
148
255
|
return [
|
|
149
256
|
`Destructive command requested: ${command}`,
|
|
@@ -199,6 +306,16 @@ function main() {
|
|
|
199
306
|
const toolName = typeof payload.tool_name === "string" ? payload.tool_name : "";
|
|
200
307
|
const toolInput = payload.tool_input ?? {};
|
|
201
308
|
const gate = classifyTool(toolName, toolInput);
|
|
309
|
+
// Unquoted @{…} refs trip the built-in Bash parser — catch them first, for
|
|
310
|
+
// both routine and destructive commands, so the quoted fix surfaces before the
|
|
311
|
+
// opaque post-hoc block (and before the destructive rollback demand).
|
|
312
|
+
if (toolName === "Bash" && typeof toolInput.command === "string") {
|
|
313
|
+
const braceHit = findUnquotedBraceRef(toolInput.command);
|
|
314
|
+
if (braceHit) {
|
|
315
|
+
emitDeny(buildBraceRefReason(braceHit));
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
202
319
|
if (gate === "allow") {
|
|
203
320
|
emitAllow();
|
|
204
321
|
return;
|
|
@@ -215,7 +332,24 @@ function main() {
|
|
|
215
332
|
const sessionDir = resolveSessionDir(sessionId);
|
|
216
333
|
const stateFilePath = join(sessionDir, "gateguard-session.json");
|
|
217
334
|
const state = loadState(sessionDir);
|
|
218
|
-
const
|
|
335
|
+
const allTargetPaths = extractFilePaths(toolInput);
|
|
336
|
+
const filePaths = allTargetPaths.filter((path) => !isExcludedPath(path));
|
|
337
|
+
if (allTargetPaths.length > 0 && filePaths.length === 0) {
|
|
338
|
+
emitAllow(); // every target is under a CI_GATEGUARD_EXCLUDE path; skip the gate
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
// Target lock runs before the fact gate and independent of clearance: a
|
|
342
|
+
// wrong-repo write is wrong even with perfect facts. Excluded paths were
|
|
343
|
+
// already filtered out above, so an explicitly-excluded scratch dir outside
|
|
344
|
+
// the root is never target-locked.
|
|
345
|
+
if (TARGET_LOCK_ON) {
|
|
346
|
+
const projectRoot = resolveProjectRoot();
|
|
347
|
+
const stray = filePaths.find((path) => isTargetOutsideRoot(path, projectRoot));
|
|
348
|
+
if (stray) {
|
|
349
|
+
emitDeny(buildTargetLockReason(stray, projectRoot));
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
219
353
|
const filePath = firstUnclearedFilePath(toolInput, state);
|
|
220
354
|
const factsFlagged = toolInput._gateguard_facts_presented === true;
|
|
221
355
|
const alreadyCleared = filePaths.length > 0 && filePaths.every((path) => isFileCleared(state, path));
|
|
@@ -59,7 +59,11 @@ function sanitizeSessionId(sessionId) {
|
|
|
59
59
|
return "";
|
|
60
60
|
return sessionId.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 64);
|
|
61
61
|
}
|
|
62
|
-
|
|
62
|
+
// Exported for the target-lock gate (RISA 2 / G2): the hook compares a mutating
|
|
63
|
+
// call's absolute target against this root to catch wrong-repo / wrong-worktree
|
|
64
|
+
// writes. Returns "global" when no CLAUDE_PROJECT_DIR and no git toplevel — the
|
|
65
|
+
// caller treats that as "no known root, do not guess".
|
|
66
|
+
export function resolveProjectRoot() {
|
|
63
67
|
const fromEnv = process.env.CLAUDE_PROJECT_DIR;
|
|
64
68
|
if (fromEnv)
|
|
65
69
|
return fromEnv;
|
|
@@ -30,7 +30,7 @@ skill set on disk.
|
|
|
30
30
|
- `grill-with-docs` — Enforces Law 1 (Research Before Executing) and Law 7 (Learn From Every Session) of the 7 Laws of AI Agent Discipline. Grilling session that challenges your plan against the existing domain model, sharpens terminology, and updates CONTEXT.md + ADRs inline as decisions crystallise. Ported from mattpocock/skills under MIT.
|
|
31
31
|
- `handoff` — Enforces Law 5 (Reflect After Every Session) of the 7 Laws of AI Agent Discipline. Compact the current conversation into a handoff document for another agent to pick up. Ported from mattpocock/skills under MIT.
|
|
32
32
|
- `intent-driven-development` — Enforces Law 2 (Plan Is Sacred) of the 7 Laws of AI Agent Discipline. Turn an ambiguous or high-impact change into scoped, verifiable acceptance criteria (observable AC-NNN, explicit in/out scope, named verification methods, and a [revised] protocol that forbids silently dropping a criterion) before or alongside implementation, so the plan that gets built is the plan that was agreed, not an invented default. Use when clarifying a feature, defining acceptance criteria, de-risking a security/data/migration/integration change, or preparing implementation requirements for another agent. Do not trigger for trivial edits, straightforward fixes, active debugging, or code review.
|
|
33
|
-
- `reconcile` — Enforces Law 1 (Research Before Executing) of the 7 Laws of AI Agent Discipline. Establishes git ground truth — branch, status, stashes, worktrees, ahead/behind — before any mutation, halts on protected or destructive operations,
|
|
33
|
+
- `reconcile` — Enforces Law 1 (Research Before Executing) of the 7 Laws of AI Agent Discipline. Establishes git ground truth — branch, status, stashes, worktrees, ahead/behind — before any mutation, halts on protected or destructive operations, then carries the known-good state through to a landed PR: stage by filename, commit one concern, push the feature branch, verify the push landed, open the PR, and after the PR merges fast-forward the default branch and check it out.
|
|
34
34
|
- `recovery-classification` — Enforces Law 4 (Verify Before Reporting) of the 7 Laws of AI Agent Discipline. After any failure in the verification ladder or auto-loop, classify the failure class before retrying — provider, tool-schema, deterministic-policy, git, worktree, runtime — so retry-vs-pause-vs-self-heal-vs-stop is an intentional decision, not a generic 'try again'.
|
|
35
35
|
- `roast` — Enforces Law 1 (Research Before Executing) of the 7 Laws of AI Agent Discipline. Convene a 5-persona adversarial council (Contrarian, Expansionist, Logician, Researcher, Buyer) that attacks an idea from every angle, then a Judge returns one GO / RESHAPE / KILL verdict plus the cheapest 48-hour test to de-risk it — so you pressure-test an idea before sinking time into building the wrong thing.
|
|
36
36
|
- `safety-guard` — Enforces Law 3 (One Thing at a Time) of the 7 Laws of AI Agent Discipline by scoping edits to a directory and blocking destructive shell commands. Use this skill to prevent destructive operations when working on production systems or running agents autonomously.
|
|
@@ -150,6 +150,14 @@ The block reason prints the exact `gateguard-session.json` path and the clearanc
|
|
|
150
150
|
|
|
151
151
|
The inline `_gateguard_facts_presented: true` retry still works on harnesses that forward unknown tool params, but Claude Code's strict tool schema (`additionalProperties: false`) rejects it with `InputValidationError` — use one of the above on Claude Code.
|
|
152
152
|
|
|
153
|
+
### Excluding low-risk paths
|
|
154
|
+
|
|
155
|
+
Set the `CI_GATEGUARD_EXCLUDE` environment variable to opt specific low-risk paths out of the gate entirely — an LLM-maintained prose wiki, a generated scratch directory, anything where the fact-forcing pause costs more than it saves. The value is a comma-separated list of path substrings, each matched case-insensitively against the forward-slash-normalized file path, so `/mywiki/` excludes `D:\Vault\MyWiki\notes\x.md`. Unset or empty (the default) changes nothing: every mutating file call is gated exactly as before, and a call that touches a mix of excluded and non-excluded paths still gates the non-excluded ones. Set it per project in `.claude/settings.json` under `env`, or globally in `~/.claude/settings.json`.
|
|
156
|
+
|
|
157
|
+
### Locking edits to the current repo
|
|
158
|
+
|
|
159
|
+
A fact-list can't catch a wrong-repo or wrong-worktree write — you can present perfect facts about the wrong file, in the wrong checkout. Set `CI_GATEGUARD_TARGET_LOCK=block` to make the runtime hook (`hooks/gateguard.mjs`) refuse any mutating call whose **absolute** target canonicalizes outside the session project root (`CLAUDE_PROJECT_DIR`, or the git toplevel). Relative paths resolve under the current directory (= the root) and always pass; only an absolute path into a different tree is denied, and the deny reason names both the stray target and the expected root. This runs before the fact gate and independent of clearance — a wrong-repo write is wrong even with facts. Unset (the default) checks nothing, so legitimate out-of-root edits (`~/.claude`, a `/tmp` scratch file, a sibling repo) are unaffected; turn it on per session in a multi-worktree or headless run where cross-repo writes are the real risk. Paths already covered by `CI_GATEGUARD_EXCLUDE` are never target-locked.
|
|
160
|
+
|
|
153
161
|
### Limitations and guarantees
|
|
154
162
|
|
|
155
163
|
- **Honor system.** Clearance is recorded by `ci_gateguard_clear`, the `gateguard-clear.mjs` CLI, a manual state-file write, or the inline `_gateguard_facts_presented` flag where the harness allows it (see "Clearing the gate" above). The hook can't verify the investigation actually happened; the 50-file cap — counted per session — bounds damage from stuck loops or rogue agents.
|
|
@@ -175,6 +183,8 @@ The standalone `gateguard-ai` Python/CLI package referenced in earlier drafts of
|
|
|
175
183
|
- Let the gate fire naturally. Don't try to pre-answer the gate questions — the investigation itself is what improves quality.
|
|
176
184
|
- Customize gate messages for your domain. If your project has specific conventions, add them to the gate prompts.
|
|
177
185
|
- Use `.gateguard.yml` to ignore paths like `.venv/`, `node_modules/`, `.git/`.
|
|
186
|
+
- For the shipped runtime hook, set `CI_GATEGUARD_EXCLUDE` (comma-separated path substrings) to exclude low-risk paths from the gate — see [Excluding low-risk paths](#excluding-low-risk-paths).
|
|
187
|
+
- In multi-worktree or headless runs, set `CI_GATEGUARD_TARGET_LOCK=block` so a write into the wrong repo/worktree is refused — see [Locking edits to the current repo](#locking-edits-to-the-current-repo).
|
|
178
188
|
|
|
179
189
|
## Related Skills
|
|
180
190
|
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: reconcile
|
|
3
3
|
tier: "2"
|
|
4
|
-
description: Enforces Law 1 (Research Before Executing) of the 7 Laws of AI Agent Discipline. Establishes git ground truth — branch, status, stashes, worktrees, ahead/behind — before any mutation, halts on protected or destructive operations,
|
|
4
|
+
description: Enforces Law 1 (Research Before Executing) of the 7 Laws of AI Agent Discipline. Establishes git ground truth — branch, status, stashes, worktrees, ahead/behind — before any mutation, halts on protected or destructive operations, then carries the known-good state through to a landed PR: stage by filename, commit one concern, push the feature branch, verify the push landed, open the PR, and after the PR merges fast-forward the default branch and check it out.
|
|
5
5
|
origin: continuous-improvement
|
|
6
6
|
user-invocable: true
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
# Reconcile — Ground
|
|
9
|
+
# Reconcile — Ground Truth, Then Commit, Push, and Open the PR
|
|
10
10
|
|
|
11
|
-
Law 1 says research before executing. The most expensive skipped research is the state of your own repo: a branch that shifted under you, a push that silently did not land, a stash from a session you forgot. This skill establishes git ground truth first, acts only on a known state,
|
|
11
|
+
Law 1 says research before executing. The most expensive skipped research is the state of your own repo: a branch that shifted under you, a push that silently did not land, a stash from a session you forgot. This skill establishes git ground truth first, acts only on a known state, stops at every operation that is hard to reverse, and then carries that known-good state all the way through a single-concern commit, a push, and an open PR — ending back on an up-to-date default branch once the PR merges.
|
|
12
12
|
|
|
13
13
|
## When to Activate
|
|
14
14
|
|
|
15
15
|
- Before any branch/merge/rebase/push when more than one session, loop, or agent may be writing to the tree.
|
|
16
16
|
- When the working tree looks different from what you expect (unexpected branch, surprise modifications, a half-finished merge).
|
|
17
17
|
- Before cleaning up: consolidating branches, dropping stashes, removing worktrees.
|
|
18
|
+
- When finished work needs to land: stage it, commit one concern, push a feature branch, open a PR, and return to an up-to-date default branch.
|
|
18
19
|
- After a push, to confirm it actually landed on the remote.
|
|
19
20
|
|
|
20
21
|
## Establish Ground Truth First
|
|
@@ -24,7 +25,7 @@ Read before you write. Capture the full state in one pass:
|
|
|
24
25
|
```
|
|
25
26
|
git branch --show-current
|
|
26
27
|
git status --porcelain=v1
|
|
27
|
-
git rev-list --left-right --count @{u}...HEAD # behind / ahead of upstream
|
|
28
|
+
git rev-list --left-right --count '@{u}...HEAD' # behind / ahead of upstream (quote the ref — bare @{u} trips the Bash parser)
|
|
28
29
|
git stash list
|
|
29
30
|
git worktree list
|
|
30
31
|
ls .git/MERGE_HEAD .git/rebase-merge .git/rebase-apply 2>/dev/null # in-progress operation?
|
|
@@ -57,10 +58,43 @@ Branch from a base only after confirming `local <base>` equals `origin/<base>`
|
|
|
57
58
|
STOP and get explicit authorization before:
|
|
58
59
|
|
|
59
60
|
- Pushing to a protected branch (e.g. `main`) — this repo's flow is feature branch + PR, never direct push.
|
|
61
|
+
- Merging the PR you opened, or force-deleting a branch (`git branch -D`) — both stay human decisions, never auto-actions on green CI.
|
|
60
62
|
- `git push --force` / `--force-with-lease`, `git reset --hard`, `git clean -fd`, `worktree remove` on a dirty worktree, or dropping a stash with uncommitted value.
|
|
61
63
|
|
|
62
64
|
If a rebase has diverged and force-push is gated, do not force-recover — supersede via a new branch + new PR.
|
|
63
65
|
|
|
66
|
+
## Commit and Open the PR
|
|
67
|
+
|
|
68
|
+
Once ground truth is known and the halt gates are clear, carry the work to an open PR without leaving the known-good state. This tail is self-contained — it reimplements the commit → push → PR steps with plain git/`gh` and depends on no companion plugin.
|
|
69
|
+
|
|
70
|
+
1. **Cut or confirm a feature branch from a fresh base.** Never commit onto a protected branch. Sync the default branch first so the feature branch is not born stale:
|
|
71
|
+
```
|
|
72
|
+
git switch main && git pull --ff-only origin main # master on older repos
|
|
73
|
+
git switch -c <type>/<slug> # only if not already on a feature branch
|
|
74
|
+
```
|
|
75
|
+
Confirm `local main` equals `origin/main` before branching — a squash-merge otherwise bundles ahead-of-origin commits.
|
|
76
|
+
|
|
77
|
+
2. **Stage by explicit filename.** One concern per commit. On an `autocrlf` tree `git add -A` / `git add .` commits phantom line-ending-only changes — name each path and read real drift with `git diff --stat`.
|
|
78
|
+
```
|
|
79
|
+
git add path/one path/two
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
3. **Commit with a Windows-safe message.** Lead with the observable outcome. Use a single-line `-m` (repeat `-m` for paragraphs) or `git commit -F <tempfile>` — never a multi-line here-doc/here-string, which CRLF and shell quoting corrupt on Windows.
|
|
83
|
+
```
|
|
84
|
+
git commit -m "feat(scope): <observable outcome>"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
4. **Push the feature branch** (never the protected branch), then verify it landed via the section below:
|
|
88
|
+
```
|
|
89
|
+
git push -u origin <type>/<slug>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
5. **Open one PR** citing the plan or issue, then stop:
|
|
93
|
+
```
|
|
94
|
+
gh pr create --fill --base main
|
|
95
|
+
```
|
|
96
|
+
**Stop here.** The merge is a human decision. `reconcile` never merges the PR, never uses `--admin` / `--force` / `--no-verify`, never auto-merges on green CI, and never deploys.
|
|
97
|
+
|
|
64
98
|
## Verify the Push Actually Landed
|
|
65
99
|
|
|
66
100
|
A push that printed no error is still a claim. Confirm:
|
|
@@ -72,9 +106,23 @@ git ls-remote origin refs/heads/<branch> # remote tip must equal local HEAD
|
|
|
72
106
|
|
|
73
107
|
If the remote ref is absent or behind, the push did not land — investigate before reporting success.
|
|
74
108
|
|
|
109
|
+
## Sync the Default Branch After the PR Merges
|
|
110
|
+
|
|
111
|
+
"All the latest work on main" is only true once the PR actually merges — and on a protected branch that merge is a human action, not something `reconcile` performs. After the merge lands, return to an up-to-date default branch:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
git switch main # or master on older repos
|
|
115
|
+
git pull --ff-only origin main # fast-forward only; never a merge commit or --force
|
|
116
|
+
git rev-parse HEAD # confirm this equals the squash-merge SHA from the PR
|
|
117
|
+
git branch -d <type>/<slug> # delete the merged feature branch (safe -d, never -D)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`--ff-only` is deliberate: if the pull would not fast-forward, main diverged under you — stop and re-survey from **Establish Ground Truth First** instead of forcing it. You end on the default branch with every merged change present and the feature branch cleaned up.
|
|
121
|
+
|
|
75
122
|
## Pairs With
|
|
76
123
|
|
|
77
124
|
- **`recall`** (Law 1) — before a risky git op, recall whether the same operation failed on this repo before.
|
|
78
125
|
- **`gateguard`** (Law 1) — the runtime gate (`hooks/gateguard.mjs`); `reconcile` is the procedure you run once a destructive git action is in play.
|
|
79
126
|
- **`safety-guard`** — destructive-operation guardrails for production and autonomous runs.
|
|
80
127
|
- **`audit`** (Law 4) — when an audit ends in a fix, `reconcile` is the safe path from branch to landed PR.
|
|
128
|
+
- **`commit-commands:commit-push-pr`** — the external-plugin equivalent of the commit → push → PR tail; `reconcile` reimplements it inline so the flow works with no companion installed. For a TDD-gated single-defect variant, use `/ship`.
|
package/plugins/expert.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "continuous-improvement",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.18.0",
|
|
4
4
|
"mode": "expert",
|
|
5
5
|
"description": "Expert mode: tune confidence, manage instincts, and persist plans on disk. Adds safety, token-budget, and strategic-compact skills plus the /learn-eval command so long sessions stay sharp and learnings survive context resets.",
|
|
6
6
|
"tools": [
|
package/skills/gateguard.md
CHANGED
|
@@ -150,6 +150,14 @@ The block reason prints the exact `gateguard-session.json` path and the clearanc
|
|
|
150
150
|
|
|
151
151
|
The inline `_gateguard_facts_presented: true` retry still works on harnesses that forward unknown tool params, but Claude Code's strict tool schema (`additionalProperties: false`) rejects it with `InputValidationError` — use one of the above on Claude Code.
|
|
152
152
|
|
|
153
|
+
### Excluding low-risk paths
|
|
154
|
+
|
|
155
|
+
Set the `CI_GATEGUARD_EXCLUDE` environment variable to opt specific low-risk paths out of the gate entirely — an LLM-maintained prose wiki, a generated scratch directory, anything where the fact-forcing pause costs more than it saves. The value is a comma-separated list of path substrings, each matched case-insensitively against the forward-slash-normalized file path, so `/mywiki/` excludes `D:\Vault\MyWiki\notes\x.md`. Unset or empty (the default) changes nothing: every mutating file call is gated exactly as before, and a call that touches a mix of excluded and non-excluded paths still gates the non-excluded ones. Set it per project in `.claude/settings.json` under `env`, or globally in `~/.claude/settings.json`.
|
|
156
|
+
|
|
157
|
+
### Locking edits to the current repo
|
|
158
|
+
|
|
159
|
+
A fact-list can't catch a wrong-repo or wrong-worktree write — you can present perfect facts about the wrong file, in the wrong checkout. Set `CI_GATEGUARD_TARGET_LOCK=block` to make the runtime hook (`hooks/gateguard.mjs`) refuse any mutating call whose **absolute** target canonicalizes outside the session project root (`CLAUDE_PROJECT_DIR`, or the git toplevel). Relative paths resolve under the current directory (= the root) and always pass; only an absolute path into a different tree is denied, and the deny reason names both the stray target and the expected root. This runs before the fact gate and independent of clearance — a wrong-repo write is wrong even with facts. Unset (the default) checks nothing, so legitimate out-of-root edits (`~/.claude`, a `/tmp` scratch file, a sibling repo) are unaffected; turn it on per session in a multi-worktree or headless run where cross-repo writes are the real risk. Paths already covered by `CI_GATEGUARD_EXCLUDE` are never target-locked.
|
|
160
|
+
|
|
153
161
|
### Limitations and guarantees
|
|
154
162
|
|
|
155
163
|
- **Honor system.** Clearance is recorded by `ci_gateguard_clear`, the `gateguard-clear.mjs` CLI, a manual state-file write, or the inline `_gateguard_facts_presented` flag where the harness allows it (see "Clearing the gate" above). The hook can't verify the investigation actually happened; the 50-file cap — counted per session — bounds damage from stuck loops or rogue agents.
|
|
@@ -175,6 +183,8 @@ The standalone `gateguard-ai` Python/CLI package referenced in earlier drafts of
|
|
|
175
183
|
- Let the gate fire naturally. Don't try to pre-answer the gate questions — the investigation itself is what improves quality.
|
|
176
184
|
- Customize gate messages for your domain. If your project has specific conventions, add them to the gate prompts.
|
|
177
185
|
- Use `.gateguard.yml` to ignore paths like `.venv/`, `node_modules/`, `.git/`.
|
|
186
|
+
- For the shipped runtime hook, set `CI_GATEGUARD_EXCLUDE` (comma-separated path substrings) to exclude low-risk paths from the gate — see [Excluding low-risk paths](#excluding-low-risk-paths).
|
|
187
|
+
- In multi-worktree or headless runs, set `CI_GATEGUARD_TARGET_LOCK=block` so a write into the wrong repo/worktree is refused — see [Locking edits to the current repo](#locking-edits-to-the-current-repo).
|
|
178
188
|
|
|
179
189
|
## Related Skills
|
|
180
190
|
|
package/skills/reconcile.md
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: reconcile
|
|
3
3
|
tier: "2"
|
|
4
|
-
description: Enforces Law 1 (Research Before Executing) of the 7 Laws of AI Agent Discipline. Establishes git ground truth — branch, status, stashes, worktrees, ahead/behind — before any mutation, halts on protected or destructive operations,
|
|
4
|
+
description: Enforces Law 1 (Research Before Executing) of the 7 Laws of AI Agent Discipline. Establishes git ground truth — branch, status, stashes, worktrees, ahead/behind — before any mutation, halts on protected or destructive operations, then carries the known-good state through to a landed PR: stage by filename, commit one concern, push the feature branch, verify the push landed, open the PR, and after the PR merges fast-forward the default branch and check it out.
|
|
5
5
|
origin: continuous-improvement
|
|
6
6
|
user-invocable: true
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
# Reconcile — Ground
|
|
9
|
+
# Reconcile — Ground Truth, Then Commit, Push, and Open the PR
|
|
10
10
|
|
|
11
|
-
Law 1 says research before executing. The most expensive skipped research is the state of your own repo: a branch that shifted under you, a push that silently did not land, a stash from a session you forgot. This skill establishes git ground truth first, acts only on a known state,
|
|
11
|
+
Law 1 says research before executing. The most expensive skipped research is the state of your own repo: a branch that shifted under you, a push that silently did not land, a stash from a session you forgot. This skill establishes git ground truth first, acts only on a known state, stops at every operation that is hard to reverse, and then carries that known-good state all the way through a single-concern commit, a push, and an open PR — ending back on an up-to-date default branch once the PR merges.
|
|
12
12
|
|
|
13
13
|
## When to Activate
|
|
14
14
|
|
|
15
15
|
- Before any branch/merge/rebase/push when more than one session, loop, or agent may be writing to the tree.
|
|
16
16
|
- When the working tree looks different from what you expect (unexpected branch, surprise modifications, a half-finished merge).
|
|
17
17
|
- Before cleaning up: consolidating branches, dropping stashes, removing worktrees.
|
|
18
|
+
- When finished work needs to land: stage it, commit one concern, push a feature branch, open a PR, and return to an up-to-date default branch.
|
|
18
19
|
- After a push, to confirm it actually landed on the remote.
|
|
19
20
|
|
|
20
21
|
## Establish Ground Truth First
|
|
@@ -24,7 +25,7 @@ Read before you write. Capture the full state in one pass:
|
|
|
24
25
|
```
|
|
25
26
|
git branch --show-current
|
|
26
27
|
git status --porcelain=v1
|
|
27
|
-
git rev-list --left-right --count @{u}...HEAD # behind / ahead of upstream
|
|
28
|
+
git rev-list --left-right --count '@{u}...HEAD' # behind / ahead of upstream (quote the ref — bare @{u} trips the Bash parser)
|
|
28
29
|
git stash list
|
|
29
30
|
git worktree list
|
|
30
31
|
ls .git/MERGE_HEAD .git/rebase-merge .git/rebase-apply 2>/dev/null # in-progress operation?
|
|
@@ -57,10 +58,43 @@ Branch from a base only after confirming `local <base>` equals `origin/<base>`
|
|
|
57
58
|
STOP and get explicit authorization before:
|
|
58
59
|
|
|
59
60
|
- Pushing to a protected branch (e.g. `main`) — this repo's flow is feature branch + PR, never direct push.
|
|
61
|
+
- Merging the PR you opened, or force-deleting a branch (`git branch -D`) — both stay human decisions, never auto-actions on green CI.
|
|
60
62
|
- `git push --force` / `--force-with-lease`, `git reset --hard`, `git clean -fd`, `worktree remove` on a dirty worktree, or dropping a stash with uncommitted value.
|
|
61
63
|
|
|
62
64
|
If a rebase has diverged and force-push is gated, do not force-recover — supersede via a new branch + new PR.
|
|
63
65
|
|
|
66
|
+
## Commit and Open the PR
|
|
67
|
+
|
|
68
|
+
Once ground truth is known and the halt gates are clear, carry the work to an open PR without leaving the known-good state. This tail is self-contained — it reimplements the commit → push → PR steps with plain git/`gh` and depends on no companion plugin.
|
|
69
|
+
|
|
70
|
+
1. **Cut or confirm a feature branch from a fresh base.** Never commit onto a protected branch. Sync the default branch first so the feature branch is not born stale:
|
|
71
|
+
```
|
|
72
|
+
git switch main && git pull --ff-only origin main # master on older repos
|
|
73
|
+
git switch -c <type>/<slug> # only if not already on a feature branch
|
|
74
|
+
```
|
|
75
|
+
Confirm `local main` equals `origin/main` before branching — a squash-merge otherwise bundles ahead-of-origin commits.
|
|
76
|
+
|
|
77
|
+
2. **Stage by explicit filename.** One concern per commit. On an `autocrlf` tree `git add -A` / `git add .` commits phantom line-ending-only changes — name each path and read real drift with `git diff --stat`.
|
|
78
|
+
```
|
|
79
|
+
git add path/one path/two
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
3. **Commit with a Windows-safe message.** Lead with the observable outcome. Use a single-line `-m` (repeat `-m` for paragraphs) or `git commit -F <tempfile>` — never a multi-line here-doc/here-string, which CRLF and shell quoting corrupt on Windows.
|
|
83
|
+
```
|
|
84
|
+
git commit -m "feat(scope): <observable outcome>"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
4. **Push the feature branch** (never the protected branch), then verify it landed via the section below:
|
|
88
|
+
```
|
|
89
|
+
git push -u origin <type>/<slug>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
5. **Open one PR** citing the plan or issue, then stop:
|
|
93
|
+
```
|
|
94
|
+
gh pr create --fill --base main
|
|
95
|
+
```
|
|
96
|
+
**Stop here.** The merge is a human decision. `reconcile` never merges the PR, never uses `--admin` / `--force` / `--no-verify`, never auto-merges on green CI, and never deploys.
|
|
97
|
+
|
|
64
98
|
## Verify the Push Actually Landed
|
|
65
99
|
|
|
66
100
|
A push that printed no error is still a claim. Confirm:
|
|
@@ -72,9 +106,23 @@ git ls-remote origin refs/heads/<branch> # remote tip must equal local HEAD
|
|
|
72
106
|
|
|
73
107
|
If the remote ref is absent or behind, the push did not land — investigate before reporting success.
|
|
74
108
|
|
|
109
|
+
## Sync the Default Branch After the PR Merges
|
|
110
|
+
|
|
111
|
+
"All the latest work on main" is only true once the PR actually merges — and on a protected branch that merge is a human action, not something `reconcile` performs. After the merge lands, return to an up-to-date default branch:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
git switch main # or master on older repos
|
|
115
|
+
git pull --ff-only origin main # fast-forward only; never a merge commit or --force
|
|
116
|
+
git rev-parse HEAD # confirm this equals the squash-merge SHA from the PR
|
|
117
|
+
git branch -d <type>/<slug> # delete the merged feature branch (safe -d, never -D)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`--ff-only` is deliberate: if the pull would not fast-forward, main diverged under you — stop and re-survey from **Establish Ground Truth First** instead of forcing it. You end on the default branch with every merged change present and the feature branch cleaned up.
|
|
121
|
+
|
|
75
122
|
## Pairs With
|
|
76
123
|
|
|
77
124
|
- **`recall`** (Law 1) — before a risky git op, recall whether the same operation failed on this repo before.
|
|
78
125
|
- **`gateguard`** (Law 1) — the runtime gate (`hooks/gateguard.mjs`); `reconcile` is the procedure you run once a destructive git action is in play.
|
|
79
126
|
- **`safety-guard`** — destructive-operation guardrails for production and autonomous runs.
|
|
80
127
|
- **`audit`** (Law 4) — when an audit ends in a fix, `reconcile` is the safe path from branch to landed PR.
|
|
128
|
+
- **`commit-commands:commit-push-pr`** — the external-plugin equivalent of the commit → push → PR tail; `reconcile` reimplements it inline so the flow works with no companion installed. For a TDD-gated single-defect variant, use `/ship`.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# GitHub Actions Security Checklist
|
|
2
|
+
|
|
3
|
+
> Apply to every workflow in `.github/workflows/`. Each item is checkable by reading the YAML — no runtime access needed. The `audit-actions` command automates the mechanical checks; this checklist is the human review layer.
|
|
4
|
+
|
|
5
|
+
## Permissions
|
|
6
|
+
|
|
7
|
+
- [ ] Workflow (or every job) declares explicit `permissions:` — never relies on the default token grant
|
|
8
|
+
- [ ] Default is `permissions: contents: read`; write scopes are added per-job only where provably needed
|
|
9
|
+
- [ ] No `permissions: write-all`
|
|
10
|
+
- [ ] Workflows triggered by `pull_request_target`, `issue_comment`, or `issues` do NOT get write tokens or secrets unless a maintainer-approval gate exists
|
|
11
|
+
|
|
12
|
+
## Untrusted input
|
|
13
|
+
|
|
14
|
+
- [ ] No direct interpolation of `github.event.*` text (issue/PR title, body, comment, branch name, commit message) inside `run:` shell — pass through `env:` and quote as `"$VAR"`
|
|
15
|
+
- [ ] Untrusted content passed to agents/LLMs is wrapped in a prompt boundary and stripped of tool-invocation-looking text
|
|
16
|
+
- [ ] `actions/checkout` of PR head refs in privileged contexts is treated as executing untrusted code
|
|
17
|
+
|
|
18
|
+
## Supply chain
|
|
19
|
+
|
|
20
|
+
- [ ] Third-party actions are pinned to a full commit SHA (tags are mutable); first-party `actions/*` at minimum pinned to a major version
|
|
21
|
+
- [ ] No `curl | bash` of unpinned remote scripts
|
|
22
|
+
- [ ] Artifacts downloaded from other workflows are treated as untrusted input
|
|
23
|
+
|
|
24
|
+
## Runaway control
|
|
25
|
+
|
|
26
|
+
- [ ] Every job has `timeout-minutes`
|
|
27
|
+
- [ ] Workflows that deploy or mutate state declare `concurrency:` with a stable group key
|
|
28
|
+
- [ ] Scheduled/agentic workflows have an explicit cost ceiling (matrix size, iteration cap)
|
|
29
|
+
|
|
30
|
+
## Agentic workflows
|
|
31
|
+
|
|
32
|
+
- [ ] Agent runs triggered by issue/PR/comment text run with read-only tokens
|
|
33
|
+
- [ ] A human approves before any agent-generated change is pushed or merged
|
|
34
|
+
- [ ] Prompt boundary and tool boundary are logged for each agent run
|
|
35
|
+
|
|
36
|
+
## Secrets
|
|
37
|
+
|
|
38
|
+
- [ ] Secrets are not exposed to workflows runnable by untrusted PRs
|
|
39
|
+
- [ ] No secrets echoed to logs or written to artifacts
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Experiment
|
|
2
|
+
|
|
3
|
+
> One file per experiment. Copy to `.experiments/<YYYY-MM-DD>-<slug>.md`. No experiment record = no claim of learning.
|
|
4
|
+
|
|
5
|
+
## Definition
|
|
6
|
+
|
|
7
|
+
| Field | Value |
|
|
8
|
+
|---|---|
|
|
9
|
+
| Experiment name | |
|
|
10
|
+
| Lane | trading / quran / workflow / infra |
|
|
11
|
+
| Repo | |
|
|
12
|
+
| Owner | |
|
|
13
|
+
| Feature flag (if any) | |
|
|
14
|
+
| Start date | |
|
|
15
|
+
| Stop date (hard stop — decide even if inconclusive) | |
|
|
16
|
+
|
|
17
|
+
## Hypothesis
|
|
18
|
+
|
|
19
|
+
State as: "If we [change], then [segment] will [measurable behavior], because [reasoning]."
|
|
20
|
+
|
|
21
|
+
## Metrics
|
|
22
|
+
|
|
23
|
+
- Primary metric (one only):
|
|
24
|
+
- Pass threshold (exact number):
|
|
25
|
+
- Guardrail metric (what must NOT get worse):
|
|
26
|
+
- Segment (who is measured):
|
|
27
|
+
|
|
28
|
+
## Result
|
|
29
|
+
|
|
30
|
+
Fill after stop date. Do not leave open past the stop date.
|
|
31
|
+
|
|
32
|
+
- Observed primary metric:
|
|
33
|
+
- Guardrail status:
|
|
34
|
+
- Evidence links (dashboards, exports, screenshots):
|
|
35
|
+
|
|
36
|
+
## Decision
|
|
37
|
+
|
|
38
|
+
One of: **ship** / **iterate** / **kill**. State the decision and the single next action.
|