opencode-swarm 6.32.1 → 6.32.3

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
@@ -31619,8 +31619,11 @@ async function rewriteKnowledge(filePath, entries) {
31619
31619
  ` : "");
31620
31620
  await writeFile(filePath, content, "utf-8");
31621
31621
  } finally {
31622
- if (release)
31623
- await release();
31622
+ if (release) {
31623
+ try {
31624
+ await release();
31625
+ } catch {}
31626
+ }
31624
31627
  }
31625
31628
  }
31626
31629
  function normalize2(text) {
@@ -33624,6 +33627,7 @@ async function handleExportCommand(directory, _args) {
33624
33627
  }
33625
33628
  // src/commands/handoff.ts
33626
33629
  init_utils2();
33630
+ import crypto3 from "crypto";
33627
33631
  import { renameSync as renameSync5 } from "fs";
33628
33632
 
33629
33633
  // src/services/handoff-service.ts
@@ -34019,7 +34023,7 @@ async function handleHandoffCommand(directory, _args) {
34019
34023
  const handoffData = await getHandoffData(directory);
34020
34024
  const markdown = formatHandoffMarkdown(handoffData);
34021
34025
  const resolvedPath = validateSwarmPath(directory, "handoff.md");
34022
- const tempPath = `${resolvedPath}.tmp.${Date.now()}.${Math.random().toString(36).slice(2)}`;
34026
+ const tempPath = `${resolvedPath}.tmp.${crypto3.randomUUID()}`;
34023
34027
  await Bun.write(tempPath, markdown);
34024
34028
  renameSync5(tempPath, resolvedPath);
34025
34029
  await writeSnapshot(directory, swarmState);
@@ -32,3 +32,10 @@ export declare function detectAdversarialPatterns(text: string): AdversarialPatt
32
32
  * Format a precedent manipulation detection event for JSONL emission.
33
33
  */
34
34
  export declare function formatPrecedentManipulationEvent(match: AdversarialPatternMatch, agentName: string, phase: number): string;
35
+ export declare function formatDebuggingSpiralEvent(match: AdversarialPatternMatch, taskId: string): string;
36
+ export declare function handleDebuggingSpiral(match: AdversarialPatternMatch, taskId: string, directory: string): Promise<{
37
+ eventLogged: boolean;
38
+ checkpointCreated: boolean;
39
+ message: string;
40
+ }>;
41
+ export declare function detectDebuggingSpiral(_directory: string): Promise<AdversarialPatternMatch | null>;
@@ -11,7 +11,7 @@ import type { DelegationEnvelope, EnvelopeValidationResult } from '../types/dele
11
11
  * Returns null if no valid envelope is found.
12
12
  * Never throws - all errors are caught and result in null.
13
13
  */
14
- export declare function parseDelegationEnvelope(content: string): DelegationEnvelope | null;
14
+ export declare function parseDelegationEnvelope(content: string, directory?: string): DelegationEnvelope | null;
15
15
  interface ValidationContext {
16
16
  planTasks: string[];
17
17
  validAgents: string[];
@@ -7,6 +7,7 @@
7
7
  * - Layer 2 (Hard Block @ 100%): Throws error in toolBefore to block further calls, injects STOP message
8
8
  */
9
9
  import { type GuardrailsConfig } from '../config/schema';
10
+ import { type FileZone } from '../context/zone-classifier';
10
11
  /**
11
12
  * Retrieves stored input args for a given callID.
12
13
  * Used by other hooks (e.g., delegation-gate) to access tool input args.
@@ -72,3 +73,43 @@ export declare function createGuardrailsHooks(directory: string, directoryOrConf
72
73
  * @returns Numeric hash (0 if hashing fails)
73
74
  */
74
75
  export declare function hashArgs(args: unknown): number;
76
+ /** A record of an agent attesting to (resolving/suppressing/deferring) a finding. */
77
+ export interface AttestationRecord {
78
+ findingId: string;
79
+ agent: string;
80
+ attestation: string;
81
+ action: 'resolve' | 'suppress' | 'defer';
82
+ timestamp: string;
83
+ }
84
+ /**
85
+ * Validates that an attestation string meets the minimum length requirement.
86
+ */
87
+ export declare function validateAttestation(attestation: string, _findingId: string, _agent: string, _action: 'resolve' | 'suppress' | 'defer'): {
88
+ valid: true;
89
+ } | {
90
+ valid: false;
91
+ reason: string;
92
+ };
93
+ /**
94
+ * Appends an attestation record to `.swarm/evidence/attestations.jsonl`.
95
+ */
96
+ export declare function recordAttestation(dir: string, record: AttestationRecord): Promise<void>;
97
+ /**
98
+ * Validates an attestation and, on success, records it; on failure, logs a rejection event.
99
+ */
100
+ export declare function validateAndRecordAttestation(dir: string, findingId: string, agent: string, attestation: string, action: 'resolve' | 'suppress' | 'defer'): Promise<{
101
+ valid: true;
102
+ } | {
103
+ valid: false;
104
+ reason: string;
105
+ }>;
106
+ /**
107
+ * Checks whether the given agent is authorised to write to the given file path.
108
+ */
109
+ export declare function checkFileAuthority(agentName: string, filePath: string, _cwd: string): {
110
+ allowed: true;
111
+ } | {
112
+ allowed: false;
113
+ reason: string;
114
+ zone?: FileZone;
115
+ };
@@ -5,6 +5,7 @@ export interface MigrationResult {
5
5
  entriesMigrated: number;
6
6
  entriesDropped: number;
7
7
  entriesTotal: number;
8
- skippedReason?: 'sentinel-exists' | 'no-context-file' | 'empty-context';
8
+ skippedReason?: 'sentinel-exists' | 'no-context-file' | 'empty-context' | 'external-sentinel-exists';
9
9
  }
10
+ export declare function migrateKnowledgeToExternal(_directory: string, _config: KnowledgeConfig): Promise<MigrationResult>;
10
11
  export declare function migrateContextToKnowledge(directory: string, config: KnowledgeConfig): Promise<MigrationResult>;
package/dist/index.js CHANGED
@@ -29876,6 +29876,285 @@ var init_create_tool = __esm(() => {
29876
29876
  init_dist();
29877
29877
  });
29878
29878
 
29879
+ // src/tools/checkpoint.ts
29880
+ import { spawnSync } from "child_process";
29881
+ import * as fs6 from "fs";
29882
+ import * as path9 from "path";
29883
+ function containsNonAsciiChars(label) {
29884
+ for (let i2 = 0;i2 < label.length; i2++) {
29885
+ const charCode = label.charCodeAt(i2);
29886
+ if (charCode < 32 || charCode > 126) {
29887
+ return true;
29888
+ }
29889
+ }
29890
+ return false;
29891
+ }
29892
+ function validateLabel(label) {
29893
+ if (!label || label.length === 0) {
29894
+ return "label is required";
29895
+ }
29896
+ if (label.length > MAX_LABEL_LENGTH) {
29897
+ return `label exceeds maximum length of ${MAX_LABEL_LENGTH}`;
29898
+ }
29899
+ if (label.startsWith("--")) {
29900
+ return 'label cannot start with "--" (git flag pattern)';
29901
+ }
29902
+ if (CONTROL_CHAR_PATTERN.test(label)) {
29903
+ return "label contains control characters";
29904
+ }
29905
+ if (NON_ASCII_PATTERN.test(label)) {
29906
+ return "label contains non-ASCII or invalid characters";
29907
+ }
29908
+ if (containsNonAsciiChars(label)) {
29909
+ return "label contains non-ASCII characters (must be printable ASCII only)";
29910
+ }
29911
+ if (SHELL_METACHARACTERS.test(label)) {
29912
+ return "label contains shell metacharacters";
29913
+ }
29914
+ if (!SAFE_LABEL_PATTERN.test(label)) {
29915
+ return "label contains invalid characters (use alphanumeric, hyphen, underscore, space)";
29916
+ }
29917
+ if (!/[a-zA-Z0-9_]/.test(label)) {
29918
+ return "label cannot be whitespace-only";
29919
+ }
29920
+ if (label.includes("..") || label.includes("/") || label.includes("\\")) {
29921
+ return "label contains path traversal sequence";
29922
+ }
29923
+ return null;
29924
+ }
29925
+ function getCheckpointLogPath(directory) {
29926
+ return path9.join(directory, CHECKPOINT_LOG_PATH);
29927
+ }
29928
+ function readCheckpointLog(directory) {
29929
+ const logPath = getCheckpointLogPath(directory);
29930
+ try {
29931
+ if (fs6.existsSync(logPath)) {
29932
+ const content = fs6.readFileSync(logPath, "utf-8");
29933
+ const parsed = JSON.parse(content);
29934
+ if (!parsed.checkpoints || !Array.isArray(parsed.checkpoints)) {
29935
+ return { version: 1, checkpoints: [] };
29936
+ }
29937
+ return parsed;
29938
+ }
29939
+ } catch {}
29940
+ return { version: 1, checkpoints: [] };
29941
+ }
29942
+ function writeCheckpointLog(log2, directory) {
29943
+ const logPath = getCheckpointLogPath(directory);
29944
+ const dir = path9.dirname(logPath);
29945
+ if (!fs6.existsSync(dir)) {
29946
+ fs6.mkdirSync(dir, { recursive: true });
29947
+ }
29948
+ const tempPath = `${logPath}.tmp`;
29949
+ fs6.writeFileSync(tempPath, JSON.stringify(log2, null, 2), "utf-8");
29950
+ fs6.renameSync(tempPath, logPath);
29951
+ }
29952
+ function gitExec(args2) {
29953
+ const result = spawnSync("git", args2, {
29954
+ encoding: "utf-8",
29955
+ timeout: GIT_TIMEOUT_MS,
29956
+ stdio: ["pipe", "pipe", "pipe"]
29957
+ });
29958
+ if (result.status !== 0) {
29959
+ const err2 = new Error(result.stderr?.trim() || `git exited with code ${result.status}`);
29960
+ throw err2;
29961
+ }
29962
+ return result.stdout;
29963
+ }
29964
+ function getCurrentSha() {
29965
+ const output = gitExec(["rev-parse", "HEAD"]);
29966
+ return output.trim();
29967
+ }
29968
+ function isGitRepo() {
29969
+ try {
29970
+ gitExec(["rev-parse", "--git-dir"]);
29971
+ return true;
29972
+ } catch {
29973
+ return false;
29974
+ }
29975
+ }
29976
+ function handleSave(label, directory) {
29977
+ try {
29978
+ const log2 = readCheckpointLog(directory);
29979
+ const existingCheckpoint = log2.checkpoints.find((c) => c.label === label);
29980
+ if (existingCheckpoint) {
29981
+ return JSON.stringify({
29982
+ action: "save",
29983
+ success: false,
29984
+ error: `duplicate label: "${label}" already exists. Use a different label or delete the existing checkpoint first.`
29985
+ }, null, 2);
29986
+ }
29987
+ const _sha = getCurrentSha();
29988
+ const timestamp = new Date().toISOString();
29989
+ gitExec(["commit", "--allow-empty", "-m", `checkpoint: ${label}`]);
29990
+ const newSha = getCurrentSha();
29991
+ log2.checkpoints.push({
29992
+ label,
29993
+ sha: newSha,
29994
+ timestamp
29995
+ });
29996
+ writeCheckpointLog(log2, directory);
29997
+ return JSON.stringify({
29998
+ action: "save",
29999
+ success: true,
30000
+ label,
30001
+ sha: newSha,
30002
+ message: `Checkpoint saved: "${label}"`
30003
+ }, null, 2);
30004
+ } catch (e) {
30005
+ const errorMessage = e instanceof Error ? `save failed: ${e.message}` : "save failed: unknown error";
30006
+ return JSON.stringify({
30007
+ action: "save",
30008
+ success: false,
30009
+ error: errorMessage
30010
+ }, null, 2);
30011
+ }
30012
+ }
30013
+ function handleRestore(label, directory) {
30014
+ try {
30015
+ const log2 = readCheckpointLog(directory);
30016
+ const checkpoint = log2.checkpoints.find((c) => c.label === label);
30017
+ if (!checkpoint) {
30018
+ return JSON.stringify({
30019
+ action: "restore",
30020
+ success: false,
30021
+ error: `checkpoint not found: "${label}"`
30022
+ }, null, 2);
30023
+ }
30024
+ gitExec(["reset", "--soft", checkpoint.sha]);
30025
+ return JSON.stringify({
30026
+ action: "restore",
30027
+ success: true,
30028
+ label,
30029
+ sha: checkpoint.sha,
30030
+ message: `Restored to checkpoint: "${label}" (soft reset)`
30031
+ }, null, 2);
30032
+ } catch (e) {
30033
+ const errorMessage = e instanceof Error ? `restore failed: ${e.message}` : "restore failed: unknown error";
30034
+ return JSON.stringify({
30035
+ action: "restore",
30036
+ success: false,
30037
+ error: errorMessage
30038
+ }, null, 2);
30039
+ }
30040
+ }
30041
+ function handleList(directory) {
30042
+ const log2 = readCheckpointLog(directory);
30043
+ const sorted = [...log2.checkpoints].sort((a, b) => b.timestamp.localeCompare(a.timestamp));
30044
+ return JSON.stringify({
30045
+ action: "list",
30046
+ success: true,
30047
+ count: sorted.length,
30048
+ checkpoints: sorted
30049
+ }, null, 2);
30050
+ }
30051
+ function handleDelete(label, directory) {
30052
+ try {
30053
+ const log2 = readCheckpointLog(directory);
30054
+ const initialLength = log2.checkpoints.length;
30055
+ log2.checkpoints = log2.checkpoints.filter((c) => c.label !== label);
30056
+ if (log2.checkpoints.length === initialLength) {
30057
+ return JSON.stringify({
30058
+ action: "delete",
30059
+ success: false,
30060
+ error: `checkpoint not found: "${label}"`
30061
+ }, null, 2);
30062
+ }
30063
+ writeCheckpointLog(log2, directory);
30064
+ return JSON.stringify({
30065
+ action: "delete",
30066
+ success: true,
30067
+ label,
30068
+ message: `Checkpoint deleted: "${label}" (git commit preserved)`
30069
+ }, null, 2);
30070
+ } catch (e) {
30071
+ const errorMessage = e instanceof Error ? `delete failed: ${e.message}` : "delete failed: unknown error";
30072
+ return JSON.stringify({
30073
+ action: "delete",
30074
+ success: false,
30075
+ error: errorMessage
30076
+ }, null, 2);
30077
+ }
30078
+ }
30079
+ var CHECKPOINT_LOG_PATH = ".swarm/checkpoints.json", MAX_LABEL_LENGTH = 100, GIT_TIMEOUT_MS = 30000, SHELL_METACHARACTERS, SAFE_LABEL_PATTERN, CONTROL_CHAR_PATTERN, NON_ASCII_PATTERN, checkpoint;
30080
+ var init_checkpoint = __esm(() => {
30081
+ init_tool();
30082
+ init_create_tool();
30083
+ SHELL_METACHARACTERS = /[;|&$`(){}<>!'"]/;
30084
+ SAFE_LABEL_PATTERN = /^[a-zA-Z0-9_ -]+$/;
30085
+ CONTROL_CHAR_PATTERN = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
30086
+ NON_ASCII_PATTERN = /[^\x20-\x7E]/;
30087
+ checkpoint = createSwarmTool({
30088
+ description: "Save, restore, list, and delete git checkpoints. " + "Use save to create a named snapshot, restore to return to a checkpoint (soft reset), " + "list to see all checkpoints, and delete to remove a checkpoint from the log. " + "Git commits are preserved on delete.",
30089
+ args: {
30090
+ action: tool.schema.string().describe("Action to perform: save, restore, list, or delete"),
30091
+ label: tool.schema.string().optional().describe("Checkpoint label (required for save, restore, delete)")
30092
+ },
30093
+ execute: async (args2, directory) => {
30094
+ if (!isGitRepo()) {
30095
+ return JSON.stringify({
30096
+ action: "unknown",
30097
+ success: false,
30098
+ error: "not a git repository"
30099
+ }, null, 2);
30100
+ }
30101
+ let action;
30102
+ let label;
30103
+ try {
30104
+ action = String(args2.action);
30105
+ label = args2.label !== undefined ? String(args2.label) : undefined;
30106
+ } catch {
30107
+ return JSON.stringify({
30108
+ action: "unknown",
30109
+ success: false,
30110
+ error: "invalid arguments"
30111
+ }, null, 2);
30112
+ }
30113
+ const validActions = ["save", "restore", "list", "delete"];
30114
+ if (!validActions.includes(action)) {
30115
+ return JSON.stringify({
30116
+ action,
30117
+ success: false,
30118
+ error: `invalid action: "${action}". Valid actions: ${validActions.join(", ")}`
30119
+ }, null, 2);
30120
+ }
30121
+ if (["save", "restore", "delete"].includes(action)) {
30122
+ if (!label) {
30123
+ return JSON.stringify({
30124
+ action,
30125
+ success: false,
30126
+ error: `label is required for ${action} action`
30127
+ }, null, 2);
30128
+ }
30129
+ const labelError = validateLabel(label);
30130
+ if (labelError) {
30131
+ return JSON.stringify({
30132
+ action,
30133
+ success: false,
30134
+ error: `invalid label: ${labelError}`
30135
+ }, null, 2);
30136
+ }
30137
+ }
30138
+ switch (action) {
30139
+ case "save":
30140
+ return handleSave(label, directory);
30141
+ case "restore":
30142
+ return handleRestore(label, directory);
30143
+ case "list":
30144
+ return handleList(directory);
30145
+ case "delete":
30146
+ return handleDelete(label, directory);
30147
+ default:
30148
+ return JSON.stringify({
30149
+ action,
30150
+ success: false,
30151
+ error: "unreachable"
30152
+ }, null, 2);
30153
+ }
30154
+ }
30155
+ });
30156
+ });
30157
+
29879
30158
  // node_modules/graceful-fs/polyfills.js
