opencode-swarm 6.19.6 → 6.19.7

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
@@ -694,7 +694,9 @@ The following tools can be assigned to agents via overrides:
694
694
  | `secretscan` | Scan for secrets in code |
695
695
  | `symbols` | Extract exported symbols |
696
696
  | `test_runner` | Run project tests |
697
+ | `update_task_status` | Mark plan tasks as pending/in_progress/completed/blocked; track phase progress |
697
698
  | `todo_extract` | Extract TODO/FIXME comments |
699
+ | `write_retro` | Document phase retrospectives via the phase_complete workflow; capture lessons learned |
698
700
  | `phase_complete` | Enforces phase completion, verifies required agents, logs events, resets state |
699
701
 
700
702
  ---
package/dist/cli/index.js CHANGED
@@ -14326,16 +14326,68 @@ async function saveEvidence(directory, taskId, evidence) {
14326
14326
  }
14327
14327
  return updatedBundle;
14328
14328
  }
14329
+ function isFlatRetrospective(parsed) {
14330
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) && parsed.type === "retrospective" && !parsed.schema_version;
14331
+ }
14332
+ function remapLegacyTaskComplexity(entry) {
14333
+ const taskComplexity = entry.task_complexity;
14334
+ if (typeof taskComplexity === "string" && taskComplexity in LEGACY_TASK_COMPLEXITY_MAP) {
14335
+ return {
14336
+ ...entry,
14337
+ task_complexity: LEGACY_TASK_COMPLEXITY_MAP[taskComplexity]
14338
+ };
14339
+ }
14340
+ return entry;
14341
+ }
14342
+ function wrapFlatRetrospective(flatEntry, taskId) {
14343
+ const now = new Date().toISOString();
14344
+ const remappedEntry = remapLegacyTaskComplexity(flatEntry);
14345
+ return {
14346
+ schema_version: "1.0.0",
14347
+ task_id: remappedEntry.task_id ?? taskId,
14348
+ created_at: remappedEntry.timestamp ?? now,
14349
+ updated_at: remappedEntry.timestamp ?? now,
14350
+ entries: [remappedEntry]
14351
+ };
14352
+ }
14329
14353
  async function loadEvidence(directory, taskId) {
14330
14354
  const sanitizedTaskId = sanitizeTaskId(taskId);
14331
14355
  const relativePath = path3.join("evidence", sanitizedTaskId, "evidence.json");
14332
- validateSwarmPath(directory, relativePath);
14356
+ const evidencePath = validateSwarmPath(directory, relativePath);
14333
14357
  const content = await readSwarmFileAsync(directory, relativePath);
14334
14358
  if (content === null) {
14335
14359
  return { status: "not_found" };
14336
14360
  }
14361
+ let parsed;
14362
+ try {
14363
+ parsed = JSON.parse(content);
14364
+ } catch {
14365
+ return { status: "invalid_schema", errors: ["Invalid JSON"] };
14366
+ }
14367
+ if (isFlatRetrospective(parsed)) {
14368
+ const wrappedBundle = wrapFlatRetrospective(parsed, sanitizedTaskId);
14369
+ try {
14370
+ const validated = EvidenceBundleSchema.parse(wrappedBundle);
14371
+ const evidenceDir = path3.dirname(evidencePath);
14372
+ const bundleJson = JSON.stringify(validated);
14373
+ const tempPath = path3.join(evidenceDir, `evidence.json.tmp.${Date.now()}.${process.pid}`);
14374
+ try {
14375
+ await Bun.write(tempPath, bundleJson);
14376
+ renameSync(tempPath, evidencePath);
14377
+ } catch (writeError) {
14378
+ try {
14379
+ rmSync(tempPath, { force: true });
14380
+ } catch {}
14381
+ warn(`Failed to persist repaired flat retrospective for task ${sanitizedTaskId}: ${writeError instanceof Error ? writeError.message : String(writeError)}`);
14382
+ }
14383
+ return { status: "found", bundle: validated };
14384
+ } catch (error49) {
14385
+ warn(`Wrapped flat retrospective failed validation for task ${sanitizedTaskId}: ${error49 instanceof Error ? error49.message : String(error49)}`);
14386
+ const errors3 = error49 instanceof ZodError ? error49.issues.map((e) => e.path.join(".") + ": " + e.message) : [String(error49)];
14387
+ return { status: "invalid_schema", errors: errors3 };
14388
+ }
14389
+ }
14337
14390
  try {
14338
- const parsed = JSON.parse(content);
14339
14391
  const validated = EvidenceBundleSchema.parse(parsed);
14340
14392
  return { status: "found", bundle: validated };
14341
14393
  } catch (error49) {
@@ -14428,7 +14480,7 @@ async function archiveEvidence(directory, maxAgeDays, maxBundles) {
14428
14480
  }
14429
14481
  return archived;
14430
14482
  }
14431
- var VALID_EVIDENCE_TYPES, TASK_ID_REGEX;
14483
+ var VALID_EVIDENCE_TYPES, TASK_ID_REGEX, LEGACY_TASK_COMPLEXITY_MAP;
14432
14484
  var init_manager = __esm(() => {
14433
14485
  init_zod();
14434
14486
  init_evidence_schema();
@@ -14449,6 +14501,11 @@ var init_manager = __esm(() => {
14449
14501
  "quality_budget"
14450
14502
  ];
14451
14503
  TASK_ID_REGEX = /^[\w-]+(\.[\w-]+)*$/;
14504
+ LEGACY_TASK_COMPLEXITY_MAP = {
14505
+ low: "simple",
14506
+ medium: "moderate",
14507
+ high: "complex"
14508
+ };
14452
14509
  });
14453
14510
 
14454
14511
  // node_modules/graceful-fs/polyfills.js
@@ -17468,7 +17525,9 @@ var TOOL_NAMES = [
17468
17525
  "retrieve_summary",
17469
17526
  "extract_code_blocks",
17470
17527
  "phase_complete",
17471
- "save_plan"
17528
+ "save_plan",
17529
+ "update_task_status",
17530
+ "write_retro"
17472
17531
  ];
17473
17532
  var TOOL_NAME_SET = new Set(TOOL_NAMES);
17474
17533
 
@@ -17506,7 +17565,9 @@ var AGENT_TOOL_MAP = {
17506
17565
  "secretscan",
17507
17566
  "symbols",
17508
17567
  "test_runner",
17509
- "todo_extract"
17568
+ "todo_extract",
17569
+ "update_task_status",
17570
+ "write_retro"
17510
17571
  ],
17511
17572
  explorer: [
17512
17573
  "complexity_hotspots",
package/dist/index.js CHANGED
@@ -14490,16 +14490,68 @@ async function saveEvidence(directory, taskId, evidence) {
14490
14490
  }
14491
14491
  return updatedBundle;
14492
14492
  }
14493
+ function isFlatRetrospective(parsed) {
14494
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) && parsed.type === "retrospective" && !parsed.schema_version;
14495
+ }
14496
+ function remapLegacyTaskComplexity(entry) {
14497
+ const taskComplexity = entry.task_complexity;
14498
+ if (typeof taskComplexity === "string" && taskComplexity in LEGACY_TASK_COMPLEXITY_MAP) {
14499
+ return {
14500
+ ...entry,
14501
+ task_complexity: LEGACY_TASK_COMPLEXITY_MAP[taskComplexity]
14502
+ };
14503
+ }
14504
+ return entry;
14505
+ }
14506
+ function wrapFlatRetrospective(flatEntry, taskId) {
14507
+ const now = new Date().toISOString();
14508
+ const remappedEntry = remapLegacyTaskComplexity(flatEntry);
14509
+ return {
14510
+ schema_version: "1.0.0",
14511
+ task_id: remappedEntry.task_id ?? taskId,
14512
+ created_at: remappedEntry.timestamp ?? now,
14513
+ updated_at: remappedEntry.timestamp ?? now,
14514
+ entries: [remappedEntry]
14515
+ };
14516
+ }
14493
14517
  async function loadEvidence(directory, taskId) {
14494
14518
  const sanitizedTaskId = sanitizeTaskId(taskId);
14495
14519
  const relativePath = path3.join("evidence", sanitizedTaskId, "evidence.json");
14496
- validateSwarmPath(directory, relativePath);
14520
+ const evidencePath = validateSwarmPath(directory, relativePath);
14497
14521
  const content = await readSwarmFileAsync(directory, relativePath);
14498
14522
  if (content === null) {
14499
14523
  return { status: "not_found" };
14500
14524
  }
14525
+ let parsed;
14526
+ try {
14527
+ parsed = JSON.parse(content);
14528
+ } catch {
14529
+ return { status: "invalid_schema", errors: ["Invalid JSON"] };
14530
+ }
14531
+ if (isFlatRetrospective(parsed)) {
14532
+ const wrappedBundle = wrapFlatRetrospective(parsed, sanitizedTaskId);
14533
+ try {
14534
+ const validated = EvidenceBundleSchema.parse(wrappedBundle);
14535
+ const evidenceDir = path3.dirname(evidencePath);
14536
+ const bundleJson = JSON.stringify(validated);
14537
+ const tempPath = path3.join(evidenceDir, `evidence.json.tmp.${Date.now()}.${process.pid}`);
14538
+ try {
14539
+ await Bun.write(tempPath, bundleJson);
14540
+ renameSync(tempPath, evidencePath);
14541
+ } catch (writeError) {
14542
+ try {
14543
+ rmSync(tempPath, { force: true });
14544
+ } catch {}
14545
+ warn(`Failed to persist repaired flat retrospective for task ${sanitizedTaskId}: ${writeError instanceof Error ? writeError.message : String(writeError)}`);
14546
+ }
14547
+ return { status: "found", bundle: validated };
14548
+ } catch (error49) {
14549
+ warn(`Wrapped flat retrospective failed validation for task ${sanitizedTaskId}: ${error49 instanceof Error ? error49.message : String(error49)}`);
14550
+ const errors3 = error49 instanceof ZodError ? error49.issues.map((e) => e.path.join(".") + ": " + e.message) : [String(error49)];
14551
+ return { status: "invalid_schema", errors: errors3 };
14552
+ }
14553
+ }
14501
14554
  try {
14502
- const parsed = JSON.parse(content);
14503
14555
  const validated = EvidenceBundleSchema.parse(parsed);
14504
14556
  return { status: "found", bundle: validated };
14505
14557
  } catch (error49) {
@@ -14592,7 +14644,7 @@ async function archiveEvidence(directory, maxAgeDays, maxBundles) {
14592
14644
  }
14593
14645
  return archived;
14594
14646
  }
14595
- var VALID_EVIDENCE_TYPES, TASK_ID_REGEX;
14647
+ var VALID_EVIDENCE_TYPES, TASK_ID_REGEX, LEGACY_TASK_COMPLEXITY_MAP;
14596
14648
  var init_manager = __esm(() => {
14597
14649
  init_zod();
14598
14650
  init_evidence_schema();
@@ -14613,6 +14665,11 @@ var init_manager = __esm(() => {
14613
14665
  "quality_budget"
14614
14666
  ];
14615
14667
  TASK_ID_REGEX = /^[\w-]+(\.[\w-]+)*$/;
14668
+ LEGACY_TASK_COMPLEXITY_MAP = {
14669
+ low: "simple",
14670
+ medium: "moderate",
14671
+ high: "complex"
14672
+ };
14616
14673
  });
14617
14674
 
14618
14675
  // src/plan/manager.ts
@@ -14783,6 +14840,29 @@ ${markdown}`;
14783
14840
  } catch {}
14784
14841
  }
14785
14842
  }
14843
+ async function updateTaskStatus(directory, taskId, status) {
14844
+ const plan = await loadPlan(directory);
14845
+ if (plan === null) {
14846
+ throw new Error(`Plan not found in directory: ${directory}`);
14847
+ }
14848
+ let taskFound = false;
14849
+ const updatedPhases = plan.phases.map((phase) => {
14850
+ const updatedTasks = phase.tasks.map((task) => {
14851
+ if (task.id === taskId) {
14852
+ taskFound = true;
14853
+ return { ...task, status };
14854
+ }
14855
+ return task;
14856
+ });
14857
+ return { ...phase, tasks: updatedTasks };
14858
+ });
14859
+ if (!taskFound) {
14860
+ throw new Error(`Task not found: ${taskId}`);
14861
+ }
14862
+ const updatedPlan = { ...plan, phases: updatedPhases };
14863
+ await savePlan(directory, updatedPlan);
14864
+ return updatedPlan;
14865
+ }
14786
14866
  function derivePlanMarkdown(plan) {
14787
14867
  const statusMap = {
14788
14868
  pending: "PENDING",
@@ -38002,7 +38082,7 @@ var init_runtime = __esm(() => {
38002
38082
  });
38003
38083
 
38004
38084
  // src/index.ts
38005
- import * as path43 from "path";
38085
+ import * as path44 from "path";
38006
38086
 
38007
38087
  // src/tools/tool-names.ts
38008
38088
  var TOOL_NAMES = [
@@ -38030,7 +38110,9 @@ var TOOL_NAMES = [
38030
38110
  "retrieve_summary",
38031
38111
  "extract_code_blocks",
38032
38112
  "phase_complete",
38033
- "save_plan"
38113
+ "save_plan",
38114
+ "update_task_status",
38115
+ "write_retro"
38034
38116
  ];
38035
38117
  var TOOL_NAME_SET = new Set(TOOL_NAMES);
38036
38118
 
@@ -38068,7 +38150,9 @@ var AGENT_TOOL_MAP = {
38068
38150
  "secretscan",
38069
38151
  "symbols",
38070
38152
  "test_runner",
38071
- "todo_extract"
38153
+ "todo_extract",
38154
+ "update_task_status",
38155
+ "write_retro"
38072
38156
  ],
38073
38157
  explorer: [
38074
38158
  "complexity_hotspots",
@@ -39009,9 +39093,10 @@ writeCount > 0 on source files from the Architect is equivalent to GATE_DELEGATI
39009
39093
 
39010
39094
  PLAN STATE PROTECTION
39011
39095
  .swarm/plan.md and .swarm/plan.json are READABLE but NOT DIRECTLY WRITABLE for state transitions.
39012
- Task status changes (- [ ] to - [x], "pending" to "complete") must go through phase_complete() ONLY.
39096
+ Task-level status changes (marking individual tasks as "completed") must use update_task_status().
39097
+ Phase-level completion (marking an entire phase as done) must use phase_complete().
39013
39098
  You may write to plan.md/plan.json for STRUCTURAL changes (adding tasks, updating descriptions).
39014
- You may NOT write to plan.md/plan.json to change task completion status or phase status.
39099
+ You may NOT write to plan.md/plan.json to change task completion status or phase status directly.
39015
39100
  "I'll just mark it done directly" is a bypass \u2014 equivalent to GATE_DELEGATION_BYPASS.
39016
39101
 
39017
39102
  6i. **DELEGATION DISCIPLINE**
@@ -39047,7 +39132,7 @@ It is the same severity as skipping all gates. The QA gate is ALL steps or NONE.
39047
39132
  - Target file is in: pages/, components/, views/, screens/, ui/, layouts/
39048
39133
  If triggered: delegate to {{AGENT_PREFIX}}designer FIRST to produce a code scaffold. Then pass the scaffold to {{AGENT_PREFIX}}coder as INPUT alongside the task. The coder implements the TODOs in the scaffold without changing component structure or accessibility attributes.
39049
39134
  If not triggered: delegate directly to {{AGENT_PREFIX}}coder as normal.
39050
- 10. **RETROSPECTIVE TRACKING**: At the end of every phase, record phase metrics in .swarm/context.md under "## Phase Metrics" and write a retrospective evidence entry via the evidence manager. Track: phase_number, total_tool_calls, coder_revisions, reviewer_rejections, test_failures, security_findings, integration_issues, task_count, task_complexity, top_rejection_reasons, lessons_learned (max 5). Reset Phase Metrics to 0 after writing.
39135
+ 10. **RETROSPECTIVE TRACKING**: At the end of every phase, record phase metrics in .swarm/context.md under "## Phase Metrics" and write a retrospective evidence entry via write_retro. Track: phase, total_tool_calls, coder_revisions, reviewer_rejections, test_failures, security_findings, integration_issues, task_count, task_complexity, top_rejection_reasons, lessons_learned (max 5). Reset Phase Metrics to 0 after writing.
39051
39136
  11. **CHECKPOINTS**: Before delegating multi-file refactor tasks (3+ files), create a checkpoint save. On critical failures when redo is faster than iterative fixes, restore from checkpoint. Use checkpoint tool: \`checkpoint save\` before risky operations, \`checkpoint restore\` on failure.
39052
39137
 
39053
39138
  SECURITY_KEYWORDS: password, secret, token, credential, auth, login, encryption, hash, key, certificate, ssl, tls, jwt, oauth, session, csrf, xss, injection, sanitization, permission, access, vulnerable, exploit, privilege, authorization, roles, authentication, mfa, 2fa, totp, otp, salt, iv, nonce, hmac, aes, rsa, sha256, bcrypt, scrypt, argon2, api_key, apikey, private_key, public_key, rbac, admin, superuser, sqli, rce, ssrf, xxe, nosql, command_injection
@@ -39072,7 +39157,7 @@ Outside OpenCode, invoke any plugin command via: \`bunx opencode-swarm run <comm
39072
39157
 
39073
39158
  SMEs advise only. Reviewer and critic review only. None of them write code.
39074
39159
 
39075
- Available Tools: symbols (code symbol search), checkpoint (state snapshots), diff (structured git diff with contract change detection), imports (dependency audit), lint (code quality), placeholder_scan (placeholder/todo detection), secretscan (secret detection), sast_scan (static analysis security scan), syntax_check (syntax validation), test_runner (auto-detect and run tests), pkg_audit (dependency vulnerability scan \u2014 npm/pip/cargo), complexity_hotspots (git churn \xD7 complexity risk map), schema_drift (OpenAPI spec vs route drift), todo_extract (structured TODO/FIXME extraction), evidence_check (verify task evidence completeness), sbom_generate (SBOM generation for dependency inventory), build_check (build verification), quality_budget (code quality budget check), pre_check_batch (parallel verification: lint:check + secretscan + sast_scan + quality_budget)
39160
+ Available Tools: symbols (code symbol search), checkpoint (state snapshots), diff (structured git diff with contract change detection), imports (dependency audit), lint (code quality), placeholder_scan (placeholder/todo detection), secretscan (secret detection), sast_scan (static analysis security scan), syntax_check (syntax validation), test_runner (auto-detect and run tests), pkg_audit (dependency vulnerability scan \u2014 npm/pip/cargo), complexity_hotspots (git churn \xD7 complexity risk map), schema_drift (OpenAPI spec vs route drift), todo_extract (structured TODO/FIXME extraction), evidence_check (verify task evidence completeness), sbom_generate (SBOM generation for dependency inventory), build_check (build verification), quality_budget (code quality budget check), pre_check_batch (parallel verification: lint:check + secretscan + sast_scan + quality_budget), update_task_status (mark tasks complete, track phase progress), write_retro (document phase retrospectives via phase_complete workflow, capture lessons learned)
39076
39161
 
39077
39162
  ## DELEGATION FORMAT
39078
39163
 
@@ -39455,6 +39540,9 @@ The ONLY exception: lint tool in fix mode (step 5g) auto-corrects by design.
39455
39540
  All other gates: failure \u2192 return to coder. No self-fixes. No workarounds.
39456
39541
 
39457
39542
  5a. **UI DESIGN GATE** (conditional \u2014 Rule 9): If task matches UI trigger \u2192 {{AGENT_PREFIX}}designer produces scaffold \u2192 pass scaffold to coder as INPUT. If no match \u2192 skip.
39543
+
39544
+ \u2192 After step 5a (or immediately if no UI task applies): Call update_task_status with status in_progress for the current task. Then proceed to step 5b.
39545
+
39458
39546
  5b. {{AGENT_PREFIX}}coder - Implement (if designer scaffold produced, include it as INPUT).
39459
39547
  5c. Run \`diff\` tool. If \`hasContractChanges\` \u2192 {{AGENT_PREFIX}}explorer integration analysis. BREAKING \u2192 coder retry.
39460
39548
  \u2192 REQUIRED: Print "diff: [PASS | CONTRACT CHANGE \u2014 details]"
@@ -39533,7 +39621,7 @@ PRE-COMMIT RULE \u2014 Before ANY commit or push:
39533
39621
  Any blank "value: ___" field = gate was not run = task is NOT complete.
39534
39622
  Filling this checklist from memory ("I think I ran it") is INVALID. Each value must come from actual tool/agent output in this session.
39535
39623
 
39536
- 5o. Update plan.md [x], proceed to next task.
39624
+ 5o. Call update_task_status with status "completed", proceed to next task.
39537
39625
 
39538
39626
  ## \u26D4 RETROSPECTIVE GATE
39539
39627
 
@@ -39541,31 +39629,26 @@ PRE-COMMIT RULE \u2014 Before ANY commit or push:
39541
39629
 
39542
39630
  **How to write the retrospective:**
39543
39631
 
39544
- Use the evidence manager tool to write a bundle at \`retro-{N}\` (where N is the phase number being completed):
39545
-
39546
- \`\`\`json
39547
- {
39548
- "type": "retrospective",
39549
- "phase_number": <N>,
39550
- "verdict": "pass",
39551
- "reviewer_rejections": <count>,
39552
- "coder_revisions": <count>,
39553
- "test_failures": <count>,
39554
- "security_findings": <count>,
39555
- "lessons_learned": ["lesson 1 (max 5)", "lesson 2"],
39556
- "top_rejection_reasons": ["reason 1"],
39557
- "user_directives": [],
39558
- "approaches_tried": [],
39559
- "task_complexity": "low|medium|high",
39560
- "timestamp": "<ISO 8601>",
39561
- "agent": "architect",
39562
- "metadata": { "plan_id": "<current plan title from .swarm/plan.json>" }
39563
- }
39564
- \`\`\`
39632
+ Call the \`write_retro\` tool with the required fields:
39633
+ - \`phase\`: The phase number being completed (e.g., 1, 2, 3)
39634
+ - \`summary\`: Human-readable summary of the phase
39635
+ - \`task_count\`: Count of tasks completed in this phase
39636
+ - \`task_complexity\`: One of \`trivial\` | \`simple\` | \`moderate\` | \`complex\`
39637
+ - \`total_tool_calls\`: Total number of tool calls in this phase
39638
+ - \`coder_revisions\`: Number of coder revisions made
39639
+ - \`reviewer_rejections\`: Number of reviewer rejections received
39640
+ - \`test_failures\`: Number of test failures encountered
39641
+ - \`security_findings\`: Number of security findings
39642
+ - \`integration_issues\`: Number of integration issues
39643
+ - \`lessons_learned\`: (optional) Key lessons learned from this phase (max 5)
39644
+ - \`top_rejection_reasons\`: (optional) Top reasons for reviewer rejections
39645
+ - \`metadata\`: (optional) Additional metadata, e.g., \`{ "plan_id": "<current plan title from .swarm/plan.json>" }\`
39646
+
39647
+ The tool will automatically write the retrospective to \`.swarm/evidence/retro-{phase}/evidence.json\` with the correct schema wrapper.
39565
39648
 
39566
39649
  **Required field rules:**
39567
- - \`verdict\` MUST be \`"pass"\` \u2014 a verdict of \`"fail"\` or missing verdict blocks phase_complete
39568
- - \`phase_number\` MUST match the phase number you are completing
39650
+ - \`verdict\` is auto-generated by write_retro with value \`"pass"\`. The resulting retrospective entry will have verdict \`"pass"\`; this is required for phase_complete to succeed.
39651
+ - \`phase\` MUST match the phase number you are completing
39569
39652
  - \`lessons_learned\` should be 3-5 concrete, actionable items from this phase
39570
39653
  - Write the bundle as task_id \`retro-{N}\` (e.g., \`retro-1\` for Phase 1, \`retro-2\` for Phase 2)
39571
39654
  - \`metadata.plan_id\` should be set to the current project's plan title (from \`.swarm/plan.json\` header). This enables cross-project filtering in the retrospective injection system.
@@ -39590,7 +39673,7 @@ Use the evidence manager tool to write a bundle at \`retro-{N}\` (where N is the
39590
39673
  - Summary of what was added/modified/removed
39591
39674
  - List of doc files that may need updating (README.md, CONTRIBUTING.md, docs/)
39592
39675
  3. Update context.md
39593
- 4. Write retrospective evidence: record phase_number, total_tool_calls, coder_revisions, reviewer_rejections, test_failures, security_findings, integration_issues, task_count, task_complexity, top_rejection_reasons, lessons_learned to .swarm/evidence/ via the evidence manager. Reset Phase Metrics in context.md to 0.
39676
+ 4. Write retrospective evidence: record phase, total_tool_calls, coder_revisions, reviewer_rejections, test_failures, security_findings, integration_issues, task_count, task_complexity, top_rejection_reasons, lessons_learned to .swarm/evidence/ via write_retro. Reset Phase Metrics in context.md to 0.
39594
39677
  4.5. Run \`evidence_check\` to verify all completed tasks have required evidence (review + test). If gaps found, note in retrospective lessons_learned. Optionally run \`pkg_audit\` if dependencies were modified during this phase. Optionally run \`schema_drift\` if API routes were modified during this phase.
39595
39678
  5. Run \`sbom_generate\` with scope='changed' to capture post-implementation dependency snapshot (saved to \`.swarm/evidence/sbom/\`). This is a non-blocking step - always proceeds to summary.
39596
39679
  5.5. If \`.swarm/spec.md\` exists: delegate {{AGENT_PREFIX}}critic with DRIFT-CHECK context \u2014 include phase number, list of completed task IDs and descriptions, and evidence path (\`.swarm/evidence/\`). If SIGNIFICANT DRIFT is returned: surface as a warning to the user before proceeding. If spec.md does not exist: skip silently.
@@ -40757,6 +40840,9 @@ function getAgentConfigs(config2) {
40757
40840
  } else {
40758
40841
  sdkConfig.mode = "subagent";
40759
40842
  }
40843
+ if (sdkConfig.mode === "primary") {
40844
+ delete sdkConfig.model;
40845
+ }
40760
40846
  const baseAgentName = stripKnownSwarmPrefix(agent.name);
40761
40847
  if (!toolFilterEnabled) {
40762
40848
  sdkConfig.tools = agent.config.tools ?? {};
@@ -51890,10 +51976,17 @@ async function executePhaseComplete(args2, workingDirectory) {
51890
51976
  let retroFound = false;
51891
51977
  let retroEntry = null;
51892
51978
  let invalidSchemaErrors = [];
51979
+ let loadedRetroTaskId = null;
51980
+ let loadedRetroBundle = null;
51981
+ const primaryRetroTaskId = `retro-${phase}`;
51893
51982
  if (retroResult.status === "found") {
51894
51983
  const validEntry = retroResult.bundle.entries?.find((entry) => isValidRetroEntry(entry, phase));
51895
- retroFound = !!validEntry;
51896
- retroEntry = validEntry;
51984
+ if (validEntry) {
51985
+ retroFound = true;
51986
+ retroEntry = validEntry;
51987
+ loadedRetroTaskId = primaryRetroTaskId;
51988
+ loadedRetroBundle = retroResult.bundle;
51989
+ }
51897
51990
  } else if (retroResult.status === "invalid_schema") {
51898
51991
  invalidSchemaErrors = retroResult.errors;
51899
51992
  }
@@ -51909,9 +52002,11 @@ async function executePhaseComplete(args2, workingDirectory) {
51909
52002
  continue;
51910
52003
  }
51911
52004
  const validEntry = bundleResult.bundle.entries?.find((entry) => isValidRetroEntry(entry, phase));
51912
- retroFound = !!validEntry;
51913
- if (retroFound) {
52005
+ if (validEntry) {
52006
+ retroFound = true;
51914
52007
  retroEntry = validEntry;
52008
+ loadedRetroTaskId = taskId;
52009
+ loadedRetroBundle = bundleResult.bundle;
51915
52010
  break;
51916
52011
  }
51917
52012
  }
@@ -51989,6 +52084,11 @@ async function executePhaseComplete(args2, workingDirectory) {
51989
52084
  }
51990
52085
  const agentsMissing = effectiveRequired.filter((req) => !crossSessionResult.agents.has(req));
51991
52086
  const warnings = [];
52087
+ const VALID_TASK_COMPLEXITY = ["trivial", "simple", "moderate", "complex"];
52088
+ const firstEntry = loadedRetroBundle?.entries?.[0];
52089
+ if (loadedRetroTaskId?.startsWith("retro-") && loadedRetroBundle?.schema_version === "1.0.0" && firstEntry?.task_complexity && VALID_TASK_COMPLEXITY.includes(firstEntry.task_complexity)) {
52090
+ warnings.push(`Retrospective data for phase ${phase} may have been automatically migrated to current schema format.`);
52091
+ }
51992
52092
  let success3 = true;
51993
52093
  let status = "success";
51994
52094
  const safeSummary = summary?.trim().slice(0, 500);
@@ -57672,11 +57772,133 @@ var MAX_FILE_SIZE2 = 5 * 1024 * 1024;
57672
57772
  // src/tools/index.ts
57673
57773
  init_test_runner();
57674
57774
 
57675
- // src/tools/todo-extract.ts
57676
- init_dist();
57775
+ // src/tools/update-task-status.ts
57776
+ init_tool();
57777
+ init_manager2();
57677
57778
  init_create_tool();
57678
57779
  import * as fs29 from "fs";
57679
57780
  import * as path42 from "path";
57781
+ var VALID_STATUSES = [
57782
+ "pending",
57783
+ "in_progress",
57784
+ "completed",
57785
+ "blocked"
57786
+ ];
57787
+ function validateStatus(status) {
57788
+ if (!VALID_STATUSES.includes(status)) {
57789
+ return `Invalid status "${status}". Must be one of: ${VALID_STATUSES.join(", ")}`;
57790
+ }
57791
+ return;
57792
+ }
57793
+ function validateTaskId(taskId) {
57794
+ const taskIdPattern = /^\d+\.\d+(\.\d+)*$/;
57795
+ if (!taskIdPattern.test(taskId)) {
57796
+ return `Invalid task_id "${taskId}". Must match pattern N.M or N.M.P (e.g., "1.1", "1.2.3")`;
57797
+ }
57798
+ return;
57799
+ }
57800
+ async function executeUpdateTaskStatus(args2, fallbackDir) {
57801
+ const statusError = validateStatus(args2.status);
57802
+ if (statusError) {
57803
+ return {
57804
+ success: false,
57805
+ message: "Validation failed",
57806
+ errors: [statusError]
57807
+ };
57808
+ }
57809
+ const taskIdError = validateTaskId(args2.task_id);
57810
+ if (taskIdError) {
57811
+ return {
57812
+ success: false,
57813
+ message: "Validation failed",
57814
+ errors: [taskIdError]
57815
+ };
57816
+ }
57817
+ let normalizedDir;
57818
+ if (args2.working_directory != null) {
57819
+ if (args2.working_directory.includes("\x00")) {
57820
+ return {
57821
+ success: false,
57822
+ message: "Invalid working_directory: null bytes are not allowed"
57823
+ };
57824
+ }
57825
+ if (process.platform === "win32") {
57826
+ const devicePathPattern = /^\\\\|^(NUL|CON|AUX|COM[1-9]|LPT[1-9])(\..*)?$/i;
57827
+ if (devicePathPattern.test(args2.working_directory)) {
57828
+ return {
57829
+ success: false,
57830
+ message: "Invalid working_directory: Windows device paths are not allowed"
57831
+ };
57832
+ }
57833
+ }
57834
+ normalizedDir = path42.normalize(args2.working_directory);
57835
+ const pathParts = normalizedDir.split(path42.sep);
57836
+ if (pathParts.includes("..")) {
57837
+ return {
57838
+ success: false,
57839
+ message: "Invalid working_directory: path traversal sequences (..) are not allowed",
57840
+ errors: [
57841
+ "Invalid working_directory: path traversal sequences (..) are not allowed"
57842
+ ]
57843
+ };
57844
+ }
57845
+ const resolvedDir = path42.resolve(normalizedDir);
57846
+ try {
57847
+ const realPath = fs29.realpathSync(resolvedDir);
57848
+ const planPath = path42.join(realPath, ".swarm", "plan.json");
57849
+ if (!fs29.existsSync(planPath)) {
57850
+ return {
57851
+ success: false,
57852
+ message: `Invalid working_directory: plan not found in "${realPath}"`,
57853
+ errors: [
57854
+ `Invalid working_directory: plan not found in "${realPath}"`
57855
+ ]
57856
+ };
57857
+ }
57858
+ } catch {
57859
+ return {
57860
+ success: false,
57861
+ message: `Invalid working_directory: path "${resolvedDir}" does not exist or is inaccessible`,
57862
+ errors: [
57863
+ `Invalid working_directory: path "${resolvedDir}" does not exist or is inaccessible`
57864
+ ]
57865
+ };
57866
+ }
57867
+ }
57868
+ const directory = normalizedDir ?? fallbackDir ?? process.cwd();
57869
+ try {
57870
+ const updatedPlan = await updateTaskStatus(directory, args2.task_id, args2.status);
57871
+ return {
57872
+ success: true,
57873
+ message: "Task status updated successfully",
57874
+ task_id: args2.task_id,
57875
+ new_status: args2.status,
57876
+ current_phase: updatedPlan.current_phase
57877
+ };
57878
+ } catch (error93) {
57879
+ return {
57880
+ success: false,
57881
+ message: "Failed to update task status",
57882
+ errors: [String(error93)]
57883
+ };
57884
+ }
57885
+ }
57886
+ var update_task_status = createSwarmTool({
57887
+ description: "Update the status of a specific task in the implementation plan. " + "Task status can be one of: pending, in_progress, completed, blocked.",
57888
+ args: {
57889
+ task_id: tool.schema.string().min(1).regex(/^\d+\.\d+(\.\d+)*$/, "Task ID must be in N.M or N.M.P format").describe('Task ID in N.M format, e.g. "1.1", "1.2.3"'),
57890
+ status: tool.schema.enum(["pending", "in_progress", "completed", "blocked"]).describe("New status for the task: pending, in_progress, completed, or blocked"),
57891
+ working_directory: tool.schema.string().optional().describe("Working directory where the plan is located")
57892
+ },
57893
+ execute: async (args2, _directory) => {
57894
+ return JSON.stringify(await executeUpdateTaskStatus(args2, _directory), null, 2);
57895
+ }
57896
+ });
57897
+ // src/tools/todo-extract.ts
57898
+ init_dist();
57899
+ init_create_tool();
57900
+ import * as fs30 from "fs";
57901
+ import * as path43 from "path";
57680
57902
  var MAX_TEXT_LENGTH = 200;
57681
57903
  var MAX_FILE_SIZE_BYTES8 = 1024 * 1024;
57682
57904
  var SUPPORTED_EXTENSIONS2 = new Set([
@@ -57747,9 +57969,9 @@ function validatePathsInput(paths, cwd) {
57747
57969
  return { error: "paths contains path traversal", resolvedPath: null };
57748
57970
  }
57749
57971
  try {
57750
- const resolvedPath = path42.resolve(paths);
57751
- const normalizedCwd = path42.resolve(cwd);
57752
- const normalizedResolved = path42.resolve(resolvedPath);
57972
+ const resolvedPath = path43.resolve(paths);
57973
+ const normalizedCwd = path43.resolve(cwd);
57974
+ const normalizedResolved = path43.resolve(resolvedPath);
57753
57975
  if (!normalizedResolved.startsWith(normalizedCwd)) {
57754
57976
  return {
57755
57977
  error: "paths must be within the current working directory",
@@ -57765,13 +57987,13 @@ function validatePathsInput(paths, cwd) {
57765
57987
  }
57766
57988
  }
57767
57989
  function isSupportedExtension(filePath) {
57768
- const ext = path42.extname(filePath).toLowerCase();
57990
+ const ext = path43.extname(filePath).toLowerCase();
57769
57991
  return SUPPORTED_EXTENSIONS2.has(ext);
57770
57992
  }
57771
57993
  function findSourceFiles3(dir, files = []) {
57772
57994
  let entries;
57773
57995
  try {
57774
- entries = fs29.readdirSync(dir);
57996
+ entries = fs30.readdirSync(dir);
57775
57997
  } catch {
57776
57998
  return files;
57777
57999
  }
@@ -57780,10 +58002,10 @@ function findSourceFiles3(dir, files = []) {
57780
58002
  if (SKIP_DIRECTORIES3.has(entry)) {
57781
58003
  continue;
57782
58004
  }
57783
- const fullPath = path42.join(dir, entry);
58005
+ const fullPath = path43.join(dir, entry);
57784
58006
  let stat2;
57785
58007
  try {
57786
- stat2 = fs29.statSync(fullPath);
58008
+ stat2 = fs30.statSync(fullPath);
57787
58009
  } catch {
57788
58010
  continue;
57789
58011
  }
@@ -57876,7 +58098,7 @@ var todo_extract = createSwarmTool({
57876
58098
  return JSON.stringify(errorResult, null, 2);
57877
58099
  }
57878
58100
  const scanPath = resolvedPath;
57879
- if (!fs29.existsSync(scanPath)) {
58101
+ if (!fs30.existsSync(scanPath)) {
57880
58102
  const errorResult = {
57881
58103
  error: `path not found: ${pathsInput}`,
57882
58104
  total: 0,
@@ -57886,13 +58108,13 @@ var todo_extract = createSwarmTool({
57886
58108
  return JSON.stringify(errorResult, null, 2);
57887
58109
  }
57888
58110
  const filesToScan = [];
57889
- const stat2 = fs29.statSync(scanPath);
58111
+ const stat2 = fs30.statSync(scanPath);
57890
58112
  if (stat2.isFile()) {
57891
58113
  if (isSupportedExtension(scanPath)) {
57892
58114
  filesToScan.push(scanPath);
57893
58115
  } else {
57894
58116
  const errorResult = {
57895
- error: `unsupported file extension: ${path42.extname(scanPath)}`,
58117
+ error: `unsupported file extension: ${path43.extname(scanPath)}`,
57896
58118
  total: 0,
57897
58119
  byPriority: { high: 0, medium: 0, low: 0 },
57898
58120
  entries: []
@@ -57905,11 +58127,11 @@ var todo_extract = createSwarmTool({
57905
58127
  const allEntries = [];
57906
58128
  for (const filePath of filesToScan) {
57907
58129
  try {
57908
- const fileStat = fs29.statSync(filePath);
58130
+ const fileStat = fs30.statSync(filePath);
57909
58131
  if (fileStat.size > MAX_FILE_SIZE_BYTES8) {
57910
58132
  continue;
57911
58133
  }
57912
- const content = fs29.readFileSync(filePath, "utf-8");
58134
+ const content = fs30.readFileSync(filePath, "utf-8");
57913
58135
  const entries = parseTodoComments(content, filePath, tagsSet);
57914
58136
  allEntries.push(...entries);
57915
58137
  } catch {}
@@ -58017,7 +58239,7 @@ var OpenCodeSwarm = async (ctx) => {
58017
58239
  const { PreflightTriggerManager: PTM } = await Promise.resolve().then(() => (init_trigger(), exports_trigger));
58018
58240
  preflightTriggerManager = new PTM(automationConfig);
58019
58241
  const { AutomationStatusArtifact: ASA } = await Promise.resolve().then(() => (init_status_artifact(), exports_status_artifact));
58020
- const swarmDir = path43.resolve(ctx.directory, ".swarm");
58242
+ const swarmDir = path44.resolve(ctx.directory, ".swarm");
58021
58243
  statusArtifact = new ASA(swarmDir);
58022
58244
  statusArtifact.updateConfig(automationConfig.mode, automationConfig.capabilities);
58023
58245
  if (automationConfig.capabilities?.evidence_auto_summaries === true) {
@@ -58129,7 +58351,9 @@ var OpenCodeSwarm = async (ctx) => {
58129
58351
  secretscan,
58130
58352
  symbols,
58131
58353
  test_runner,
58132
- todo_extract
58354
+ todo_extract,
58355
+ update_task_status,
58356
+ write_retro
58133
58357
  },
58134
58358
  config: async (opencodeConfig) => {
58135
58359
  if (!opencodeConfig.agent) {
@@ -23,5 +23,6 @@ export { type SecretFinding, type SecretscanResult, secretscan, } from './secret
23
23
  export { symbols } from './symbols';
24
24
  export { type SyntaxCheckFileResult, type SyntaxCheckInput, type SyntaxCheckResult, syntaxCheck, } from './syntax-check';
25
25
  export { test_runner } from './test-runner';
26
+ export { type UpdateTaskStatusArgs, type UpdateTaskStatusResult, executeUpdateTaskStatus, update_task_status, } from './update-task-status';
26
27
  export { todo_extract } from './todo-extract';
27
28
  export { executeWriteRetro, write_retro } from './write-retro';
@@ -3,7 +3,7 @@
3
3
  * Used for constants and agent setup references.
4
4
  */
5
5
  /** Union type of all valid tool names */
6
- export type ToolName = 'diff' | 'syntax_check' | 'placeholder_scan' | 'imports' | 'lint' | 'secretscan' | 'sast_scan' | 'build_check' | 'pre_check_batch' | 'quality_budget' | 'symbols' | 'complexity_hotspots' | 'schema_drift' | 'todo_extract' | 'evidence_check' | 'sbom_generate' | 'checkpoint' | 'pkg_audit' | 'test_runner' | 'detect_domains' | 'gitingest' | 'retrieve_summary' | 'extract_code_blocks' | 'phase_complete' | 'save_plan';
6
+ export type ToolName = 'diff' | 'syntax_check' | 'placeholder_scan' | 'imports' | 'lint' | 'secretscan' | 'sast_scan' | 'build_check' | 'pre_check_batch' | 'quality_budget' | 'symbols' | 'complexity_hotspots' | 'schema_drift' | 'todo_extract' | 'evidence_check' | 'sbom_generate' | 'checkpoint' | 'pkg_audit' | 'test_runner' | 'detect_domains' | 'gitingest' | 'retrieve_summary' | 'extract_code_blocks' | 'phase_complete' | 'save_plan' | 'update_task_status' | 'write_retro';
7
7
  /** Readonly array of all tool names */
8
8
  export declare const TOOL_NAMES: readonly ToolName[];
9
9
  /** Set for O(1) tool name validation */
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Update task status tool for changing the status of individual tasks in a plan.
3
+ * Allows agents to mark tasks as pending, in_progress, completed, or blocked.
4
+ */
5
+ import { type ToolDefinition } from '@opencode-ai/plugin/tool';
6
+ /**
7
+ * Arguments for the update_task_status tool
8
+ */
9
+ export interface UpdateTaskStatusArgs {
10
+ task_id: string;
11
+ status: string;
12
+ working_directory?: string;
13
+ }
14
+ /**
15
+ * Result from executing update_task_status
16
+ */
17
+ export interface UpdateTaskStatusResult {
18
+ success: boolean;
19
+ message: string;
20
+ task_id?: string;
21
+ new_status?: string;
22
+ current_phase?: number;
23
+ errors?: string[];
24
+ }
25
+ /**
26
+ * Validate that the status is one of the allowed values.
27
+ * @param status - The status to validate
28
+ * @returns Error message if invalid, undefined if valid
29
+ */
30
+ export declare function validateStatus(status: string): string | undefined;
31
+ /**
32
+ * Validate that task_id matches the required format (N.M or N.M.P).
33
+ * @param taskId - The task ID to validate
34
+ * @returns Error message if invalid, undefined if valid
35
+ */
36
+ export declare function validateTaskId(taskId: string): string | undefined;
37
+ /**
38
+ * Execute the update_task_status tool.
39
+ * Validates the task_id and status, then updates the task status in the plan.
40
+ * @param args - The update task status arguments
41
+ * @returns UpdateTaskStatusResult with success status and details
42
+ */
43
+ export declare function executeUpdateTaskStatus(args: UpdateTaskStatusArgs, fallbackDir?: string): Promise<UpdateTaskStatusResult>;
44
+ /**
45
+ * Tool definition for update_task_status
46
+ */
47
+ export declare const update_task_status: ToolDefinition;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "6.19.6",
3
+ "version": "6.19.7",
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",