opencode-swarm 6.45.0 → 6.46.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 CHANGED
@@ -601,6 +601,117 @@ Per-agent overrides:
601
601
 
602
602
  </details>
603
603
 
604
+ <details>
605
+ <summary><strong>File Authority (Per-Agent Write Permissions)</strong></summary>
606
+
607
+ Swarm enforces per-agent file write authority — each agent can only write to specific paths. By default, these rules are hardcoded, but you can override them via config.
608
+
609
+ ### Default Rules
610
+
611
+ | Agent | Can Write | Blocked | Zones |
612
+ |-------|-----------|---------|-------|
613
+ | `architect` | Everything (except plan files) | `.swarm/plan.md`, `.swarm/plan.json` | `generated` |
614
+ | `coder` | `src/`, `tests/`, `docs/`, `scripts/` | `.swarm/` (entire directory) | `generated`, `config` |
615
+ | `reviewer` | `.swarm/evidence/`, `.swarm/outputs/` | `src/`, `.swarm/plan.md`, `.swarm/plan.json` | `generated` |
616
+ | `test_engineer` | `tests/`, `.swarm/evidence/` | `src/`, `.swarm/plan.md`, `.swarm/plan.json` | `generated` |
617
+ | `explorer` | Read-only | Everything | — |
618
+ | `sme` | Read-only | Everything | — |
619
+ | `docs` | `docs/`, `.swarm/outputs/` | — | `generated` |
620
+ | `designer` | `docs/`, `.swarm/outputs/` | — | `generated` |
621
+ | `critic` | `.swarm/evidence/` | — | `generated` |
622
+
623
+ ### Prefixed Agents
624
+
625
+ Prefixed agents (e.g., `paid_coder`, `mega_reviewer`, `local_architect`) inherit defaults from their canonical base agent via `stripKnownSwarmPrefix`. The lookup order is:
626
+
627
+ 1. Exact match for the prefixed name (if explicitly defined in user config)
628
+ 2. Fall back to the canonical agent's defaults (e.g., `paid_coder` → `coder`)
629
+
630
+ ```json
631
+ {
632
+ "authority": {
633
+ "rules": {
634
+ "coder": { "allowedPrefix": ["src/", "lib/"] },
635
+ "paid_coder": { "allowedPrefix": ["vendor/", "plugins/"] }
636
+ }
637
+ }
638
+ }
639
+ ```
640
+
641
+ In this example, `paid_coder` gets its own explicit rule, while other prefixed coders (e.g., `mega_coder`) fall back to `coder`.
642
+
643
+ ### Runtime Enforcement
644
+
645
+ Architect direct writes are enforced at runtime via `toolBefore` hook. This tracks writes to source code paths outside `.swarm/` and protects `.swarm/plan.md` and `.swarm/plan.json` from direct modification.
646
+
647
+ ### Configuration
648
+
649
+ Override default rules in `.opencode/opencode-swarm.json`:
650
+
651
+ ```json
652
+ {
653
+ "authority": {
654
+ "enabled": true,
655
+ "rules": {
656
+ "coder": {
657
+ "allowedPrefix": ["src/", "lib/", "scripts/"],
658
+ "blockedPrefix": [".swarm/"],
659
+ "blockedZones": ["generated"]
660
+ },
661
+ "explorer": {
662
+ "readOnly": false,
663
+ "allowedPrefix": ["notes/", "scratch/"]
664
+ }
665
+ }
666
+ }
667
+ }
668
+ ```
669
+
670
+ ### Rule Fields
671
+
672
+ | Field | Type | Description |
673
+ |-------|------|-------------|
674
+ | `readOnly` | boolean | If `true`, agent cannot write anywhere |
675
+ | `blockedExact` | string[] | Exact file paths that are blocked |
676
+ | `blockedPrefix` | string[] | Path prefixes that are blocked (e.g., `.swarm/`) |
677
+ | `allowedPrefix` | string[] | Only these path prefixes are allowed. Omit to remove restriction; set `[]` to deny all |
678
+ | `blockedZones` | string[] | File zones to block: `production`, `test`, `config`, `generated`, `docs`, `build` |
679
+
680
+ ### Merge Behavior
681
+
682
+ - User rules **override** hardcoded defaults for the specified agent
683
+ - Scalar fields (`readOnly`) — user value replaces default
684
+ - Array fields (`blockedPrefix`, `allowedPrefix`, etc.) — user array **replaces** entirely (not merged)
685
+ - If a field is omitted in the user rule for a **known agent** (one with hardcoded defaults), the default value for that field is preserved
686
+ - If a field is omitted in the user rule for a **custom agent** (not in the defaults list), that field is `undefined` — there are no defaults to inherit
687
+ - `allowedPrefix: []` explicitly denies all writes; omitting `allowedPrefix` entirely means no allowlist restriction is applied (all paths are evaluated against blocklist rules only)
688
+ - Setting `enabled: false` ignores all custom rules and uses hardcoded defaults
689
+
690
+ ### Custom Agents
691
+
692
+ Custom agents (not in the defaults list) start with no rules. Their write authority depends entirely on what you configure:
693
+
694
+ - **Not in config at all** — agent is denied with `Unknown agent` (no rule exists; this is not the same as "blocked from all writes")
695
+ - **In config without `allowedPrefix`** — no allowlist restriction applies; only any `blockedPrefix`, `blockedZones`, or `readOnly` rules you explicitly set will enforce limits
696
+ - **In config with `allowedPrefix: []`** — all writes are denied
697
+
698
+ To safely restrict a custom agent, always set `allowedPrefix` explicitly:
699
+
700
+ ```json
701
+ {
702
+ "authority": {
703
+ "rules": {
704
+ "my_custom_agent": {
705
+ "allowedPrefix": ["plugins/", "extensions/"],
706
+ "blockedZones": ["generated"]
707
+ }
708
+ }
709
+ }
710
+ }
711
+ ```
712
+
713
+ </details>
714
+
604
715
  <details>
605
716
  <summary><strong>Context Budget Guard</strong></summary>
606
717
 
