@dev-loops/core 1.0.2-pre.0 → 1.0.2-slim.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/package.json +2 -1
- package/src/analysis/diff-analyzer.mjs +85 -137
- package/src/claude/asset-generation.mjs +7 -7
- package/src/claude/hook-decisions.mjs +29 -36
- package/src/config/config.mjs +388 -787
- package/src/github/comment-id-guard.mjs +2 -2
- package/src/github/copilot-helpers.mjs +90 -158
- package/src/loop/bash-command-classify.mjs +34 -49
- package/src/loop/conductor-routing.mjs +15 -23
- package/src/loop/copilot-loop-state.mjs +46 -94
- package/src/loop/gate-carry-forward.mjs +2 -2
- package/src/loop/gate-fanin.mjs +252 -442
- package/src/loop/handoff-envelope.mjs +19 -19
- package/src/loop/issue-refinement-artifact.mjs +158 -252
- package/src/loop/lifecycle-state.mjs +10 -21
- package/src/loop/pr-gate-coordination.mjs +37 -37
- package/src/loop/queue-board-sync.mjs +16 -55
- package/src/loop/review-dispatch-plan.mjs +60 -122
- package/src/loop/review-lineage.mjs +19 -44
- package/src/loop/spec-authority.mjs +39 -69
- package/src/loop/steering.mjs +16 -68
- package/src/projects/list-queue-items.mjs +16 -146
- package/src/projects/move-queue-item.mjs +15 -141
- package/src/projects/projects-access.mjs +202 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pure decision logic for the Claude Code dev-loop hooks
|
|
2
|
+
* Pure decision logic for the Claude Code dev-loop hooks.
|
|
3
3
|
*
|
|
4
4
|
* The hook *scripts* are thin: they read the PreToolUse/PostToolUse stdin payload, gather facts
|
|
5
5
|
* (git tracked/ignored status, gate-evidence result), and call these pure deciders. Keeping the
|
|
@@ -46,7 +46,7 @@ const ALLOW = Object.freeze({ decision: "allow" });
|
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
48
|
* Whether the command string also invokes an evidence-writing script (findings-log ledger or
|
|
49
|
-
* checkpoint-verdict upsert). Used only to enrich the merge-block message
|
|
49
|
+
* checkpoint-verdict upsert). Used only to enrich the merge-block message — a compound
|
|
50
50
|
* command combining an evidence write with `gh pr merge` is blocked pre-execution, so the write
|
|
51
51
|
* never runs; this substring check has no false-negative cost (worst case: the plain message).
|
|
52
52
|
*/
|
|
@@ -64,22 +64,15 @@ export const DEV_LOOP_AGENT_TYPE = "dev-loop";
|
|
|
64
64
|
/**
|
|
65
65
|
* Decide whether a PreToolUse Bash command must be blocked by a dev-loop gate boundary.
|
|
66
66
|
*
|
|
67
|
-
*
|
|
67
|
+
* Gated commands on the target repo (each rationale sits inline at its check):
|
|
68
68
|
* - `gh pr create` — blocked outright; PR creation must flow through the canonical wrapper
|
|
69
|
-
* (`scripts/github/create-pr.mjs` / `dev-loops pr create`), which always drafts and
|
|
70
|
-
*
|
|
71
|
-
* - `gh pr
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* - raw `gh issue create` / `gh issue comment` / `gh issue edit` / `gh pr comment` — blocked ONLY when the call
|
|
77
|
-
* originates from a SUBAGENT context (`agentType` is a non-null string) and targets the repo.
|
|
78
|
-
* Sanctioned external writes flow through node wrappers (gate-verdict comments via
|
|
79
|
-
* `upsert-checkpoint-verdict.mjs`, review replies via `reply-resolve*.mjs`, board sync,
|
|
80
|
-
* `comment-issue.mjs`), whose Bash command string is `node scripts/…` and never matches these
|
|
81
|
-
* raw-`gh` matchers. The MAIN AGENT / operator (agentType null) retains direct `gh issue
|
|
82
|
-
* create` — that path is authorized (#1051).
|
|
69
|
+
* (`scripts/github/create-pr.mjs` / `dev-loops pr create`), which always drafts and self-assigns.
|
|
70
|
+
* - `gh pr ready` — blocked without clean draft_gate evidence.
|
|
71
|
+
* - `gh pr merge` — blocked without full pre-merge gate evidence (clean current-head draft_gate +
|
|
72
|
+
* pre_approval_gate).
|
|
73
|
+
* - raw `gh issue create` / `gh issue comment` / `gh issue edit` / `gh pr comment` — blocked ONLY
|
|
74
|
+
* from a SUBAGENT context (`agentType` non-null) on the target repo. Sanctioned external writes
|
|
75
|
+
* flow through node wrappers; the MAIN AGENT / operator (agentType null) retains direct access.
|
|
83
76
|
*
|
|
84
77
|
* The hook computes `gatePassed`/`gateError` from the gate script appropriate to the command kind.
|
|
85
78
|
*
|
|
@@ -101,10 +94,10 @@ export function decideBashGate({ command, repoSlug = null, gatePassed = false, g
|
|
|
101
94
|
return ALLOW;
|
|
102
95
|
}
|
|
103
96
|
// Normalize (trim + case-fold) so a divergent slug (surrounding whitespace, casing) does not
|
|
104
|
-
// silently fail OPEN and disable every guard that depends on inTargetRepo
|
|
97
|
+
// silently fail OPEN and disable every guard that depends on inTargetRepo.
|
|
105
98
|
const inTargetRepo = (repoSlug ?? "").trim().toLowerCase() === TARGET_REPO_SLUG.trim().toLowerCase();
|
|
106
99
|
|
|
107
|
-
// OPS-NO-INLINE-INTERPRETER
|
|
100
|
+
// OPS-NO-INLINE-INTERPRETER: inline interpreters (`node -e`/`--eval`/`-p`, `python3 -c`,
|
|
108
101
|
// heredocs fed to node/python) are barred actor-independently on the target repo — the rule bars
|
|
109
102
|
// "Coordinator and agent flows"; sanctioned output parsing uses `--jq`/`--silent`, never an
|
|
110
103
|
// inline interpreter.
|
|
@@ -118,10 +111,10 @@ export function decideBashGate({ command, repoSlug = null, gatePassed = false, g
|
|
|
118
111
|
};
|
|
119
112
|
}
|
|
120
113
|
|
|
121
|
-
// SUBISSUE-NO-ADHOC-BYPASS
|
|
114
|
+
// SUBISSUE-NO-ADHOC-BYPASS: ad-hoc `gh api` writes to the target repo's sub-issue endpoints.
|
|
122
115
|
// Actor-independent (no reserved direct path). Gated on the target repo: the absolute slug-embedded
|
|
123
116
|
// form identifies the target repo; the bare relative form (`gh api issues/5/sub_issues`) resolves
|
|
124
|
-
// against the cwd repo, so it is in scope only when running in the target repo (mirrors the
|
|
117
|
+
// against the cwd repo, so it is in scope only when running in the target repo (mirrors the
|
|
125
118
|
// explicit-`--repo`/cwd-target posture).
|
|
126
119
|
if (inTargetRepo && commandContainsSubIssueAdHocBypass(command)) {
|
|
127
120
|
return {
|
|
@@ -132,7 +125,7 @@ export function decideBashGate({ command, repoSlug = null, gatePassed = false, g
|
|
|
132
125
|
};
|
|
133
126
|
}
|
|
134
127
|
|
|
135
|
-
// COPILOT-FOLLOWUP-REPLY-RESOLVE-HELPER
|
|
128
|
+
// COPILOT-FOLLOWUP-REPLY-RESOLVE-HELPER: ad-hoc thread-resolution writes — raw `gh api` POST
|
|
136
129
|
// to pulls/<n>/comments/<m>/replies, or a `gh api graphql` resolveReviewThread mutation (the Rest
|
|
137
130
|
// path names the target repo; the graphql form has no path-host repo, so it is scoped to the cwd
|
|
138
131
|
// repo). Actor-independent: reply through reply-resolve-review-thread(s).mjs.
|
|
@@ -146,7 +139,7 @@ export function decideBashGate({ command, repoSlug = null, gatePassed = false, g
|
|
|
146
139
|
};
|
|
147
140
|
}
|
|
148
141
|
|
|
149
|
-
// COPILOT-FOLLOWUP-REQUEST-HELPER-ONLY
|
|
142
|
+
// COPILOT-FOLLOWUP-REQUEST-HELPER-ONLY: ad-hoc Copilot review requests — raw `gh api` writes
|
|
150
143
|
// to pulls/<n>/requested_reviewers, or a bare `/copilot` / `/copilot re-review` comment summon on the
|
|
151
144
|
// target repo. Actor-independent: request Copilot via scripts/github/request-copilot-review.mjs.
|
|
152
145
|
if (inTargetRepo && (commandContainsCopilotRequestBypass(command) || commandContainsCopilotSummonComment(command))) {
|
|
@@ -174,7 +167,7 @@ export function decideBashGate({ command, repoSlug = null, gatePassed = false, g
|
|
|
174
167
|
}
|
|
175
168
|
// Subagent-scoped external-write guard: block ad-hoc `gh issue create`/`gh issue comment`/
|
|
176
169
|
// `gh issue edit`/`gh pr comment` on the target repo from a subagent, so external writes flow through the
|
|
177
|
-
// sanctioned node wrappers. The main-agent/operator path (agentType null) is unaffected
|
|
170
|
+
// sanctioned node wrappers. The main-agent/operator path (agentType null) is unaffected.
|
|
178
171
|
if (typeof agentType === "string" && commandContainsRawExternalWrite(command)) {
|
|
179
172
|
const cwdTargets = (repoSlug ?? "").toLowerCase() === TARGET_REPO_SLUG.toLowerCase();
|
|
180
173
|
// Scope PER segment, mirroring the `gh pr create` block: in scope when no explicit --repo and
|
|
@@ -205,7 +198,7 @@ export function decideBashGate({ command, repoSlug = null, gatePassed = false, g
|
|
|
205
198
|
const isMerge = commandContainsGhPrMerge(command);
|
|
206
199
|
const isCreate = commandContainsGhPrCreate(command);
|
|
207
200
|
|
|
208
|
-
// STOP-HUMAN-MERGE-001
|
|
201
|
+
// STOP-HUMAN-MERGE-001: when the repo resolves `autonomy.humanMergeOnly`, `gh pr merge` is
|
|
209
202
|
// refused actor-independently — the main agent is the actor that performs GitHub writes, so only an
|
|
210
203
|
// actor-independent deny enforces the human-merge invariant (an agent-scoped deny would enforce
|
|
211
204
|
// nothing on the main-agent write path).
|
|
@@ -220,7 +213,7 @@ export function decideBashGate({ command, repoSlug = null, gatePassed = false, g
|
|
|
220
213
|
}
|
|
221
214
|
|
|
222
215
|
if (!isReady && !isMerge && !isCreate) {
|
|
223
|
-
// COPILOT-FOLLOWUP-WAIT-TOOLS
|
|
216
|
+
// COPILOT-FOLLOWUP-WAIT-TOOLS: banned detached/polling wait wrappers. Subagent-only — the
|
|
224
217
|
// rule is classified `agent` (behavioral guidance for the dev-loop driving agent); the main
|
|
225
218
|
// agent/operator retains manual wait tooling. The main agent's own sanctioned wait path is still
|
|
226
219
|
// the deterministic tools.
|
|
@@ -244,7 +237,7 @@ export function decideBashGate({ command, repoSlug = null, gatePassed = false, g
|
|
|
244
237
|
const cwdTargets = (repoSlug ?? "").toLowerCase() === TARGET_REPO_SLUG.toLowerCase();
|
|
245
238
|
// Evaluate scope PER create segment, not just the first: a create is in scope when it
|
|
246
239
|
// explicitly targets the repo, or (with no explicit --repo) the cwd is the repo. An explicit
|
|
247
|
-
// `--repo <target>` is denied regardless of cwd
|
|
240
|
+
// `--repo <target>` is denied regardless of cwd. DENY if ANY create segment is in
|
|
248
241
|
// scope — otherwise a leading out-of-scope create (`gh pr create --repo other/repo`) would
|
|
249
242
|
// short-circuit and shield a later in-scope raw create (`&& gh pr create --fill`).
|
|
250
243
|
const anyCreateInScope = extractRepoFlagsFromGhPrCreateSegments(command).some((seg) =>
|
|
@@ -307,7 +300,7 @@ export function decideBashGate({ command, repoSlug = null, gatePassed = false, g
|
|
|
307
300
|
// This hook evaluates PreToolUse — BEFORE the Bash tool call runs. A compound command that
|
|
308
301
|
// writes gate evidence (findings-log ledger, checkpoint verdict) and merges in the same call
|
|
309
302
|
// is blocked here with the write never having executed, which looks like the evidence
|
|
310
|
-
// "vanished"
|
|
303
|
+
// "vanished". Hint the split when the command carries an evidence-writing invocation
|
|
311
304
|
// alongside the merge, so the failure is self-explaining instead of looking like data loss.
|
|
312
305
|
const alsoWritesEvidence = commandContainsEvidenceWrite(command);
|
|
313
306
|
return {
|
|
@@ -464,7 +457,7 @@ export function decideWorktreeCheckoutGuard({
|
|
|
464
457
|
|
|
465
458
|
/**
|
|
466
459
|
* Env var that exempts an interactive session awaiting commit authorization from the
|
|
467
|
-
* SubagentStop uncommitted-work guard
|
|
460
|
+
* SubagentStop uncommitted-work guard.
|
|
468
461
|
*
|
|
469
462
|
* An opt-in signal set by the operator or the interactive coordination path
|
|
470
463
|
* (`DEVLOOPS_COMMIT_AUTH_PENDING=1`) when intentionally holding uncommitted work pending
|
|
@@ -477,7 +470,7 @@ export function decideWorktreeCheckoutGuard({
|
|
|
477
470
|
export const DEVLOOPS_COMMIT_AUTH_PENDING_VAR = "DEVLOOPS_COMMIT_AUTH_PENDING";
|
|
478
471
|
|
|
479
472
|
/**
|
|
480
|
-
* Subagent roles whose contract forbids committing to the repository
|
|
473
|
+
* Subagent roles whose contract forbids committing to the repository.
|
|
481
474
|
*
|
|
482
475
|
* The `judge` and `review` agents are read-only over the repository: the judge writes only its
|
|
483
476
|
* own verdict artifact (under `tmp/`, gitignored) and the gate reviewer writes only its findings
|
|
@@ -489,7 +482,7 @@ export const DEVLOOPS_COMMIT_AUTH_PENDING_VAR = "DEVLOOPS_COMMIT_AUTH_PENDING";
|
|
|
489
482
|
* contract (`agents/judge.agent.md`: "The only thing you write is your own verdict artifact").
|
|
490
483
|
* The data-loss protection is enforced against the OWNER of the edit (the orchestrator) on its own
|
|
491
484
|
* stop instead. This is intentionally scoped to read-only roles: editing roles (`developer`,
|
|
492
|
-
* `fixer`, `docs`, `quality`) and the orchestrator stay enforced
|
|
485
|
+
* `fixer`, `docs`, `quality`) and the orchestrator stay enforced.
|
|
493
486
|
*/
|
|
494
487
|
export const READONLY_SUBAGENT_ROLES = Object.freeze(["judge", "review"]);
|
|
495
488
|
|
|
@@ -500,7 +493,7 @@ export function isReadOnlySubagentRole(agentType) {
|
|
|
500
493
|
|
|
501
494
|
/**
|
|
502
495
|
* Decide whether a SubagentStop must be blocked because the subagent's worktree has
|
|
503
|
-
* uncommitted changes
|
|
496
|
+
* uncommitted changes.
|
|
504
497
|
*
|
|
505
498
|
* `scripts/loop/cleanup-worktree.mjs` runs `git worktree remove --force` after a merge, so
|
|
506
499
|
* uncommitted changes in a worktree are destroyed with no warning. `LOCAL-COMMIT-BEFORE-EXIT`
|
|
@@ -512,10 +505,10 @@ export function isReadOnlySubagentRole(agentType) {
|
|
|
512
505
|
* Editing roles (`developer`/`fixer`/`docs`/`quality`) stay fully enforced: an editing
|
|
513
506
|
* sub-delegate commits its own work before exit (`LOCAL-COMMIT-BEFORE-EXIT`), so a dirty exit is
|
|
514
507
|
* always a real defect, never a sanctioned "orchestrator owns the commit" split. The removed
|
|
515
|
-
* `DEVLOOPS_ORCHESTRATOR_OWNS_COMMIT` env-var exemption
|
|
508
|
+
* `DEVLOOPS_ORCHESTRATOR_OWNS_COMMIT` env-var exemption deadlocked such a role under a
|
|
516
509
|
* task-scoped no-commit instruction whenever the orchestrator could not set a per-dispatch env
|
|
517
510
|
* var (the Claude harness): the hook demanded a commit the session then denied, then re-blocked
|
|
518
|
-
* the exit
|
|
511
|
+
* the exit. Disallowing the edit-here/commit-there split at the contract level makes the
|
|
519
512
|
* guard the enforcer and the deadlock structurally impossible while preserving data-loss
|
|
520
513
|
* protection. An orchestrator that wants one consolidated commit performs the edits itself.
|
|
521
514
|
*
|
|
@@ -532,7 +525,7 @@ export function isReadOnlySubagentRole(agentType) {
|
|
|
532
525
|
* awaiting commit authorization (exempt) — derived by the hook script from the
|
|
533
526
|
* `DEVLOOPS_COMMIT_AUTH_PENDING=1` opt-in env signal.
|
|
534
527
|
* @param {string|null} [params.agentType] - Claude `agent_type` from the SubagentStop payload;
|
|
535
|
-
* a read-only role (`judge`/`review`, per `READONLY_SUBAGENT_ROLES`) is exempt
|
|
528
|
+
* a read-only role (`judge`/`review`, per `READONLY_SUBAGENT_ROLES`) is exempt — its
|
|
536
529
|
* contract forbids commits, so any dirty tracked edit in its worktree is foreign
|
|
537
530
|
* (orchestrator-owned) and must not be pinned on it.
|
|
538
531
|
* @returns {HookDecision}
|
|
@@ -547,7 +540,7 @@ export function decideSubagentStopGuard({ cwd, porcelain, pendingCommitAuthoriza
|
|
|
547
540
|
if (typeof porcelain !== "string" || porcelain.trim() === "") {
|
|
548
541
|
return ALLOW;
|
|
549
542
|
}
|
|
550
|
-
// Read-only role exemption
|
|
543
|
+
// Read-only role exemption: the worktree is dirty, but a `judge`/`review` subagent's
|
|
551
544
|
// contract forbids commits, so this pending tracked edit is foreign — it belongs to the
|
|
552
545
|
// orchestrator that dispatched this pass. Do not force a verdict-only role to commit it; allow
|
|
553
546
|
// the stop with an advisory naming the orchestrator as the actor responsible for the edit. The
|