opencode-swarm-plugin 0.13.2 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.beads/analysis/skill-architecture-meta-skills.md +1562 -0
- package/.beads/issues.jsonl +73 -0
- package/README.md +20 -18
- package/VERIFICATION_QUALITY_PATTERNS.md +565 -0
- package/bin/swarm.ts +5 -5
- package/dist/index.js +425 -14
- package/dist/plugin.js +426 -27
- package/docs/analysis/subagent-coordination-patterns.md +900 -0
- package/docs/analysis-socratic-planner-pattern.md +504 -0
- package/examples/commands/swarm.md +69 -7
- package/examples/plugin-wrapper-template.ts +2 -145
- package/global-skills/swarm-coordination/SKILL.md +70 -20
- package/global-skills/swarm-coordination/references/coordinator-patterns.md +1 -1
- package/package.json +1 -1
- package/src/index.ts +0 -2
- package/src/learning.integration.test.ts +310 -0
- package/src/learning.ts +198 -0
- package/src/skills.test.ts +194 -0
- package/src/skills.ts +184 -15
- package/src/swarm.integration.test.ts +4 -4
- package/src/swarm.ts +496 -19
- package/workflow-integration-analysis.md +876 -0
package/src/swarm.ts
CHANGED
|
@@ -629,7 +629,7 @@ You MUST keep your bead updated as you work:
|
|
|
629
629
|
|
|
630
630
|
**Never work silently.** Your bead status is how the swarm tracks progress.
|
|
631
631
|
|
|
632
|
-
## MANDATORY:
|
|
632
|
+
## MANDATORY: Swarm Mail Communication
|
|
633
633
|
|
|
634
634
|
You MUST communicate with other agents:
|
|
635
635
|
|
|
@@ -638,9 +638,9 @@ You MUST communicate with other agents:
|
|
|
638
638
|
3. **Announce blockers** immediately - don't spin trying to fix alone
|
|
639
639
|
4. **Coordinate on shared concerns** - if you see something affecting other agents, say so
|
|
640
640
|
|
|
641
|
-
Use
|
|
641
|
+
Use Swarm Mail for all communication:
|
|
642
642
|
\`\`\`
|
|
643
|
-
|
|
643
|
+
swarmmail_send(
|
|
644
644
|
to: ["coordinator" or specific agent],
|
|
645
645
|
subject: "Brief subject",
|
|
646
646
|
body: "Message content",
|
|
@@ -652,7 +652,7 @@ agentmail_send(
|
|
|
652
652
|
|
|
653
653
|
1. **Start**: Your bead is already marked in_progress
|
|
654
654
|
2. **Progress**: Use swarm_progress to report status updates
|
|
655
|
-
3. **Blocked**: Report immediately via
|
|
655
|
+
3. **Blocked**: Report immediately via Swarm Mail - don't spin
|
|
656
656
|
4. **Complete**: Use swarm_complete when done - it handles:
|
|
657
657
|
- Closing your bead with a summary
|
|
658
658
|
- Releasing file reservations
|
|
@@ -674,15 +674,15 @@ Before writing code:
|
|
|
674
674
|
1. **Read the files** you're assigned to understand current state
|
|
675
675
|
2. **Plan your approach** - what changes, in what order?
|
|
676
676
|
3. **Identify risks** - what could go wrong? What dependencies?
|
|
677
|
-
4. **Communicate your plan** via
|
|
677
|
+
4. **Communicate your plan** via Swarm Mail if non-trivial
|
|
678
678
|
|
|
679
679
|
Begin work on your subtask now.`;
|
|
680
680
|
|
|
681
681
|
/**
|
|
682
|
-
* Streamlined subtask prompt (V2) -
|
|
682
|
+
* Streamlined subtask prompt (V2) - uses Swarm Mail and beads
|
|
683
683
|
*
|
|
684
684
|
* This is a cleaner version of SUBTASK_PROMPT that's easier to parse.
|
|
685
|
-
* Agents MUST use
|
|
685
|
+
* Agents MUST use Swarm Mail for communication and beads for tracking.
|
|
686
686
|
*
|
|
687
687
|
* Supports {error_context} placeholder for retry prompts.
|
|
688
688
|
*/
|
|
@@ -1858,6 +1858,237 @@ interface UbsScanResult {
|
|
|
1858
1858
|
};
|
|
1859
1859
|
}
|
|
1860
1860
|
|
|
1861
|
+
// ============================================================================
|
|
1862
|
+
// Verification Gate
|
|
1863
|
+
// ============================================================================
|
|
1864
|
+
|
|
1865
|
+
/**
|
|
1866
|
+
* Verification Gate result - tracks each verification step
|
|
1867
|
+
*
|
|
1868
|
+
* Based on the Gate Function from superpowers:
|
|
1869
|
+
* 1. IDENTIFY: What command proves this claim?
|
|
1870
|
+
* 2. RUN: Execute the FULL command (fresh, complete)
|
|
1871
|
+
* 3. READ: Full output, check exit code, count failures
|
|
1872
|
+
* 4. VERIFY: Does output confirm the claim?
|
|
1873
|
+
* 5. ONLY THEN: Make the claim
|
|
1874
|
+
*/
|
|
1875
|
+
interface VerificationStep {
|
|
1876
|
+
name: string;
|
|
1877
|
+
command: string;
|
|
1878
|
+
passed: boolean;
|
|
1879
|
+
exitCode: number;
|
|
1880
|
+
output?: string;
|
|
1881
|
+
error?: string;
|
|
1882
|
+
skipped?: boolean;
|
|
1883
|
+
skipReason?: string;
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
interface VerificationGateResult {
|
|
1887
|
+
passed: boolean;
|
|
1888
|
+
steps: VerificationStep[];
|
|
1889
|
+
summary: string;
|
|
1890
|
+
blockers: string[];
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
/**
|
|
1894
|
+
* Run typecheck verification
|
|
1895
|
+
*
|
|
1896
|
+
* Attempts to run TypeScript type checking on the project.
|
|
1897
|
+
* Falls back gracefully if tsc is not available.
|
|
1898
|
+
*/
|
|
1899
|
+
async function runTypecheckVerification(): Promise<VerificationStep> {
|
|
1900
|
+
const step: VerificationStep = {
|
|
1901
|
+
name: "typecheck",
|
|
1902
|
+
command: "tsc --noEmit",
|
|
1903
|
+
passed: false,
|
|
1904
|
+
exitCode: -1,
|
|
1905
|
+
};
|
|
1906
|
+
|
|
1907
|
+
try {
|
|
1908
|
+
// Check if tsconfig.json exists in current directory
|
|
1909
|
+
const tsconfigExists = await Bun.file("tsconfig.json").exists();
|
|
1910
|
+
if (!tsconfigExists) {
|
|
1911
|
+
step.skipped = true;
|
|
1912
|
+
step.skipReason = "No tsconfig.json found";
|
|
1913
|
+
step.passed = true; // Don't block if no TypeScript
|
|
1914
|
+
return step;
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
const result = await Bun.$`tsc --noEmit`.quiet().nothrow();
|
|
1918
|
+
step.exitCode = result.exitCode;
|
|
1919
|
+
step.passed = result.exitCode === 0;
|
|
1920
|
+
|
|
1921
|
+
if (!step.passed) {
|
|
1922
|
+
step.error = result.stderr.toString().slice(0, 1000); // Truncate for context
|
|
1923
|
+
step.output = result.stdout.toString().slice(0, 1000);
|
|
1924
|
+
}
|
|
1925
|
+
} catch (error) {
|
|
1926
|
+
step.skipped = true;
|
|
1927
|
+
step.skipReason = `tsc not available: ${error instanceof Error ? error.message : String(error)}`;
|
|
1928
|
+
step.passed = true; // Don't block if tsc unavailable
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
return step;
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
/**
|
|
1935
|
+
* Run test verification for specific files
|
|
1936
|
+
*
|
|
1937
|
+
* Attempts to find and run tests related to the touched files.
|
|
1938
|
+
* Uses common test patterns (*.test.ts, *.spec.ts, __tests__/).
|
|
1939
|
+
*/
|
|
1940
|
+
async function runTestVerification(
|
|
1941
|
+
filesTouched: string[],
|
|
1942
|
+
): Promise<VerificationStep> {
|
|
1943
|
+
const step: VerificationStep = {
|
|
1944
|
+
name: "tests",
|
|
1945
|
+
command: "bun test <related-files>",
|
|
1946
|
+
passed: false,
|
|
1947
|
+
exitCode: -1,
|
|
1948
|
+
};
|
|
1949
|
+
|
|
1950
|
+
if (filesTouched.length === 0) {
|
|
1951
|
+
step.skipped = true;
|
|
1952
|
+
step.skipReason = "No files touched";
|
|
1953
|
+
step.passed = true;
|
|
1954
|
+
return step;
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
// Find test files related to touched files
|
|
1958
|
+
const testPatterns: string[] = [];
|
|
1959
|
+
for (const file of filesTouched) {
|
|
1960
|
+
// Skip if already a test file
|
|
1961
|
+
if (file.includes(".test.") || file.includes(".spec.")) {
|
|
1962
|
+
testPatterns.push(file);
|
|
1963
|
+
continue;
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
// Look for corresponding test file
|
|
1967
|
+
const baseName = file.replace(/\.(ts|tsx|js|jsx)$/, "");
|
|
1968
|
+
testPatterns.push(`${baseName}.test.ts`);
|
|
1969
|
+
testPatterns.push(`${baseName}.test.tsx`);
|
|
1970
|
+
testPatterns.push(`${baseName}.spec.ts`);
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
// Check if any test files exist
|
|
1974
|
+
const existingTests: string[] = [];
|
|
1975
|
+
for (const pattern of testPatterns) {
|
|
1976
|
+
try {
|
|
1977
|
+
const exists = await Bun.file(pattern).exists();
|
|
1978
|
+
if (exists) {
|
|
1979
|
+
existingTests.push(pattern);
|
|
1980
|
+
}
|
|
1981
|
+
} catch {
|
|
1982
|
+
// File doesn't exist, skip
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
if (existingTests.length === 0) {
|
|
1987
|
+
step.skipped = true;
|
|
1988
|
+
step.skipReason = "No related test files found";
|
|
1989
|
+
step.passed = true;
|
|
1990
|
+
return step;
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
try {
|
|
1994
|
+
step.command = `bun test ${existingTests.join(" ")}`;
|
|
1995
|
+
const result = await Bun.$`bun test ${existingTests}`.quiet().nothrow();
|
|
1996
|
+
step.exitCode = result.exitCode;
|
|
1997
|
+
step.passed = result.exitCode === 0;
|
|
1998
|
+
|
|
1999
|
+
if (!step.passed) {
|
|
2000
|
+
step.error = result.stderr.toString().slice(0, 1000);
|
|
2001
|
+
step.output = result.stdout.toString().slice(0, 1000);
|
|
2002
|
+
}
|
|
2003
|
+
} catch (error) {
|
|
2004
|
+
step.skipped = true;
|
|
2005
|
+
step.skipReason = `Test runner failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
2006
|
+
step.passed = true; // Don't block if test runner unavailable
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
return step;
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
/**
|
|
2013
|
+
* Run the full Verification Gate
|
|
2014
|
+
*
|
|
2015
|
+
* Implements the Gate Function (IDENTIFY → RUN → READ → VERIFY → CLAIM):
|
|
2016
|
+
* 1. UBS scan (already exists)
|
|
2017
|
+
* 2. Typecheck
|
|
2018
|
+
* 3. Tests for touched files
|
|
2019
|
+
*
|
|
2020
|
+
* All steps must pass (or be skipped with valid reason) to proceed.
|
|
2021
|
+
*/
|
|
2022
|
+
async function runVerificationGate(
|
|
2023
|
+
filesTouched: string[],
|
|
2024
|
+
skipUbs: boolean = false,
|
|
2025
|
+
): Promise<VerificationGateResult> {
|
|
2026
|
+
const steps: VerificationStep[] = [];
|
|
2027
|
+
const blockers: string[] = [];
|
|
2028
|
+
|
|
2029
|
+
// Step 1: UBS scan
|
|
2030
|
+
if (!skipUbs && filesTouched.length > 0) {
|
|
2031
|
+
const ubsResult = await runUbsScan(filesTouched);
|
|
2032
|
+
if (ubsResult) {
|
|
2033
|
+
const ubsStep: VerificationStep = {
|
|
2034
|
+
name: "ubs_scan",
|
|
2035
|
+
command: `ubs scan ${filesTouched.join(" ")}`,
|
|
2036
|
+
passed: ubsResult.summary.critical === 0,
|
|
2037
|
+
exitCode: ubsResult.exitCode,
|
|
2038
|
+
};
|
|
2039
|
+
|
|
2040
|
+
if (!ubsStep.passed) {
|
|
2041
|
+
ubsStep.error = `Found ${ubsResult.summary.critical} critical bugs`;
|
|
2042
|
+
blockers.push(`UBS: ${ubsResult.summary.critical} critical bugs found`);
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
steps.push(ubsStep);
|
|
2046
|
+
} else {
|
|
2047
|
+
steps.push({
|
|
2048
|
+
name: "ubs_scan",
|
|
2049
|
+
command: "ubs scan",
|
|
2050
|
+
passed: true,
|
|
2051
|
+
exitCode: 0,
|
|
2052
|
+
skipped: true,
|
|
2053
|
+
skipReason: "UBS not available",
|
|
2054
|
+
});
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
// Step 2: Typecheck
|
|
2059
|
+
const typecheckStep = await runTypecheckVerification();
|
|
2060
|
+
steps.push(typecheckStep);
|
|
2061
|
+
if (!typecheckStep.passed && !typecheckStep.skipped) {
|
|
2062
|
+
blockers.push(
|
|
2063
|
+
`Typecheck: ${typecheckStep.error?.slice(0, 100) || "failed"}`,
|
|
2064
|
+
);
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
// Step 3: Tests
|
|
2068
|
+
const testStep = await runTestVerification(filesTouched);
|
|
2069
|
+
steps.push(testStep);
|
|
2070
|
+
if (!testStep.passed && !testStep.skipped) {
|
|
2071
|
+
blockers.push(`Tests: ${testStep.error?.slice(0, 100) || "failed"}`);
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
// Build summary
|
|
2075
|
+
const passedCount = steps.filter((s) => s.passed).length;
|
|
2076
|
+
const skippedCount = steps.filter((s) => s.skipped).length;
|
|
2077
|
+
const failedCount = steps.filter((s) => !s.passed && !s.skipped).length;
|
|
2078
|
+
|
|
2079
|
+
const summary =
|
|
2080
|
+
failedCount === 0
|
|
2081
|
+
? `Verification passed: ${passedCount} checks passed, ${skippedCount} skipped`
|
|
2082
|
+
: `Verification FAILED: ${failedCount} checks failed, ${passedCount} passed, ${skippedCount} skipped`;
|
|
2083
|
+
|
|
2084
|
+
return {
|
|
2085
|
+
passed: failedCount === 0,
|
|
2086
|
+
steps,
|
|
2087
|
+
summary,
|
|
2088
|
+
blockers,
|
|
2089
|
+
};
|
|
2090
|
+
}
|
|
2091
|
+
|
|
1861
2092
|
/**
|
|
1862
2093
|
* Run UBS scan on files before completion
|
|
1863
2094
|
*
|
|
@@ -2025,12 +2256,18 @@ export const swarm_broadcast = tool({
|
|
|
2025
2256
|
/**
|
|
2026
2257
|
* Mark a subtask as complete
|
|
2027
2258
|
*
|
|
2259
|
+
* Implements the Verification Gate (from superpowers):
|
|
2260
|
+
* 1. IDENTIFY: What commands prove this claim?
|
|
2261
|
+
* 2. RUN: Execute verification (UBS, typecheck, tests)
|
|
2262
|
+
* 3. READ: Check exit codes and output
|
|
2263
|
+
* 4. VERIFY: All checks must pass
|
|
2264
|
+
* 5. ONLY THEN: Close the bead
|
|
2265
|
+
*
|
|
2028
2266
|
* Closes bead, releases reservations, notifies coordinator.
|
|
2029
|
-
* Optionally runs UBS scan on modified files before completion.
|
|
2030
2267
|
*/
|
|
2031
2268
|
export const swarm_complete = tool({
|
|
2032
2269
|
description:
|
|
2033
|
-
"Mark subtask complete
|
|
2270
|
+
"Mark subtask complete with Verification Gate. Runs UBS scan, typecheck, and tests before allowing completion.",
|
|
2034
2271
|
args: {
|
|
2035
2272
|
project_key: tool.schema.string().describe("Project path"),
|
|
2036
2273
|
agent_name: tool.schema.string().describe("Your Agent Mail name"),
|
|
@@ -2043,18 +2280,62 @@ export const swarm_complete = tool({
|
|
|
2043
2280
|
files_touched: tool.schema
|
|
2044
2281
|
.array(tool.schema.string())
|
|
2045
2282
|
.optional()
|
|
2046
|
-
.describe("Files modified - will be
|
|
2283
|
+
.describe("Files modified - will be verified (UBS, typecheck, tests)"),
|
|
2047
2284
|
skip_ubs_scan: tool.schema
|
|
2048
2285
|
.boolean()
|
|
2049
2286
|
.optional()
|
|
2050
2287
|
.describe("Skip UBS bug scan (default: false)"),
|
|
2288
|
+
skip_verification: tool.schema
|
|
2289
|
+
.boolean()
|
|
2290
|
+
.optional()
|
|
2291
|
+
.describe(
|
|
2292
|
+
"Skip ALL verification (UBS, typecheck, tests). Use sparingly! (default: false)",
|
|
2293
|
+
),
|
|
2051
2294
|
},
|
|
2052
2295
|
async execute(args) {
|
|
2053
|
-
// Run
|
|
2296
|
+
// Run Verification Gate unless explicitly skipped
|
|
2297
|
+
let verificationResult: VerificationGateResult | null = null;
|
|
2298
|
+
|
|
2299
|
+
if (!args.skip_verification && args.files_touched?.length) {
|
|
2300
|
+
verificationResult = await runVerificationGate(
|
|
2301
|
+
args.files_touched,
|
|
2302
|
+
args.skip_ubs_scan ?? false,
|
|
2303
|
+
);
|
|
2304
|
+
|
|
2305
|
+
// Block completion if verification failed
|
|
2306
|
+
if (!verificationResult.passed) {
|
|
2307
|
+
return JSON.stringify(
|
|
2308
|
+
{
|
|
2309
|
+
success: false,
|
|
2310
|
+
error: "Verification Gate FAILED - fix issues before completing",
|
|
2311
|
+
verification: {
|
|
2312
|
+
passed: false,
|
|
2313
|
+
summary: verificationResult.summary,
|
|
2314
|
+
blockers: verificationResult.blockers,
|
|
2315
|
+
steps: verificationResult.steps.map((s) => ({
|
|
2316
|
+
name: s.name,
|
|
2317
|
+
passed: s.passed,
|
|
2318
|
+
skipped: s.skipped,
|
|
2319
|
+
skipReason: s.skipReason,
|
|
2320
|
+
error: s.error?.slice(0, 200),
|
|
2321
|
+
})),
|
|
2322
|
+
},
|
|
2323
|
+
hint: "Fix the failing checks and try again. Use skip_verification=true only as last resort.",
|
|
2324
|
+
gate_function:
|
|
2325
|
+
"IDENTIFY → RUN → READ → VERIFY → CLAIM (you are at VERIFY, claim blocked)",
|
|
2326
|
+
},
|
|
2327
|
+
null,
|
|
2328
|
+
2,
|
|
2329
|
+
);
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
// Legacy UBS-only path for backward compatibility (when no files_touched)
|
|
2054
2334
|
let ubsResult: UbsScanResult | null = null;
|
|
2055
2335
|
if (
|
|
2056
|
-
args.
|
|
2057
|
-
|
|
2336
|
+
!args.skip_verification &&
|
|
2337
|
+
!verificationResult &&
|
|
2338
|
+
args.files_touched?.length &&
|
|
2058
2339
|
!args.skip_ubs_scan
|
|
2059
2340
|
) {
|
|
2060
2341
|
ubsResult = await runUbsScan(args.files_touched);
|
|
@@ -2176,6 +2457,20 @@ export const swarm_complete = tool({
|
|
|
2176
2457
|
closed: true,
|
|
2177
2458
|
reservations_released: true,
|
|
2178
2459
|
message_sent: true,
|
|
2460
|
+
verification_gate: verificationResult
|
|
2461
|
+
? {
|
|
2462
|
+
passed: true,
|
|
2463
|
+
summary: verificationResult.summary,
|
|
2464
|
+
steps: verificationResult.steps.map((s) => ({
|
|
2465
|
+
name: s.name,
|
|
2466
|
+
passed: s.passed,
|
|
2467
|
+
skipped: s.skipped,
|
|
2468
|
+
skipReason: s.skipReason,
|
|
2469
|
+
})),
|
|
2470
|
+
}
|
|
2471
|
+
: args.skip_verification
|
|
2472
|
+
? { skipped: true, reason: "skip_verification=true" }
|
|
2473
|
+
: { skipped: true, reason: "no files_touched provided" },
|
|
2179
2474
|
ubs_scan: ubsResult
|
|
2180
2475
|
? {
|
|
2181
2476
|
ran: true,
|
|
@@ -2183,12 +2478,14 @@ export const swarm_complete = tool({
|
|
|
2183
2478
|
summary: ubsResult.summary,
|
|
2184
2479
|
warnings: ubsResult.bugs.filter((b) => b.severity !== "critical"),
|
|
2185
2480
|
}
|
|
2186
|
-
:
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
:
|
|
2191
|
-
|
|
2481
|
+
: verificationResult
|
|
2482
|
+
? { ran: true, included_in_verification_gate: true }
|
|
2483
|
+
: {
|
|
2484
|
+
ran: false,
|
|
2485
|
+
reason: args.skip_ubs_scan
|
|
2486
|
+
? "skipped"
|
|
2487
|
+
: "no files or ubs unavailable",
|
|
2488
|
+
},
|
|
2192
2489
|
learning_prompt: `## Reflection
|
|
2193
2490
|
|
|
2194
2491
|
Did you learn anything reusable during this subtask? Consider:
|
|
@@ -3157,3 +3454,183 @@ export const swarmTools = {
|
|
|
3157
3454
|
swarm_get_error_context: swarm_get_error_context,
|
|
3158
3455
|
swarm_resolve_error: swarm_resolve_error,
|
|
3159
3456
|
};
|
|
3457
|
+
|
|
3458
|
+
// ============================================================================
|
|
3459
|
+
// 3-Strike Detection
|
|
3460
|
+
// ============================================================================
|
|
3461
|
+
|
|
3462
|
+
/**
|
|
3463
|
+
* Global strike storage for tracking consecutive fix failures
|
|
3464
|
+
*/
|
|
3465
|
+
import {
|
|
3466
|
+
InMemoryStrikeStorage,
|
|
3467
|
+
addStrike,
|
|
3468
|
+
getStrikes,
|
|
3469
|
+
isStrikedOut,
|
|
3470
|
+
getArchitecturePrompt,
|
|
3471
|
+
clearStrikes,
|
|
3472
|
+
type StrikeStorage,
|
|
3473
|
+
} from "./learning";
|
|
3474
|
+
|
|
3475
|
+
const globalStrikeStorage: StrikeStorage = new InMemoryStrikeStorage();
|
|
3476
|
+
|
|
3477
|
+
/**
|
|
3478
|
+
* Check if a bead has struck out (3 consecutive failures)
|
|
3479
|
+
*
|
|
3480
|
+
* The 3-Strike Rule:
|
|
3481
|
+
* IF 3+ fixes have failed:
|
|
3482
|
+
* STOP → Question the architecture
|
|
3483
|
+
* DON'T attempt Fix #4
|
|
3484
|
+
* Discuss with human partner
|
|
3485
|
+
*
|
|
3486
|
+
* This is NOT a failed hypothesis.
|
|
3487
|
+
* This is a WRONG ARCHITECTURE.
|
|
3488
|
+
*
|
|
3489
|
+
* Use this tool to:
|
|
3490
|
+
* - Check strike count before attempting a fix
|
|
3491
|
+
* - Get architecture review prompt if struck out
|
|
3492
|
+
* - Record a strike when a fix fails
|
|
3493
|
+
* - Clear strikes when a fix succeeds
|
|
3494
|
+
*/
|
|
3495
|
+
export const swarm_check_strikes = tool({
|
|
3496
|
+
description:
|
|
3497
|
+
"Check 3-strike status for a bead. Records failures, detects architectural problems, generates architecture review prompts.",
|
|
3498
|
+
args: {
|
|
3499
|
+
bead_id: tool.schema.string().describe("Bead ID to check"),
|
|
3500
|
+
action: tool.schema
|
|
3501
|
+
.enum(["check", "add_strike", "clear", "get_prompt"])
|
|
3502
|
+
.describe(
|
|
3503
|
+
"Action: check count, add strike, clear strikes, or get prompt",
|
|
3504
|
+
),
|
|
3505
|
+
attempt: tool.schema
|
|
3506
|
+
.string()
|
|
3507
|
+
.optional()
|
|
3508
|
+
.describe("Description of fix attempt (required for add_strike)"),
|
|
3509
|
+
reason: tool.schema
|
|
3510
|
+
.string()
|
|
3511
|
+
.optional()
|
|
3512
|
+
.describe("Why the fix failed (required for add_strike)"),
|
|
3513
|
+
},
|
|
3514
|
+
async execute(args) {
|
|
3515
|
+
switch (args.action) {
|
|
3516
|
+
case "check": {
|
|
3517
|
+
const count = await getStrikes(args.bead_id, globalStrikeStorage);
|
|
3518
|
+
const strikedOut = await isStrikedOut(
|
|
3519
|
+
args.bead_id,
|
|
3520
|
+
globalStrikeStorage,
|
|
3521
|
+
);
|
|
3522
|
+
|
|
3523
|
+
return JSON.stringify(
|
|
3524
|
+
{
|
|
3525
|
+
bead_id: args.bead_id,
|
|
3526
|
+
strike_count: count,
|
|
3527
|
+
is_striked_out: strikedOut,
|
|
3528
|
+
message: strikedOut
|
|
3529
|
+
? "⚠️ STRUCK OUT: 3 strikes reached. Use get_prompt action for architecture review."
|
|
3530
|
+
: count === 0
|
|
3531
|
+
? "No strikes. Clear to proceed."
|
|
3532
|
+
: `${count} strike${count > 1 ? "s" : ""}. ${3 - count} remaining before architecture review required.`,
|
|
3533
|
+
next_action: strikedOut
|
|
3534
|
+
? "Call with action=get_prompt to get architecture review questions"
|
|
3535
|
+
: "Continue with fix attempt",
|
|
3536
|
+
},
|
|
3537
|
+
null,
|
|
3538
|
+
2,
|
|
3539
|
+
);
|
|
3540
|
+
}
|
|
3541
|
+
|
|
3542
|
+
case "add_strike": {
|
|
3543
|
+
if (!args.attempt || !args.reason) {
|
|
3544
|
+
return JSON.stringify(
|
|
3545
|
+
{
|
|
3546
|
+
error: "add_strike requires 'attempt' and 'reason' parameters",
|
|
3547
|
+
},
|
|
3548
|
+
null,
|
|
3549
|
+
2,
|
|
3550
|
+
);
|
|
3551
|
+
}
|
|
3552
|
+
|
|
3553
|
+
const record = await addStrike(
|
|
3554
|
+
args.bead_id,
|
|
3555
|
+
args.attempt,
|
|
3556
|
+
args.reason,
|
|
3557
|
+
globalStrikeStorage,
|
|
3558
|
+
);
|
|
3559
|
+
|
|
3560
|
+
const strikedOut = record.strike_count >= 3;
|
|
3561
|
+
|
|
3562
|
+
return JSON.stringify(
|
|
3563
|
+
{
|
|
3564
|
+
bead_id: args.bead_id,
|
|
3565
|
+
strike_count: record.strike_count,
|
|
3566
|
+
is_striked_out: strikedOut,
|
|
3567
|
+
failures: record.failures,
|
|
3568
|
+
message: strikedOut
|
|
3569
|
+
? "⚠️ STRUCK OUT: 3 strikes reached. STOP and question the architecture."
|
|
3570
|
+
: `Strike ${record.strike_count} recorded. ${3 - record.strike_count} remaining.`,
|
|
3571
|
+
warning: strikedOut
|
|
3572
|
+
? "DO NOT attempt Fix #4. Call with action=get_prompt for architecture review."
|
|
3573
|
+
: undefined,
|
|
3574
|
+
},
|
|
3575
|
+
null,
|
|
3576
|
+
2,
|
|
3577
|
+
);
|
|
3578
|
+
}
|
|
3579
|
+
|
|
3580
|
+
case "clear": {
|
|
3581
|
+
await clearStrikes(args.bead_id, globalStrikeStorage);
|
|
3582
|
+
|
|
3583
|
+
return JSON.stringify(
|
|
3584
|
+
{
|
|
3585
|
+
bead_id: args.bead_id,
|
|
3586
|
+
strike_count: 0,
|
|
3587
|
+
is_striked_out: false,
|
|
3588
|
+
message: "Strikes cleared. Fresh start.",
|
|
3589
|
+
},
|
|
3590
|
+
null,
|
|
3591
|
+
2,
|
|
3592
|
+
);
|
|
3593
|
+
}
|
|
3594
|
+
|
|
3595
|
+
case "get_prompt": {
|
|
3596
|
+
const prompt = await getArchitecturePrompt(
|
|
3597
|
+
args.bead_id,
|
|
3598
|
+
globalStrikeStorage,
|
|
3599
|
+
);
|
|
3600
|
+
|
|
3601
|
+
if (!prompt) {
|
|
3602
|
+
return JSON.stringify(
|
|
3603
|
+
{
|
|
3604
|
+
bead_id: args.bead_id,
|
|
3605
|
+
has_prompt: false,
|
|
3606
|
+
message: "No architecture prompt (not struck out yet)",
|
|
3607
|
+
},
|
|
3608
|
+
null,
|
|
3609
|
+
2,
|
|
3610
|
+
);
|
|
3611
|
+
}
|
|
3612
|
+
|
|
3613
|
+
return JSON.stringify(
|
|
3614
|
+
{
|
|
3615
|
+
bead_id: args.bead_id,
|
|
3616
|
+
has_prompt: true,
|
|
3617
|
+
architecture_review_prompt: prompt,
|
|
3618
|
+
message:
|
|
3619
|
+
"Architecture review required. Present this prompt to the human partner.",
|
|
3620
|
+
},
|
|
3621
|
+
null,
|
|
3622
|
+
2,
|
|
3623
|
+
);
|
|
3624
|
+
}
|
|
3625
|
+
|
|
3626
|
+
default:
|
|
3627
|
+
return JSON.stringify(
|
|
3628
|
+
{
|
|
3629
|
+
error: `Unknown action: ${args.action}`,
|
|
3630
|
+
},
|
|
3631
|
+
null,
|
|
3632
|
+
2,
|
|
3633
|
+
);
|
|
3634
|
+
}
|
|
3635
|
+
},
|
|
3636
|
+
});
|