opencode-swarm 7.21.3 → 7.21.5
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/dist/cli/index.js
CHANGED
|
@@ -34,7 +34,7 @@ var package_default;
|
|
|
34
34
|
var init_package = __esm(() => {
|
|
35
35
|
package_default = {
|
|
36
36
|
name: "opencode-swarm",
|
|
37
|
-
version: "7.21.
|
|
37
|
+
version: "7.21.5",
|
|
38
38
|
description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
|
|
39
39
|
main: "dist/index.js",
|
|
40
40
|
types: "dist/index.d.ts",
|
|
@@ -16043,7 +16043,7 @@ var init_manager = __esm(() => {
|
|
|
16043
16043
|
|
|
16044
16044
|
// src/commands/acknowledge-spec-drift.ts
|
|
16045
16045
|
import { promises as fsPromises3 } from "fs";
|
|
16046
|
-
async function handleAcknowledgeSpecDriftCommand(directory, _args) {
|
|
16046
|
+
async function handleAcknowledgeSpecDriftCommand(directory, _args, acknowledgedBy = "unknown") {
|
|
16047
16047
|
const specStalenessPath = validateSwarmPath(directory, "spec-staleness.json");
|
|
16048
16048
|
let stalenessContent;
|
|
16049
16049
|
try {
|
|
@@ -16084,7 +16084,7 @@ async function handleAcknowledgeSpecDriftCommand(directory, _args) {
|
|
|
16084
16084
|
timestamp: new Date().toISOString(),
|
|
16085
16085
|
phase,
|
|
16086
16086
|
planTitle,
|
|
16087
|
-
acknowledgedBy
|
|
16087
|
+
acknowledgedBy,
|
|
16088
16088
|
previousHash: stalenessData.specHash_plan,
|
|
16089
16089
|
newHash: currentHash
|
|
16090
16090
|
};
|
|
@@ -17378,7 +17378,8 @@ var init_schema = __esm(() => {
|
|
|
17378
17378
|
AuthorityConfigSchema = exports_external.object({
|
|
17379
17379
|
enabled: exports_external.boolean().default(true),
|
|
17380
17380
|
rules: exports_external.record(exports_external.string(), AgentAuthorityRuleSchema).default({}),
|
|
17381
|
-
universal_deny_prefixes: exports_external.array(exports_external.string().min(1)).default([])
|
|
17381
|
+
universal_deny_prefixes: exports_external.array(exports_external.string().min(1)).default([]),
|
|
17382
|
+
verifier_config_paths: exports_external.array(exports_external.string()).optional().describe("Additional glob patterns for verifier config files that are merged into the architect agent's blockedGlobs at plugin init. Writes to matching files are blocked by the authority layer.")
|
|
17382
17383
|
});
|
|
17383
17384
|
GeneralCouncilMemberConfigSchema = exports_external.object({
|
|
17384
17385
|
memberId: exports_external.string().min(1),
|
|
@@ -52031,7 +52032,8 @@ async function executeSwarmCommand(args) {
|
|
|
52031
52032
|
directory,
|
|
52032
52033
|
args: resolved.remainingArgs,
|
|
52033
52034
|
sessionID,
|
|
52034
|
-
agents
|
|
52035
|
+
agents,
|
|
52036
|
+
source: "chat"
|
|
52035
52037
|
});
|
|
52036
52038
|
} catch (_err) {
|
|
52037
52039
|
const cmdName = tokens[0] || "unknown";
|
|
@@ -52063,6 +52065,12 @@ function classifySwarmCommandToolUse(resolved) {
|
|
|
52063
52065
|
const canonicalKey = canonicalCommandKey(resolved);
|
|
52064
52066
|
const args = resolved.remainingArgs;
|
|
52065
52067
|
if (!SWARM_COMMAND_TOOL_ALLOWLIST.has(canonicalKey)) {
|
|
52068
|
+
if (HUMAN_ONLY_SWARM_COMMANDS.has(canonicalKey)) {
|
|
52069
|
+
return {
|
|
52070
|
+
allowed: false,
|
|
52071
|
+
message: `/swarm ${canonicalKey} is a human-only command. ` + `Present the situation to the user and ask them to run \`/swarm ${canonicalKey}\` themselves ` + `(or \`bunx opencode-swarm run ${canonicalKey}\` from a terminal). ` + `You MUST NOT run it yourself via Bash, swarm_command, or any other tool \u2014 ` + `the runtime guardrail will block such attempts.`
|
|
52072
|
+
};
|
|
52073
|
+
}
|
|
52066
52074
|
return {
|
|
52067
52075
|
allowed: false,
|
|
52068
52076
|
message: `/swarm ${canonicalKey} is not available through the chat tool yet.
|
|
@@ -52152,7 +52160,7 @@ function classifySwarmCommandChatFallbackUse(resolved) {
|
|
|
52152
52160
|
}
|
|
52153
52161
|
return { allowed: true };
|
|
52154
52162
|
}
|
|
52155
|
-
var SWARM_COMMAND_TOOL_COMMANDS, SWARM_COMMAND_TOOL_ALLOWLIST, NO_ARGS, SUMMARY_ID_PATTERN, TASK_ID_PATTERN;
|
|
52163
|
+
var SWARM_COMMAND_TOOL_COMMANDS, SWARM_COMMAND_TOOL_ALLOWLIST, HUMAN_ONLY_SWARM_COMMANDS, NO_ARGS, SUMMARY_ID_PATTERN, TASK_ID_PATTERN;
|
|
52156
52164
|
var init_tool_policy = __esm(() => {
|
|
52157
52165
|
init_command_dispatch();
|
|
52158
52166
|
SWARM_COMMAND_TOOL_COMMANDS = [
|
|
@@ -52198,6 +52206,13 @@ var init_tool_policy = __esm(() => {
|
|
|
52198
52206
|
"sync-plan",
|
|
52199
52207
|
"export"
|
|
52200
52208
|
]);
|
|
52209
|
+
HUMAN_ONLY_SWARM_COMMANDS = new Set([
|
|
52210
|
+
"acknowledge-spec-drift",
|
|
52211
|
+
"reset",
|
|
52212
|
+
"reset-session",
|
|
52213
|
+
"rollback",
|
|
52214
|
+
"checkpoint"
|
|
52215
|
+
]);
|
|
52201
52216
|
NO_ARGS = new Set([
|
|
52202
52217
|
"agents",
|
|
52203
52218
|
"config",
|
|
@@ -52755,7 +52770,7 @@ var init_registry = __esm(() => {
|
|
|
52755
52770
|
init_write_retro2();
|
|
52756
52771
|
COMMAND_REGISTRY = {
|
|
52757
52772
|
"acknowledge-spec-drift": {
|
|
52758
|
-
handler: (ctx) => handleAcknowledgeSpecDriftCommand(ctx.directory, ctx.args),
|
|
52773
|
+
handler: (ctx) => handleAcknowledgeSpecDriftCommand(ctx.directory, ctx.args, ctx.source === "cli" ? "cli" : ctx.source === "chat" ? "user" : "unknown"),
|
|
52759
52774
|
description: "Acknowledge that the spec has drifted from the plan and suppress further warnings",
|
|
52760
52775
|
args: "",
|
|
52761
52776
|
category: "diagnostics"
|
|
@@ -53601,7 +53616,8 @@ Valid commands: ${VALID_COMMANDS.join(", ")}`);
|
|
|
53601
53616
|
directory: cwd,
|
|
53602
53617
|
args: resolved.remainingArgs,
|
|
53603
53618
|
sessionID: "",
|
|
53604
|
-
agents: {}
|
|
53619
|
+
agents: {},
|
|
53620
|
+
source: "cli"
|
|
53605
53621
|
});
|
|
53606
53622
|
console.log(result);
|
|
53607
53623
|
return 0;
|
|
@@ -1,5 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Caller identification for spec-drift acknowledgment audit trail.
|
|
3
|
+
* Previously hardcoded as 'architect' — see issue #890, where the architect
|
|
4
|
+
* could shell out to `bunx opencode-swarm run acknowledge-spec-drift` and
|
|
5
|
+
* the resulting event mis-attributed the action. Callers now pass an
|
|
6
|
+
* explicit actor so events.jsonl can distinguish the legitimate paths
|
|
7
|
+
* ('user' from chat slash command, 'cli' from a real terminal) from any
|
|
8
|
+
* unidentified caller ('unknown'). The Bash guardrail
|
|
9
|
+
* (`src/hooks/guardrails.ts` section 23) blocks the agent-shell bypass at
|
|
10
|
+
* the runtime layer; this parameter exists for forensic clarity.
|
|
11
|
+
*/
|
|
12
|
+
export type SpecDriftAcknowledgedBy = 'user' | 'cli' | 'unknown';
|
|
1
13
|
/**
|
|
2
14
|
* Handle /swarm acknowledge-spec-drift command
|
|
3
15
|
* Acknowledges and clears a previously detected spec drift staleness warning
|
|
4
16
|
*/
|
|
5
|
-
export declare function handleAcknowledgeSpecDriftCommand(directory: string, _args: string[]): Promise<string>;
|
|
17
|
+
export declare function handleAcknowledgeSpecDriftCommand(directory: string, _args: string[], acknowledgedBy?: SpecDriftAcknowledgedBy): Promise<string>;
|
|
@@ -8,6 +8,14 @@ export type CommandContext = {
|
|
|
8
8
|
args: string[];
|
|
9
9
|
sessionID: string;
|
|
10
10
|
agents: Record<string, AgentDefinition>;
|
|
11
|
+
/**
|
|
12
|
+
* Dispatch path identifier. Issue #890: forensic audit trail for
|
|
13
|
+
* commands that need to distinguish "user typed /swarm <cmd>" (chat)
|
|
14
|
+
* from "user ran bunx opencode-swarm run <cmd>" (cli). Handlers that
|
|
15
|
+
* don't care can ignore this field. Optional for backwards-compatibility
|
|
16
|
+
* with existing callers.
|
|
17
|
+
*/
|
|
18
|
+
source?: 'cli' | 'chat';
|
|
11
19
|
};
|
|
12
20
|
export type CommandResult = Promise<string>;
|
|
13
21
|
export type CommandCategory = 'core' | 'agent' | 'config' | 'diagnostics' | 'utility';
|
|
@@ -2,5 +2,14 @@ import type { ResolvedSwarmCommand, SwarmCommandPolicyResult } from './command-d
|
|
|
2
2
|
export declare const SWARM_COMMAND_TOOL_COMMANDS: readonly ["agents", "config", "config doctor", "config-doctor", "doctor", "doctor tools", "status", "show-plan", "plan", "help", "history", "evidence", "evidence summary", "evidence-summary", "retrieve", "diagnose", "preflight", "benchmark", "knowledge", "sync-plan", "export", "list-agents"];
|
|
3
3
|
export type SwarmCommandToolInputCommand = (typeof SWARM_COMMAND_TOOL_COMMANDS)[number];
|
|
4
4
|
export declare const SWARM_COMMAND_TOOL_ALLOWLIST: Set<string>;
|
|
5
|
+
/**
|
|
6
|
+
* Issue #890: subcommands that must be invoked by a human user, not by the
|
|
7
|
+
* agent. The runtime Bash guardrail
|
|
8
|
+
* (`src/hooks/guardrails.ts` section 23) blocks the equivalent
|
|
9
|
+
* `bunx opencode-swarm run <cmd>` shell invocation; this set drives the
|
|
10
|
+
* chat-tool refusal message so the agent is told to surface to the user
|
|
11
|
+
* instead of being pointed at the CLI bypass it just attempted.
|
|
12
|
+
*/
|
|
13
|
+
export declare const HUMAN_ONLY_SWARM_COMMANDS: Set<string>;
|
|
5
14
|
export declare function classifySwarmCommandToolUse(resolved: ResolvedSwarmCommand): SwarmCommandPolicyResult;
|
|
6
15
|
export declare function classifySwarmCommandChatFallbackUse(resolved: ResolvedSwarmCommand): SwarmCommandPolicyResult;
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -641,6 +641,7 @@ export declare const AuthorityConfigSchema: z.ZodObject<{
|
|
|
641
641
|
allowedGlobs: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
642
642
|
}, z.core.$strip>>>;
|
|
643
643
|
universal_deny_prefixes: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
644
|
+
verifier_config_paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
644
645
|
}, z.core.$strip>;
|
|
645
646
|
export type AuthorityConfig = z.infer<typeof AuthorityConfigSchema>;
|
|
646
647
|
export declare const GeneralCouncilConfigSchema: z.ZodObject<{
|
|
@@ -1005,6 +1006,7 @@ export declare const PluginConfigSchema: z.ZodObject<{
|
|
|
1005
1006
|
allowedGlobs: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
1006
1007
|
}, z.core.$strip>>>;
|
|
1007
1008
|
universal_deny_prefixes: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
1009
|
+
verifier_config_paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
1008
1010
|
}, z.core.$strip>>;
|
|
1009
1011
|
plan_cursor: z.ZodOptional<z.ZodObject<{
|
|
1010
1012
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
package/dist/index.js
CHANGED
|
@@ -33,7 +33,7 @@ var package_default;
|
|
|
33
33
|
var init_package = __esm(() => {
|
|
34
34
|
package_default = {
|
|
35
35
|
name: "opencode-swarm",
|
|
36
|
-
version: "7.21.
|
|
36
|
+
version: "7.21.5",
|
|
37
37
|
description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
|
|
38
38
|
main: "dist/index.js",
|
|
39
39
|
types: "dist/index.d.ts",
|
|
@@ -15591,7 +15591,8 @@ var init_schema = __esm(() => {
|
|
|
15591
15591
|
AuthorityConfigSchema = exports_external.object({
|
|
15592
15592
|
enabled: exports_external.boolean().default(true),
|
|
15593
15593
|
rules: exports_external.record(exports_external.string(), AgentAuthorityRuleSchema).default({}),
|
|
15594
|
-
universal_deny_prefixes: exports_external.array(exports_external.string().min(1)).default([])
|
|
15594
|
+
universal_deny_prefixes: exports_external.array(exports_external.string().min(1)).default([]),
|
|
15595
|
+
verifier_config_paths: exports_external.array(exports_external.string()).optional().describe("Additional glob patterns for verifier config files that are merged into the architect agent's blockedGlobs at plugin init. Writes to matching files are blocked by the authority layer.")
|
|
15595
15596
|
});
|
|
15596
15597
|
GeneralCouncilMemberConfigSchema = exports_external.object({
|
|
15597
15598
|
memberId: exports_external.string().min(1),
|
|
@@ -18564,7 +18565,7 @@ var init_manager = __esm(() => {
|
|
|
18564
18565
|
|
|
18565
18566
|
// src/commands/acknowledge-spec-drift.ts
|
|
18566
18567
|
import { promises as fsPromises4 } from "node:fs";
|
|
18567
|
-
async function handleAcknowledgeSpecDriftCommand(directory, _args) {
|
|
18568
|
+
async function handleAcknowledgeSpecDriftCommand(directory, _args, acknowledgedBy = "unknown") {
|
|
18568
18569
|
const specStalenessPath = validateSwarmPath(directory, "spec-staleness.json");
|
|
18569
18570
|
let stalenessContent;
|
|
18570
18571
|
try {
|
|
@@ -18605,7 +18606,7 @@ async function handleAcknowledgeSpecDriftCommand(directory, _args) {
|
|
|
18605
18606
|
timestamp: new Date().toISOString(),
|
|
18606
18607
|
phase,
|
|
18607
18608
|
planTitle,
|
|
18608
|
-
acknowledgedBy
|
|
18609
|
+
acknowledgedBy,
|
|
18609
18610
|
previousHash: stalenessData.specHash_plan,
|
|
18610
18611
|
newHash: currentHash
|
|
18611
18612
|
};
|
|
@@ -24166,6 +24167,21 @@ var init_normalize_tool_name = __esm(() => {
|
|
|
24166
24167
|
import * as fsSync2 from "node:fs";
|
|
24167
24168
|
import * as fs8 from "node:fs/promises";
|
|
24168
24169
|
import * as path10 from "node:path";
|
|
24170
|
+
function isConfigFilePath(filePath, cwd, extraGlobs) {
|
|
24171
|
+
const normalized = path10.relative(path10.resolve(cwd), path10.resolve(cwd, filePath)).replace(/\\/g, "/");
|
|
24172
|
+
const { zone } = classifyFile(normalized);
|
|
24173
|
+
if (zone === "config") {
|
|
24174
|
+
return true;
|
|
24175
|
+
}
|
|
24176
|
+
const allGlobs = extraGlobs && extraGlobs.length > 0 ? [...KNOWN_VERIFIER_CONFIG_GLOBS, ...extraGlobs] : KNOWN_VERIFIER_CONFIG_GLOBS;
|
|
24177
|
+
for (const glob of allGlobs) {
|
|
24178
|
+
const matcher = getGlobMatcher(glob);
|
|
24179
|
+
if (matcher(normalized)) {
|
|
24180
|
+
return true;
|
|
24181
|
+
}
|
|
24182
|
+
}
|
|
24183
|
+
return false;
|
|
24184
|
+
}
|
|
24169
24185
|
function enforceSpecDriftGate(directory, toolName) {
|
|
24170
24186
|
if (!directory)
|
|
24171
24187
|
return;
|
|
@@ -24713,6 +24729,17 @@ function createGuardrailsHooks(directory, directoryOrConfig, config2, authorityC
|
|
|
24713
24729
|
};
|
|
24714
24730
|
}
|
|
24715
24731
|
const precomputedAuthorityRules = buildEffectiveRules(authorityConfig);
|
|
24732
|
+
const verifierPaths = authorityConfig?.verifier_config_paths;
|
|
24733
|
+
if (verifierPaths && verifierPaths.length > 0) {
|
|
24734
|
+
const existingArchitect = precomputedAuthorityRules.architect ?? {};
|
|
24735
|
+
precomputedAuthorityRules.architect = {
|
|
24736
|
+
...existingArchitect,
|
|
24737
|
+
blockedGlobs: [
|
|
24738
|
+
...existingArchitect.blockedGlobs ?? [],
|
|
24739
|
+
...verifierPaths
|
|
24740
|
+
]
|
|
24741
|
+
};
|
|
24742
|
+
}
|
|
24716
24743
|
const universalDenyPrefixes = authorityConfig?.universal_deny_prefixes ?? [];
|
|
24717
24744
|
const cfg = guardrailsConfig;
|
|
24718
24745
|
const requiredQaGates = cfg.qa_gates?.required_tools ?? [
|
|
@@ -24962,6 +24989,45 @@ function createGuardrailsHooks(directory, directoryOrConfig, config2, authorityC
|
|
|
24962
24989
|
if (/^7z\b.*\s-sdel\b/i.test(seg) && /\.swarm(?:[\x5c/\s]|$)/i.test(seg)) {
|
|
24963
24990
|
throw new Error(`BLOCKED: "7z" with delete-source flag targeting .swarm/ detected — archive with source deletion under .swarm/ is not allowed`);
|
|
24964
24991
|
}
|
|
24992
|
+
{
|
|
24993
|
+
const HUMAN_ONLY_SWARM_SUBCOMMANDS = new Set([
|
|
24994
|
+
"acknowledge-spec-drift",
|
|
24995
|
+
"reset",
|
|
24996
|
+
"reset-session",
|
|
24997
|
+
"rollback",
|
|
24998
|
+
"checkpoint"
|
|
24999
|
+
]);
|
|
25000
|
+
let probe = seg.replace(/^(?:[A-Za-z_][A-Za-z0-9_]*=\S+\s+)+/, "").replace(/^eval(?:\s+--)?\s+["']?/, "").replace(/["']\s*$/, "").replace(/^\$\(\s*/, "").replace(/^\(\s*/, "").replace(/\s*\)$/, "").replace(/^`/, "").replace(/`$/, "").trim();
|
|
25001
|
+
for (let i2 = 0;i2 < 4; i2++) {
|
|
25002
|
+
const before = probe;
|
|
25003
|
+
probe = probe.replace(/^env\s+(?:-i\b|--ignore-environment\b|-u\s+\S+|-[a-zA-Z]+\s+)*\s*/, "").replace(/^command\s+(?:-[pvV]\s+)*/, "").replace(/^(?:[A-Za-z_][A-Za-z0-9_]*=\S+\s+)+/, "").trim();
|
|
25004
|
+
if (probe === before)
|
|
25005
|
+
break;
|
|
25006
|
+
}
|
|
25007
|
+
const swarmCliBypassMatch = probe.match(/^\\?(?:bunx|npx|pnpx|npm(?:\s+(?:exec|x)(?:\s+--)?)?|pnpm(?:\s+(?:dlx|exec))?|yarn(?:\s+(?:dlx|exec))?|bun(?:\s+x)?|node|deno\s+run|tsx|ts-node)\b[^|;&]*?\bopencode-swarm\b[^|;&]*?\brun\s+([A-Za-z0-9_-]+)/i);
|
|
25008
|
+
if (swarmCliBypassMatch && HUMAN_ONLY_SWARM_SUBCOMMANDS.has(swarmCliBypassMatch[1])) {
|
|
25009
|
+
throw new Error(`BLOCKED: "${swarmCliBypassMatch[1]}" is a human-only swarm command and may not be invoked from shell by an agent. ` + `Present the situation to the user and ask them to run \`/swarm ${swarmCliBypassMatch[1]}\` themselves.`);
|
|
25010
|
+
}
|
|
25011
|
+
const swarmBareBinMatch = probe.match(/^\\?opencode-swarm\b[^|;&]*?\brun\s+([A-Za-z0-9_-]+)/i);
|
|
25012
|
+
if (swarmBareBinMatch && HUMAN_ONLY_SWARM_SUBCOMMANDS.has(swarmBareBinMatch[1])) {
|
|
25013
|
+
throw new Error(`BLOCKED: "${swarmBareBinMatch[1]}" is a human-only swarm command and may not be invoked from shell by an agent. ` + `Present the situation to the user and ask them to run \`/swarm ${swarmBareBinMatch[1]}\` themselves.`);
|
|
25014
|
+
}
|
|
25015
|
+
const swarmCliPathMatch = probe.match(/\bcli[/\\]+index\.[mc]?(?:js|ts)\b[^|;&]*?\brun\s+([A-Za-z0-9_-]+)/i);
|
|
25016
|
+
if (swarmCliPathMatch && HUMAN_ONLY_SWARM_SUBCOMMANDS.has(swarmCliPathMatch[1])) {
|
|
25017
|
+
throw new Error(`BLOCKED: "${swarmCliPathMatch[1]}" is a human-only swarm command and may not be invoked from shell by an agent. ` + `Present the situation to the user and ask them to run \`/swarm ${swarmCliPathMatch[1]}\` themselves.`);
|
|
25018
|
+
}
|
|
25019
|
+
}
|
|
25020
|
+
{
|
|
25021
|
+
const normForPathCheck = seg.replace(/\\/g, "/").replace(/\/(?:\.\/+)+/g, "/").replace(/\/{2,}/g, "/");
|
|
25022
|
+
if (/\.swarm\/spec-staleness\.json\b/i.test(normForPathCheck)) {
|
|
25023
|
+
const trimmed = seg.trim();
|
|
25024
|
+
const looksReadOnly = /^(?:cat|less|more|head|tail|file|stat|ls|dir|Get-Content|gc|Get-Item|gi|type)\b/i.test(trimmed);
|
|
25025
|
+
const hasWriteRedirect = />{1,2}\s*[^\s>]/.test(trimmed);
|
|
25026
|
+
if (!looksReadOnly || hasWriteRedirect) {
|
|
25027
|
+
throw new Error("BLOCKED: shell command targeting .swarm/spec-staleness.json detected. " + "This file is system-managed and gates plan-mutating tools while spec drift is unresolved. " + "Present the drift to the user and ask them to run /swarm clarify or /swarm acknowledge-spec-drift.");
|
|
25028
|
+
}
|
|
25029
|
+
}
|
|
25030
|
+
}
|
|
24965
25031
|
}
|
|
24966
25032
|
}
|
|
24967
25033
|
async function checkGateLimits(params) {
|
|
@@ -25063,6 +25129,19 @@ function createGuardrailsHooks(directory, directoryOrConfig, config2, authorityC
|
|
|
25063
25129
|
if (!authorityCheck.allowed) {
|
|
25064
25130
|
throw new Error(`WRITE BLOCKED: Agent "${agentName}" is not authorised to write "${delegTargetPath}". Reason: ${authorityCheck.reason}`);
|
|
25065
25131
|
}
|
|
25132
|
+
if (isConfigFilePath(delegTargetPath, cwd, authorityConfig?.verifier_config_paths)) {
|
|
25133
|
+
const normalizedPath = path10.relative(path10.resolve(cwd), path10.resolve(cwd, delegTargetPath)).replace(/\\/g, "/");
|
|
25134
|
+
const logEntry = {
|
|
25135
|
+
agent: agentName,
|
|
25136
|
+
path: normalizedPath,
|
|
25137
|
+
allowed: authorityCheck.allowed,
|
|
25138
|
+
type: "delegated_write"
|
|
25139
|
+
};
|
|
25140
|
+
if (!authorityCheck.allowed && "reason" in authorityCheck) {
|
|
25141
|
+
logEntry.reason = authorityCheck.reason;
|
|
25142
|
+
}
|
|
25143
|
+
warn("Config file write attempt", logEntry);
|
|
25144
|
+
}
|
|
25066
25145
|
if (!currentSession.modifiedFilesThisCoderTask.includes(delegTargetPath)) {
|
|
25067
25146
|
currentSession.modifiedFilesThisCoderTask.push(delegTargetPath);
|
|
25068
25147
|
}
|
|
@@ -25073,6 +25152,19 @@ function createGuardrailsHooks(directory, directoryOrConfig, config2, authorityC
|
|
|
25073
25152
|
const cwd = effectiveDirectory;
|
|
25074
25153
|
for (const p of extractPatchTargetPaths(tool, args2)) {
|
|
25075
25154
|
const authorityCheck = checkFileAuthorityWithRules(agentName, p, cwd, precomputedAuthorityRules, { declaredScope: resolveDeclaredScope(sessionID) });
|
|
25155
|
+
if (isConfigFilePath(p, cwd, authorityConfig?.verifier_config_paths)) {
|
|
25156
|
+
const normalizedPath = path10.relative(path10.resolve(cwd), path10.resolve(cwd, p)).replace(/\\/g, "/");
|
|
25157
|
+
const logEntry = {
|
|
25158
|
+
agent: agentName,
|
|
25159
|
+
path: normalizedPath,
|
|
25160
|
+
allowed: authorityCheck.allowed,
|
|
25161
|
+
type: "delegated_patch"
|
|
25162
|
+
};
|
|
25163
|
+
if (!authorityCheck.allowed && "reason" in authorityCheck) {
|
|
25164
|
+
logEntry.reason = authorityCheck.reason;
|
|
25165
|
+
}
|
|
25166
|
+
warn("Config file write attempt", logEntry);
|
|
25167
|
+
}
|
|
25076
25168
|
if (!authorityCheck.allowed) {
|
|
25077
25169
|
throw new Error(`WRITE BLOCKED: Agent "${agentName}" is not authorised to write "${p}" (via patch). Reason: ${authorityCheck.reason}`);
|
|
25078
25170
|
}
|
|
@@ -25149,11 +25241,37 @@ function createGuardrailsHooks(directory, directoryOrConfig, config2, authorityC
|
|
|
25149
25241
|
}
|
|
25150
25242
|
}
|
|
25151
25243
|
}
|
|
25244
|
+
function extractAllPatchPayloads(args2) {
|
|
25245
|
+
const toolArgs = args2;
|
|
25246
|
+
if (!toolArgs)
|
|
25247
|
+
return [];
|
|
25248
|
+
const out2 = [];
|
|
25249
|
+
for (const key of ["patch", "input", "diff"]) {
|
|
25250
|
+
const v = toolArgs[key];
|
|
25251
|
+
if (typeof v === "string" && v.length > 0)
|
|
25252
|
+
out2.push(v);
|
|
25253
|
+
}
|
|
25254
|
+
const cmd = toolArgs.cmd;
|
|
25255
|
+
if (Array.isArray(cmd)) {
|
|
25256
|
+
for (const entry of cmd) {
|
|
25257
|
+
if (typeof entry === "string" && entry.length > 0)
|
|
25258
|
+
out2.push(entry);
|
|
25259
|
+
}
|
|
25260
|
+
}
|
|
25261
|
+
return out2;
|
|
25262
|
+
}
|
|
25263
|
+
function patchPayloadHasHumanOnlyInvocation(args2) {
|
|
25264
|
+
const payloads = extractAllPatchPayloads(args2);
|
|
25265
|
+
if (payloads.length === 0)
|
|
25266
|
+
return false;
|
|
25267
|
+
const re = /\bopencode-swarm\b[\s\S]*?\brun\s+(?:acknowledge-spec-drift|reset|reset-session|rollback|checkpoint)\b/i;
|
|
25268
|
+
return payloads.some((p) => re.test(p));
|
|
25269
|
+
}
|
|
25152
25270
|
function extractPatchTargetPaths(tool, args2) {
|
|
25153
25271
|
if (tool !== "apply_patch" && tool !== "patch")
|
|
25154
25272
|
return [];
|
|
25155
25273
|
const toolArgs = args2;
|
|
25156
|
-
const patchText = toolArgs?.input ?? toolArgs?.patch ?? (Array.isArray(toolArgs?.cmd) ? toolArgs.cmd[1] : undefined);
|
|
25274
|
+
const patchText = toolArgs?.input ?? toolArgs?.patch ?? toolArgs?.diff ?? (Array.isArray(toolArgs?.cmd) ? toolArgs.cmd[1] : undefined);
|
|
25157
25275
|
if (typeof patchText !== "string")
|
|
25158
25276
|
return [];
|
|
25159
25277
|
if (patchText.length > 1e6) {
|
|
@@ -25201,6 +25319,11 @@ function createGuardrailsHooks(directory, directoryOrConfig, config2, authorityC
|
|
|
25201
25319
|
function handlePlanAndScopeProtection(sessionID, tool, args2) {
|
|
25202
25320
|
const toolArgs = args2;
|
|
25203
25321
|
const targetPath = toolArgs?.filePath ?? toolArgs?.path ?? toolArgs?.file ?? toolArgs?.target;
|
|
25322
|
+
if (tool === "apply_patch" || tool === "patch") {
|
|
25323
|
+
if (patchPayloadHasHumanOnlyInvocation(args2)) {
|
|
25324
|
+
throw new Error("BLOCKED: apply_patch would introduce a script invoking a human-only swarm CLI subcommand. " + "Present the situation to the user and ask them to run the command themselves.");
|
|
25325
|
+
}
|
|
25326
|
+
}
|
|
25204
25327
|
if (typeof targetPath === "string" && targetPath.length > 0) {
|
|
25205
25328
|
const resolvedTarget = path10.resolve(effectiveDirectory, targetPath).toLowerCase();
|
|
25206
25329
|
const planMdPath = path10.resolve(effectiveDirectory, ".swarm", "plan.md").toLowerCase();
|
|
@@ -25208,15 +25331,27 @@ function createGuardrailsHooks(directory, directoryOrConfig, config2, authorityC
|
|
|
25208
25331
|
if (resolvedTarget === planMdPath || resolvedTarget === planJsonPath) {
|
|
25209
25332
|
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 save_plan for ALL structural plan changes (adding/removing tasks, updating descriptions, dependencies, or phase names). " + "Use update_task_status() for task status only. " + "Use phase_complete() for phase transitions only.");
|
|
25210
25333
|
}
|
|
25334
|
+
const specStalenessPath = path10.resolve(effectiveDirectory, ".swarm", "spec-staleness.json").toLowerCase();
|
|
25335
|
+
if (resolvedTarget === specStalenessPath) {
|
|
25336
|
+
throw new Error("SPEC_DRIFT_VIOLATION: Direct writes to .swarm/spec-staleness.json are blocked. " + "This file is system-managed and gates plan-mutating tools while spec drift is unresolved. " + "Present the drift to the user and ask them to run /swarm clarify or /swarm acknowledge-spec-drift.");
|
|
25337
|
+
}
|
|
25338
|
+
const content = toolArgs?.content ?? toolArgs?.text ?? toolArgs?.new_string ?? toolArgs?.newText;
|
|
25339
|
+
if (typeof content === "string" && /\bopencode-swarm\b[\s\S]*?\brun\s+(?:acknowledge-spec-drift|reset|reset-session|rollback|checkpoint)\b/i.test(content)) {
|
|
25340
|
+
throw new Error("BLOCKED: write/edit tool would create a script invoking a human-only swarm CLI subcommand. " + "Present the situation to the user and ask them to run the command themselves.");
|
|
25341
|
+
}
|
|
25211
25342
|
}
|
|
25212
25343
|
if (!targetPath && (tool === "apply_patch" || tool === "patch")) {
|
|
25213
25344
|
for (const p of extractPatchTargetPaths(tool, args2)) {
|
|
25214
25345
|
const resolvedP = path10.resolve(effectiveDirectory, p);
|
|
25215
25346
|
const planMdPath = path10.resolve(effectiveDirectory, ".swarm", "plan.md").toLowerCase();
|
|
25216
25347
|
const planJsonPath = path10.resolve(effectiveDirectory, ".swarm", "plan.json").toLowerCase();
|
|
25348
|
+
const specStalenessPath = path10.resolve(effectiveDirectory, ".swarm", "spec-staleness.json").toLowerCase();
|
|
25217
25349
|
if (resolvedP.toLowerCase() === planMdPath || resolvedP.toLowerCase() === planJsonPath) {
|
|
25218
25350
|
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 save_plan for ALL structural plan changes (adding/removing tasks, updating descriptions, dependencies, or phase names). " + "Use update_task_status() for task status only. " + "Use phase_complete() for phase transitions only.");
|
|
25219
25351
|
}
|
|
25352
|
+
if (resolvedP.toLowerCase() === specStalenessPath) {
|
|
25353
|
+
throw new Error("SPEC_DRIFT_VIOLATION: Direct writes to .swarm/spec-staleness.json are blocked. " + "This file is system-managed and gates plan-mutating tools while spec drift is unresolved. " + "Present the drift to the user and ask them to run /swarm clarify or /swarm acknowledge-spec-drift.");
|
|
25354
|
+
}
|
|
25220
25355
|
if (isOutsideSwarmDir(p, effectiveDirectory) && (isSourceCodePath(p) || hasTraversalSegments(p))) {
|
|
25221
25356
|
const session = swarmState.agentSessions.get(sessionID);
|
|
25222
25357
|
if (session) {
|
|
@@ -25361,6 +25496,19 @@ function createGuardrailsHooks(directory, directoryOrConfig, config2, authorityC
|
|
|
25361
25496
|
if (!authorityCheck.allowed) {
|
|
25362
25497
|
throw new Error(`WRITE BLOCKED: Agent "${agentName}" is not authorised to write "${targetPath}". Reason: ${authorityCheck.reason}`);
|
|
25363
25498
|
}
|
|
25499
|
+
if (isConfigFilePath(targetPath, effectiveDirectory, authorityConfig?.verifier_config_paths)) {
|
|
25500
|
+
const normalizedPath = path10.relative(path10.resolve(effectiveDirectory), path10.resolve(effectiveDirectory, targetPath)).replace(/\\/g, "/");
|
|
25501
|
+
const logEntry = {
|
|
25502
|
+
agent: agentName,
|
|
25503
|
+
path: normalizedPath,
|
|
25504
|
+
allowed: authorityCheck.allowed,
|
|
25505
|
+
type: "direct_write"
|
|
25506
|
+
};
|
|
25507
|
+
if (!authorityCheck.allowed && "reason" in authorityCheck) {
|
|
25508
|
+
logEntry.reason = authorityCheck.reason;
|
|
25509
|
+
}
|
|
25510
|
+
warn("Config file write attempt", logEntry);
|
|
25511
|
+
}
|
|
25364
25512
|
}
|
|
25365
25513
|
}
|
|
25366
25514
|
if (input.tool === "apply_patch" || input.tool === "patch") {
|
|
@@ -25386,6 +25534,19 @@ function createGuardrailsHooks(directory, directoryOrConfig, config2, authorityC
|
|
|
25386
25534
|
if (!authorityCheck.allowed) {
|
|
25387
25535
|
throw new Error(`WRITE BLOCKED: Agent "${patchAgentName}" is not authorised to write "${p}" (via patch). Reason: ${authorityCheck.reason}`);
|
|
25388
25536
|
}
|
|
25537
|
+
if (isConfigFilePath(p, effectiveDirectory, authorityConfig?.verifier_config_paths)) {
|
|
25538
|
+
const normalizedPath = path10.relative(path10.resolve(effectiveDirectory), path10.resolve(effectiveDirectory, p)).replace(/\\/g, "/");
|
|
25539
|
+
const logEntry = {
|
|
25540
|
+
agent: patchAgentName,
|
|
25541
|
+
path: normalizedPath,
|
|
25542
|
+
allowed: authorityCheck.allowed,
|
|
25543
|
+
type: "direct_patch"
|
|
25544
|
+
};
|
|
25545
|
+
if (!authorityCheck.allowed && "reason" in authorityCheck) {
|
|
25546
|
+
logEntry.reason = authorityCheck.reason;
|
|
25547
|
+
}
|
|
25548
|
+
warn("Config file write attempt", logEntry);
|
|
25549
|
+
}
|
|
25389
25550
|
}
|
|
25390
25551
|
}
|
|
25391
25552
|
{
|
|
@@ -26088,11 +26249,11 @@ function checkWriteTargetForSymlink(targetPath, cwd) {
|
|
|
26088
26249
|
}
|
|
26089
26250
|
function buildEffectiveRules(authorityConfig) {
|
|
26090
26251
|
if (authorityConfig?.enabled === false || !authorityConfig?.rules) {
|
|
26091
|
-
return DEFAULT_AGENT_AUTHORITY_RULES;
|
|
26252
|
+
return { ...DEFAULT_AGENT_AUTHORITY_RULES };
|
|
26092
26253
|
}
|
|
26093
26254
|
const entries = Object.entries(authorityConfig.rules);
|
|
26094
26255
|
if (entries.length === 0) {
|
|
26095
|
-
return DEFAULT_AGENT_AUTHORITY_RULES;
|
|
26256
|
+
return { ...DEFAULT_AGENT_AUTHORITY_RULES };
|
|
26096
26257
|
}
|
|
26097
26258
|
const merged = {
|
|
26098
26259
|
...DEFAULT_AGENT_AUTHORITY_RULES
|
|
@@ -26230,7 +26391,7 @@ function checkFileAuthorityWithRules(agentName, filePath, cwd, effectiveRules, o
|
|
|
26230
26391
|
}
|
|
26231
26392
|
return { allowed: true };
|
|
26232
26393
|
}
|
|
26233
|
-
var import_picomatch, _internals10, SPEC_DRIFT_BLOCKED_TOOLS, storedInputArgs, TRANSIENT_STATUS_CODES, TRANSIENT_MODEL_ERROR_PATTERN, TRANSIENT_PROVIDER_RECOVERY_TAG = "TRANSIENT PROVIDER RECOVERY", DEGRADED_ERROR_PATTERN, CONTENT_FILTER_PATTERN, toolCallsSinceLastWrite, noOpWarningIssued, consecutiveNoToolTurns, DC_MAX_UNWRAP_DEPTH = 5, DC_SAFE_TARGETS, DC_BLOCKED_ABSOLUTE_PREFIXES, DC_FS_ROOTS, DC_REMOTE_PREFIXES, pathNormalizationCache, globMatcherCache, DEFAULT_AGENT_AUTHORITY_RULES;
|
|
26394
|
+
var import_picomatch, KNOWN_VERIFIER_CONFIG_GLOBS, _internals10, SPEC_DRIFT_BLOCKED_TOOLS, storedInputArgs, TRANSIENT_STATUS_CODES, TRANSIENT_MODEL_ERROR_PATTERN, TRANSIENT_PROVIDER_RECOVERY_TAG = "TRANSIENT PROVIDER RECOVERY", DEGRADED_ERROR_PATTERN, CONTENT_FILTER_PATTERN, toolCallsSinceLastWrite, noOpWarningIssued, consecutiveNoToolTurns, DC_MAX_UNWRAP_DEPTH = 5, DC_SAFE_TARGETS, DC_BLOCKED_ABSOLUTE_PREFIXES, DC_FS_ROOTS, DC_REMOTE_PREFIXES, pathNormalizationCache, globMatcherCache, DEFAULT_AGENT_AUTHORITY_RULES;
|
|
26234
26395
|
var init_guardrails = __esm(() => {
|
|
26235
26396
|
init_quick_lru();
|
|
26236
26397
|
init_agents2();
|
|
@@ -26250,6 +26411,17 @@ var init_guardrails = __esm(() => {
|
|
|
26250
26411
|
init_model_limits();
|
|
26251
26412
|
init_normalize_tool_name();
|
|
26252
26413
|
import_picomatch = __toESM(require_picomatch2(), 1);
|
|
26414
|
+
KNOWN_VERIFIER_CONFIG_GLOBS = [
|
|
26415
|
+
"**/oxlintrc*",
|
|
26416
|
+
"**/.oxlintrc*",
|
|
26417
|
+
"**/.eslintrc*",
|
|
26418
|
+
"**/eslint.config.*",
|
|
26419
|
+
"**/.prettierrc*",
|
|
26420
|
+
"**/prettier.config.*",
|
|
26421
|
+
"**/biome.jsonc",
|
|
26422
|
+
"**/.secretscanignore",
|
|
26423
|
+
"**/.golangci*"
|
|
26424
|
+
];
|
|
26253
26425
|
_internals10 = {
|
|
26254
26426
|
getSwarmAgents,
|
|
26255
26427
|
getMostRecentAssistantText,
|
|
@@ -26344,7 +26516,18 @@ var init_guardrails = __esm(() => {
|
|
|
26344
26516
|
explore: {},
|
|
26345
26517
|
architect: {
|
|
26346
26518
|
blockedExact: [".swarm/plan.md", ".swarm/plan.json"],
|
|
26347
|
-
blockedZones: ["generated"]
|
|
26519
|
+
blockedZones: ["generated", "config"],
|
|
26520
|
+
blockedGlobs: [
|
|
26521
|
+
"**/oxlintrc*",
|
|
26522
|
+
"**/.oxlintrc*",
|
|
26523
|
+
"**/.eslintrc*",
|
|
26524
|
+
"**/eslint.config.*",
|
|
26525
|
+
"**/.prettierrc*",
|
|
26526
|
+
"**/prettier.config.*",
|
|
26527
|
+
"**/biome.jsonc",
|
|
26528
|
+
"**/.secretscanignore",
|
|
26529
|
+
"**/.golangci*"
|
|
26530
|
+
]
|
|
26348
26531
|
},
|
|
26349
26532
|
coder: {
|
|
26350
26533
|
blockedPrefix: [".swarm/"],
|
|
@@ -61048,7 +61231,8 @@ async function executeSwarmCommand(args2) {
|
|
|
61048
61231
|
directory,
|
|
61049
61232
|
args: resolved.remainingArgs,
|
|
61050
61233
|
sessionID,
|
|
61051
|
-
agents
|
|
61234
|
+
agents,
|
|
61235
|
+
source: "chat"
|
|
61052
61236
|
});
|
|
61053
61237
|
} catch (_err) {
|
|
61054
61238
|
const cmdName = tokens[0] || "unknown";
|
|
@@ -61080,6 +61264,12 @@ function classifySwarmCommandToolUse(resolved) {
|
|
|
61080
61264
|
const canonicalKey = canonicalCommandKey(resolved);
|
|
61081
61265
|
const args2 = resolved.remainingArgs;
|
|
61082
61266
|
if (!SWARM_COMMAND_TOOL_ALLOWLIST.has(canonicalKey)) {
|
|
61267
|
+
if (HUMAN_ONLY_SWARM_COMMANDS.has(canonicalKey)) {
|
|
61268
|
+
return {
|
|
61269
|
+
allowed: false,
|
|
61270
|
+
message: `/swarm ${canonicalKey} is a human-only command. ` + `Present the situation to the user and ask them to run \`/swarm ${canonicalKey}\` themselves ` + `(or \`bunx opencode-swarm run ${canonicalKey}\` from a terminal). ` + `You MUST NOT run it yourself via Bash, swarm_command, or any other tool — ` + `the runtime guardrail will block such attempts.`
|
|
61271
|
+
};
|
|
61272
|
+
}
|
|
61083
61273
|
return {
|
|
61084
61274
|
allowed: false,
|
|
61085
61275
|
message: `/swarm ${canonicalKey} is not available through the chat tool yet.
|
|
@@ -61169,7 +61359,7 @@ function classifySwarmCommandChatFallbackUse(resolved) {
|
|
|
61169
61359
|
}
|
|
61170
61360
|
return { allowed: true };
|
|
61171
61361
|
}
|
|
61172
|
-
var SWARM_COMMAND_TOOL_COMMANDS, SWARM_COMMAND_TOOL_ALLOWLIST, NO_ARGS, SUMMARY_ID_PATTERN, TASK_ID_PATTERN;
|
|
61362
|
+
var SWARM_COMMAND_TOOL_COMMANDS, SWARM_COMMAND_TOOL_ALLOWLIST, HUMAN_ONLY_SWARM_COMMANDS, NO_ARGS, SUMMARY_ID_PATTERN, TASK_ID_PATTERN;
|
|
61173
61363
|
var init_tool_policy = __esm(() => {
|
|
61174
61364
|
init_command_dispatch();
|
|
61175
61365
|
SWARM_COMMAND_TOOL_COMMANDS = [
|
|
@@ -61215,6 +61405,13 @@ var init_tool_policy = __esm(() => {
|
|
|
61215
61405
|
"sync-plan",
|
|
61216
61406
|
"export"
|
|
61217
61407
|
]);
|
|
61408
|
+
HUMAN_ONLY_SWARM_COMMANDS = new Set([
|
|
61409
|
+
"acknowledge-spec-drift",
|
|
61410
|
+
"reset",
|
|
61411
|
+
"reset-session",
|
|
61412
|
+
"rollback",
|
|
61413
|
+
"checkpoint"
|
|
61414
|
+
]);
|
|
61218
61415
|
NO_ARGS = new Set([
|
|
61219
61416
|
"agents",
|
|
61220
61417
|
"config",
|
|
@@ -61772,7 +61969,7 @@ var init_registry = __esm(() => {
|
|
|
61772
61969
|
init_write_retro2();
|
|
61773
61970
|
COMMAND_REGISTRY = {
|
|
61774
61971
|
"acknowledge-spec-drift": {
|
|
61775
|
-
handler: (ctx) => handleAcknowledgeSpecDriftCommand(ctx.directory, ctx.args),
|
|
61972
|
+
handler: (ctx) => handleAcknowledgeSpecDriftCommand(ctx.directory, ctx.args, ctx.source === "cli" ? "cli" : ctx.source === "chat" ? "user" : "unknown"),
|
|
61776
61973
|
description: "Acknowledge that the spec has drifted from the plan and suppress further warnings",
|
|
61777
61974
|
args: "",
|
|
61778
61975
|
category: "diagnostics"
|
|
@@ -63028,7 +63225,7 @@ Continue handling small touch-ups (typos, cross-references) inline.
|
|
|
63028
63225
|
## SLASH COMMANDS
|
|
63029
63226
|
{{SLASH_COMMANDS}}
|
|
63030
63227
|
Commands above are documented with args and behavioral details. Run commands via /swarm <command> [args].
|
|
63031
|
-
Outside OpenCode, invoke any plugin command via: \`bunx opencode-swarm run <command> [args]\` (e.g. \`bunx opencode-swarm run knowledge migrate\`). Do not use \`bun -e\` or look for \`src/commands/\` — those paths are internal to the plugin source and do not exist in user project directories.
|
|
63228
|
+
Outside OpenCode, invoke any plugin command via: \`bunx opencode-swarm run <command> [args]\` (e.g. \`bunx opencode-swarm run knowledge migrate\`). Do not use \`bun -e\` or look for \`src/commands/\` — those paths are internal to the plugin source and do not exist in user project directories. EXCEPTION — human-only commands (including but not limited to \`acknowledge-spec-drift\`, \`reset\`, \`reset-session\`, \`rollback\`, \`checkpoint\`, and any command that releases a runtime safety gate or destroys plan state): you MUST present these to the user and ask them to run the command themselves. Never invoke a human-only command via Bash, swarm_command, or chat fallback. The runtime guardrail will block such attempts; if a Bash call returns \`BLOCKED\` with a "human-only" message, do not retry under a different shell form — present the situation to the user instead.
|
|
63032
63229
|
|
|
63033
63230
|
SMEs advise only. Reviewer and critic review only. None of them write code.
|
|
63034
63231
|
|
|
@@ -63916,10 +64113,17 @@ If resuming a project with an existing approved plan, CRITIC-GATE is already sat
|
|
|
63916
64113
|
- This rule is satisfied by the save_plan tool's own spec gate — it exists as a reminder that planning requires a spec.
|
|
63917
64114
|
|
|
63918
64115
|
6k. SPEC-STALENESS GUARD:
|
|
63919
|
-
- If _specStale or .swarm/spec-staleness.json exists, the Architect MUST
|
|
63920
|
-
|
|
63921
|
-
|
|
63922
|
-
-
|
|
64116
|
+
- If _specStale or .swarm/spec-staleness.json exists, the Architect MUST stop
|
|
64117
|
+
and SURFACE THE DRIFT TO THE USER. The user (not the Architect) then runs
|
|
64118
|
+
either:
|
|
64119
|
+
- /swarm clarify to update the spec and align it with the plan, OR
|
|
64120
|
+
- /swarm acknowledge-spec-drift to acknowledge the drift and suppress further warnings
|
|
64121
|
+
- The Architect MUST NOT run /swarm acknowledge-spec-drift itself — not via
|
|
64122
|
+
the swarm_command tool, not via the chat fallback, and NOT by shelling out
|
|
64123
|
+
to \`bunx opencode-swarm run acknowledge-spec-drift\` (or any equivalent
|
|
64124
|
+
\`npx\`/\`node\`/\`bun\` invocation). Any such self-invocation is a
|
|
64125
|
+
control-bypass and will be refused by the runtime guardrails.
|
|
64126
|
+
- Do NOT proceed with implementation until the user resolves the staleness.
|
|
63923
64127
|
- When re-saving a plan in response to spec drift, save_plan REQUIRES that ANY task
|
|
63924
64128
|
present in the prior plan but absent from the new args.phases be enumerated
|
|
63925
64129
|
in removed_task_ids with a removal_reason. save_plan will reject the call
|
|
@@ -63930,8 +64134,8 @@ If resuming a project with an existing approved plan, CRITIC-GATE is already sat
|
|
|
63930
64134
|
- While .swarm/spec-staleness.json exists, the runtime STRUCTURALLY BLOCKS the
|
|
63931
64135
|
following tools (SPEC_DRIFT_BLOCKED_TOOLS): save_plan, update_task_status,
|
|
63932
64136
|
phase_complete, lean_turbo_run_phase, lean_turbo_acquire_locks. If a call
|
|
63933
|
-
returns SPEC_DRIFT_BLOCK, do NOT retry;
|
|
63934
|
-
|
|
64137
|
+
returns SPEC_DRIFT_BLOCK, do NOT retry; surface the drift to the user and
|
|
64138
|
+
WAIT for them to run /swarm clarify or /swarm acknowledge-spec-drift.
|
|
63935
64139
|
|
|
63936
64140
|
### MODE: EXECUTE
|
|
63937
64141
|
For each task (respecting dependencies):
|
|
@@ -65528,9 +65732,10 @@ WORKFLOW:
|
|
|
65528
65732
|
- TODO comments in code (those go through the task system, not code comments)
|
|
65529
65733
|
|
|
65530
65734
|
## RELEASE NOTES
|
|
65531
|
-
When writing release notes (docs/releases/
|
|
65532
|
-
-
|
|
65533
|
-
-
|
|
65735
|
+
When writing release notes (docs/releases/pending/<slug>.md):
|
|
65736
|
+
- Do NOT determine the next version. Do NOT create docs/releases/vX.Y.Z.md. release-please owns the version; the release workflow aggregates pending fragments.
|
|
65737
|
+
- Pick a short, kebab-case slug describing your change (e.g. spec-drift-self-ack-guardrail.md). Pick one unlikely to collide with concurrent PRs.
|
|
65738
|
+
- Follow the established format in existing release notes files (descriptive topic heading, not a version prefix).
|
|
65534
65739
|
- Include: overview, breaking changes (if any), new features, bug fixes, internal improvements
|
|
65535
65740
|
- Do NOT manually edit package.json version, CHANGELOG.md, or .release-please-manifest.json — release-please owns these
|
|
65536
65741
|
|
|
@@ -65675,6 +65880,18 @@ DO (explicitly):
|
|
|
65675
65880
|
- VERIFY platform compatibility: path.join() used for all paths, no hardcoded separators
|
|
65676
65881
|
- For confirmed issues requiring a concrete fix: use suggest_patch to produce a structured patch artifact for the coder
|
|
65677
65882
|
|
|
65883
|
+
## CONFIG STRICTNESS VERIFICATION
|
|
65884
|
+
|
|
65885
|
+
When the declared scope includes a verifier/linter config file (biome.json, biome.jsonc, oxlintrc, oxlintrc.json, .eslintrc, .eslintrc.json, eslint.config.*, .prettierrc, .prettierrc.json, prettier.config.*, biome.jsonc, .secretscanignore, golangci-lint configs, tsconfig.json, tsconfig.*.json, or any other linter/formatter/security-tool configuration):
|
|
65886
|
+
|
|
65887
|
+
- Verify the change does NOT reduce strictness of any existing rule
|
|
65888
|
+
- Reject changes that downgrade "error" to "warn", remove rules, weaken validation thresholds, or narrow file/directory scopes
|
|
65889
|
+
- Allow changes that ADD new stricter rules, enable additional rule categories, fix syntax errors, or correct misconfigured paths
|
|
65890
|
+
- Document the specific config change and its impact on validation strictness in your review output
|
|
65891
|
+
- If a rule is changed from "error" to "warn" or a rule is removed: REJECT with STRICTNESS_REDUCTION: [rule name] — [original setting] → [new setting]
|
|
65892
|
+
|
|
65893
|
+
This is a pre-review gate: if config strictness is reduced, reject immediately without proceeding to Tier review.
|
|
65894
|
+
|
|
65678
65895
|
## REUSE RE-VERIFICATION (MANDATORY FOR NEW EXPORTS)
|
|
65679
65896
|
|
|
65680
65897
|
When EXPORTS_ADDED is non-empty in the coder's completion report:
|
|
@@ -80512,7 +80729,9 @@ var DENY_SHELL_PATTERNS = [
|
|
|
80512
80729
|
/\bbunx?\s+publish\b/i,
|
|
80513
80730
|
/\bterraform\s+(?:apply|destroy)\b/i,
|
|
80514
80731
|
/\bkubectl\s+(?:delete|apply\s+-f)/i,
|
|
80515
|
-
/\bdrop\s+(?:database|table)\b/i
|
|
80732
|
+
/\bdrop\s+(?:database|table)\b/i,
|
|
80733
|
+
/\bsed\s+-i\b(?=[^\n]*\b(?:biome\.json|eslintrc|oxlintrc)\b)(?=[^\n]*\b(?:error|warn|off)\b)/i,
|
|
80734
|
+
/\b(?:cat|tee)\b[^\n]*>\s*[^\n]*\b(biome\.json|eslintrc|oxlintrc)\b[^\n]*\b(error|warn|off)\b/i
|
|
80516
80735
|
];
|
|
80517
80736
|
var ESCALATE_SHELL_PATTERNS = [
|
|
80518
80737
|
/\bcurl\b/i,
|
|
@@ -80532,7 +80751,9 @@ var ESCALATE_SHELL_PATTERNS = [
|
|
|
80532
80751
|
/\bgit\s+pull\b/i,
|
|
80533
80752
|
/\bgit\s+rebase\b/i,
|
|
80534
80753
|
/\bgit\s+merge\b/i,
|
|
80535
|
-
/\bgit\s+commit\b/i
|
|
80754
|
+
/\bgit\s+commit\b/i,
|
|
80755
|
+
/\b(sed\s+-i|echo\s+|printf\s+)[^\n]*\b(biome\.jsonc?|eslintrc|eslint\.config|oxlintrc|prettierrc|secretscanignore|golangci|tsconfig\.json|tsconfig\.[^.]+\.json)\b/i,
|
|
80756
|
+
/\b(?:cat|tee)\b[^\n]*>\s*[^\n]*\b(biome\.jsonc?|eslintrc|eslint\.config|oxlintrc|prettierrc|secretscanignore|golangci|tsconfig\.json|tsconfig\.[^.]+\.json)\b/i
|
|
80536
80757
|
];
|
|
80537
80758
|
function isReadOnlyTool(toolName) {
|
|
80538
80759
|
if (!toolName)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-swarm",
|
|
3
|
-
"version": "7.21.
|
|
3
|
+
"version": "7.21.5",
|
|
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",
|