@@ -780,6 +891,16 @@ Config file location: `~/.config/opencode/opencode-swarm.json` (global) or `.ope
780
891
  "coder": { "max_tool_calls": 500 }
781
892
  }
782
893
  },
894
+ "authority": {
895
+ "enabled": true,
896
+ "rules": {
897
+ "coder": {
898
+ "allowedPrefix": ["src/", "lib/"],
899
+ "blockedPrefix": [".swarm/"],
900
+ "blockedZones": ["generated"]
901
+ }
902
+ }
903
+ },
783
904
  "review_passes": {
784
905
  "always_security_review": false,
785
906
  "security_globs": ["**/*auth*", "**/*crypto*", "**/*session*"]
@@ -1,5 +1,5 @@
1
1
  import type { AgentDefinition } from './architect';
2
2
  export declare const CURATOR_INIT_PROMPT = "## IDENTITY\nYou are Explorer in CURATOR_INIT mode. You consolidate prior session knowledge into an architect briefing.\nDO NOT use the Task tool to delegate. You ARE the agent that does the work.\n\nINPUT FORMAT:\nTASK: CURATOR_INIT\nPRIOR_SUMMARY: [JSON or \"none\"]\nKNOWLEDGE_ENTRIES: [JSON array of high-confidence entries]\nPROJECT_CONTEXT: [context.md excerpt]\n\nACTIONS:\n- Read the prior summary to understand session history\n- Cross-reference knowledge entries against project context\n- Identify contradictions (knowledge says X, project state shows Y)\n- Produce a concise briefing for the architect\n\nRULES:\n- Output under 2000 chars\n- No code modifications\n- Flag contradictions explicitly with CONTRADICTION: prefix\n- If no prior summary exists, state \"First session \u2014 no prior context\"\n\nOUTPUT FORMAT:\nBRIEFING:\n[concise summary of prior session state, key decisions, active blockers]\n\nCONTRADICTIONS:\n- [entry_id]: [description] (or \"None detected\")\n\nKNOWLEDGE_STATS:\n- Entries reviewed: [N]\n- Prior phases covered: [N]\n";
3
- export declare const CURATOR_PHASE_PROMPT = "## IDENTITY\nYou are Explorer in CURATOR_PHASE mode. You consolidate a completed phase into a digest.\nDO NOT use the Task tool to delegate. You ARE the agent that does the work.\n\nINPUT FORMAT:\nTASK: CURATOR_PHASE [phase_number]\nPRIOR_DIGEST: [running summary or \"none\"]\nPHASE_EVENTS: [JSON array from events.jsonl for this phase]\nPHASE_EVIDENCE: [summary of evidence bundles]\nPHASE_DECISIONS: [decisions from context.md]\nAGENTS_DISPATCHED: [list]\nAGENTS_EXPECTED: [list from config]\n\nACTIONS:\n- Extend the prior digest with this phase's outcomes (do NOT regenerate from scratch)\n- Identify workflow deviations: missing reviewer, missing retro, skipped test_engineer\n- Recommend knowledge updates: entries to promote, archive, or flag as contradicted\n- Summarize key decisions and blockers resolved\n\nRULES:\n- Output under 2000 chars\n- No code modifications\n- Compliance observations are READ-ONLY \u2014 report, do not enforce\n- Extend the digest, never replace it\n\nOUTPUT FORMAT:\nPHASE_DIGEST:\nphase: [N]\nsummary: [what was accomplished]\nagents_used: [list]\ntasks_completed: [N]/[total]\nkey_decisions: [list]\nblockers_resolved: [list]\n\nCOMPLIANCE:\n- [type]: [description] (or \"No deviations observed\")\n\nKNOWLEDGE_UPDATES:\n- [action] [entry_id or \"new\"]: [reason] (or \"No recommendations\")\n\nEXTENDED_DIGEST:\n[the full running digest with this phase appended]\n";
3
+ export declare const CURATOR_PHASE_PROMPT = "## IDENTITY\nYou are Explorer in CURATOR_PHASE mode. You consolidate a completed phase into a digest.\nDO NOT use the Task tool to delegate. You ARE the agent that does the work.\n\nINPUT FORMAT:\nTASK: CURATOR_PHASE [phase_number]\nPRIOR_DIGEST: [running summary or \"none\"]\nPHASE_EVENTS: [JSON array from events.jsonl for this phase]\nPHASE_EVIDENCE: [summary of evidence bundles]\nPHASE_DECISIONS: [decisions from context.md]\nAGENTS_DISPATCHED: [list]\nAGENTS_EXPECTED: [list from config]\n\nACTIONS:\n- Extend the prior digest with this phase's outcomes (do NOT regenerate from scratch)\n- Identify workflow deviations: missing reviewer, missing retro, skipped test_engineer\n- Recommend knowledge updates: entries to promote, archive, or flag as contradicted\n- Summarize key decisions and blockers resolved\n\nRULES:\n- Output under 2000 chars\n- No code modifications\n- Compliance observations are READ-ONLY \u2014 report, do not enforce\n- Extend the digest, never replace it\n\nOUTPUT FORMAT:\nPHASE_DIGEST:\nphase: [N]\nsummary: [what was accomplished]\nagents_used: [list]\ntasks_completed: [N]/[total]\nkey_decisions: [list]\nblockers_resolved: [list]\n\nCOMPLIANCE:\n- [type]: [description] (or \"No deviations observed\")\n\nKNOWLEDGE_UPDATES:\n- [action] new: [reason] (or \"No recommendations\")\nNOTE: Always use \"new\" as the token \u2014 existing entry IDs (UUID v4) are not available in this context. Any non-UUID token is treated as \"new\" by the parser. Only \"promote new:\" creates a new entry; \"archive new:\" and \"flag_contradiction new:\" are silently skipped because those actions require an existing entry to operate on.\n\nEXTENDED_DIGEST:\n[the full running digest with this phase appended]\n";
4
4
  export declare function createExplorerAgent(model: string, customPrompt?: string, customAppendPrompt?: string): AgentDefinition;
5
5
  export declare function createExplorerCuratorAgent(model: string, mode: 'CURATOR_INIT' | 'CURATOR_PHASE', customAppendPrompt?: string): AgentDefinition;
package/dist/cli/index.js CHANGED
@@ -18649,6 +18649,17 @@ var CompactionConfigSchema = exports_external.object({
18649
18649
  emergencyThreshold: exports_external.number().min(1).max(99).default(80),
18650
18650
  preserveLastNTurns: exports_external.number().int().min(1).default(5)
18651
18651
  });
18652
+ var AgentAuthorityRuleSchema = exports_external.object({
18653
+ readOnly: exports_external.boolean().optional(),
18654
+ blockedExact: exports_external.array(exports_external.string()).optional(),
18655
+ blockedPrefix: exports_external.array(exports_external.string()).optional(),
18656
+ allowedPrefix: exports_external.array(exports_external.string()).optional(),
18657
+ blockedZones: exports_external.array(exports_external.enum(["production", "test", "config", "generated", "docs", "build"])).optional()
18658
+ });
18659
+ var AuthorityConfigSchema = exports_external.object({
18660
+ enabled: exports_external.boolean().default(true),
18661
+ rules: exports_external.record(exports_external.string(), AgentAuthorityRuleSchema).default({})
18662
+ });
18652
18663
  var PluginConfigSchema = exports_external.object({
18653
18664
  agents: exports_external.record(exports_external.string(), AgentOverrideConfigSchema).optional(),
18654
18665
  swarms: exports_external.record(exports_external.string(), SwarmConfigSchema).optional(),
@@ -18665,6 +18676,7 @@ var PluginConfigSchema = exports_external.object({
18665
18676
  watchdog: WatchdogConfigSchema.optional(),
18666
18677
  self_review: SelfReviewConfigSchema.optional(),
18667
18678
  tool_filter: ToolFilterConfigSchema.optional(),
18679
+ authority: AuthorityConfigSchema.optional(),
18668
18680
  plan_cursor: PlanCursorConfigSchema.optional(),
18669
18681
  evidence: EvidenceConfigSchema.optional(),
18670
18682
  summaries: SummaryConfigSchema.optional(),
@@ -8,6 +8,7 @@ export declare const EvidenceTypeSchema: z.ZodEnum<{
8
8
  quality_budget: "quality_budget";
9
9
  placeholder: "placeholder";
10
10
  test: "test";
11
+ build: "build";
11
12
  review: "review";
12
13
  approval: "approval";
13
14
  note: "note";
@@ -15,7 +16,6 @@ export declare const EvidenceTypeSchema: z.ZodEnum<{
15
16
  syntax: "syntax";
16
17
  sast: "sast";
17
18
  sbom: "sbom";
18
- build: "build";
19
19
  }>;
20
20
  export type EvidenceType = z.infer<typeof EvidenceTypeSchema>;
21
21
  export declare const EvidenceVerdictSchema: z.ZodEnum<{
@@ -34,6 +34,7 @@ export declare const BaseEvidenceSchema: z.ZodObject<{
34
34
  quality_budget: "quality_budget";
35
35
  placeholder: "placeholder";
36
36
  test: "test";
37
+ build: "build";
37
38
  review: "review";
38
39
  approval: "approval";
39
40
  note: "note";
@@ -41,7 +42,6 @@ export declare const BaseEvidenceSchema: z.ZodObject<{
41
42
  syntax: "syntax";
42
43
  sast: "sast";
43
44
  sbom: "sbom";
44
- build: "build";
45
45
  }>;
46
46
  timestamp: z.ZodString;
47
47
  agent: z.ZodString;
@@ -468,6 +468,39 @@ export declare const CompactionConfigSchema: z.ZodObject<{
468
468
  preserveLastNTurns: z.ZodDefault<z.ZodNumber>;
469
469
  }, z.core.$strip>;
470
470
  export type CompactionConfig = z.infer<typeof CompactionConfigSchema>;
471
+ export declare const AgentAuthorityRuleSchema: z.ZodObject<{
472
+ readOnly: z.ZodOptional<z.ZodBoolean>;
473
+ blockedExact: z.ZodOptional<z.ZodArray<z.ZodString>>;
474
+ blockedPrefix: z.ZodOptional<z.ZodArray<z.ZodString>>;
475
+ allowedPrefix: z.ZodOptional<z.ZodArray<z.ZodString>>;
476
+ blockedZones: z.ZodOptional<z.ZodArray<z.ZodEnum<{
477
+ docs: "docs";
478
+ production: "production";
479
+ test: "test";
480
+ config: "config";
481
+ generated: "generated";
482
+ build: "build";
483
+ }>>>;
484
+ }, z.core.$strip>;
485
+ export type AgentAuthorityRule = z.infer<typeof AgentAuthorityRuleSchema>;
486
+ export declare const AuthorityConfigSchema: z.ZodObject<{
487
+ enabled: z.ZodDefault<z.ZodBoolean>;
488
+ rules: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
489
+ readOnly: z.ZodOptional<z.ZodBoolean>;
490
+ blockedExact: z.ZodOptional<z.ZodArray<z.ZodString>>;
491
+ blockedPrefix: z.ZodOptional<z.ZodArray<z.ZodString>>;
492
+ allowedPrefix: z.ZodOptional<z.ZodArray<z.ZodString>>;
493
+ blockedZones: z.ZodOptional<z.ZodArray<z.ZodEnum<{
494
+ docs: "docs";
495
+ production: "production";
496
+ test: "test";
497
+ config: "config";
498
+ generated: "generated";
499
+ build: "build";
500
+ }>>>;
501
+ }, z.core.$strip>>>;
502
+ }, z.core.$strip>;
503
+ export type AuthorityConfig = z.infer<typeof AuthorityConfigSchema>;
471
504
  export declare const PluginConfigSchema: z.ZodObject<{
472
505
  agents: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
473
506
  model: z.ZodOptional<z.ZodString>;
@@ -627,6 +660,23 @@ export declare const PluginConfigSchema: z.ZodObject<{
627
660
  enabled: z.ZodDefault<z.ZodBoolean>;
628
661
  overrides: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
629
662
  }, z.core.$strip>>;
663
+ authority: z.ZodOptional<z.ZodObject<{
664
+ enabled: z.ZodDefault<z.ZodBoolean>;
665
+ rules: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
666
+ readOnly: z.ZodOptional<z.ZodBoolean>;
667
+ blockedExact: z.ZodOptional<z.ZodArray<z.ZodString>>;
668
+ blockedPrefix: z.ZodOptional<z.ZodArray<z.ZodString>>;
669
+ allowedPrefix: z.ZodOptional<z.ZodArray<z.ZodString>>;
670
+ blockedZones: z.ZodOptional<z.ZodArray<z.ZodEnum<{
671
+ docs: "docs";
672
+ production: "production";
673
+ test: "test";
674
+ config: "config";
675
+ generated: "generated";
676
+ build: "build";
677
+ }>>>;
678
+ }, z.core.$strip>>>;
679
+ }, z.core.$strip>>;
630
680
  plan_cursor: z.ZodOptional<z.ZodObject<{
631
681
  enabled: z.ZodDefault<z.ZodBoolean>;
632
682
  max_tokens: z.ZodDefault<z.ZodNumber>;
@@ -6,7 +6,7 @@
6
6
  * - Layer 1 (Soft Warning @ warning_threshold): Sets warning flag for messagesTransform to inject warning
7
7
  * - Layer 2 (Hard Block @ 100%): Throws error in toolBefore to block further calls, injects STOP message
8
8
  */
9
- import { type GuardrailsConfig } from '../config/schema';
9
+ import { type AuthorityConfig, type GuardrailsConfig } from '../config/schema';
10
10
  import { type FileZone } from '../context/zone-classifier';
11
11
  /**
12
12
  * Retrieves stored input args for a given callID.
@@ -34,7 +34,7 @@ export declare function deleteStoredInputArgs(callID: string): void;
34
34
  * @param config Guardrails configuration (optional)
35
35
  * @returns Tool before/after hooks and messages transform hook
36
36
  */
37
- export declare function createGuardrailsHooks(directory: string, directoryOrConfig?: string | GuardrailsConfig, config?: GuardrailsConfig): {
37
+ export declare function createGuardrailsHooks(directory: string, directoryOrConfig?: string | GuardrailsConfig, config?: GuardrailsConfig, authorityConfig?: AuthorityConfig): {
38
38
  toolBefore: (input: {
39
39
  tool: string;
40
40
  sessionID: string;
@@ -103,13 +103,22 @@ export declare function validateAndRecordAttestation(dir: string, findingId: str
103
103
  valid: false;
104
104
  reason: string;
105
105
  }>;
106
+ type AgentRule = {
107
+ readOnly?: boolean;
108
+ blockedExact?: string[];
109
+ blockedPrefix?: string[];
110
+ allowedPrefix?: string[];
111
+ blockedZones?: FileZone[];
112
+ };
113
+ export declare const DEFAULT_AGENT_AUTHORITY_RULES: Record<string, AgentRule>;
106
114
  /**
107
115
  * Checks whether the given agent is authorised to write to the given file path.
108
116
  */
109
- export declare function checkFileAuthority(agentName: string, filePath: string, cwd: string): {
117
+ export declare function checkFileAuthority(agentName: string, filePath: string, cwd: string, authorityConfig?: AuthorityConfig): {
110
118
  allowed: true;
111
119
  } | {
112
120
  allowed: false;
113
121
  reason: string;
114
122
  zone?: FileZone;
115
123
  };
124
+ export {};
@@ -6,7 +6,7 @@ export { createDelegationGateHook } from './delegation-gate';
6
6
  export { createDelegationSanitizerHook } from './delegation-sanitizer';
7
7
  export { createDelegationTrackerHook } from './delegation-tracker';
8
8
  export { extractCurrentPhase, extractCurrentPhaseFromPlan, extractCurrentTask, extractCurrentTaskFromPlan, extractDecisions, extractIncompleteTasks, extractIncompleteTasksFromPlan, extractPatterns, } from './extractors';
9
- export { createGuardrailsHooks } from './guardrails';
9
+ export { checkFileAuthority, createGuardrailsHooks, DEFAULT_AGENT_AUTHORITY_RULES, } from './guardrails';
10
10
  export { classifyMessage, classifyMessages, containsPlanContent, isDuplicateToolRead, isStaleError, isToolResult, MessagePriority, type MessagePriorityType, type MessageWithParts, } from './message-priority';
11
11
  export { consolidateSystemMessages } from './messages-transform';
12
12
  export { extractModelInfo, NATIVE_MODEL_LIMITS, PROVIDER_CAPS, resolveModelLimit, } from './model-limits';
package/dist/index.js CHANGED
@@ -14573,7 +14573,7 @@ function resolveGuardrailsConfig(config2, agentName) {
14573
14573
  };
14574
14574
  return resolved;
14575
14575
  }
14576
- var KNOWN_SWARM_PREFIXES, SEPARATORS, AgentOverrideConfigSchema, SwarmConfigSchema, HooksConfigSchema, ScoringWeightsSchema, DecisionDecaySchema, TokenRatiosSchema, ScoringConfigSchema, ContextBudgetConfigSchema, EvidenceConfigSchema, GateFeatureSchema, PlaceholderScanConfigSchema, QualityBudgetConfigSchema, GateConfigSchema, PipelineConfigSchema, PhaseCompleteConfigSchema, SummaryConfigSchema, ReviewPassesConfigSchema, AdversarialDetectionConfigSchema, AdversarialTestingConfigSchemaBase, AdversarialTestingConfigSchema, IntegrationAnalysisConfigSchema, DocsConfigSchema, UIReviewConfigSchema, CompactionAdvisoryConfigSchema, LintConfigSchema, SecretscanConfigSchema, GuardrailsProfileSchema, DEFAULT_AGENT_PROFILES, DEFAULT_ARCHITECT_PROFILE, GuardrailsConfigSchema, WatchdogConfigSchema, SelfReviewConfigSchema, ToolFilterConfigSchema, PlanCursorConfigSchema, CheckpointConfigSchema, AutomationModeSchema, AutomationCapabilitiesSchema, AutomationConfigSchemaBase, AutomationConfigSchema, KnowledgeConfigSchema, CuratorConfigSchema, SlopDetectorConfigSchema, IncrementalVerifyConfigSchema, CompactionConfigSchema, PluginConfigSchema;
14576
+ var KNOWN_SWARM_PREFIXES, SEPARATORS, AgentOverrideConfigSchema, SwarmConfigSchema, HooksConfigSchema, ScoringWeightsSchema, DecisionDecaySchema, TokenRatiosSchema, ScoringConfigSchema, ContextBudgetConfigSchema, EvidenceConfigSchema, GateFeatureSchema, PlaceholderScanConfigSchema, QualityBudgetConfigSchema, GateConfigSchema, PipelineConfigSchema, PhaseCompleteConfigSchema, SummaryConfigSchema, ReviewPassesConfigSchema, AdversarialDetectionConfigSchema, AdversarialTestingConfigSchemaBase, AdversarialTestingConfigSchema, IntegrationAnalysisConfigSchema, DocsConfigSchema, UIReviewConfigSchema, CompactionAdvisoryConfigSchema, LintConfigSchema, SecretscanConfigSchema, GuardrailsProfileSchema, DEFAULT_AGENT_PROFILES, DEFAULT_ARCHITECT_PROFILE, GuardrailsConfigSchema, WatchdogConfigSchema, SelfReviewConfigSchema, ToolFilterConfigSchema, PlanCursorConfigSchema, CheckpointConfigSchema, AutomationModeSchema, AutomationCapabilitiesSchema, AutomationConfigSchemaBase, AutomationConfigSchema, KnowledgeConfigSchema, CuratorConfigSchema, SlopDetectorConfigSchema, IncrementalVerifyConfigSchema, CompactionConfigSchema, AgentAuthorityRuleSchema, AuthorityConfigSchema, PluginConfigSchema;
14577
14577
  var init_schema = __esm(() => {
14578
14578
  init_zod();
14579
14579
  init_constants();
@@ -15047,6 +15047,17 @@ var init_schema = __esm(() => {
15047
15047
  emergencyThreshold: exports_external.number().min(1).max(99).default(80),
15048
15048
  preserveLastNTurns: exports_external.number().int().min(1).default(5)
15049
15049
  });
15050
+ AgentAuthorityRuleSchema = exports_external.object({
15051
+ readOnly: exports_external.boolean().optional(),
15052
+ blockedExact: exports_external.array(exports_external.string()).optional(),
15053
+ blockedPrefix: exports_external.array(exports_external.string()).optional(),
15054
+ allowedPrefix: exports_external.array(exports_external.string()).optional(),
15055
+ blockedZones: exports_external.array(exports_external.enum(["production", "test", "config", "generated", "docs", "build"])).optional()
15056
+ });
15057
+ AuthorityConfigSchema = exports_external.object({
15058
+ enabled: exports_external.boolean().default(true),
15059
+ rules: exports_external.record(exports_external.string(), AgentAuthorityRuleSchema).default({})
15060
+ });
15050
15061
  PluginConfigSchema = exports_external.object({
15051
15062
  agents: exports_external.record(exports_external.string(), AgentOverrideConfigSchema).optional(),
15052
15063
  swarms: exports_external.record(exports_external.string(), SwarmConfigSchema).optional(),
@@ -15063,6 +15074,7 @@ var init_schema = __esm(() => {
15063
15074
  watchdog: WatchdogConfigSchema.optional(),
15064
15075
  self_review: SelfReviewConfigSchema.optional(),
15065
15076
  tool_filter: ToolFilterConfigSchema.optional(),
15077
+ authority: AuthorityConfigSchema.optional(),
15066
15078
  plan_cursor: PlanCursorConfigSchema.optional(),
15067
15079
  evidence: EvidenceConfigSchema.optional(),
15068
15080
  summaries: SummaryConfigSchema.optional(),
@@ -32519,7 +32531,7 @@ __export(exports_co_change_analyzer, {
32519
32531
  buildCoChangeMatrix: () => buildCoChangeMatrix
32520
32532
  });
32521
32533
  import * as child_process2 from "child_process";
32522
- import { randomUUID } from "crypto";
32534
+ import { randomUUID as randomUUID2 } from "crypto";
32523
32535
  import { readdir as readdir2, readFile as readFile4, stat } from "fs/promises";
32524
32536
  import * as path19 from "path";
32525
32537
  import { promisify } from "util";
@@ -32779,7 +32791,7 @@ function darkMatterToKnowledgeEntries(pairs, projectName) {
32779
32791
  }
32780
32792
  const confidence = Math.min(0.3 + 0.2 * Math.min(pair.coChangeCount / 10, 1), 0.5);
32781
32793
  entries.push({
32782
- id: randomUUID(),
32794
+ id: randomUUID2(),
32783
32795
  tier: "swarm",
32784
32796
  lesson,
32785
32797
  category: "architecture",
@@ -36214,17 +36226,17 @@ function getTestFilesFromConvention(sourceFiles) {
36214
36226
  const testFiles = [];
36215
36227
  for (const file3 of sourceFiles) {
36216
36228
  const normalizedPath = file3.replace(/\\/g, "/");
36217
- const basename4 = path28.basename(file3);
36229
+ const basename5 = path28.basename(file3);
36218
36230
  const dirname12 = path28.dirname(file3);
36219
- if (hasCompoundTestExtension(basename4) || basename4.includes(".spec.") || basename4.includes(".test.") || normalizedPath.includes("/__tests__/") || normalizedPath.includes("/tests/") || normalizedPath.includes("/test/")) {
36231
+ if (hasCompoundTestExtension(basename5) || basename5.includes(".spec.") || basename5.includes(".test.") || normalizedPath.includes("/__tests__/") || normalizedPath.includes("/tests/") || normalizedPath.includes("/test/")) {
36220
36232
  if (!testFiles.includes(file3)) {
36221
36233
  testFiles.push(file3);
36222
36234
  }
36223
36235
  continue;
36224
36236
  }
36225
36237
  for (const _pattern of TEST_PATTERNS) {
36226
- const nameWithoutExt = basename4.replace(/\.[^.]+$/, "");
36227
- const ext = path28.extname(basename4);
36238
+ const nameWithoutExt = basename5.replace(/\.[^.]+$/, "");
36239
+ const ext = path28.extname(basename5);
36228
36240
  const possibleTestFiles = [
36229
36241
  path28.join(dirname12, `${nameWithoutExt}.spec${ext}`),
36230
36242
  path28.join(dirname12, `${nameWithoutExt}.test${ext}`),
@@ -38054,17 +38066,17 @@ function normalizeSeparators(filePath) {
38054
38066
  }
38055
38067
  function matchesDocPattern(filePath, patterns) {
38056
38068
  const normalizedPath = normalizeSeparators(filePath);
38057
- const basename5 = path42.basename(filePath);
38069
+ const basename6 = path42.basename(filePath);
38058
38070
  for (const pattern of patterns) {
38059
38071
  if (!pattern.includes("/") && !pattern.includes("\\")) {
38060
- if (basename5 === pattern) {
38072
+ if (basename6 === pattern) {
38061
38073
  return true;
38062
38074
  }
38063
38075
  continue;
38064
38076
  }
38065
38077
  if (pattern.startsWith("**/")) {
38066
38078
  const filenamePattern = pattern.slice(3);
38067
- if (basename5 === filenamePattern) {
38079
+ if (basename6 === filenamePattern) {
38068
38080
  return true;
38069
38081
  }
38070
38082
  continue;
@@ -44118,7 +44130,8 @@ COMPLIANCE:
44118
44130
  - [type]: [description] (or "No deviations observed")
44119
44131
 
44120
44132
  KNOWLEDGE_UPDATES:
44121
- - [action] [entry_id or "new"]: [reason] (or "No recommendations")
44133
+ - [action] new: [reason] (or "No recommendations")
44134
+ NOTE: Always use "new" as the token \u2014 existing entry IDs (UUID v4) are not available in this context. Any non-UUID token is treated as "new" by the parser. Only "promote new:" creates a new entry; "archive new:" and "flag_contradiction new:" are silently skipped because those actions require an existing entry to operate on.
44122
44135
 
44123
44136
  EXTENDED_DIGEST:
44124
44137
  [the full running digest with this phase appended]
@@ -48202,6 +48215,7 @@ async function handleConfigCommand(directory, _args) {
48202
48215
  init_schema();
48203
48216
 
48204
48217
  // src/hooks/curator.ts
48218
+ import { randomUUID } from "crypto";
48205
48219
  import * as fs12 from "fs";
48206
48220
  import * as path18 from "path";
48207
48221
  init_event_bus();
@@ -48223,7 +48237,8 @@ function parseKnowledgeRecommendations(llmOutput) {
48223
48237
  const match = trimmed.match(/^-\s+(promote|archive|flag_contradiction)\s+(\S+):\s+(.+)$/i);
48224
48238
  if (match) {
48225
48239
  const action = match[1].toLowerCase();
48226
- const entryId = match[2] === "new" ? undefined : match[2];
48240
+ const UUID_V4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
48241
+ const entryId = match[2] === "new" || !UUID_V4.test(match[2]) ? undefined : match[2];
48227
48242
  const reason = match[3].trim();
48228
48243
  recommendations.push({
48229
48244
  action,
@@ -48737,6 +48752,43 @@ async function applyCuratorKnowledgeUpdates(directory, recommendations, _knowled
48737
48752
  if (modified) {
48738
48753
  await rewriteKnowledge(knowledgePath, updatedEntries);
48739
48754
  }
48755
+ for (const rec of recommendations) {
48756
+ if (rec.entry_id !== undefined)
48757
+ continue;
48758
+ if (rec.action !== "promote") {
48759
+ skipped++;
48760
+ continue;
48761
+ }
48762
+ const lesson = rec.lesson?.trim() ?? "";
48763
+ if (lesson.length < 15) {
48764
+ skipped++;
48765
+ continue;
48766
+ }
48767
+ const now = new Date().toISOString();
48768
+ const newEntry = {
48769
+ id: randomUUID(),
48770
+ tier: "swarm",
48771
+ lesson: lesson.slice(0, 280),
48772
+ category: "other",
48773
+ tags: [],
48774
+ scope: "global",
48775
+ confidence: 0.5,
48776
+ status: "candidate",
48777
+ confirmed_by: [],
48778
+ retrieval_outcomes: {
48779
+ applied_count: 0,
48780
+ succeeded_after_count: 0,
48781
+ failed_after_count: 0
48782
+ },
48783
+ schema_version: 1,
48784
+ created_at: now,
48785
+ updated_at: now,
48786
+ auto_generated: true,
48787
+ project_name: path18.basename(directory)
48788
+ };
48789
+ await appendKnowledge(knowledgePath, newEntry);
48790
+ applied++;
48791
+ }
48740
48792
  return { applied, skipped };
48741
48793
  }
48742
48794
 
@@ -50416,7 +50468,7 @@ init_schema();
50416
50468
 
50417
50469
  // src/hooks/knowledge-migrator.ts
50418
50470
  init_knowledge_store();
50419
- import { randomUUID as randomUUID2 } from "crypto";
50471
+ import { randomUUID as randomUUID3 } from "crypto";
50420
50472
  import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
50421
50473
  import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
50422
50474
  import * as path23 from "path";
@@ -50486,7 +50538,7 @@ async function migrateContextToKnowledge(directory, config3) {
50486
50538
  }
50487
50539
  const inferredTags = inferTags(raw.text);
50488
50540
  const entry = {
50489
- id: randomUUID2(),
50541
+ id: randomUUID3(),
50490
50542
  tier: "swarm",
50491
50543
  lesson: truncateLesson(raw.text),
50492
50544
  category: raw.categoryHint ?? inferCategoryFromText(raw.text),
@@ -53518,7 +53570,7 @@ function isInDeclaredScope(filePath, scopeEntries, cwd) {
53518
53570
  return rel.length > 0 && !rel.startsWith("..") && !path35.isAbsolute(rel);
53519
53571
  });
53520
53572
  }
53521
- function createGuardrailsHooks(directory, directoryOrConfig, config3) {
53573
+ function createGuardrailsHooks(directory, directoryOrConfig, config3, authorityConfig) {
53522
53574
  let guardrailsConfig;
53523
53575
  if (directory && typeof directory === "object" && "enabled" in directory) {
53524
53576
  console.warn("[guardrails] Legacy call without directory, falling back to process.cwd()");
@@ -53536,6 +53588,7 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3) {
53536
53588
  messagesTransform: async () => {}
53537
53589
  };
53538
53590
  }
53591
+ const precomputedAuthorityRules = buildEffectiveRules(authorityConfig);
53539
53592
  const cfg = guardrailsConfig;
53540
53593
  const requiredQaGates = cfg.qa_gates?.required_tools ?? [
53541
53594
  "diff",
@@ -53630,7 +53683,7 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3) {
53630
53683
  if (typeof delegTargetPath === "string" && delegTargetPath.length > 0) {
53631
53684
  const agentName = swarmState.activeAgent.get(sessionID) ?? "unknown";
53632
53685
  const cwd = effectiveDirectory;
53633
- const authorityCheck = checkFileAuthority(agentName, delegTargetPath, cwd);
53686
+ const authorityCheck = checkFileAuthorityWithRules(agentName, delegTargetPath, cwd, precomputedAuthorityRules);
53634
53687
  if (!authorityCheck.allowed) {
53635
53688
  throw new Error(`WRITE BLOCKED: Agent "${agentName}" is not authorised to write "${delegTargetPath}". Reason: ${authorityCheck.reason}`);
53636
53689
  }
@@ -53639,6 +53692,19 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3) {
53639
53692
  }
53640
53693
  }
53641
53694
  }
53695
+ if (tool3 === "apply_patch" || tool3 === "patch") {
53696
+ const agentName = swarmState.activeAgent.get(sessionID) ?? "unknown";
53697
+ const cwd = effectiveDirectory;
53698
+ for (const p of extractPatchTargetPaths(tool3, args2)) {
53699
+ const authorityCheck = checkFileAuthorityWithRules(agentName, p, cwd, precomputedAuthorityRules);
53700
+ if (!authorityCheck.allowed) {
53701
+ throw new Error(`WRITE BLOCKED: Agent "${agentName}" is not authorised to write "${p}" (via patch). Reason: ${authorityCheck.reason}`);
53702
+ }
53703
+ if (!currentSession.modifiedFilesThisCoderTask.includes(p)) {
53704
+ currentSession.modifiedFilesThisCoderTask.push(p);
53705
+ }
53706
+ }
53707
+ }
53642
53708
  } else if (isArchitect(sessionID)) {
53643
53709
  const coderDelegArgs = args2;
53644
53710
  const rawSubagentType = coderDelegArgs?.subagent_type;
@@ -53707,6 +53773,55 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3) {
53707
53773
  }
53708
53774
  }
53709
53775
  }
53776
+ function extractPatchTargetPaths(tool3, args2) {
53777
+ if (tool3 !== "apply_patch" && tool3 !== "patch")
53778
+ return [];
53779
+ const toolArgs = args2;
53780
+ const patchText = toolArgs?.input ?? toolArgs?.patch ?? (Array.isArray(toolArgs?.cmd) ? toolArgs.cmd[1] : undefined);
53781
+ if (typeof patchText !== "string")
53782
+ return [];
53783
+ if (patchText.length > 1e6) {
53784
+ throw new Error("WRITE BLOCKED: Patch payload exceeds 1 MB \u2014 authority cannot be verified for all modified paths. Split into smaller patches.");
53785
+ }
53786
+ const paths = new Set;
53787
+ const patchPathPattern = /\*\*\*\s+(?:Update|Add|Delete)\s+File:\s*(.+)/gi;
53788
+ const diffPathPattern = /\+\+\+\s+b\/(.+)/gm;
53789
+ const gitDiffPathPattern = /^diff --git a\/(.+?) b\/(.+?)$/gm;
53790
+ const minusPathPattern = /^---\s+a\/(.+)$/gm;
53791
+ const traditionalMinusPattern = /^---\s+([^\s].+?)(?:\t.*)?$/gm;
53792
+ const traditionalPlusPattern = /^\+\+\+\s+([^\s].+?)(?:\t.*)?$/gm;
53793
+ for (const match of patchText.matchAll(patchPathPattern))
53794
+ paths.add(match[1].trim());
53795
+ for (const match of patchText.matchAll(diffPathPattern)) {
53796
+ const p = match[1].trim();
53797
+ if (p !== "/dev/null")
53798
+ paths.add(p);
53799
+ }
53800
+ for (const match of patchText.matchAll(gitDiffPathPattern)) {
53801
+ const aPath = match[1].trim();
53802
+ const bPath = match[2].trim();
53803
+ if (aPath !== "/dev/null")
53804
+ paths.add(aPath);
53805
+ if (bPath !== "/dev/null")
53806
+ paths.add(bPath);
53807
+ }
53808
+ for (const match of patchText.matchAll(minusPathPattern)) {
53809
+ const p = match[1].trim();
53810
+ if (p !== "/dev/null")
53811
+ paths.add(p);
53812
+ }
53813
+ for (const match of patchText.matchAll(traditionalMinusPattern)) {
53814
+ const p = match[1].trim();
53815
+ if (p !== "/dev/null" && !p.startsWith("a/") && !p.startsWith("b/"))
53816
+ paths.add(p);
53817
+ }
53818
+ for (const match of patchText.matchAll(traditionalPlusPattern)) {
53819
+ const p = match[1].trim();
53820
+ if (p !== "/dev/null" && !p.startsWith("a/") && !p.startsWith("b/"))
53821
+ paths.add(p);
53822
+ }
53823
+ return Array.from(paths);
53824
+ }
53710
53825
  function handlePlanAndScopeProtection(sessionID, tool3, args2) {
53711
53826
  const toolArgs = args2;
53712
53827
  const targetPath = toolArgs?.filePath ?? toolArgs?.path ?? toolArgs?.file ?? toolArgs?.target;
@@ -53719,68 +53834,25 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3) {
53719
53834
  }
53720
53835
  }
53721
53836
  if (!targetPath && (tool3 === "apply_patch" || tool3 === "patch")) {
53722
- const patchText = toolArgs?.input ?? toolArgs?.patch ?? (Array.isArray(toolArgs?.cmd) ? toolArgs.cmd[1] : undefined);
53723
- if (typeof patchText === "string" && patchText.length <= 1e6) {
53724
- const patchPathPattern = /\*\*\*\s+(?:Update|Add|Delete)\s+File:\s*(.+)/gi;
53725
- const diffPathPattern = /\+\+\+\s+b\/(.+)/gm;
53726
- const paths = new Set;
53727
- for (const match of patchText.matchAll(patchPathPattern)) {
53728
- paths.add(match[1].trim());
53729
- }
53730
- for (const match of patchText.matchAll(diffPathPattern)) {
53731
- const p = match[1].trim();
53732
- if (p !== "/dev/null")
53733
- paths.add(p);
53734
- }
53735
- const gitDiffPathPattern = /^diff --git a\/(.+?) b\/(.+?)$/gm;
53736
- for (const match of patchText.matchAll(gitDiffPathPattern)) {
53737
- const aPath = match[1].trim();
53738
- const bPath = match[2].trim();
53739
- if (aPath !== "/dev/null")
53740
- paths.add(aPath);
53741
- if (bPath !== "/dev/null")
53742
- paths.add(bPath);
53743
- }
53744
- const minusPathPattern = /^---\s+a\/(.+)$/gm;
53745
- for (const match of patchText.matchAll(minusPathPattern)) {
53746
- const p = match[1].trim();
53747
- if (p !== "/dev/null")
53748
- paths.add(p);
53749
- }
53750
- const traditionalMinusPattern = /^---\s+([^\s].+?)(?:\t.*)?$/gm;
53751
- const traditionalPlusPattern = /^\+\+\+\s+([^\s].+?)(?:\t.*)?$/gm;
53752
- for (const match of patchText.matchAll(traditionalMinusPattern)) {
53753
- const p = match[1].trim();
53754
- if (p !== "/dev/null" && !p.startsWith("a/") && !p.startsWith("b/")) {
53755
- paths.add(p);
53756
- }
53757
- }
53758
- for (const match of patchText.matchAll(traditionalPlusPattern)) {
53759
- const p = match[1].trim();
53760
- if (p !== "/dev/null" && !p.startsWith("a/") && !p.startsWith("b/")) {
53761
- paths.add(p);
53762
- }
53763
- }
53764
- for (const p of paths) {
53765
- const resolvedP = path35.resolve(effectiveDirectory, p);
53766
- const planMdPath = path35.resolve(effectiveDirectory, ".swarm", "plan.md").toLowerCase();
53767
- const planJsonPath = path35.resolve(effectiveDirectory, ".swarm", "plan.json").toLowerCase();
53768
- if (resolvedP.toLowerCase() === planMdPath || resolvedP.toLowerCase() === planJsonPath) {
53769
- throw new Error("PLAN STATE VIOLATION: Direct writes to .swarm/plan.md and .swarm/plan.json are blocked. " + "plan.md is auto-regenerated from plan.json by PlanSyncWorker. " + "Use update_task_status() to mark tasks complete, " + "phase_complete() for phase transitions, or " + "save_plan to create/restructure plans.");
53770
- }
53771
- if (isOutsideSwarmDir(p, effectiveDirectory) && (isSourceCodePath(p) || hasTraversalSegments(p))) {
53772
- const session = swarmState.agentSessions.get(sessionID);
53773
- if (session) {
53774
- session.architectWriteCount++;
53775
- warn("Architect direct code edit detected via apply_patch", {
53776
- tool: tool3,
53777
- sessionID,
53778
- targetPath: p,
53779
- writeCount: session.architectWriteCount
53780
- });
53781
- }
53782
- break;
53837
+ for (const p of extractPatchTargetPaths(tool3, args2)) {
53838
+ const resolvedP = path35.resolve(effectiveDirectory, p);
53839
+ const planMdPath = path35.resolve(effectiveDirectory, ".swarm", "plan.md").toLowerCase();
53840
+ const planJsonPath = path35.resolve(effectiveDirectory, ".swarm", "plan.json").toLowerCase();
53841
+ if (resolvedP.toLowerCase() === planMdPath || resolvedP.toLowerCase() === planJsonPath) {
53842
+ throw new Error("PLAN STATE VIOLATION: Direct writes to .swarm/plan.md and .swarm/plan.json are blocked. " + "plan.md is auto-regenerated from plan.json by PlanSyncWorker. " + "Use update_task_status() to mark tasks complete, " + "phase_complete() for phase transitions, or " + "save_plan to create/restructure plans.");
53843
+ }
53844
+ if (isOutsideSwarmDir(p, effectiveDirectory) && (isSourceCodePath(p) || hasTraversalSegments(p))) {
53845
+ const session = swarmState.agentSessions.get(sessionID);
53846
+ if (session) {
53847
+ session.architectWriteCount++;
53848
+ warn("Architect direct code edit detected via apply_patch", {
53849
+ tool: tool3,
53850
+ sessionID,
53851
+ targetPath: p,
53852
+ writeCount: session.architectWriteCount
53853
+ });
53783
53854
  }
53855
+ break;
53784
53856
  }
53785
53857
  }
53786
53858
  }
@@ -53877,6 +53949,24 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3) {
53877
53949
  handleTestSuiteBlocking(input.tool, output.args);
53878
53950
  if (isArchitect(input.sessionID) && isWriteTool(input.tool)) {
53879
53951
  handlePlanAndScopeProtection(input.sessionID, input.tool, output.args);
53952
+ const toolArgs = output.args;
53953
+ const targetPath = toolArgs?.filePath ?? toolArgs?.path ?? toolArgs?.file ?? toolArgs?.target;
53954
+ if (typeof targetPath === "string" && targetPath.length > 0) {
53955
+ const agentName = swarmState.activeAgent.get(input.sessionID) ?? "architect";
53956
+ const authorityCheck = checkFileAuthorityWithRules(agentName, targetPath, effectiveDirectory, precomputedAuthorityRules);
53957
+ if (!authorityCheck.allowed) {
53958
+ throw new Error(`WRITE BLOCKED: Agent "${agentName}" is not authorised to write "${targetPath}". Reason: ${authorityCheck.reason}`);
53959
+ }
53960
+ }
53961
+ }
53962
+ if (input.tool === "apply_patch" || input.tool === "patch") {
53963
+ const agentName = swarmState.activeAgent.get(input.sessionID) ?? "architect";
53964
+ for (const p of extractPatchTargetPaths(input.tool, output.args)) {
53965
+ const authorityCheck = checkFileAuthorityWithRules(agentName, p, effectiveDirectory, precomputedAuthorityRules);
53966
+ if (!authorityCheck.allowed) {
53967
+ throw new Error(`WRITE BLOCKED: Agent "${agentName}" is not authorised to write "${p}" (via patch). Reason: ${authorityCheck.reason}`);
53968
+ }
53969
+ }
53880
53970
  }
53881
53971
  const resolved = resolveSessionAndWindow(input.sessionID);
53882
53972
  if (!resolved)
@@ -54371,7 +54461,7 @@ function hashArgs(args2) {
54371
54461
  return 0;
54372
54462
  }
54373
54463
  }
54374
- var AGENT_AUTHORITY_RULES = {
54464
+ var DEFAULT_AGENT_AUTHORITY_RULES = {
54375
54465
  architect: {
54376
54466
  blockedExact: [".swarm/plan.md", ".swarm/plan.json"],
54377
54467
  blockedZones: ["generated"]
@@ -54412,12 +54502,38 @@ var AGENT_AUTHORITY_RULES = {
54412
54502
  blockedZones: ["generated"]
54413
54503
  }
54414
54504
  };
54415
- function checkFileAuthority(agentName, filePath, cwd) {
54505
+ function buildEffectiveRules(authorityConfig) {
54506
+ if (authorityConfig?.enabled === false || !authorityConfig?.rules) {
54507
+ return DEFAULT_AGENT_AUTHORITY_RULES;
54508
+ }
54509
+ const entries = Object.entries(authorityConfig.rules);
54510
+ if (entries.length === 0) {
54511
+ return DEFAULT_AGENT_AUTHORITY_RULES;
54512
+ }
54513
+ const merged = {
54514
+ ...DEFAULT_AGENT_AUTHORITY_RULES
54515
+ };
54516
+ for (const [agent, userRule] of entries) {
54517
+ const normalizedRuleKey = agent.toLowerCase();
54518
+ const existing = merged[normalizedRuleKey] ?? {};
54519
+ merged[normalizedRuleKey] = {
54520
+ ...existing,
54521
+ ...userRule,
54522
+ blockedExact: userRule.blockedExact ?? existing.blockedExact,
54523
+ blockedPrefix: userRule.blockedPrefix ?? existing.blockedPrefix,
54524
+ allowedPrefix: userRule.allowedPrefix ?? existing.allowedPrefix,
54525
+ blockedZones: userRule.blockedZones ?? existing.blockedZones
54526
+ };
54527
+ }
54528
+ return merged;
54529
+ }
54530
+ function checkFileAuthorityWithRules(agentName, filePath, cwd, effectiveRules) {
54416
54531
  const normalizedAgent = agentName.toLowerCase();
54532
+ const strippedAgent = stripKnownSwarmPrefix(agentName).toLowerCase();
54417
54533
  const dir = cwd || process.cwd();
54418
54534
  const resolved = path35.resolve(dir, filePath);
54419
54535
  const normalizedPath = path35.relative(dir, resolved).replace(/\\/g, "/");
54420
- const rules = AGENT_AUTHORITY_RULES[normalizedAgent];
54536
+ const rules = effectiveRules[normalizedAgent] ?? effectiveRules[strippedAgent];
54421
54537
  if (!rules) {
54422
54538
  return { allowed: false, reason: `Unknown agent: ${agentName}` };
54423
54539
  }
@@ -54444,8 +54560,8 @@ function checkFileAuthority(agentName, filePath, cwd) {
54444
54560
  }
54445
54561
  }
54446
54562
  }
54447
- if (rules.allowedPrefix && rules.allowedPrefix.length > 0) {
54448
- const isAllowed = rules.allowedPrefix.some((prefix) => normalizedPath.startsWith(prefix));
54563
+ if (rules.allowedPrefix != null) {
54564
+ const isAllowed = rules.allowedPrefix.length > 0 ? rules.allowedPrefix.some((prefix) => normalizedPath.startsWith(prefix)) : false;
54449
54565
  if (!isAllowed) {
54450
54566
  return {
54451
54567
  allowed: false,
@@ -59858,7 +59974,7 @@ function countCodeLines(content) {
59858
59974
  return lines.length;
59859
59975
  }
59860
59976
  function isTestFile(filePath) {
59861
- const basename7 = path52.basename(filePath);
59977
+ const basename8 = path52.basename(filePath);
59862
59978
  const _ext = path52.extname(filePath).toLowerCase();
59863
59979
  const testPatterns = [
59864
59980
  ".test.",
@@ -59874,7 +59990,7 @@ function isTestFile(filePath) {
59874
59990
  ".spec.jsx"
59875
59991
  ];
59876
59992
  for (const pattern of testPatterns) {
59877
- if (basename7.includes(pattern)) {
59993
+ if (basename8.includes(pattern)) {
59878
59994
  return true;
59879
59995
  }
59880
59996
  }
@@ -60540,7 +60656,12 @@ var curator_analyze = createSwarmTool({
60540
60656
  let applied = 0;
60541
60657
  let skipped = 0;
60542
60658
  if (typedArgs.recommendations && typedArgs.recommendations.length > 0) {
60543
- const result = await applyCuratorKnowledgeUpdates(directory, typedArgs.recommendations, knowledgeConfig);
60659
+ const UUID_V4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
60660
+ const sanitizedRecs = typedArgs.recommendations.map((rec) => ({
60661
+ ...rec,
60662
+ entry_id: rec.entry_id === undefined || UUID_V4.test(rec.entry_id) ? rec.entry_id : undefined
60663
+ }));
60664
+ const result = await applyCuratorKnowledgeUpdates(directory, sanitizedRecs, knowledgeConfig);
60544
60665
  applied = result.applied;
60545
60666
  skipped = result.skipped;
60546
60667
  }
@@ -62218,7 +62339,7 @@ var imports = createSwarmTool({
62218
62339
  init_dist();
62219
62340
  init_config();
62220
62341
  init_knowledge_store();
62221
- import { randomUUID as randomUUID5 } from "crypto";
62342
+ import { randomUUID as randomUUID6 } from "crypto";
62222
62343
  init_manager2();
62223
62344
  init_create_tool();
62224
62345
  var VALID_CATEGORIES2 = [
@@ -62293,7 +62414,7 @@ var knowledgeAdd = createSwarmTool({
62293
62414
  project_name = plan?.title ?? "";
62294
62415
  } catch {}
62295
62416
  const entry = {
62296
- id: randomUUID5(),
62417
+ id: randomUUID6(),
62297
62418
  tier: "swarm",
62298
62419
  lesson,
62299
62420
  category,
@@ -69968,7 +70089,8 @@ var OpenCodeSwarm = async (ctx) => {
69968
70089
  console.warn("");
69969
70090
  }
69970
70091
  const delegationHandler = createDelegationTrackerHook(config3, guardrailsConfig.enabled);
69971
- const guardrailsHooks = createGuardrailsHooks(ctx.directory, undefined, guardrailsConfig);
70092
+ const authorityConfig = AuthorityConfigSchema.parse(config3.authority ?? {});
70093
+ const guardrailsHooks = createGuardrailsHooks(ctx.directory, undefined, guardrailsConfig, authorityConfig);
69972
70094
  const watchdogConfig = WatchdogConfigSchema.parse(config3.watchdog ?? {});
69973
70095
  const advisoryInjector = (sessionId, message) => {
69974
70096
  const s = swarmState.agentSessions.get(sessionId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "6.45.0",
3
+ "version": "6.46.0",
4
4
  "description": "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",