@orchestrator-claude/definitions 3.33.0 → 3.35.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/README.md +32 -0
- package/agents/adversarial-reviewer.md +2 -2
- package/agents/azure-devops-engineer.md +9 -1
- package/agents/cicd-platform-engineer.md +477 -0
- package/agents/ciclo-engineer.md +267 -0
- package/agents/debug-sidecar.md +5 -0
- package/agents/doc-sidecar.md +5 -0
- package/agents/frontend-engineer.md +173 -0
- package/agents/implementer.md +39 -23
- package/agents/methodology-researcher.md +2 -1
- package/agents/operational-artifact-builder.md +2 -1
- package/agents/orchestrator.md +54 -72
- package/agents/planner.md +2 -2
- package/agents/presentation-engineer.md +1 -1
- package/agents/python-data-engineer.md +214 -0
- package/agents/reviewer.md +16 -8
- package/agents/specifier.md +1 -1
- package/agents/task-generator.md +1 -1
- package/agents/test-sidecar.md +14 -3
- package/dist/install/DevCheckoutDetector.d.ts +30 -13
- package/dist/install/DevCheckoutDetector.d.ts.map +1 -1
- package/dist/install/DevCheckoutDetector.js +33 -16
- package/dist/install/DevCheckoutDetector.js.map +1 -1
- package/hooks/agent-git-guard.mjs +118 -0
- package/hooks/agent-git-guard.ts +166 -0
- package/hooks/hooks.json +78 -0
- package/hooks/playwright-output-guard.mjs +136 -0
- package/hooks/playwright-output-guard.ts +130 -0
- package/hooks/pre-agent-dispatch.mjs +346 -0
- package/hooks/pre-agent-dispatch.ts +287 -0
- package/hooks/session-start.mjs +8 -36
- package/hooks/session-start.ts +15 -72
- package/hooks/subagent-stop.mjs +0 -18
- package/hooks/subagent-stop.ts +29 -13
- package/hooks/verification-before-completion.mjs +105 -0
- package/hooks/verification-before-completion.ts +97 -0
- package/hooks-settings-template.json +19 -4
- package/manifest.json +125 -29
- package/package.json +1 -1
- package/personas/INDEX.md +46 -0
- package/skills/artifact-generator/SKILL.md +1 -1
- package/skills/artifact-production/SKILL.md +28 -0
- package/skills/backlog-synthesis/SKILL.md +8 -3
- package/skills/checkpoint-protocol/SKILL.md +7 -0
- package/skills/ciclo-lane-dispatch/SKILL.md +158 -0
- package/skills/infographic-engine/SKILL.md +116 -0
- package/skills/orchestrator/SKILL.md +95 -7
- package/skills/persona-library/SKILL.md +1 -1
- package/skills/project-conventions/SKILL.md +53 -30
- package/skills/project-management-enterprise/SKILL.md +1 -1
- package/skills/python-data-engineering/SKILL.md +792 -0
- package/skills/release/SKILL.md +29 -18
- package/skills/release/release.sh +65 -4
- package/skills/storybook-validated-loop/SKILL.md +110 -0
- package/skills/tdd-discipline/SKILL.md +16 -0
- package/skills/workflow-coordination/SKILL.md +117 -16
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DevCheckoutDetector.d.ts","sourceRoot":"","sources":["../../src/install/DevCheckoutDetector.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"DevCheckoutDetector.d.ts","sourceRoot":"","sources":["../../src/install/DevCheckoutDetector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AA4BlD,qBAAa,mBAAmB;IAClB,OAAO,CAAC,QAAQ,CAAC,aAAa;gBAAb,aAAa,EAAE,MAAM,EAAE;IAEpD;;;;OAIG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe;IAkBpC;;;;OAIG;IACH,OAAO,CAAC,mCAAmC;IAiB3C,OAAO,CAAC,eAAe;IASvB;;;;;OAKG;IACH,OAAO,CAAC,0BAA0B;IA4BlC,OAAO,CAAC,YAAY;CAOrB"}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* DevCheckoutDetector — read-only dual-condition heuristic (RF-002 / spec
|
|
3
|
-
* §5.1, ADR-004) for the "inverted precedence" risk between
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* the
|
|
7
|
-
* source — they
|
|
3
|
+
* §5.1, ADR-004) for the "inverted precedence" risk between the ADR-023
|
|
4
|
+
* canonical `packages/definitions/agents` (project-scope, authored source)
|
|
5
|
+
* and `~/.claude/skills` (user-scope, per Claude Code's documentation): once
|
|
6
|
+
* the installer lands, `~/.claude/agents` resolves from the distributed
|
|
7
|
+
* package while the monorepo's own authored source remains separate — they
|
|
8
|
+
* can diverge silently.
|
|
8
9
|
*
|
|
9
10
|
* Detection requires BOTH conditions (reduces false-positives, risk R-6):
|
|
10
11
|
* (i) walk-up from `cwd` to the nearest ancestor `package.json` whose
|
|
@@ -12,13 +13,27 @@
|
|
|
12
13
|
* (`@orchestrator-cli/core` — confirmed via direct Read of
|
|
13
14
|
* `/home/cmac/workOrch-Claude/orchestrator/package.json`, plan
|
|
14
15
|
* §11 Verify-Before-Assert);
|
|
15
|
-
* (ii) that same ancestor's
|
|
16
|
-
* (never symlinks — `lstatSync(...).isSymbolicLink()`,
|
|
17
|
-
* authored files from the C6/TD-204 INTERIM per-file
|
|
18
|
-
* slug (basename without `.md`) collides with
|
|
16
|
+
* (ii) that same ancestor's `packages/definitions/agents/*.md` contains
|
|
17
|
+
* REAL files (never symlinks — `lstatSync(...).isSymbolicLink()`,
|
|
18
|
+
* distinguishing authored files from the C6/TD-204 INTERIM per-file
|
|
19
|
+
* symlinks) whose slug (basename without `.md`) collides with
|
|
20
|
+
* `manifestSlugs`.
|
|
21
|
+
*
|
|
22
|
+
* REPOINTED (ADR-023 §7, S401): condition (ii) used to read
|
|
23
|
+
* `<root>/.claude/agents/` — the `.claude/` mirror. That mirror is being
|
|
24
|
+
* retired (RFC-040 §18.15 — `<repo>/.claude/{agents,skills}` gets deleted as
|
|
25
|
+
* part of the harness graduation to an installed-plugin route), which would
|
|
26
|
+
* silently blind this detector (measured, S401: with `.claude/agents`
|
|
27
|
+
* absent, `detect()` always returned `clean-install`, and the
|
|
28
|
+
* `cli-install.test.ts` TASK-034 regression guard caught it via a real,
|
|
29
|
+
* non-mocked `install` invocation timing out instead of aborting). Condition
|
|
30
|
+
* (ii) now reads `<root>/packages/definitions/agents/` — the ADR-023
|
|
31
|
+
* canonical SoT, which is NOT deleted by the travessia and is exactly the
|
|
32
|
+
* tree this monorepo's own contributors actually author. Condition (i) is
|
|
33
|
+
* unchanged.
|
|
19
34
|
*
|
|
20
35
|
* Never mutates anything — `detect()` only reads `package.json` and lists
|
|
21
|
-
*
|
|
36
|
+
* `packages/definitions/agents/`. This module is purely read-only by design.
|
|
22
37
|
*
|
|
23
38
|
* ---
|
|
24
39
|
* MEASURED WARNING TEXT (TASK-036/AC-007 gate — CLOSED, S385):
|
|
@@ -49,8 +64,8 @@ const MONOREPO_PACKAGE_NAME = '@orchestrator-cli/core';
|
|
|
49
64
|
* real practical risk it implies (silent drift), not a hypothetical.
|
|
50
65
|
*/
|
|
51
66
|
const DEV_CHECKOUT_WARNING_REASON = 'Dev-checkout detectado: este cwd pertence ao monorepo orchestrator e ' +
|
|
52
|
-
'
|
|
53
|
-
'manifest do pacote. Precedência MEDIDA empiricamente (AC-007/TASK-036, ' +
|
|
67
|
+
'`packages/definitions/agents/` contém arquivos reais cujos slugs colidem ' +
|
|
68
|
+
'com o manifest do pacote. Precedência MEDIDA empiricamente (AC-007/TASK-036, ' +
|
|
54
69
|
'gate S385, 3/3 runs determinísticos por eixo, probe isolado): agents ' +
|
|
55
70
|
'resolvem por PROJECT-scope (edições locais neste repo têm efeito ' +
|
|
56
71
|
'imediato numa sessão live) e skills resolvem por USER-scope ' +
|
|
@@ -66,7 +81,7 @@ export class DevCheckoutDetector {
|
|
|
66
81
|
this.manifestSlugs = manifestSlugs;
|
|
67
82
|
}
|
|
68
83
|
/**
|
|
69
|
-
* Read-only detection. Never throws on a missing
|
|
84
|
+
* Read-only detection. Never throws on a missing `packages/definitions/agents`
|
|
70
85
|
* directory or a missing/unparseable ancestor `package.json` — those
|
|
71
86
|
* cases simply resolve to `'clean-install'`.
|
|
72
87
|
*/
|
|
@@ -114,11 +129,13 @@ export class DevCheckoutDetector {
|
|
|
114
129
|
}
|
|
115
130
|
}
|
|
116
131
|
/**
|
|
117
|
-
* Real (non-symlink)
|
|
118
|
-
* (basename without `.md`) collides with `manifestSlugs`.
|
|
132
|
+
* Real (non-symlink) `packages/definitions/agents/*.md` files at `root`
|
|
133
|
+
* whose slug (basename without `.md`) collides with `manifestSlugs`.
|
|
134
|
+
* Repointed from `.claude/agents/` to the ADR-023 canonical SoT (§7,
|
|
135
|
+
* S401) — see the module JSDoc "REPOINTED" note above.
|
|
119
136
|
*/
|
|
120
137
|
findMatchingRealAgentSlugs(root) {
|
|
121
|
-
const agentsDir = join(root, '
|
|
138
|
+
const agentsDir = join(root, 'packages', 'definitions', 'agents');
|
|
122
139
|
if (!existsSync(agentsDir)) {
|
|
123
140
|
return [];
|
|
124
141
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DevCheckoutDetector.js","sourceRoot":"","sources":["../../src/install/DevCheckoutDetector.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"DevCheckoutDetector.js","sourceRoot":"","sources":["../../src/install/DevCheckoutDetector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { createRequire as __createRequireShim } from 'node:module'; const require = __createRequireShim(import.meta.url);
|
|
2
|
+
|
|
3
|
+
// hooks/lib/api-client.ts
|
|
4
|
+
import { readFileSync, writeFileSync, mkdirSync, statSync, unlinkSync, readdirSync } from "node:fs";
|
|
5
|
+
import { join, dirname } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
var __dirname = dirname(__filename);
|
|
9
|
+
var PROJECT_ROOT = join(__dirname, "..", "..", "..");
|
|
10
|
+
var STATE_DIR = join(PROJECT_ROOT, ".orchestrator", ".state");
|
|
11
|
+
var JWT_CACHE = join(STATE_DIR, "jwt-token");
|
|
12
|
+
var DEBUG_LOG = join(STATE_DIR, "hook-debug.log");
|
|
13
|
+
mkdirSync(STATE_DIR, { recursive: true });
|
|
14
|
+
var API_URL = process.env.ORCHESTRATOR_API_URL || "http://localhost:3001";
|
|
15
|
+
var AUTH_EMAIL = process.env.ORCHESTRATOR_ADMIN_EMAIL || process.env.ORCHESTRATOR_AUTH_EMAIL || "system@orchestrator.local";
|
|
16
|
+
var AUTH_PASSWORD = process.env.ORCHESTRATOR_ADMIN_PASSWORD || process.env.ORCHESTRATOR_AUTH_PASSWORD || "system123";
|
|
17
|
+
var PROJECT_ID = process.env.ORCHESTRATOR_PROJECT_ID || "";
|
|
18
|
+
function log(prefix, msg) {
|
|
19
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+Z$/, "Z");
|
|
20
|
+
try {
|
|
21
|
+
const line = `[${ts}] ${prefix}: ${msg}
|
|
22
|
+
`;
|
|
23
|
+
writeFileSync(DEBUG_LOG, line, { flag: "a" });
|
|
24
|
+
} catch {
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
var LAST_COMPLETED_FILE = join(STATE_DIR, "last-completed-workflow.json");
|
|
28
|
+
var LAST_COMPLETED_TTL_MS = 60 * 60 * 1e3;
|
|
29
|
+
var INVOCATIONS_DIR = join(STATE_DIR, "invocations");
|
|
30
|
+
var INVOCATION_STATE_TTL_MS = 4 * 60 * 60 * 1e3;
|
|
31
|
+
var DISPATCH_INTENT_DIR = join(STATE_DIR, "dispatch-intent");
|
|
32
|
+
async function readStdin() {
|
|
33
|
+
const chunks = [];
|
|
34
|
+
for await (const chunk of process.stdin) {
|
|
35
|
+
chunks.push(chunk);
|
|
36
|
+
}
|
|
37
|
+
const raw = Buffer.concat(chunks).toString("utf-8").trim();
|
|
38
|
+
if (!raw) return {};
|
|
39
|
+
try {
|
|
40
|
+
return JSON.parse(raw);
|
|
41
|
+
} catch {
|
|
42
|
+
return {};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function getField(obj, path) {
|
|
46
|
+
const parts = path.split(".");
|
|
47
|
+
let current = obj;
|
|
48
|
+
for (const part of parts) {
|
|
49
|
+
if (current == null || typeof current !== "object") return "";
|
|
50
|
+
current = current[part];
|
|
51
|
+
}
|
|
52
|
+
return current != null ? String(current) : "";
|
|
53
|
+
}
|
|
54
|
+
function outputPreToolUse(opts) {
|
|
55
|
+
const output = {
|
|
56
|
+
hookSpecificOutput: {
|
|
57
|
+
hookEventName: "PreToolUse",
|
|
58
|
+
permissionDecision: opts.decision,
|
|
59
|
+
...opts.reason && { permissionDecisionReason: opts.reason },
|
|
60
|
+
...opts.context && { additionalContext: opts.context }
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
console.log(JSON.stringify(output));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// hooks/agent-git-guard.ts
|
|
67
|
+
var HOOK = "AGENT-GIT-GUARD";
|
|
68
|
+
var defaultDeps = {
|
|
69
|
+
readStdin,
|
|
70
|
+
output: outputPreToolUse,
|
|
71
|
+
log,
|
|
72
|
+
getEnv: (key) => process.env[key]
|
|
73
|
+
};
|
|
74
|
+
var PUSH = /\bgit\s+push(\s|$)/;
|
|
75
|
+
var MERGE = /\bgit\s+merge(\s|$)/;
|
|
76
|
+
var GH_PR_MERGE = /\bgh\s+pr\s+merge(\s|$)/;
|
|
77
|
+
function commandIsPushOrMerge(command) {
|
|
78
|
+
return PUSH.test(command) || MERGE.test(command) || GH_PR_MERGE.test(command);
|
|
79
|
+
}
|
|
80
|
+
function evaluateGitGuard(command, agentId, agentType) {
|
|
81
|
+
if (!agentId) return { decision: "allow" };
|
|
82
|
+
if (!commandIsPushOrMerge(command)) return { decision: "allow" };
|
|
83
|
+
const who = agentType && agentType.length > 0 ? `Sub-agent '${agentType}'` : "Sub-agent";
|
|
84
|
+
return {
|
|
85
|
+
decision: "deny",
|
|
86
|
+
reason: `[AGENT-GIT-GUARD] ${who} is not permitted to run git push / git merge / gh pr merge. Per RFC-022 + TD-169, push and merge are owned EXCLUSIVELY by the main orchestrator (the main conversation). Make your changes locally and return them in your report \u2014 the orchestrator reviews, then pushes/merges. (Prevents the S288 unauthorized merge-to-main breach.)`
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
async function mainWithDeps(deps) {
|
|
90
|
+
const { readStdin: readStdin2, output, log: log2, getEnv } = deps;
|
|
91
|
+
const stdin = await readStdin2();
|
|
92
|
+
const command = getField(stdin, "tool_input.command") || getField(stdin, "command") || "";
|
|
93
|
+
const agentId = getField(stdin, "agent_id");
|
|
94
|
+
const agentType = getField(stdin, "agent_type");
|
|
95
|
+
if (getEnv("SKIP_AGENT_GIT_GUARD") === "true") {
|
|
96
|
+
log2(HOOK, `BYPASS (SKIP_AGENT_GIT_GUARD): ${command.slice(0, 60)}`);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const result = evaluateGitGuard(command, agentId, agentType);
|
|
100
|
+
if (result.decision === "deny") {
|
|
101
|
+
log2(HOOK, `DENY (agent=${agentType || "unknown"}): ${command.slice(0, 80)}`);
|
|
102
|
+
output({ decision: "deny", reason: result.reason });
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
log2(
|
|
106
|
+
HOOK,
|
|
107
|
+
`ALLOW (agent=${agentId ? agentType || "sub-agent" : "main"}): ${command.slice(0, 60)}`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
async function main() {
|
|
111
|
+
await mainWithDeps(defaultDeps);
|
|
112
|
+
}
|
|
113
|
+
main().catch(() => process.exit(0));
|
|
114
|
+
export {
|
|
115
|
+
commandIsPushOrMerge,
|
|
116
|
+
evaluateGitGuard,
|
|
117
|
+
mainWithDeps
|
|
118
|
+
};
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent-git-guard.ts — PreToolUse[Bash] hook (TD-169 enforcement)
|
|
3
|
+
*
|
|
4
|
+
* BLOCKS `git push`, `git merge`, and `gh pr merge` when the command is run by a
|
|
5
|
+
* SUB-AGENT, while ALLOWING the main orchestrator (main conversation) to run them.
|
|
6
|
+
*
|
|
7
|
+
* Mechanism: Claude Code injects `agent_id` into PreToolUse stdin ONLY when the
|
|
8
|
+
* hook fires inside a sub-agent call (see hook-reference: "Present only when the
|
|
9
|
+
* hook fires inside a subagent call"). workflow-guard.ts uses the same signal for
|
|
10
|
+
* Write/Edit (the sub-agent branch of decideWorkflowGuardAction). This hook extends
|
|
11
|
+
* it to the git escape-to-remote path.
|
|
12
|
+
*
|
|
13
|
+
* Why this exists: S288 governance breach — an `implementer` sub-agent autonomously
|
|
14
|
+
* committed + pushed + `gh pr merge`'d an unauthorized FK-drop to main. Prose
|
|
15
|
+
* commit-policy in dispatch prompts is unenforced (agents hold Bash + gh). This hook
|
|
16
|
+
* is the enforcement layer (cf. workflow-guard model; S288-handoff §TD-169).
|
|
17
|
+
*
|
|
18
|
+
* Scope: push / merge ONLY — NOT commit. A local commit by a sub-agent is recoverable
|
|
19
|
+
* and is sometimes legitimate (orchestrator Commit Policy option b). Push/merge to a
|
|
20
|
+
* remote is the unrecoverable breach vector, so that is what we deny.
|
|
21
|
+
*
|
|
22
|
+
* RFC-040 TD-204 §⑤-A (S366) — Classe-A asset-safety classification:
|
|
23
|
+
* this gate is classified Classe-A (same family as workflow-guard.ts's
|
|
24
|
+
* `.orchestrator`-registration and workflow-active checks — asset-safety
|
|
25
|
+
* gates that must remain intact everywhere, never silently no-op). UNLIKE
|
|
26
|
+
* workflow-guard.ts, this gate's decision (evaluateGitGuard) is a pure
|
|
27
|
+
* function of (command, agentId, agentType) ONLY — it never inspects any
|
|
28
|
+
* file_path or cwd. A sub-agent attempting `git push`/`git merge`/`gh pr
|
|
29
|
+
* merge` is denied identically no matter which project's working tree the
|
|
30
|
+
* Bash command runs against. This makes it **globally cwd-safe already**:
|
|
31
|
+
* no TD-204 ⑤-A remediation (project-root resolution / `.unmanaged`
|
|
32
|
+
* sentinel / unregistered-path branching) is needed here — there is no
|
|
33
|
+
* path-dependent branch to make cwd-scoped-safe in the first place. See
|
|
34
|
+
* tests/unit/hooks/agent-git-guard-path-invariance.test.ts for the
|
|
35
|
+
* property-style proof.
|
|
36
|
+
*
|
|
37
|
+
* Decision:
|
|
38
|
+
* - agent_id absent (main orchestrator) → silent pass-through (allow)
|
|
39
|
+
* - agent_id present + push/merge/gh-pr-merge → DENY
|
|
40
|
+
* - agent_id present + commit/read-only/other → silent pass-through (allow)
|
|
41
|
+
* - SKIP_AGENT_GIT_GUARD=true → allow (escape hatch, logged)
|
|
42
|
+
* - parse error / empty stdin → fail-open (allow)
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
import { log, readStdin, getField, outputPreToolUse } from './lib/api-client.js';
|
|
46
|
+
|
|
47
|
+
const HOOK = 'AGENT-GIT-GUARD';
|
|
48
|
+
|
|
49
|
+
// ─────────────────────────────────────────────────────────────
|
|
50
|
+
// Dependency injection interface (enables testing without IO)
|
|
51
|
+
// ─────────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
export interface HookDeps {
|
|
54
|
+
readStdin: () => Promise<Record<string, unknown>>;
|
|
55
|
+
output: (opts: { decision: 'allow' | 'deny'; reason?: string; context?: string }) => void;
|
|
56
|
+
log: (prefix: string, msg: string) => void;
|
|
57
|
+
getEnv: (key: string) => string | undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const defaultDeps: HookDeps = {
|
|
61
|
+
readStdin,
|
|
62
|
+
output: outputPreToolUse,
|
|
63
|
+
log,
|
|
64
|
+
getEnv: (key) => process.env[key],
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// ─────────────────────────────────────────────────────────────
|
|
68
|
+
// Pure helpers (exported for testing)
|
|
69
|
+
// ─────────────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
// `(\s|$)` after the verb avoids matching plumbing like `git merge-base`.
|
|
72
|
+
const PUSH = /\bgit\s+push(\s|$)/;
|
|
73
|
+
const MERGE = /\bgit\s+merge(\s|$)/;
|
|
74
|
+
const GH_PR_MERGE = /\bgh\s+pr\s+merge(\s|$)/;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* True when the bash command performs a push or merge (the escape-to-remote
|
|
78
|
+
* operations owned exclusively by the main orchestrator). Excludes `git merge-base`
|
|
79
|
+
* (read-only plumbing), `git commit`, and read-only git/gh commands.
|
|
80
|
+
*/
|
|
81
|
+
export function commandIsPushOrMerge(command: string): boolean {
|
|
82
|
+
return PUSH.test(command) || MERGE.test(command) || GH_PR_MERGE.test(command);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface GuardResult {
|
|
86
|
+
decision: 'allow' | 'deny';
|
|
87
|
+
reason?: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Pure decision: deny only when a sub-agent (agentId present) attempts a
|
|
92
|
+
* push/merge. Empty agentId == main orchestrator == always allowed.
|
|
93
|
+
*
|
|
94
|
+
* Path-independent by construction (RFC-040 TD-204 §⑤-A, Classe-A): the
|
|
95
|
+
* signature takes no file_path/cwd input, so the decision is provably the
|
|
96
|
+
* same for every project's working tree — globally cwd-safe without any
|
|
97
|
+
* project-root resolution.
|
|
98
|
+
*/
|
|
99
|
+
export function evaluateGitGuard(
|
|
100
|
+
command: string,
|
|
101
|
+
agentId: string,
|
|
102
|
+
agentType?: string
|
|
103
|
+
): GuardResult {
|
|
104
|
+
if (!agentId) return { decision: 'allow' }; // main orchestrator owns git
|
|
105
|
+
if (!commandIsPushOrMerge(command)) return { decision: 'allow' }; // commit/read-only fine
|
|
106
|
+
|
|
107
|
+
const who = agentType && agentType.length > 0 ? `Sub-agent '${agentType}'` : 'Sub-agent';
|
|
108
|
+
return {
|
|
109
|
+
decision: 'deny',
|
|
110
|
+
reason:
|
|
111
|
+
`[AGENT-GIT-GUARD] ${who} is not permitted to run git push / git merge / gh pr merge. ` +
|
|
112
|
+
`Per RFC-022 + TD-169, push and merge are owned EXCLUSIVELY by the main orchestrator ` +
|
|
113
|
+
`(the main conversation). Make your changes locally and return them in your report — ` +
|
|
114
|
+
`the orchestrator reviews, then pushes/merges. (Prevents the S288 unauthorized merge-to-main breach.)`,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ─────────────────────────────────────────────────────────────
|
|
119
|
+
// Core logic (exported for testing)
|
|
120
|
+
// ─────────────────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
export async function mainWithDeps(deps: HookDeps): Promise<void> {
|
|
123
|
+
const { readStdin, output, log, getEnv } = deps;
|
|
124
|
+
|
|
125
|
+
const stdin = await readStdin();
|
|
126
|
+
|
|
127
|
+
const command =
|
|
128
|
+
(getField(stdin, 'tool_input.command') as string | undefined) ||
|
|
129
|
+
(getField(stdin, 'command') as string | undefined) ||
|
|
130
|
+
'';
|
|
131
|
+
|
|
132
|
+
const agentId = getField(stdin, 'agent_id');
|
|
133
|
+
const agentType = getField(stdin, 'agent_type');
|
|
134
|
+
|
|
135
|
+
// Escape hatch (parity with workflow-guard SKIP_*). Process-env only — a
|
|
136
|
+
// sub-agent's transient shell env does not persist into the hook process.
|
|
137
|
+
if (getEnv('SKIP_AGENT_GIT_GUARD') === 'true') {
|
|
138
|
+
log(HOOK, `BYPASS (SKIP_AGENT_GIT_GUARD): ${command.slice(0, 60)}`);
|
|
139
|
+
return; // silent pass-through
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const result = evaluateGitGuard(command, agentId, agentType);
|
|
143
|
+
|
|
144
|
+
if (result.decision === 'deny') {
|
|
145
|
+
log(HOOK, `DENY (agent=${agentType || 'unknown'}): ${command.slice(0, 80)}`);
|
|
146
|
+
output({ decision: 'deny', reason: result.reason });
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Allow path is a silent pass-through so we never clobber the context that the
|
|
151
|
+
// sibling Bash hook (verification-before-completion) injects.
|
|
152
|
+
log(
|
|
153
|
+
HOOK,
|
|
154
|
+
`ALLOW (agent=${agentId ? agentType || 'sub-agent' : 'main'}): ${command.slice(0, 60)}`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ─────────────────────────────────────────────────────────────
|
|
159
|
+
// Entry point (production use)
|
|
160
|
+
// ─────────────────────────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
async function main(): Promise<void> {
|
|
163
|
+
await mainWithDeps(defaultDeps);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
main().catch(() => process.exit(0));
|
package/hooks/hooks.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"SessionStart": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "startup|resume",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{ "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/session-start.mjs\"", "timeout": 10000 },
|
|
8
|
+
{ "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/sprint-registry.mjs\"", "timeout": 5000 }
|
|
9
|
+
]
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"UserPromptSubmit": [
|
|
13
|
+
{
|
|
14
|
+
"matcher": "",
|
|
15
|
+
"hooks": [{ "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/user-prompt.mjs\"", "timeout": 10000 }]
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"PreToolUse": [
|
|
19
|
+
{
|
|
20
|
+
"matcher": "mcp__(plugin_orchestrator_)?orchestrator-tools__advancePhase",
|
|
21
|
+
"hooks": [{ "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gate-guardian.mjs\"", "timeout": 15000 }]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"matcher": "Write|Edit",
|
|
25
|
+
"hooks": [{ "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/workflow-guard.mjs\"", "timeout": 10000 }]
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"matcher": "Bash",
|
|
29
|
+
"hooks": [
|
|
30
|
+
{ "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/verification-before-completion.mjs\"", "timeout": 5000 },
|
|
31
|
+
{ "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/agent-git-guard.mjs\"", "timeout": 5000 }
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"matcher": "Agent",
|
|
36
|
+
"hooks": [{ "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/pre-agent-dispatch.mjs\"", "timeout": 5000 }]
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"matcher": "mcp__plugin_playwright_playwright__browser_take_screenshot|mcp__plugin_playwright_playwright__browser_pdf_save",
|
|
40
|
+
"hooks": [{ "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/playwright-output-guard.mjs\"", "timeout": 5000 }]
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
"SubagentStart": [
|
|
44
|
+
{
|
|
45
|
+
"matcher": "(orchestrator:)?(implementer|specifier|planner|task-generator|reviewer|researcher)",
|
|
46
|
+
"hooks": [{ "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/subagent-start.mjs\"", "timeout": 10000 }]
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
"SubagentStop": [
|
|
50
|
+
{
|
|
51
|
+
"matcher": "(orchestrator:)?(implementer|specifier|planner|task-generator|reviewer|researcher)",
|
|
52
|
+
"hooks": [{ "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/subagent-stop.mjs\"", "timeout": 30000 }]
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
"Stop": [
|
|
56
|
+
{
|
|
57
|
+
"matcher": "",
|
|
58
|
+
"hooks": [{ "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/dangling-guard.mjs\"", "timeout": 15000 }]
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
"PostToolUse": [
|
|
62
|
+
{
|
|
63
|
+
"matcher": "",
|
|
64
|
+
"hooks": [{ "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/mailbox-listener.mjs\"", "timeout": 5000 }]
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"matcher": "mcp__(plugin_orchestrator_)?orchestrator-tools__advancePhase",
|
|
68
|
+
"hooks": [{ "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/post-implement.mjs\"", "timeout": 15000 }]
|
|
69
|
+
}
|
|
70
|
+
],
|
|
71
|
+
"PostCompact": [
|
|
72
|
+
{
|
|
73
|
+
"matcher": "",
|
|
74
|
+
"hooks": [{ "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/post-compact.mjs\"", "timeout": 5000 }]
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { createRequire as __createRequireShim } from 'node:module'; const require = __createRequireShim(import.meta.url);
|
|
2
|
+
|
|
3
|
+
// hooks/playwright-output-guard.ts
|
|
4
|
+
import { dirname as dirname3, isAbsolute, normalize, resolve } from "node:path";
|
|
5
|
+
|
|
6
|
+
// hooks/lib/api-client.ts
|
|
7
|
+
import { readFileSync, writeFileSync, mkdirSync, statSync, unlinkSync, readdirSync } from "node:fs";
|
|
8
|
+
import { join as join2, dirname as dirname2 } from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
|
|
11
|
+
// ../project-root-resolver/dist/index.js
|
|
12
|
+
import { existsSync } from "node:fs";
|
|
13
|
+
import { dirname, join } from "node:path";
|
|
14
|
+
function resolveProjectRoot(cwd, sentinel = ".orchestrator") {
|
|
15
|
+
let currentDir = cwd;
|
|
16
|
+
for (; ; ) {
|
|
17
|
+
if (existsSync(join(currentDir, sentinel))) {
|
|
18
|
+
return currentDir;
|
|
19
|
+
}
|
|
20
|
+
const parentDir = dirname(currentDir);
|
|
21
|
+
if (parentDir === currentDir) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
currentDir = parentDir;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// hooks/lib/api-client.ts
|
|
29
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
30
|
+
var __dirname = dirname2(__filename);
|
|
31
|
+
var PROJECT_ROOT = join2(__dirname, "..", "..", "..");
|
|
32
|
+
var STATE_DIR = join2(PROJECT_ROOT, ".orchestrator", ".state");
|
|
33
|
+
var JWT_CACHE = join2(STATE_DIR, "jwt-token");
|
|
34
|
+
var DEBUG_LOG = join2(STATE_DIR, "hook-debug.log");
|
|
35
|
+
mkdirSync(STATE_DIR, { recursive: true });
|
|
36
|
+
var API_URL = process.env.ORCHESTRATOR_API_URL || "http://localhost:3001";
|
|
37
|
+
var AUTH_EMAIL = process.env.ORCHESTRATOR_ADMIN_EMAIL || process.env.ORCHESTRATOR_AUTH_EMAIL || "system@orchestrator.local";
|
|
38
|
+
var AUTH_PASSWORD = process.env.ORCHESTRATOR_ADMIN_PASSWORD || process.env.ORCHESTRATOR_AUTH_PASSWORD || "system123";
|
|
39
|
+
var PROJECT_ID = process.env.ORCHESTRATOR_PROJECT_ID || "";
|
|
40
|
+
function resolveHookProjectRoot(cwd) {
|
|
41
|
+
return resolveProjectRoot(cwd);
|
|
42
|
+
}
|
|
43
|
+
function log(prefix, msg) {
|
|
44
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+Z$/, "Z");
|
|
45
|
+
try {
|
|
46
|
+
const line = `[${ts}] ${prefix}: ${msg}
|
|
47
|
+
`;
|
|
48
|
+
writeFileSync(DEBUG_LOG, line, { flag: "a" });
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
var LAST_COMPLETED_FILE = join2(STATE_DIR, "last-completed-workflow.json");
|
|
53
|
+
var LAST_COMPLETED_TTL_MS = 60 * 60 * 1e3;
|
|
54
|
+
var INVOCATIONS_DIR = join2(STATE_DIR, "invocations");
|
|
55
|
+
var INVOCATION_STATE_TTL_MS = 4 * 60 * 60 * 1e3;
|
|
56
|
+
var DISPATCH_INTENT_DIR = join2(STATE_DIR, "dispatch-intent");
|
|
57
|
+
async function readStdin() {
|
|
58
|
+
const chunks = [];
|
|
59
|
+
for await (const chunk of process.stdin) {
|
|
60
|
+
chunks.push(chunk);
|
|
61
|
+
}
|
|
62
|
+
const raw = Buffer.concat(chunks).toString("utf-8").trim();
|
|
63
|
+
if (!raw) return {};
|
|
64
|
+
try {
|
|
65
|
+
return JSON.parse(raw);
|
|
66
|
+
} catch {
|
|
67
|
+
return {};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function getField(obj, path) {
|
|
71
|
+
const parts = path.split(".");
|
|
72
|
+
let current = obj;
|
|
73
|
+
for (const part of parts) {
|
|
74
|
+
if (current == null || typeof current !== "object") return "";
|
|
75
|
+
current = current[part];
|
|
76
|
+
}
|
|
77
|
+
return current != null ? String(current) : "";
|
|
78
|
+
}
|
|
79
|
+
function outputPreToolUse(opts) {
|
|
80
|
+
const output = {
|
|
81
|
+
hookSpecificOutput: {
|
|
82
|
+
hookEventName: "PreToolUse",
|
|
83
|
+
permissionDecision: opts.decision,
|
|
84
|
+
...opts.reason && { permissionDecisionReason: opts.reason },
|
|
85
|
+
...opts.context && { additionalContext: opts.context }
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
console.log(JSON.stringify(output));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// hooks/playwright-output-guard.ts
|
|
92
|
+
var HOOK = "PLAYWRIGHT-OUTPUT-GUARD";
|
|
93
|
+
var PROJECT_ROOT2 = process.env.CLAUDE_PROJECT_DIR ?? resolveHookProjectRoot(process.cwd()) ?? process.cwd();
|
|
94
|
+
var GUIDANCE = 'Playwright Output Guard: screenshots n\xE3o podem cair na raiz do repo (auto-checkpoint os varre \u2014 recorr\xEAncia S266-S268). Use `filename: ".playwright-mcp/<nome>.png"` (descart\xE1vel, gitignored), `filename: ".cobaia-evidence/<sess\xE3o>/<nome>.png"` (evid\xEAncia a versionar), ou omita `filename` (default j\xE1 vai para .playwright-mcp/).';
|
|
95
|
+
function evaluateFilename(filename) {
|
|
96
|
+
if (!filename) return { decision: "allow" };
|
|
97
|
+
const resolved = isAbsolute(filename) ? normalize(filename) : resolve(PROJECT_ROOT2, filename);
|
|
98
|
+
const dir = dirname3(resolved);
|
|
99
|
+
if (dir === PROJECT_ROOT2) {
|
|
100
|
+
return { decision: "deny", reason: GUIDANCE };
|
|
101
|
+
}
|
|
102
|
+
if (!resolved.startsWith(PROJECT_ROOT2 + "/") && !isAbsolute(filename)) {
|
|
103
|
+
return { decision: "deny", reason: GUIDANCE };
|
|
104
|
+
}
|
|
105
|
+
return { decision: "allow" };
|
|
106
|
+
}
|
|
107
|
+
var defaultDeps = {
|
|
108
|
+
readStdin,
|
|
109
|
+
output: outputPreToolUse,
|
|
110
|
+
log
|
|
111
|
+
};
|
|
112
|
+
async function mainWithDeps(deps) {
|
|
113
|
+
const stdin = await deps.readStdin();
|
|
114
|
+
const filename = getField(stdin, "tool_input.filename");
|
|
115
|
+
const result = evaluateFilename(filename || void 0);
|
|
116
|
+
deps.log(HOOK, `filename=${filename || "(none)"} decision=${result.decision}`);
|
|
117
|
+
if (result.decision === "deny") {
|
|
118
|
+
deps.output({ decision: "deny", reason: result.reason });
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
deps.output({
|
|
122
|
+
decision: "allow",
|
|
123
|
+
...filename ? {} : {
|
|
124
|
+
context: "[PLAYWRIGHT-OUTPUT-GUARD] sem filename: output em .playwright-mcp/ (gitignored)."
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
async function main() {
|
|
129
|
+
await mainWithDeps(defaultDeps);
|
|
130
|
+
}
|
|
131
|
+
main().catch(() => process.exit(0));
|
|
132
|
+
export {
|
|
133
|
+
PROJECT_ROOT2 as PROJECT_ROOT,
|
|
134
|
+
evaluateFilename,
|
|
135
|
+
mainWithDeps
|
|
136
|
+
};
|