29880
30159
  var require_polyfills = __commonJS((exports, module2) => {
29881
30160
  var constants = __require("constants");
@@ -43511,286 +43790,8 @@ async function handleBenchmarkCommand(directory, args2) {
43511
43790
  `);
43512
43791
  }
43513
43792
 
43514
- // src/tools/checkpoint.ts
43515
- init_tool();
43516
- init_create_tool();
43517
- import { spawnSync } from "child_process";
43518
- import * as fs6 from "fs";
43519
- import * as path9 from "path";
43520
- var CHECKPOINT_LOG_PATH = ".swarm/checkpoints.json";
43521
- var MAX_LABEL_LENGTH = 100;
43522
- var GIT_TIMEOUT_MS = 30000;
43523
- var SHELL_METACHARACTERS = /[;|&$`(){}<>!'"]/;
43524
- var SAFE_LABEL_PATTERN = /^[a-zA-Z0-9_ -]+$/;
43525
- var CONTROL_CHAR_PATTERN = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
43526
- var NON_ASCII_PATTERN = /[^\x20-\x7E]/;
43527
- function containsNonAsciiChars(label) {
43528
- for (let i2 = 0;i2 < label.length; i2++) {
43529
- const charCode = label.charCodeAt(i2);
43530
- if (charCode < 32 || charCode > 126) {
43531
- return true;
43532
- }
43533
- }
43534
- return false;
43535
- }
43536
- function validateLabel(label) {
43537
- if (!label || label.length === 0) {
43538
- return "label is required";
43539
- }
43540
- if (label.length > MAX_LABEL_LENGTH) {
43541
- return `label exceeds maximum length of ${MAX_LABEL_LENGTH}`;
43542
- }
43543
- if (label.startsWith("--")) {
43544
- return 'label cannot start with "--" (git flag pattern)';
43545
- }
43546
- if (CONTROL_CHAR_PATTERN.test(label)) {
43547
- return "label contains control characters";
43548
- }
43549
- if (NON_ASCII_PATTERN.test(label)) {
43550
- return "label contains non-ASCII or invalid characters";
43551
- }
43552
- if (containsNonAsciiChars(label)) {
43553
- return "label contains non-ASCII characters (must be printable ASCII only)";
43554
- }
43555
- if (SHELL_METACHARACTERS.test(label)) {
43556
- return "label contains shell metacharacters";
43557
- }
43558
- if (!SAFE_LABEL_PATTERN.test(label)) {
43559
- return "label contains invalid characters (use alphanumeric, hyphen, underscore, space)";
43560
- }
43561
- if (!/[a-zA-Z0-9_]/.test(label)) {
43562
- return "label cannot be whitespace-only";
43563
- }
43564
- if (label.includes("..") || label.includes("/") || label.includes("\\")) {
43565
- return "label contains path traversal sequence";
43566
- }
43567
- return null;
43568
- }
43569
- function getCheckpointLogPath(directory) {
43570
- return path9.join(directory, CHECKPOINT_LOG_PATH);
43571
- }
43572
- function readCheckpointLog(directory) {
43573
- const logPath = getCheckpointLogPath(directory);
43574
- try {
43575
- if (fs6.existsSync(logPath)) {
43576
- const content = fs6.readFileSync(logPath, "utf-8");
43577
- const parsed = JSON.parse(content);
43578
- if (!parsed.checkpoints || !Array.isArray(parsed.checkpoints)) {
43579
- return { version: 1, checkpoints: [] };
43580
- }
43581
- return parsed;
43582
- }
43583
- } catch {}
43584
- return { version: 1, checkpoints: [] };
43585
- }
43586
- function writeCheckpointLog(log2, directory) {
43587
- const logPath = getCheckpointLogPath(directory);
43588
- const dir = path9.dirname(logPath);
43589
- if (!fs6.existsSync(dir)) {
43590
- fs6.mkdirSync(dir, { recursive: true });
43591
- }
43592
- const tempPath = `${logPath}.tmp`;
43593
- fs6.writeFileSync(tempPath, JSON.stringify(log2, null, 2), "utf-8");
43594
- fs6.renameSync(tempPath, logPath);
43595
- }
43596
- function gitExec(args2) {
43597
- const result = spawnSync("git", args2, {
43598
- encoding: "utf-8",
43599
- timeout: GIT_TIMEOUT_MS,
43600
- stdio: ["pipe", "pipe", "pipe"]
43601
- });
43602
- if (result.status !== 0) {
43603
- const err2 = new Error(result.stderr?.trim() || `git exited with code ${result.status}`);
43604
- throw err2;
43605
- }
43606
- return result.stdout;
43607
- }
43608
- function getCurrentSha() {
43609
- const output = gitExec(["rev-parse", "HEAD"]);
43610
- return output.trim();
43611
- }
43612
- function isGitRepo() {
43613
- try {
43614
- gitExec(["rev-parse", "--git-dir"]);
43615
- return true;
43616
- } catch {
43617
- return false;
43618
- }
43619
- }
43620
- function handleSave(label, directory) {
43621
- try {
43622
- const log2 = readCheckpointLog(directory);
43623
- const existingCheckpoint = log2.checkpoints.find((c) => c.label === label);
43624
- if (existingCheckpoint) {
43625
- return JSON.stringify({
43626
- action: "save",
43627
- success: false,
43628
- error: `duplicate label: "${label}" already exists. Use a different label or delete the existing checkpoint first.`
43629
- }, null, 2);
43630
- }
43631
- const _sha = getCurrentSha();
43632
- const timestamp = new Date().toISOString();
43633
- gitExec(["commit", "--allow-empty", "-m", `checkpoint: ${label}`]);
43634
- const newSha = getCurrentSha();
43635
- log2.checkpoints.push({
43636
- label,
43637
- sha: newSha,
43638
- timestamp
43639
- });
43640
- writeCheckpointLog(log2, directory);
43641
- return JSON.stringify({
43642
- action: "save",
43643
- success: true,
43644
- label,
43645
- sha: newSha,
43646
- message: `Checkpoint saved: "${label}"`
43647
- }, null, 2);
43648
- } catch (e) {
43649
- const errorMessage = e instanceof Error ? `save failed: ${e.message}` : "save failed: unknown error";
43650
- return JSON.stringify({
43651
- action: "save",
43652
- success: false,
43653
- error: errorMessage
43654
- }, null, 2);
43655
- }
43656
- }
43657
- function handleRestore(label, directory) {
43658
- try {
43659
- const log2 = readCheckpointLog(directory);
43660
- const checkpoint = log2.checkpoints.find((c) => c.label === label);
43661
- if (!checkpoint) {
43662
- return JSON.stringify({
43663
- action: "restore",
43664
- success: false,
43665
- error: `checkpoint not found: "${label}"`
43666
- }, null, 2);
43667
- }
43668
- gitExec(["reset", "--soft", checkpoint.sha]);
43669
- return JSON.stringify({
43670
- action: "restore",
43671
- success: true,
43672
- label,
43673
- sha: checkpoint.sha,
43674
- message: `Restored to checkpoint: "${label}" (soft reset)`
43675
- }, null, 2);
43676
- } catch (e) {
43677
- const errorMessage = e instanceof Error ? `restore failed: ${e.message}` : "restore failed: unknown error";
43678
- return JSON.stringify({
43679
- action: "restore",
43680
- success: false,
43681
- error: errorMessage
43682
- }, null, 2);
43683
- }
43684
- }
43685
- function handleList(directory) {
43686
- const log2 = readCheckpointLog(directory);
43687
- const sorted = [...log2.checkpoints].sort((a, b) => b.timestamp.localeCompare(a.timestamp));
43688
- return JSON.stringify({
43689
- action: "list",
43690
- success: true,
43691
- count: sorted.length,
43692
- checkpoints: sorted
43693
- }, null, 2);
43694
- }
43695
- function handleDelete(label, directory) {
43696
- try {
43697
- const log2 = readCheckpointLog(directory);
43698
- const initialLength = log2.checkpoints.length;
43699
- log2.checkpoints = log2.checkpoints.filter((c) => c.label !== label);
43700
- if (log2.checkpoints.length === initialLength) {
43701
- return JSON.stringify({
43702
- action: "delete",
43703
- success: false,
43704
- error: `checkpoint not found: "${label}"`
43705
- }, null, 2);
43706
- }
43707
- writeCheckpointLog(log2, directory);
43708
- return JSON.stringify({
43709
- action: "delete",
43710
- success: true,
43711
- label,
43712
- message: `Checkpoint deleted: "${label}" (git commit preserved)`
43713
- }, null, 2);
43714
- } catch (e) {
43715
- const errorMessage = e instanceof Error ? `delete failed: ${e.message}` : "delete failed: unknown error";
43716
- return JSON.stringify({
43717
- action: "delete",
43718
- success: false,
43719
- error: errorMessage
43720
- }, null, 2);
43721
- }
43722
- }
43723
- var checkpoint = createSwarmTool({
43724
- description: "Save, restore, list, and delete git checkpoints. " + "Use save to create a named snapshot, restore to return to a checkpoint (soft reset), " + "list to see all checkpoints, and delete to remove a checkpoint from the log. " + "Git commits are preserved on delete.",
43725
- args: {
43726
- action: tool.schema.string().describe("Action to perform: save, restore, list, or delete"),
43727
- label: tool.schema.string().optional().describe("Checkpoint label (required for save, restore, delete)")
43728
- },
43729
- execute: async (args2, directory) => {
43730
- if (!isGitRepo()) {
43731
- return JSON.stringify({
43732
- action: "unknown",
43733
- success: false,
43734
- error: "not a git repository"
43735
- }, null, 2);
43736
- }
43737
- let action;
43738
- let label;
43739
- try {
43740
- action = String(args2.action);
43741
- label = args2.label !== undefined ? String(args2.label) : undefined;
43742
- } catch {
43743
- return JSON.stringify({
43744
- action: "unknown",
43745
- success: false,
43746
- error: "invalid arguments"
43747
- }, null, 2);
43748
- }
43749
- const validActions = ["save", "restore", "list", "delete"];
43750
- if (!validActions.includes(action)) {
43751
- return JSON.stringify({
43752
- action,
43753
- success: false,
43754
- error: `invalid action: "${action}". Valid actions: ${validActions.join(", ")}`
43755
- }, null, 2);
43756
- }
43757
- if (["save", "restore", "delete"].includes(action)) {
43758
- if (!label) {
43759
- return JSON.stringify({
43760
- action,
43761
- success: false,
43762
- error: `label is required for ${action} action`
43763
- }, null, 2);
43764
- }
43765
- const labelError = validateLabel(label);
43766
- if (labelError) {
43767
- return JSON.stringify({
43768
- action,
43769
- success: false,
43770
- error: `invalid label: ${labelError}`
43771
- }, null, 2);
43772
- }
43773
- }
43774
- switch (action) {
43775
- case "save":
43776
- return handleSave(label, directory);
43777
- case "restore":
43778
- return handleRestore(label, directory);
43779
- case "list":
43780
- return handleList(directory);
43781
- case "delete":
43782
- return handleDelete(label, directory);
43783
- default:
43784
- return JSON.stringify({
43785
- action,
43786
- success: false,
43787
- error: "unreachable"
43788
- }, null, 2);
43789
- }
43790
- }
43791
- });
43792
-
43793
43793
  // src/commands/checkpoint.ts
43794
+ init_checkpoint();
43794
43795
  async function handleCheckpointCommand(directory, args2) {
43795
43796
  const subcommand = args2[0] || "list";
43796
43797
  const label = args2[1];
@@ -44008,8 +44009,11 @@ async function rewriteKnowledge(filePath, entries) {
44008
44009
  ` : "");
44009
44010
  await writeFile(filePath, content, "utf-8");
44010
44011
  } finally {
44011
- if (release)
44012
- await release();
44012
+ if (release) {
44013
+ try {
44014
+ await release();
44015
+ } catch {}
44016
+ }
44013
44017
  }
44014
44018
  }
44015
44019
  async function appendRejectedLesson(directory, lesson) {
@@ -46466,6 +46470,7 @@ async function handleExportCommand(directory, _args) {
46466
46470
  }
46467
46471
  // src/commands/handoff.ts
46468
46472
  init_utils2();
46473
+ import crypto3 from "crypto";
46469
46474
  import { renameSync as renameSync5 } from "fs";
46470
46475
 
46471
46476
  // src/services/handoff-service.ts
@@ -46868,7 +46873,7 @@ async function handleHandoffCommand(directory, _args) {
46868
46873
  const handoffData = await getHandoffData(directory);
46869
46874
  const markdown = formatHandoffMarkdown(handoffData);
46870
46875
  const resolvedPath = validateSwarmPath(directory, "handoff.md");
46871
- const tempPath = `${resolvedPath}.tmp.${Date.now()}.${Math.random().toString(36).slice(2)}`;
46876
+ const tempPath = `${resolvedPath}.tmp.${crypto3.randomUUID()}`;
46872
46877
  await Bun.write(tempPath, markdown);
46873
46878
  renameSync5(tempPath, resolvedPath);
46874
46879
  await writeSnapshot(directory, swarmState);
@@ -49749,8 +49754,8 @@ import * as path31 from "path";
49749
49754
  // src/hooks/guardrails.ts
49750
49755
  init_constants();
49751
49756
  init_schema();
49752
- init_manager2();
49753
49757
  import * as path29 from "path";
49758
+ init_manager2();
49754
49759
  init_utils();
49755
49760
 
49756
49761
  // src/hooks/loop-detector.ts
@@ -49932,6 +49937,7 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3) {
49932
49937
  } else {
49933
49938
  guardrailsConfig = config3;
49934
49939
  }
49940
+ const effectiveDirectory = typeof directory === "string" ? directory : process.cwd();
49935
49941
  if (guardrailsConfig?.enabled === false) {
49936
49942
  return {
49937
49943
  toolBefore: async () => {},
@@ -50025,9 +50031,9 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3) {
50025
50031
  const args2 = output.args;
50026
50032
  const targetPath = args2?.filePath ?? args2?.path ?? args2?.file ?? args2?.target;
50027
50033
  if (typeof targetPath === "string" && targetPath.length > 0) {
50028
- const resolvedTarget = path29.resolve(directory, targetPath).toLowerCase();
50029
- const planMdPath = path29.resolve(directory, ".swarm", "plan.md").toLowerCase();
50030
- const planJsonPath = path29.resolve(directory, ".swarm", "plan.json").toLowerCase();
50034
+ const resolvedTarget = path29.resolve(effectiveDirectory, targetPath).toLowerCase();
50035
+ const planMdPath = path29.resolve(effectiveDirectory, ".swarm", "plan.md").toLowerCase();
50036
+ const planJsonPath = path29.resolve(effectiveDirectory, ".swarm", "plan.json").toLowerCase();
50031
50037
  if (resolvedTarget === planMdPath || resolvedTarget === planJsonPath) {
50032
50038
  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.");
50033
50039
  }
@@ -50076,13 +50082,13 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3) {
50076
50082
  }
50077
50083
  }
50078
50084
  for (const p of paths) {
50079
- const resolvedP = path29.resolve(directory, p);
50080
- const planMdPath = path29.resolve(directory, ".swarm", "plan.md").toLowerCase();
50081
- const planJsonPath = path29.resolve(directory, ".swarm", "plan.json").toLowerCase();
50085
+ const resolvedP = path29.resolve(effectiveDirectory, p);
50086
+ const planMdPath = path29.resolve(effectiveDirectory, ".swarm", "plan.md").toLowerCase();
50087
+ const planJsonPath = path29.resolve(effectiveDirectory, ".swarm", "plan.json").toLowerCase();
50082
50088
  if (resolvedP.toLowerCase() === planMdPath || resolvedP.toLowerCase() === planJsonPath) {
50083
50089
  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.");
50084
50090
  }
50085
- if (isOutsideSwarmDir(p, directory) && (isSourceCodePath(p) || hasTraversalSegments(p))) {
50091
+ if (isOutsideSwarmDir(p, effectiveDirectory) && (isSourceCodePath(p) || hasTraversalSegments(p))) {
50086
50092
  const session2 = swarmState.agentSessions.get(input.sessionID);
50087
50093
  if (session2) {
50088
50094
  session2.architectWriteCount++;
@@ -50098,7 +50104,7 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3) {
50098
50104
  }
50099
50105
  }
50100
50106
  }
50101
- if (typeof targetPath === "string" && targetPath.length > 0 && isOutsideSwarmDir(targetPath, directory) && isSourceCodePath(path29.relative(directory, path29.resolve(directory, targetPath)))) {
50107
+ if (typeof targetPath === "string" && targetPath.length > 0 && isOutsideSwarmDir(targetPath, effectiveDirectory) && isSourceCodePath(path29.relative(effectiveDirectory, path29.resolve(effectiveDirectory, targetPath)))) {
50102
50108
  const session2 = swarmState.agentSessions.get(input.sessionID);
50103
50109
  if (session2) {
50104
50110
  session2.architectWriteCount++;
@@ -50297,7 +50303,7 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3) {
50297
50303
  if (delegation.isDelegation && (delegation.targetAgent === "reviewer" || delegation.targetAgent === "test_engineer")) {
50298
50304
  let currentPhase = 1;
50299
50305
  try {
50300
- const plan = await loadPlan(directory);
50306
+ const plan = await loadPlan(effectiveDirectory);
50301
50307
  if (plan) {
50302
50308
  const phaseString = extractCurrentPhaseFromPlan2(plan);
50303
50309
  currentPhase = extractPhaseNumber(phaseString);
@@ -50470,7 +50476,7 @@ ${textPart2.text}`;
50470
50476
  }
50471
50477
  let currentPhaseForCheck = 1;
50472
50478
  try {
50473
- const plan = await loadPlan(directory);
50479
+ const plan = await loadPlan(effectiveDirectory);
50474
50480
  if (plan) {
50475
50481
  const phaseString = extractCurrentPhaseFromPlan2(plan);
50476
50482
  currentPhaseForCheck = extractPhaseNumber(phaseString);
@@ -50534,7 +50540,7 @@ ${textPart2.text}`;
50534
50540
  }
50535
50541
  if (isArchitectSessionForGates && session && session.catastrophicPhaseWarnings && requireReviewerAndTestEngineer) {
50536
50542
  try {
50537
- const plan = await loadPlan(directory);
50543
+ const plan = await loadPlan(effectiveDirectory);
50538
50544
  if (plan?.phases) {
50539
50545
  for (const phase of plan.phases) {
50540
50546
  if (phase.status === "complete") {
@@ -50604,6 +50610,7 @@ function hashArgs(args2) {
50604
50610
  }
50605
50611
 
50606
50612
  // src/hooks/delegation-gate.ts
50613
+ init_utils2();
50607
50614
  function extractTaskLine(text) {
50608
50615
  const match = text.match(/TASK:\s*(.+?)(?:\n|$)/i);
50609
50616
  return match ? match[1].trim() : null;
@@ -55131,6 +55138,10 @@ var check_gate_status = createSwarmTool({
55131
55138
  return JSON.stringify(result, null, 2);
55132
55139
  }
55133
55140
  });
55141
+
55142
+ // src/tools/index.ts
55143
+ init_checkpoint();
55144
+
55134
55145
  // src/tools/complexity-hotspots.ts
55135
55146
  init_dist();
55136
55147
  init_create_tool();
@@ -57378,10 +57389,11 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
57378
57389
  const phase = Number(args2.phase);
57379
57390
  const summary = args2.summary;
57380
57391
  const sessionID = args2.sessionID;
57381
- if (Number.isNaN(phase) || phase < 1) {
57392
+ if (Number.isNaN(phase) || phase < 1 || !Number.isFinite(phase) || !Number.isInteger(phase)) {
57382
57393
  return JSON.stringify({
57383
57394
  success: false,
57384
57395
  phase,
57396
+ status: "blocked",
57385
57397
  message: "Invalid phase number",
57386
57398
  agentsDispatched: [],
57387
57399
  warnings: ["Phase must be a positive number"]
@@ -57449,7 +57461,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
57449
57461
  }
57450
57462
  if (!retroFound) {
57451
57463
  const allTaskIds = await listEvidenceTaskIds(dir);
57452
- const retroTaskIds = allTaskIds.filter((id) => id.startsWith("retro-"));
57464
+ const retroTaskIds = allTaskIds.filter((id) => id.startsWith("retro-") && /^retro-\d+$/.test(id));
57453
57465
  for (const taskId of retroTaskIds) {
57454
57466
  const bundleResult = await loadEvidence(dir, taskId);
57455
57467
  if (bundleResult.status !== "found") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "6.32.1",
3
+ "version": "6.32.3",
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",