@orchestrator-claude/definitions 3.44.0 → 3.44.1
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/agents/adversarial-reviewer.md +35 -29
- package/agents/agentic-architect.md +1 -1
- package/agents/api-extractor.md +1 -1
- package/agents/architecture-mediator.md +1 -1
- package/agents/azure-devops-engineer.md +1 -1
- package/agents/backend-architect.md +1 -1
- package/agents/business-rule-miner.md +1 -1
- package/agents/cicd-platform-engineer.md +1 -1
- package/agents/ciclo-engineer.md +1 -1
- package/agents/code-archaeologist.md +1 -1
- package/agents/contract-architect.md +1 -1
- package/agents/datamodel-architect.md +1 -1
- package/agents/frontend-architect.md +1 -1
- package/agents/frontend-engineer.md +1 -1
- package/agents/harness-architect.md +1 -1
- package/agents/harness-engineer.md +1 -1
- package/agents/implementer.md +1 -1
- package/agents/infra-architect.md +1 -1
- package/agents/legacy-discoverer.md +1 -1
- package/agents/legacy-synthesizer.md +1 -1
- package/agents/methodology-researcher.md +2 -1
- package/agents/operational-artifact-builder.md +1 -1
- package/agents/planner.md +1 -1
- package/agents/presentation-engineer.md +1 -1
- package/agents/python-data-engineer.md +1 -1
- package/agents/researcher.md +1 -1
- package/agents/reviewer.md +1 -1
- package/agents/schema-extractor.md +1 -1
- package/agents/specifier.md +1 -1
- package/agents/task-generator.md +1 -1
- package/hooks/agent-git-guard.mjs +0 -2
- package/hooks/dangling-guard.mjs +18 -3
- package/hooks/gate-guardian.mjs +8244 -20
- package/hooks/gate-guardian.ts +154 -24
- package/hooks/lib/api-client.ts +30 -7
- package/hooks/lib/artifact-gate.ts +518 -0
- package/hooks/lib/artifact-producers.ts +106 -0
- package/hooks/lib/workflow-artifact-client.ts +351 -0
- package/hooks/mailbox-listener.mjs +0 -2
- package/hooks/playwright-output-guard.mjs +0 -2
- package/hooks/post-implement.mjs +18 -3
- package/hooks/pre-agent-dispatch.mjs +18 -3
- package/hooks/security-gate-honest.mjs +0 -2
- package/hooks/session-start.mjs +18 -3
- package/hooks/subagent-start.mjs +18 -3
- package/hooks/subagent-stop.mjs +18 -3
- package/hooks/user-prompt.mjs +18 -3
- package/hooks/verification-before-completion.mjs +0 -2
- package/hooks/workflow-guard.mjs +504 -29
- package/hooks/workflow-guard.ts +370 -27
- package/manifest.json +46 -46
- package/package.json +1 -1
package/hooks/workflow-guard.mjs
CHANGED
|
@@ -7321,9 +7321,69 @@ var require_dist = __commonJS({
|
|
|
7321
7321
|
});
|
|
7322
7322
|
|
|
7323
7323
|
// hooks/workflow-guard.ts
|
|
7324
|
-
import { dirname as dirname2, join as join4 } from "node:path";
|
|
7324
|
+
import { dirname as dirname2, join as join4, resolve as resolvePath, sep as PATH_SEP } from "node:path";
|
|
7325
7325
|
import { existsSync as existsSync3 } from "node:fs";
|
|
7326
7326
|
|
|
7327
|
+
// ../vault-projection/dist/workflowArtifact/path.js
|
|
7328
|
+
var WORKFLOW_ARTIFACT_ROOT = "project-guidelines/workflows";
|
|
7329
|
+
var PATH_PATTERN = new RegExp(`^${WORKFLOW_ARTIFACT_ROOT}/([A-Za-z0-9][A-Za-z0-9-]*)/([a-z][a-z0-9_-]*)/([a-z0-9][a-z0-9_-]*)\\.md$`);
|
|
7330
|
+
function parseVaultPath(path) {
|
|
7331
|
+
const match = PATH_PATTERN.exec(path);
|
|
7332
|
+
if (match === null)
|
|
7333
|
+
return null;
|
|
7334
|
+
const [, workflowId, phase, artifactType] = match;
|
|
7335
|
+
if (workflowId === void 0 || phase === void 0 || artifactType === void 0)
|
|
7336
|
+
return null;
|
|
7337
|
+
return { workflowId, phase, artifactType };
|
|
7338
|
+
}
|
|
7339
|
+
|
|
7340
|
+
// ../vault-projection/dist/workflowArtifact/wire.js
|
|
7341
|
+
var VERIFY_VERDICT_CODES = [
|
|
7342
|
+
"ARTIFACT_INTEGRITY_VIOLATED",
|
|
7343
|
+
"ARTIFACT_NOT_REGISTERED",
|
|
7344
|
+
"ARTIFACT_CONTENT_MISSING"
|
|
7345
|
+
];
|
|
7346
|
+
var WORKFLOW_ARTIFACT_ERROR_CODES = [
|
|
7347
|
+
"WORKFLOW_NOT_FOUND",
|
|
7348
|
+
"PHASE_NOT_CURRENT",
|
|
7349
|
+
"INVALID_ARTIFACT_TYPE",
|
|
7350
|
+
"INVALID_CONTENT_HASH",
|
|
7351
|
+
"VALIDATION_ERROR",
|
|
7352
|
+
"PATH_MISMATCH",
|
|
7353
|
+
"IMPLEMENTS_WITHOUT_RFC",
|
|
7354
|
+
"IMPLEMENTS_MISMATCH",
|
|
7355
|
+
"INVALID_FRONTMATTER",
|
|
7356
|
+
"INTERNAL_ERROR"
|
|
7357
|
+
];
|
|
7358
|
+
var PROBLEM_TYPE_BASE = "https://api.orchestrator.io/errors/";
|
|
7359
|
+
function problemCode(body) {
|
|
7360
|
+
if (typeof body !== "object" || body === null)
|
|
7361
|
+
return null;
|
|
7362
|
+
const { code, type } = body;
|
|
7363
|
+
if (typeof code === "string" && code.length > 0)
|
|
7364
|
+
return code;
|
|
7365
|
+
if (typeof type === "string" && type.startsWith(PROBLEM_TYPE_BASE)) {
|
|
7366
|
+
const tail = type.slice(PROBLEM_TYPE_BASE.length);
|
|
7367
|
+
return tail.length > 0 && !tail.includes("/") ? tail : null;
|
|
7368
|
+
}
|
|
7369
|
+
return null;
|
|
7370
|
+
}
|
|
7371
|
+
|
|
7372
|
+
// ../vault-projection/dist/workflowArtifact/document.js
|
|
7373
|
+
var import_yaml2 = __toESM(require_dist(), 1);
|
|
7374
|
+
|
|
7375
|
+
// ../vault-projection/dist/vaultWalker.js
|
|
7376
|
+
var import_yaml = __toESM(require_dist(), 1);
|
|
7377
|
+
|
|
7378
|
+
// ../vault-projection/dist/workflowArtifact/document.js
|
|
7379
|
+
var utf8 = new TextDecoder("utf-8", { ignoreBOM: true });
|
|
7380
|
+
|
|
7381
|
+
// ../vault-projection/dist/gate/constitution.js
|
|
7382
|
+
var import_yaml4 = __toESM(require_dist(), 1);
|
|
7383
|
+
|
|
7384
|
+
// ../vault-projection/dist/relationshipsVocab.js
|
|
7385
|
+
var import_yaml3 = __toESM(require_dist(), 1);
|
|
7386
|
+
|
|
7327
7387
|
// hooks/lib/api-client.ts
|
|
7328
7388
|
import { readFileSync, writeFileSync, mkdirSync, statSync, unlinkSync, readdirSync } from "node:fs";
|
|
7329
7389
|
import { join as join2 } from "node:path";
|
|
@@ -7385,8 +7445,15 @@ if ((STATE_DIR_OVERRIDE === void 0 || STATE_DIR_OVERRIDE === "") && PROJECT_ROOT
|
|
|
7385
7445
|
);
|
|
7386
7446
|
}
|
|
7387
7447
|
var API_URL = process.env.ORCHESTRATOR_API_URL || "http://localhost:3001";
|
|
7388
|
-
var
|
|
7389
|
-
var
|
|
7448
|
+
var DEFAULT_AUTH_EMAIL = "system@orchestrator.local";
|
|
7449
|
+
var MISSING_CREDENTIAL_MESSAGE = "no credential configured (ORCHESTRATOR_ADMIN_PASSWORD / ORCHESTRATOR_AUTH_PASSWORD unset or empty) - login not attempted";
|
|
7450
|
+
var missingCredentialLogged = false;
|
|
7451
|
+
function resolveAuthCredentials() {
|
|
7452
|
+
const password = (process.env.ORCHESTRATOR_ADMIN_PASSWORD ?? "").trim() || (process.env.ORCHESTRATOR_AUTH_PASSWORD ?? "").trim();
|
|
7453
|
+
if (!password) return null;
|
|
7454
|
+
const email = process.env.ORCHESTRATOR_ADMIN_EMAIL || process.env.ORCHESTRATOR_AUTH_EMAIL || DEFAULT_AUTH_EMAIL;
|
|
7455
|
+
return { email, password };
|
|
7456
|
+
}
|
|
7390
7457
|
var PROJECT_ID = process.env.ORCHESTRATOR_PROJECT_ID || "";
|
|
7391
7458
|
function resolveHookProjectRoot(cwd) {
|
|
7392
7459
|
return resolveProjectRoot(cwd);
|
|
@@ -7427,6 +7494,9 @@ async function ensureProjectIdResolved(token) {
|
|
|
7427
7494
|
);
|
|
7428
7495
|
}
|
|
7429
7496
|
}
|
|
7497
|
+
function getEffectiveProjectId() {
|
|
7498
|
+
return PROJECT_ID;
|
|
7499
|
+
}
|
|
7430
7500
|
function normalizeAgentName(subagentType) {
|
|
7431
7501
|
const i = subagentType.lastIndexOf(":");
|
|
7432
7502
|
return i === -1 ? subagentType : subagentType.slice(i + 1);
|
|
@@ -7452,6 +7522,14 @@ function getCachedToken() {
|
|
|
7452
7522
|
return null;
|
|
7453
7523
|
}
|
|
7454
7524
|
async function login() {
|
|
7525
|
+
const credentials = resolveAuthCredentials();
|
|
7526
|
+
if (credentials === null) {
|
|
7527
|
+
if (!missingCredentialLogged) {
|
|
7528
|
+
missingCredentialLogged = true;
|
|
7529
|
+
log("API-CLIENT", MISSING_CREDENTIAL_MESSAGE);
|
|
7530
|
+
}
|
|
7531
|
+
return null;
|
|
7532
|
+
}
|
|
7455
7533
|
try {
|
|
7456
7534
|
const resp = await fetch(`${API_URL}/api/v1/auth/login`, {
|
|
7457
7535
|
method: "POST",
|
|
@@ -7459,7 +7537,7 @@ async function login() {
|
|
|
7459
7537
|
"Content-Type": "application/json",
|
|
7460
7538
|
"X-Project-ID": PROJECT_ID
|
|
7461
7539
|
},
|
|
7462
|
-
body: JSON.stringify({ email:
|
|
7540
|
+
body: JSON.stringify({ email: credentials.email, password: credentials.password }),
|
|
7463
7541
|
signal: AbortSignal.timeout(5e3)
|
|
7464
7542
|
});
|
|
7465
7543
|
if (!resp.ok) return null;
|
|
@@ -7567,7 +7645,7 @@ function outputPreToolUse(opts) {
|
|
|
7567
7645
|
}
|
|
7568
7646
|
|
|
7569
7647
|
// hooks/lib/observations-helper.ts
|
|
7570
|
-
var
|
|
7648
|
+
var import_yaml5 = __toESM(require_dist(), 1);
|
|
7571
7649
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2 } from "node:fs";
|
|
7572
7650
|
import { join as join3 } from "node:path";
|
|
7573
7651
|
var MAX_ENTRIES = 50;
|
|
@@ -7589,7 +7667,7 @@ function appendObservation(memoryDir, agent, entry) {
|
|
|
7589
7667
|
if (existsSync2(filePath)) {
|
|
7590
7668
|
try {
|
|
7591
7669
|
const raw = readFileSync2(filePath, "utf-8");
|
|
7592
|
-
const parsed = (0,
|
|
7670
|
+
const parsed = (0, import_yaml5.parse)(raw);
|
|
7593
7671
|
if (parsed?.entries && Array.isArray(parsed.entries)) {
|
|
7594
7672
|
data = {
|
|
7595
7673
|
schema: "observations/v1",
|
|
@@ -7608,7 +7686,7 @@ function appendObservation(memoryDir, agent, entry) {
|
|
|
7608
7686
|
if (data.entries.length > MAX_ENTRIES) {
|
|
7609
7687
|
data.entries = data.entries.slice(data.entries.length - MAX_ENTRIES);
|
|
7610
7688
|
}
|
|
7611
|
-
writeFileSync2(filePath, (0,
|
|
7689
|
+
writeFileSync2(filePath, (0, import_yaml5.stringify)(data), "utf-8");
|
|
7612
7690
|
} catch (err) {
|
|
7613
7691
|
console.warn(
|
|
7614
7692
|
`[OBSERVATIONS] Failed to write observation for agent '${agent}': ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -7616,8 +7694,356 @@ function appendObservation(memoryDir, agent, entry) {
|
|
|
7616
7694
|
}
|
|
7617
7695
|
}
|
|
7618
7696
|
|
|
7697
|
+
// hooks/lib/artifact-producers.ts
|
|
7698
|
+
var NAMES = [
|
|
7699
|
+
"adversarial-reviewer",
|
|
7700
|
+
"agentic-architect",
|
|
7701
|
+
"api-extractor",
|
|
7702
|
+
"architecture-mediator",
|
|
7703
|
+
"azure-devops-engineer",
|
|
7704
|
+
"backend-architect",
|
|
7705
|
+
"business-rule-miner",
|
|
7706
|
+
"cicd-platform-engineer",
|
|
7707
|
+
"ciclo-engineer",
|
|
7708
|
+
"code-archaeologist",
|
|
7709
|
+
"contract-architect",
|
|
7710
|
+
"datamodel-architect",
|
|
7711
|
+
"frontend-architect",
|
|
7712
|
+
"frontend-engineer",
|
|
7713
|
+
"harness-architect",
|
|
7714
|
+
"harness-engineer",
|
|
7715
|
+
"implementer",
|
|
7716
|
+
"infra-architect",
|
|
7717
|
+
"legacy-discoverer",
|
|
7718
|
+
"legacy-synthesizer",
|
|
7719
|
+
"methodology-researcher",
|
|
7720
|
+
"operational-artifact-builder",
|
|
7721
|
+
"planner",
|
|
7722
|
+
"presentation-engineer",
|
|
7723
|
+
"python-data-engineer",
|
|
7724
|
+
"researcher",
|
|
7725
|
+
"reviewer",
|
|
7726
|
+
"schema-extractor",
|
|
7727
|
+
"specifier",
|
|
7728
|
+
"task-generator"
|
|
7729
|
+
];
|
|
7730
|
+
function refuser(constant) {
|
|
7731
|
+
return () => {
|
|
7732
|
+
throw new TypeError(`${constant} is frozen: edit the list in artifact-producers.ts`);
|
|
7733
|
+
};
|
|
7734
|
+
}
|
|
7735
|
+
function frozenSet(values, constant) {
|
|
7736
|
+
const set = new Set(values);
|
|
7737
|
+
const refuse = refuser(constant);
|
|
7738
|
+
Object.defineProperties(set, {
|
|
7739
|
+
add: { value: refuse },
|
|
7740
|
+
delete: { value: refuse },
|
|
7741
|
+
clear: { value: refuse }
|
|
7742
|
+
});
|
|
7743
|
+
return Object.freeze(set);
|
|
7744
|
+
}
|
|
7745
|
+
var ARTIFACT_PRODUCER_AGENTS = frozenSet(
|
|
7746
|
+
NAMES,
|
|
7747
|
+
"ARTIFACT_PRODUCER_AGENTS"
|
|
7748
|
+
);
|
|
7749
|
+
var PINNED_ADVERSARIAL_REVIEW_TYPE = "adversarial_review";
|
|
7750
|
+
function frozenMap(entries) {
|
|
7751
|
+
const map = new Map(entries);
|
|
7752
|
+
const refuse = refuser("VAULT_ONLY_WRITERS");
|
|
7753
|
+
Object.defineProperties(map, {
|
|
7754
|
+
set: { value: refuse },
|
|
7755
|
+
delete: { value: refuse },
|
|
7756
|
+
clear: { value: refuse }
|
|
7757
|
+
});
|
|
7758
|
+
return Object.freeze(map);
|
|
7759
|
+
}
|
|
7760
|
+
var VAULT_ONLY_WRITERS = frozenMap([
|
|
7761
|
+
["adversarial-reviewer", frozenSet([PINNED_ADVERSARIAL_REVIEW_TYPE], "VAULT_ONLY_WRITERS")],
|
|
7762
|
+
["methodology-researcher", null]
|
|
7763
|
+
]);
|
|
7764
|
+
|
|
7765
|
+
// hooks/lib/workflow-artifact-client.ts
|
|
7766
|
+
function defaultClientDeps() {
|
|
7767
|
+
const envUrl = process.env.ORCHESTRATOR_API_URL;
|
|
7768
|
+
return {
|
|
7769
|
+
fetch: (input, init) => fetch(input, init),
|
|
7770
|
+
getToken,
|
|
7771
|
+
getProjectId: getEffectiveProjectId,
|
|
7772
|
+
// An EMPTY value falls back too, exactly like api-client.ts (`||` there).
|
|
7773
|
+
apiUrl: envUrl !== void 0 && envUrl !== "" ? envUrl : "http://localhost:3001",
|
|
7774
|
+
timeoutMs: 5e3
|
|
7775
|
+
};
|
|
7776
|
+
}
|
|
7777
|
+
var AUTH_CODES = /* @__PURE__ */ new Set(["unauthorized", "forbidden"]);
|
|
7778
|
+
var REJECT_CODES = /* @__PURE__ */ new Set([
|
|
7779
|
+
...WORKFLOW_ARTIFACT_ERROR_CODES.filter((c) => c !== "INTERNAL_ERROR"),
|
|
7780
|
+
"FST_ERR_VALIDATION"
|
|
7781
|
+
]);
|
|
7782
|
+
var VERDICTS = new Set(VERIFY_VERDICT_CODES);
|
|
7783
|
+
var isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
7784
|
+
function causeOfThrow(e) {
|
|
7785
|
+
const name = e?.name;
|
|
7786
|
+
return name === "TimeoutError" || name === "AbortError" ? "timeout" : "network";
|
|
7787
|
+
}
|
|
7788
|
+
async function readJson(resp) {
|
|
7789
|
+
try {
|
|
7790
|
+
return { parsed: true, body: await resp.json() };
|
|
7791
|
+
} catch {
|
|
7792
|
+
return { parsed: false };
|
|
7793
|
+
}
|
|
7794
|
+
}
|
|
7795
|
+
async function send(deps, path, body) {
|
|
7796
|
+
const token = await deps.getToken();
|
|
7797
|
+
if (token === null || token === "") {
|
|
7798
|
+
return { ok: false, failure: { kind: "unavailable", cause: "no-token" } };
|
|
7799
|
+
}
|
|
7800
|
+
const headers = {
|
|
7801
|
+
Authorization: `Bearer ${token}`,
|
|
7802
|
+
"X-Project-ID": deps.getProjectId()
|
|
7803
|
+
};
|
|
7804
|
+
if (body !== void 0) headers["Content-Type"] = "application/json";
|
|
7805
|
+
try {
|
|
7806
|
+
const resp = await deps.fetch(`${deps.apiUrl}${path}`, {
|
|
7807
|
+
method: body === void 0 ? "GET" : "POST",
|
|
7808
|
+
headers,
|
|
7809
|
+
...body !== void 0 && { body: JSON.stringify(body) },
|
|
7810
|
+
signal: AbortSignal.timeout(deps.timeoutMs)
|
|
7811
|
+
});
|
|
7812
|
+
return { ok: true, resp };
|
|
7813
|
+
} catch (e) {
|
|
7814
|
+
return { ok: false, failure: { kind: "unavailable", cause: causeOfThrow(e) } };
|
|
7815
|
+
}
|
|
7816
|
+
}
|
|
7817
|
+
async function classifyFailure(resp, known) {
|
|
7818
|
+
const read = await readJson(resp);
|
|
7819
|
+
const code = read.parsed ? problemCode(read.body) : null;
|
|
7820
|
+
const detail = read.parsed && isObject(read.body) && typeof read.body.detail === "string" ? read.body.detail : void 0;
|
|
7821
|
+
const base = {
|
|
7822
|
+
httpStatus: resp.status,
|
|
7823
|
+
...code !== null && { code },
|
|
7824
|
+
...detail !== void 0 && { detail }
|
|
7825
|
+
};
|
|
7826
|
+
if (resp.status >= 500) return { kind: "unavailable", cause: "http-5xx", ...base };
|
|
7827
|
+
if (code !== null && AUTH_CODES.has(code)) {
|
|
7828
|
+
return { kind: "unavailable", cause: "unauthorized", ...base };
|
|
7829
|
+
}
|
|
7830
|
+
if (code !== null && known.has(code)) {
|
|
7831
|
+
return {
|
|
7832
|
+
kind: "rejected",
|
|
7833
|
+
httpStatus: resp.status,
|
|
7834
|
+
code,
|
|
7835
|
+
...detail !== void 0 && { detail }
|
|
7836
|
+
};
|
|
7837
|
+
}
|
|
7838
|
+
return { kind: "unavailable", cause: "unknown-response", ...base };
|
|
7839
|
+
}
|
|
7840
|
+
var badBody = (httpStatus) => ({
|
|
7841
|
+
kind: "unavailable",
|
|
7842
|
+
cause: "bad-body",
|
|
7843
|
+
httpStatus
|
|
7844
|
+
});
|
|
7845
|
+
async function guard(run) {
|
|
7846
|
+
try {
|
|
7847
|
+
return await run();
|
|
7848
|
+
} catch (e) {
|
|
7849
|
+
return {
|
|
7850
|
+
kind: "unavailable",
|
|
7851
|
+
cause: "exception",
|
|
7852
|
+
detail: e instanceof Error ? e.message : String(e)
|
|
7853
|
+
};
|
|
7854
|
+
}
|
|
7855
|
+
}
|
|
7856
|
+
function probeWorkflow(workflowId, deps = defaultClientDeps()) {
|
|
7857
|
+
return guard(async () => {
|
|
7858
|
+
const sent = await send(deps, `/api/v1/workflows/${encodeURIComponent(workflowId)}/status`);
|
|
7859
|
+
if (!sent.ok) return sent.failure;
|
|
7860
|
+
const { resp } = sent;
|
|
7861
|
+
if (!resp.ok) {
|
|
7862
|
+
const failure = await classifyFailure(resp, /* @__PURE__ */ new Set(["not-found"]));
|
|
7863
|
+
return failure.kind === "rejected" ? { kind: "not-found" } : failure;
|
|
7864
|
+
}
|
|
7865
|
+
const read = await readJson(resp);
|
|
7866
|
+
if (!read.parsed || !isObject(read.body)) return badBody(resp.status);
|
|
7867
|
+
const { workflowId: id, status, currentPhase } = read.body;
|
|
7868
|
+
const phaseOk = currentPhase === null || typeof currentPhase === "string";
|
|
7869
|
+
if (id !== workflowId || typeof status !== "string" || !phaseOk) return badBody(resp.status);
|
|
7870
|
+
return { kind: "ok", workflowId: id, status, currentPhase };
|
|
7871
|
+
});
|
|
7872
|
+
}
|
|
7873
|
+
|
|
7619
7874
|
// hooks/workflow-guard.ts
|
|
7620
7875
|
var HOOK = "WORKFLOW-GUARD";
|
|
7876
|
+
var WORKFLOWS_SEGMENT = `/${WORKFLOW_ARTIFACT_ROOT}/`;
|
|
7877
|
+
var ACTIVE_WORKFLOW_STATUSES = /* @__PURE__ */ new Set([
|
|
7878
|
+
"in_progress",
|
|
7879
|
+
"awaiting_agent",
|
|
7880
|
+
"awaiting_approval"
|
|
7881
|
+
]);
|
|
7882
|
+
var R7_RUNBOOK = [
|
|
7883
|
+
'Runbook "API fora" (RFC-044 F3):',
|
|
7884
|
+
' 1. curl -sS -m 3 "${ORCHESTRATOR_API_URL:-http://localhost:3001}/api/v1/health" (200 = API up)',
|
|
7885
|
+
" 2. 200 but still denied (R7-NO-TOKEN, or cause no-token): check ORCHESTRATOR_ADMIN_EMAIL, ORCHESTRATOR_ADMIN_PASSWORD, ORCHESTRATOR_PROJECT_ID",
|
|
7886
|
+
" 3. no answer: bring the API back up (the project container/service); the hook retries on the next Write",
|
|
7887
|
+
"SKIP_WORKFLOW_GUARD nao se aplica a R7, nem .orchestrator/.unmanaged; nao edite hooks.json nem os .mjs."
|
|
7888
|
+
].join("\n");
|
|
7889
|
+
function matchR7Path(filePath) {
|
|
7890
|
+
if (filePath.length === 0) return { kind: "none" };
|
|
7891
|
+
const resolved = resolvePath(filePath);
|
|
7892
|
+
const at = resolved.indexOf(WORKFLOWS_SEGMENT);
|
|
7893
|
+
if (at === -1) return { kind: "none" };
|
|
7894
|
+
const rel = resolved.slice(at + 1);
|
|
7895
|
+
if (filePath.endsWith("/")) return { kind: "shape", rel };
|
|
7896
|
+
const key = parseVaultPath(rel);
|
|
7897
|
+
return key === null ? { kind: "shape", rel } : { kind: "key", rel, ...key };
|
|
7898
|
+
}
|
|
7899
|
+
function r7Deny(label, message, logLine, extra = {}) {
|
|
7900
|
+
return {
|
|
7901
|
+
matchedRule: "R7",
|
|
7902
|
+
emitOutput: true,
|
|
7903
|
+
output: {
|
|
7904
|
+
decision: "deny",
|
|
7905
|
+
reason: `Workflow Guard ${label}: ${message}`,
|
|
7906
|
+
context: "R7 (RFC-044 F3): project-guidelines/workflows/<wf>/<phase>/<type>.md is written only by an artifact-producing sub-agent, while <wf> is active and in <phase>."
|
|
7907
|
+
},
|
|
7908
|
+
logMessages: [`DENY (${label}) ${logLine}`],
|
|
7909
|
+
...extra
|
|
7910
|
+
};
|
|
7911
|
+
}
|
|
7912
|
+
function r7FromProbe(probe, target, agent) {
|
|
7913
|
+
const where = `file=${target.rel} agent=${agent}`;
|
|
7914
|
+
if (probe.kind === "not-found") {
|
|
7915
|
+
return r7Deny(
|
|
7916
|
+
"R7-WORKFLOW-NOT-FOUND",
|
|
7917
|
+
`workflow ${target.workflowId} (from the path) does not exist in this project.`,
|
|
7918
|
+
where
|
|
7919
|
+
);
|
|
7920
|
+
}
|
|
7921
|
+
if (probe.kind === "unavailable") {
|
|
7922
|
+
const label = probe.cause === "unauthorized" ? "R7-NO-TOKEN" : "R7-API-UNAVAILABLE";
|
|
7923
|
+
return r7Deny(
|
|
7924
|
+
label,
|
|
7925
|
+
`could not confirm workflow ${target.workflowId} with the API (cause: ${probe.cause}${probe.httpStatus === void 0 ? "" : `, HTTP ${String(probe.httpStatus)}`}).
|
|
7926
|
+
${R7_RUNBOOK}`,
|
|
7927
|
+
`${where} cause=${probe.cause}`
|
|
7928
|
+
);
|
|
7929
|
+
}
|
|
7930
|
+
if (!ACTIVE_WORKFLOW_STATUSES.has(probe.status)) {
|
|
7931
|
+
return r7Deny(
|
|
7932
|
+
"R7-WORKFLOW-NOT-ACTIVE",
|
|
7933
|
+
`workflow ${target.workflowId} is ${probe.status}, not active.`,
|
|
7934
|
+
where
|
|
7935
|
+
);
|
|
7936
|
+
}
|
|
7937
|
+
if ((probe.currentPhase ?? "").toLowerCase() !== target.phase) {
|
|
7938
|
+
return r7Deny(
|
|
7939
|
+
"R7-PHASE-NOT-CURRENT",
|
|
7940
|
+
`workflow ${target.workflowId} is in phase ${String(probe.currentPhase)}, not ${target.phase}.`,
|
|
7941
|
+
where
|
|
7942
|
+
);
|
|
7943
|
+
}
|
|
7944
|
+
return {
|
|
7945
|
+
matchedRule: "R7",
|
|
7946
|
+
emitOutput: true,
|
|
7947
|
+
output: {
|
|
7948
|
+
decision: "allow",
|
|
7949
|
+
context: `R7: ${agent} writes ${target.rel} (workflow ${target.workflowId} active, phase ${target.phase}).`
|
|
7950
|
+
},
|
|
7951
|
+
logMessages: [`ALLOW (R7) ${where}`]
|
|
7952
|
+
};
|
|
7953
|
+
}
|
|
7954
|
+
function decideR7(input) {
|
|
7955
|
+
const match = matchR7Path(input.filePath);
|
|
7956
|
+
if (match.kind === "none") return null;
|
|
7957
|
+
if (input.targetProjectRoot === null || input.isUnmanaged) return null;
|
|
7958
|
+
return decideR7ForMatch(input, match);
|
|
7959
|
+
}
|
|
7960
|
+
function decideR7ForMatch(input, match) {
|
|
7961
|
+
const agent = input.agentType ? normalizeAgentName(input.agentType) : "";
|
|
7962
|
+
if (agent === "") {
|
|
7963
|
+
return r7Deny(
|
|
7964
|
+
"R7-MAIN-AGENT",
|
|
7965
|
+
`${match.rel} is written only by an artifact-producing sub-agent, never by the main agent (Q-PM-5).`,
|
|
7966
|
+
`file=${match.rel}`
|
|
7967
|
+
);
|
|
7968
|
+
}
|
|
7969
|
+
if (!ARTIFACT_PRODUCER_AGENTS.has(agent)) {
|
|
7970
|
+
return r7Deny(
|
|
7971
|
+
"R7-NOT-PRODUCER",
|
|
7972
|
+
`agent ${agent} is not an artifact producer (ARTIFACT_PRODUCER_AGENTS).`,
|
|
7973
|
+
`file=${match.rel} agent=${agent}`
|
|
7974
|
+
);
|
|
7975
|
+
}
|
|
7976
|
+
if (match.kind === "shape") {
|
|
7977
|
+
return r7Deny(
|
|
7978
|
+
"R7-SHAPE",
|
|
7979
|
+
`${match.rel} is not project-guidelines/workflows/<workflow_id>/<phase>/<artifact_type>.md.`,
|
|
7980
|
+
`file=${match.rel} agent=${agent}`
|
|
7981
|
+
);
|
|
7982
|
+
}
|
|
7983
|
+
const target = { rel: match.rel, workflowId: match.workflowId, phase: match.phase };
|
|
7984
|
+
if (input.r7Probe === void 0) {
|
|
7985
|
+
return r7Deny("R7-PROBE-PENDING", "workflow not confirmed yet.", `file=${match.rel}`, {
|
|
7986
|
+
r7NeedsProbe: { workflowId: match.workflowId, phase: match.phase }
|
|
7987
|
+
});
|
|
7988
|
+
}
|
|
7989
|
+
return r7FromProbe(input.r7Probe, target, agent);
|
|
7990
|
+
}
|
|
7991
|
+
function vaultOnlyWriter(agentType) {
|
|
7992
|
+
if (agentType === "") return null;
|
|
7993
|
+
const agent = normalizeAgentName(agentType);
|
|
7994
|
+
return VAULT_ONLY_WRITERS.has(agent) ? agent : null;
|
|
7995
|
+
}
|
|
7996
|
+
var R8_CONTEXT = "R8 (RFC-044 F3, S6c): this agent writes only (1) its artifact project-guidelines/workflows/<wf>/<phase>/<type>.md of a managed project, under R7, and (2) its own memory under .claude/agent-memory/<slug>/. SKIP_WORKFLOW_GUARD does not apply.";
|
|
7997
|
+
function r8Deny(label, message, logLine) {
|
|
7998
|
+
return {
|
|
7999
|
+
matchedRule: "R8",
|
|
8000
|
+
emitOutput: true,
|
|
8001
|
+
output: {
|
|
8002
|
+
decision: "deny",
|
|
8003
|
+
reason: `Workflow Guard ${label}: ${message}`,
|
|
8004
|
+
context: R8_CONTEXT
|
|
8005
|
+
},
|
|
8006
|
+
logMessages: [`DENY (${label}) ${logLine}`]
|
|
8007
|
+
};
|
|
8008
|
+
}
|
|
8009
|
+
function isOwnMemory(filePath, root, agent) {
|
|
8010
|
+
const dir = join4(root, ".claude", "agent-memory", agent) + PATH_SEP;
|
|
8011
|
+
return resolvePath(filePath).startsWith(dir);
|
|
8012
|
+
}
|
|
8013
|
+
function decideR8(input) {
|
|
8014
|
+
const agent = vaultOnlyWriter(input.agentType);
|
|
8015
|
+
if (agent === null) return null;
|
|
8016
|
+
const where = `file=${input.filePath} agent=${agent}`;
|
|
8017
|
+
const match = matchR7Path(input.filePath);
|
|
8018
|
+
if (match.kind !== "none" && input.targetProjectRoot !== null && !input.isUnmanaged) {
|
|
8019
|
+
const types = VAULT_ONLY_WRITERS.get(agent) ?? null;
|
|
8020
|
+
if (match.kind === "key" && types !== null && !types.has(match.artifactType)) {
|
|
8021
|
+
return r8Deny(
|
|
8022
|
+
"R8-TYPE",
|
|
8023
|
+
`agent ${agent} writes only ${[...types].join(", ")} under workflows/, not ${match.artifactType} (R-6: never the artifact under review).`,
|
|
8024
|
+
`file=${match.rel} agent=${agent}`
|
|
8025
|
+
);
|
|
8026
|
+
}
|
|
8027
|
+
return decideR7ForMatch(input, match);
|
|
8028
|
+
}
|
|
8029
|
+
if (input.targetProjectRoot !== null && isOwnMemory(input.filePath, input.targetProjectRoot, agent)) {
|
|
8030
|
+
return { matchedRule: "R8", emitOutput: false, logMessages: [`ALLOW (R8-MEMORY) ${where}`] };
|
|
8031
|
+
}
|
|
8032
|
+
return r8Deny(
|
|
8033
|
+
"R8-OUTSIDE-VAULT",
|
|
8034
|
+
`agent ${agent} may not write ${input.filePath}. It writes only project-guidelines/workflows/<wf>/<phase>/<type>.md of a managed project (R7) and its own memory .claude/agent-memory/${agent}/.`,
|
|
8035
|
+
where
|
|
8036
|
+
);
|
|
8037
|
+
}
|
|
8038
|
+
function r8ExceptionDecision(error) {
|
|
8039
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8040
|
+
return r8Deny("R8-EXCEPTION", `unexpected error (${message}).`, "exception");
|
|
8041
|
+
}
|
|
8042
|
+
function r7ExceptionDecision(error) {
|
|
8043
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8044
|
+
return r7Deny("R7-EXCEPTION", `unexpected error (${message}).
|
|
8045
|
+
${R7_RUNBOOK}`, "exception");
|
|
8046
|
+
}
|
|
7621
8047
|
var CONFIG_DOC_RE = /\.(json|ya?ml|md|env(\..*)?)$/;
|
|
7622
8048
|
function decideWorkflowGuardAction(input) {
|
|
7623
8049
|
const {
|
|
@@ -7630,6 +8056,10 @@ function decideWorkflowGuardAction(input) {
|
|
|
7630
8056
|
agentId,
|
|
7631
8057
|
agentType
|
|
7632
8058
|
} = input;
|
|
8059
|
+
const r8 = decideR8(input);
|
|
8060
|
+
if (r8 !== null) return r8;
|
|
8061
|
+
const r7 = decideR7(input);
|
|
8062
|
+
if (r7 !== null) return r7;
|
|
7633
8063
|
if (skipWorkflowGuard) {
|
|
7634
8064
|
return {
|
|
7635
8065
|
matchedRule: 1,
|
|
@@ -7784,17 +8214,25 @@ function decideWorkflowGuardAction(input) {
|
|
|
7784
8214
|
logMessages: ["DENY (no active workflow)"]
|
|
7785
8215
|
};
|
|
7786
8216
|
}
|
|
7787
|
-
async function
|
|
7788
|
-
|
|
8217
|
+
async function decideWithProbe(input, needs, deps) {
|
|
8218
|
+
try {
|
|
8219
|
+
const r7Probe = await deps.probeWorkflow(needs.workflowId);
|
|
8220
|
+
return decideWorkflowGuardAction({ ...input, r7Probe });
|
|
8221
|
+
} catch (e) {
|
|
8222
|
+
return r7ExceptionDecision(e);
|
|
8223
|
+
}
|
|
8224
|
+
}
|
|
8225
|
+
async function mainWithDeps(deps) {
|
|
8226
|
+
const stdin = await deps.readStdin();
|
|
7789
8227
|
const filePath = getField(stdin, "tool_input.file_path") || getField(stdin, "file_path") || getField(stdin, "input.file_path");
|
|
7790
|
-
log(
|
|
7791
|
-
const skipWorkflowGuard =
|
|
7792
|
-
const skipSubagentGuard =
|
|
8228
|
+
deps.log(`file_path=${filePath}`);
|
|
8229
|
+
const skipWorkflowGuard = deps.env.SKIP_WORKFLOW_GUARD === "true";
|
|
8230
|
+
const skipSubagentGuard = deps.env.SKIP_SUBAGENT_GUARD === "true";
|
|
7793
8231
|
const agentId = getField(stdin, "agent_id");
|
|
7794
8232
|
const agentType = getField(stdin, "agent_type");
|
|
7795
8233
|
const targetDir = filePath.length > 0 ? dirname2(filePath) : process.cwd();
|
|
7796
|
-
const targetProjectRoot = resolveHookProjectRoot(targetDir);
|
|
7797
|
-
const isUnmanaged = targetProjectRoot !== null &&
|
|
8234
|
+
const targetProjectRoot = deps.resolveHookProjectRoot(targetDir);
|
|
8235
|
+
const isUnmanaged = targetProjectRoot !== null && deps.isUnmanaged(targetProjectRoot);
|
|
7798
8236
|
const baseInput = {
|
|
7799
8237
|
filePath,
|
|
7800
8238
|
skipWorkflowGuard,
|
|
@@ -7802,38 +8240,75 @@ async function main() {
|
|
|
7802
8240
|
targetProjectRoot,
|
|
7803
8241
|
isUnmanaged,
|
|
7804
8242
|
agentId,
|
|
7805
|
-
agentType
|
|
8243
|
+
agentType,
|
|
8244
|
+
workflow: null
|
|
7806
8245
|
};
|
|
7807
|
-
let decision
|
|
7808
|
-
|
|
7809
|
-
|
|
8246
|
+
let decision;
|
|
8247
|
+
try {
|
|
8248
|
+
decision = decideWorkflowGuardAction(baseInput);
|
|
8249
|
+
} catch (e) {
|
|
8250
|
+
if (vaultOnlyWriter(agentType) !== null) {
|
|
8251
|
+
decision = r8ExceptionDecision(e);
|
|
8252
|
+
} else {
|
|
8253
|
+
if (matchR7Path(filePath).kind === "none") throw e;
|
|
8254
|
+
decision = r7ExceptionDecision(e);
|
|
8255
|
+
}
|
|
8256
|
+
}
|
|
8257
|
+
if (decision.r7NeedsProbe) {
|
|
8258
|
+
decision = await decideWithProbe(baseInput, decision.r7NeedsProbe, deps);
|
|
8259
|
+
} else if (decision.matchedRule === 7) {
|
|
8260
|
+
const activeWorkflow = await deps.getActiveWorkflow();
|
|
7810
8261
|
let workflow = null;
|
|
7811
8262
|
if (activeWorkflow) {
|
|
7812
|
-
const mode = await getWorkflowMode(activeWorkflow.id);
|
|
8263
|
+
const mode = await deps.getWorkflowMode(activeWorkflow.id);
|
|
7813
8264
|
workflow = { id: activeWorkflow.id, mode, currentPhase: activeWorkflow.currentPhase };
|
|
7814
8265
|
}
|
|
7815
8266
|
decision = decideWorkflowGuardAction({ ...baseInput, workflow });
|
|
7816
8267
|
}
|
|
7817
8268
|
for (const line of decision.logMessages) {
|
|
7818
|
-
log(
|
|
8269
|
+
deps.log(line);
|
|
7819
8270
|
}
|
|
7820
8271
|
if (decision.observation) {
|
|
7821
8272
|
try {
|
|
7822
|
-
|
|
7823
|
-
const memoryBaseDir = resolveAgentMemoryBaseDir(process.cwd());
|
|
7824
|
-
appendObservation(memoryBaseDir, "main", {
|
|
7825
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7826
|
-
session_id: sessionId,
|
|
7827
|
-
...decision.observation
|
|
7828
|
-
});
|
|
8273
|
+
deps.appendObservation(stdin, decision.observation);
|
|
7829
8274
|
} catch {
|
|
7830
8275
|
}
|
|
7831
8276
|
}
|
|
7832
8277
|
if (decision.emitOutput && decision.output) {
|
|
7833
|
-
|
|
8278
|
+
deps.output(decision.output);
|
|
7834
8279
|
}
|
|
7835
8280
|
}
|
|
8281
|
+
var defaultDeps = {
|
|
8282
|
+
readStdin,
|
|
8283
|
+
env: process.env,
|
|
8284
|
+
resolveHookProjectRoot,
|
|
8285
|
+
isUnmanaged: (projectRoot) => existsSync3(join4(projectRoot, ".orchestrator", ".unmanaged")),
|
|
8286
|
+
getActiveWorkflow,
|
|
8287
|
+
getWorkflowMode,
|
|
8288
|
+
probeWorkflow: (workflowId) => probeWorkflow(workflowId),
|
|
8289
|
+
output: outputPreToolUse,
|
|
8290
|
+
log: (line) => {
|
|
8291
|
+
log(HOOK, line);
|
|
8292
|
+
},
|
|
8293
|
+
appendObservation: (stdin, observation) => {
|
|
8294
|
+
const sessionId = getField(stdin, "session_id") || getField(stdin, "claude_session_id") || "";
|
|
8295
|
+
const memoryBaseDir = resolveAgentMemoryBaseDir(process.cwd());
|
|
8296
|
+
appendObservation(memoryBaseDir, "main", {
|
|
8297
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8298
|
+
session_id: sessionId,
|
|
8299
|
+
...observation
|
|
8300
|
+
});
|
|
8301
|
+
}
|
|
8302
|
+
};
|
|
8303
|
+
async function main() {
|
|
8304
|
+
await mainWithDeps(defaultDeps);
|
|
8305
|
+
}
|
|
7836
8306
|
main().catch(() => process.exit(0));
|
|
7837
8307
|
export {
|
|
7838
|
-
|
|
8308
|
+
R7_RUNBOOK,
|
|
8309
|
+
decideWorkflowGuardAction,
|
|
8310
|
+
mainWithDeps,
|
|
8311
|
+
matchR7Path,
|
|
8312
|
+
r7ExceptionDecision,
|
|
8313
|
+
r8ExceptionDecision
|
|
7839
8314
|
};
|