@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.
Files changed (52) hide show
  1. package/agents/adversarial-reviewer.md +35 -29
  2. package/agents/agentic-architect.md +1 -1
  3. package/agents/api-extractor.md +1 -1
  4. package/agents/architecture-mediator.md +1 -1
  5. package/agents/azure-devops-engineer.md +1 -1
  6. package/agents/backend-architect.md +1 -1
  7. package/agents/business-rule-miner.md +1 -1
  8. package/agents/cicd-platform-engineer.md +1 -1
  9. package/agents/ciclo-engineer.md +1 -1
  10. package/agents/code-archaeologist.md +1 -1
  11. package/agents/contract-architect.md +1 -1
  12. package/agents/datamodel-architect.md +1 -1
  13. package/agents/frontend-architect.md +1 -1
  14. package/agents/frontend-engineer.md +1 -1
  15. package/agents/harness-architect.md +1 -1
  16. package/agents/harness-engineer.md +1 -1
  17. package/agents/implementer.md +1 -1
  18. package/agents/infra-architect.md +1 -1
  19. package/agents/legacy-discoverer.md +1 -1
  20. package/agents/legacy-synthesizer.md +1 -1
  21. package/agents/methodology-researcher.md +2 -1
  22. package/agents/operational-artifact-builder.md +1 -1
  23. package/agents/planner.md +1 -1
  24. package/agents/presentation-engineer.md +1 -1
  25. package/agents/python-data-engineer.md +1 -1
  26. package/agents/researcher.md +1 -1
  27. package/agents/reviewer.md +1 -1
  28. package/agents/schema-extractor.md +1 -1
  29. package/agents/specifier.md +1 -1
  30. package/agents/task-generator.md +1 -1
  31. package/hooks/agent-git-guard.mjs +0 -2
  32. package/hooks/dangling-guard.mjs +18 -3
  33. package/hooks/gate-guardian.mjs +8244 -20
  34. package/hooks/gate-guardian.ts +154 -24
  35. package/hooks/lib/api-client.ts +30 -7
  36. package/hooks/lib/artifact-gate.ts +518 -0
  37. package/hooks/lib/artifact-producers.ts +106 -0
  38. package/hooks/lib/workflow-artifact-client.ts +351 -0
  39. package/hooks/mailbox-listener.mjs +0 -2
  40. package/hooks/playwright-output-guard.mjs +0 -2
  41. package/hooks/post-implement.mjs +18 -3
  42. package/hooks/pre-agent-dispatch.mjs +18 -3
  43. package/hooks/security-gate-honest.mjs +0 -2
  44. package/hooks/session-start.mjs +18 -3
  45. package/hooks/subagent-start.mjs +18 -3
  46. package/hooks/subagent-stop.mjs +18 -3
  47. package/hooks/user-prompt.mjs +18 -3
  48. package/hooks/verification-before-completion.mjs +0 -2
  49. package/hooks/workflow-guard.mjs +504 -29
  50. package/hooks/workflow-guard.ts +370 -27
  51. package/manifest.json +46 -46
  52. package/package.json +1 -1
@@ -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 AUTH_EMAIL = process.env.ORCHESTRATOR_ADMIN_EMAIL || process.env.ORCHESTRATOR_AUTH_EMAIL || "system@orchestrator.local";
7389
- var AUTH_PASSWORD = process.env.ORCHESTRATOR_ADMIN_PASSWORD || process.env.ORCHESTRATOR_AUTH_PASSWORD || "system123";
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: AUTH_EMAIL, password: AUTH_PASSWORD }),
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 import_yaml = __toESM(require_dist(), 1);
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, import_yaml.parse)(raw);
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, import_yaml.stringify)(data), "utf-8");
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 main() {
7788
- const stdin = await readStdin();
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(HOOK, `file_path=${filePath}`);
7791
- const skipWorkflowGuard = process.env.SKIP_WORKFLOW_GUARD === "true";
7792
- const skipSubagentGuard = process.env.SKIP_SUBAGENT_GUARD === "true";
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 && existsSync3(join4(targetProjectRoot, ".orchestrator", ".unmanaged"));
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 = decideWorkflowGuardAction({ ...baseInput, workflow: null });
7808
- if (decision.matchedRule === 7) {
7809
- const activeWorkflow = await getActiveWorkflow();
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(HOOK, line);
8269
+ deps.log(line);
7819
8270
  }
7820
8271
  if (decision.observation) {
7821
8272
  try {
7822
- const sessionId = getField(stdin, "session_id") || getField(stdin, "claude_session_id") || "";
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
- outputPreToolUse(decision.output);
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
- decideWorkflowGuardAction
8308
+ R7_RUNBOOK,
8309
+ decideWorkflowGuardAction,
8310
+ mainWithDeps,
8311
+ matchR7Path,
8312
+ r7ExceptionDecision,
8313
+ r8ExceptionDecision
7839
8314
  };