role-os 2.2.1 → 2.3.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/CHANGELOG.md +23 -0
- package/README.md +48 -13
- package/bin/roleos.mjs +11 -0
- package/package.json +2 -2
- package/src/artifacts.mjs +70 -0
- package/src/dispatch.mjs +8 -4
- package/src/knowledge/analyze-artifact-evidence.mjs +420 -0
- package/src/knowledge/attach-bundle-to-evidence.mjs +62 -0
- package/src/knowledge/attach-bundle-to-packet.mjs +42 -0
- package/src/knowledge/fallback-policy.mjs +79 -0
- package/src/knowledge/index.mjs +14 -0
- package/src/knowledge/render-knowledge-block.mjs +215 -0
- package/src/knowledge/resolve-overlay.mjs +66 -0
- package/src/knowledge/retrieve-for-dispatch.mjs +150 -0
- package/src/mission-run.mjs +114 -2
- package/src/mission.mjs +130 -0
- package/src/packs.mjs +37 -0
- package/src/route.mjs +51 -0
- package/src/run-cmd.mjs +4 -1
- package/src/run.mjs +42 -1
- package/src/swarm/build-gate.mjs +127 -0
- package/src/swarm/domain-detect.mjs +230 -0
- package/src/swarm/persist-bridge.mjs +174 -0
- package/src/swarm-cmd.mjs +424 -0
- package/src/tool-profiles.mjs +9 -0
package/src/mission.mjs
CHANGED
|
@@ -327,6 +327,132 @@ export const MISSIONS = {
|
|
|
327
327
|
dispatchDefaults: { model: "sonnet", maxTurns: 25, maxBudgetUsd: 3.0 },
|
|
328
328
|
trialEvidence: "New mission — no trial evidence yet. Architecture designed 2026-03-27.",
|
|
329
329
|
},
|
|
330
|
+
|
|
331
|
+
// ── Dogfood Swarm (Multi-Pass Health + Feature Convergence) ─────────────────
|
|
332
|
+
"dogfood-swarm": {
|
|
333
|
+
name: "Dogfood Swarm",
|
|
334
|
+
description: "Three-stage health pass (bug/security → proactive → humanization) then iterative feature pass with exclusive file ownership, build gates, and user checkpoints. Moves a repo from 'works' to 'production-ready.' Domain agent count scales with repo structure.",
|
|
335
|
+
pack: "swarm",
|
|
336
|
+
entryPath: "Generate swarm manifest → Save-point tag → Health-A wave (5 agents parallel, loop until 0 CRITICAL+HIGH) → Health-B wave (proactive, user review) → Health-C wave (humanization, loop) → Feature wave (user approval gate, loop) → Synthesizer → Critic verdict",
|
|
337
|
+
roleChain: [
|
|
338
|
+
"Swarm Coordinator", // ×1 — orchestrates all stages, enforces gates
|
|
339
|
+
"Swarm Backend Agent", // ×1 — exclusive ownership of backend files
|
|
340
|
+
"Swarm Bridge Agent", // ×1 — exclusive ownership of bridge/integration files
|
|
341
|
+
"Swarm Tests Agent", // ×1 — exclusive ownership of test files
|
|
342
|
+
"Swarm Infra Agent", // ×1 — exclusive ownership of CI/config/docs
|
|
343
|
+
"Swarm Frontend Agent", // ×1 — exclusive ownership of frontend files
|
|
344
|
+
"Swarm Synthesizer", // ×1 — final verification report
|
|
345
|
+
"Critic Reviewer", // ×1 — final acceptance
|
|
346
|
+
],
|
|
347
|
+
// Dynamic dispatch contract:
|
|
348
|
+
// swarm-manifest.json defines domains[] with non-overlapping file paths.
|
|
349
|
+
// Each domain maps to one of the 5 domain agent roles.
|
|
350
|
+
// Domains are instantiated per stage (health-a, health-b, health-c, feature).
|
|
351
|
+
// Coordinator gates between stages evaluate exit conditions.
|
|
352
|
+
dynamicDispatch: {
|
|
353
|
+
scalingRoles: ["Swarm Backend Agent", "Swarm Bridge Agent", "Swarm Tests Agent", "Swarm Infra Agent", "Swarm Frontend Agent"],
|
|
354
|
+
manifestSource: "swarm-manifest.json",
|
|
355
|
+
domainAgentPer: "domains",
|
|
356
|
+
coordinatorAfter: "each-stage",
|
|
357
|
+
synthesisAfter: "all-stages",
|
|
358
|
+
},
|
|
359
|
+
// Wave loops — iterative convergence (new primitive, unique to swarm)
|
|
360
|
+
waveLoops: [
|
|
361
|
+
{
|
|
362
|
+
stage: "health-a",
|
|
363
|
+
lens: "Bug/Security Fix — audit for bugs, security, quality, types, test coverage, doc accuracy",
|
|
364
|
+
exitCondition: "0 CRITICAL + 0 HIGH findings open",
|
|
365
|
+
maxIterations: 4,
|
|
366
|
+
buildGate: true,
|
|
367
|
+
userApproval: false,
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
stage: "health-b",
|
|
371
|
+
lens: "Proactive Health — defensive coding, observability, graceful degradation, future-proofing",
|
|
372
|
+
exitCondition: "user approves proactive findings",
|
|
373
|
+
maxIterations: 2,
|
|
374
|
+
buildGate: true,
|
|
375
|
+
userApproval: true,
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
stage: "health-c",
|
|
379
|
+
lens: "Humanization — error messages that help users fix problems, reconnection/retry feedback, responsive layouts, loading states, state persistence, accessibility",
|
|
380
|
+
exitCondition: "0 CRITICAL + 0 HIGH humanization findings open",
|
|
381
|
+
maxIterations: 3,
|
|
382
|
+
buildGate: true,
|
|
383
|
+
userApproval: false,
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
stage: "feature",
|
|
387
|
+
lens: "Feature Audit — missing capabilities, feature gaps, UX, production readiness",
|
|
388
|
+
exitCondition: "user approves feature audit + 0 CRITICAL feature gaps",
|
|
389
|
+
maxIterations: 5,
|
|
390
|
+
buildGate: true,
|
|
391
|
+
userApproval: true,
|
|
392
|
+
},
|
|
393
|
+
],
|
|
394
|
+
// Exclusive ownership — domain file boundaries (new primitive, unique to swarm)
|
|
395
|
+
exclusiveOwnership: {
|
|
396
|
+
mode: "strict",
|
|
397
|
+
manifestSource: "swarm-manifest.json",
|
|
398
|
+
maxAgentsPerWave: 5,
|
|
399
|
+
},
|
|
400
|
+
artifactFlow: [
|
|
401
|
+
// Health-A: Bug/Security audit + remediate (loops)
|
|
402
|
+
{ role: "Swarm Backend Agent", produces: "wave-report", consumedBy: "Swarm Coordinator", stage: "health-a" },
|
|
403
|
+
{ role: "Swarm Bridge Agent", produces: "wave-report", consumedBy: "Swarm Coordinator", stage: "health-a" },
|
|
404
|
+
{ role: "Swarm Tests Agent", produces: "wave-report", consumedBy: "Swarm Coordinator", stage: "health-a" },
|
|
405
|
+
{ role: "Swarm Infra Agent", produces: "wave-report", consumedBy: "Swarm Coordinator", stage: "health-a" },
|
|
406
|
+
{ role: "Swarm Frontend Agent", produces: "wave-report", consumedBy: "Swarm Coordinator", stage: "health-a" },
|
|
407
|
+
{ role: "Swarm Coordinator", produces: "swarm-gate", consumedBy: "Swarm Backend Agent", stage: "health-a" },
|
|
408
|
+
|
|
409
|
+
// Health-B: Proactive hardening (user review gate)
|
|
410
|
+
{ role: "Swarm Backend Agent", produces: "wave-report", consumedBy: "Swarm Coordinator", stage: "health-b" },
|
|
411
|
+
{ role: "Swarm Bridge Agent", produces: "wave-report", consumedBy: "Swarm Coordinator", stage: "health-b" },
|
|
412
|
+
{ role: "Swarm Tests Agent", produces: "wave-report", consumedBy: "Swarm Coordinator", stage: "health-b" },
|
|
413
|
+
{ role: "Swarm Infra Agent", produces: "wave-report", consumedBy: "Swarm Coordinator", stage: "health-b" },
|
|
414
|
+
{ role: "Swarm Frontend Agent", produces: "wave-report", consumedBy: "Swarm Coordinator", stage: "health-b" },
|
|
415
|
+
{ role: "Swarm Coordinator", produces: "swarm-gate", consumedBy: "Swarm Backend Agent", stage: "health-b" },
|
|
416
|
+
|
|
417
|
+
// Health-C: Humanization (UX emphasis, loops)
|
|
418
|
+
{ role: "Swarm Backend Agent", produces: "wave-report", consumedBy: "Swarm Coordinator", stage: "health-c" },
|
|
419
|
+
{ role: "Swarm Bridge Agent", produces: "wave-report", consumedBy: "Swarm Coordinator", stage: "health-c" },
|
|
420
|
+
{ role: "Swarm Tests Agent", produces: "wave-report", consumedBy: "Swarm Coordinator", stage: "health-c" },
|
|
421
|
+
{ role: "Swarm Infra Agent", produces: "wave-report", consumedBy: "Swarm Coordinator", stage: "health-c" },
|
|
422
|
+
{ role: "Swarm Frontend Agent", produces: "wave-report", consumedBy: "Swarm Coordinator", stage: "health-c" },
|
|
423
|
+
{ role: "Swarm Coordinator", produces: "swarm-gate", consumedBy: "Swarm Backend Agent", stage: "health-c" },
|
|
424
|
+
|
|
425
|
+
// Feature: Audit → user approval → execute (loops)
|
|
426
|
+
{ role: "Swarm Backend Agent", produces: "wave-report", consumedBy: "Swarm Coordinator", stage: "feature" },
|
|
427
|
+
{ role: "Swarm Bridge Agent", produces: "wave-report", consumedBy: "Swarm Coordinator", stage: "feature" },
|
|
428
|
+
{ role: "Swarm Tests Agent", produces: "wave-report", consumedBy: "Swarm Coordinator", stage: "feature" },
|
|
429
|
+
{ role: "Swarm Infra Agent", produces: "wave-report", consumedBy: "Swarm Coordinator", stage: "feature" },
|
|
430
|
+
{ role: "Swarm Frontend Agent", produces: "wave-report", consumedBy: "Swarm Coordinator", stage: "feature" },
|
|
431
|
+
{ role: "Swarm Coordinator", produces: "swarm-gate", consumedBy: "Swarm Synthesizer", stage: "feature" },
|
|
432
|
+
|
|
433
|
+
// Final: Synthesize + Critic verdict
|
|
434
|
+
{ role: "Swarm Synthesizer", produces: "swarm-final-report", consumedBy: "Critic Reviewer", stage: "final" },
|
|
435
|
+
{ role: "Critic Reviewer", produces: "review-verdict", consumedBy: null, stage: "final" },
|
|
436
|
+
],
|
|
437
|
+
escalationBranches: [
|
|
438
|
+
{ trigger: "build gate fails after remediation", from: "Swarm Coordinator", to: "Swarm Coordinator", action: "halt stage, report which agent's changes broke the build" },
|
|
439
|
+
{ trigger: "domain agent touches files outside its assignment", from: "Swarm Coordinator", to: "Swarm Coordinator", action: "reject wave-report, re-run agent with strict boundary warning" },
|
|
440
|
+
{ trigger: "finding spans multiple domains", from: "Swarm Coordinator", to: "Swarm Coordinator", action: "assign to the domain with most file overlap, note cross-domain in finding" },
|
|
441
|
+
{ trigger: "health pass stuck at max iterations", from: "Swarm Coordinator", to: "Swarm Synthesizer", action: "synthesize with partial health — document remaining CRITICAL/HIGH" },
|
|
442
|
+
{ trigger: "feature audit finds no gaps", from: "Swarm Coordinator", to: "Swarm Synthesizer", action: "skip feature execution, advance to final synthesis" },
|
|
443
|
+
{ trigger: "user rejects feature audit", from: "Swarm Coordinator", to: "Swarm Coordinator", action: "re-scope feature audit with user feedback, re-run" },
|
|
444
|
+
],
|
|
445
|
+
honestPartial: "One or more health stages complete but feature pass blocked or incomplete. Per-stage findings are individually valid and actionable. Manifest and wave reports exist even if synthesis does not. Build gate status is known.",
|
|
446
|
+
stopConditions: [
|
|
447
|
+
"All four stages converge — Synthesizer produces final report, Critic accepts",
|
|
448
|
+
"Health pass stuck after max iterations — synthesize with partial health findings",
|
|
449
|
+
"Feature pass stuck after max iterations — synthesize with partial feature progress",
|
|
450
|
+
"Build gate fails repeatedly — stop and report infrastructure issue",
|
|
451
|
+
"User halts swarm — synthesize from completed stages",
|
|
452
|
+
],
|
|
453
|
+
dispatchDefaults: { model: "sonnet", maxTurns: 40, maxBudgetUsd: 6.0 },
|
|
454
|
+
trialEvidence: "Proven on claude-collaborate (2026-03-28): 35→129 tests, 106 health findings fixed, v1.1.0 shipped. Protocol v2.0.",
|
|
455
|
+
},
|
|
330
456
|
};
|
|
331
457
|
|
|
332
458
|
// ── Mission catalog ─────────────────────────────────────────────────────────
|
|
@@ -395,6 +521,10 @@ export function suggestMission(taskDescription) {
|
|
|
395
521
|
signals: ["deep audit", "component audit", "decompose and audit", "audit components", "structural audit", "deep review", "code audit", "repo deep dive"],
|
|
396
522
|
weight: 1.2,
|
|
397
523
|
},
|
|
524
|
+
"dogfood-swarm": {
|
|
525
|
+
signals: ["dogfood swarm", "swarm", "health pass", "multi-pass", "convergence", "full quality pass", "production ready", "wave-based audit", "swarm this repo"],
|
|
526
|
+
weight: 1.3,
|
|
527
|
+
},
|
|
398
528
|
};
|
|
399
529
|
|
|
400
530
|
let bestKey = null;
|
package/src/packs.mjs
CHANGED
|
@@ -323,6 +323,42 @@ export const TEAM_PACKS = {
|
|
|
323
323
|
{ notForSignals: ["handbook", "documentation", "restructure docs"], suggestInstead: "docs", reason: "This is docs work, not a brainstorm" },
|
|
324
324
|
],
|
|
325
325
|
},
|
|
326
|
+
|
|
327
|
+
// ── Dogfood Swarm (Multi-Pass Health + Feature Convergence) ─────────────────
|
|
328
|
+
swarm: {
|
|
329
|
+
name: "Dogfood Swarm (Multi-Pass Convergence)",
|
|
330
|
+
description: "Three-stage health pass (bug/security → proactive → humanization) then iterative feature pass, all with exclusive file ownership, build gates, and user checkpoints. Moves a repo from 'works' to 'production-ready.'",
|
|
331
|
+
roles: [
|
|
332
|
+
"Swarm Coordinator",
|
|
333
|
+
"Swarm Backend Agent",
|
|
334
|
+
"Swarm Bridge Agent",
|
|
335
|
+
"Swarm Tests Agent",
|
|
336
|
+
"Swarm Infra Agent",
|
|
337
|
+
"Swarm Frontend Agent",
|
|
338
|
+
"Swarm Synthesizer",
|
|
339
|
+
"Critic Reviewer",
|
|
340
|
+
],
|
|
341
|
+
orchestratorRequired: false,
|
|
342
|
+
optionalRoles: [],
|
|
343
|
+
chainOrder: "Coordinator → [5 domain agents parallel] → Coordinator gate (repeat per stage: health-a, health-b, health-c, feature) → Synthesizer → Critic Reviewer",
|
|
344
|
+
requiredArtifacts: ["swarm-gate", "wave-report", "swarm-final-report", "review-verdict"],
|
|
345
|
+
stopConditions: [
|
|
346
|
+
"All four stages converge — Synthesizer produces final report, Critic accepts",
|
|
347
|
+
"Health pass stuck after max iterations — synthesize with partial health findings",
|
|
348
|
+
"Feature pass stuck after max iterations — synthesize with partial feature progress",
|
|
349
|
+
"Build gate fails repeatedly — stop and report infrastructure issue",
|
|
350
|
+
],
|
|
351
|
+
escalationOwner: "Swarm Coordinator",
|
|
352
|
+
dispatchDefaults: { model: "sonnet", maxTurns: 40, maxBudgetUsd: 6.0 },
|
|
353
|
+
trialEvidence: "Proven on claude-collaborate (2026-03-28): 35→129 tests, 106 health findings fixed, v1.1.0 shipped. Protocol v2.0.",
|
|
354
|
+
mismatchGuards: [
|
|
355
|
+
{ notForSignals: ["fix bug", "single bug", "one crash", "quick fix"], suggestInstead: "bugfix", reason: "This is a single bug to fix, not a full swarm" },
|
|
356
|
+
{ notForSignals: ["brainstorm", "explore ideas", "concept", "ideate"], suggestInstead: "brainstorm", reason: "This is exploration, not convergence work" },
|
|
357
|
+
{ notForSignals: ["research", "competitive analysis", "user research"], suggestInstead: "research", reason: "This is research, not repo health work" },
|
|
358
|
+
{ notForSignals: ["launch", "announce", "release notes", "go-to-market"], suggestInstead: "launch", reason: "This is launch work, not repo health" },
|
|
359
|
+
{ notForSignals: ["handbook", "documentation only", "restructure docs"], suggestInstead: "docs", reason: "This is docs work — swarm is for full repo convergence" },
|
|
360
|
+
],
|
|
361
|
+
},
|
|
326
362
|
};
|
|
327
363
|
|
|
328
364
|
// ── Pack selection ────────────────────────────────────────────────────────────
|
|
@@ -337,6 +373,7 @@ const PACK_KEYWORDS = {
|
|
|
337
373
|
treatment: ["treatment", "polish", "cleanup", "repo audit", "shipcheck", "full treatment"],
|
|
338
374
|
brainstorm: ["brainstorm", "explore", "ideate", "divergent", "opportunity", "creative directions", "concept exploration", "what could", "possibilities"],
|
|
339
375
|
"deep-audit": ["deep audit", "component audit", "repo audit deep", "decompose and audit", "audit components", "code audit", "structural audit", "deep review"],
|
|
376
|
+
swarm: ["swarm", "dogfood", "health pass", "multi-pass", "convergence", "wave", "full quality", "production ready", "dogfood swarm"],
|
|
340
377
|
};
|
|
341
378
|
|
|
342
379
|
/**
|
package/src/route.mjs
CHANGED
|
@@ -374,6 +374,57 @@ export const ROLE_CATALOG = [
|
|
|
374
374
|
excludeWhen: ["component audit still running", "no findings to synthesize"],
|
|
375
375
|
deliverableAffinity: ["Review"],
|
|
376
376
|
},
|
|
377
|
+
|
|
378
|
+
// ── Dogfood Swarm (Multi-Pass Health + Feature Convergence) ─────────────────
|
|
379
|
+
{
|
|
380
|
+
name: "Swarm Coordinator", pack: "swarm", phase: 0,
|
|
381
|
+
keywords: ["swarm", "wave", "gate", "convergence", "health pass", "dogfood"],
|
|
382
|
+
triggers: ["dogfood swarm", "swarm coordinator", "wave orchestration", "health gate"],
|
|
383
|
+
excludeWhen: ["single bug fix", "brainstorm only", "docs only"],
|
|
384
|
+
deliverableAffinity: ["Review"],
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
name: "Swarm Backend Agent", pack: "swarm", phase: 3,
|
|
388
|
+
keywords: ["backend", "server", "core logic", "api", "database", "service"],
|
|
389
|
+
triggers: ["swarm backend", "backend audit and fix", "server health"],
|
|
390
|
+
excludeWhen: ["frontend only", "docs only", "test only"],
|
|
391
|
+
deliverableAffinity: ["Code", "Review"],
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
name: "Swarm Bridge Agent", pack: "swarm", phase: 3,
|
|
395
|
+
keywords: ["bridge", "integration", "websocket", "middleware", "adapter", "protocol"],
|
|
396
|
+
triggers: ["swarm bridge", "bridge audit and fix", "integration health"],
|
|
397
|
+
excludeWhen: ["no secondary services", "single module repo"],
|
|
398
|
+
deliverableAffinity: ["Code", "Review"],
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
name: "Swarm Tests Agent", pack: "swarm", phase: 3,
|
|
402
|
+
keywords: ["test", "coverage", "fixture", "mock", "assertion", "spec"],
|
|
403
|
+
triggers: ["swarm tests", "test audit and fix", "test health"],
|
|
404
|
+
excludeWhen: ["no test suite", "implementation only"],
|
|
405
|
+
deliverableAffinity: ["Test", "Review"],
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
name: "Swarm Infra Agent", pack: "swarm", phase: 3,
|
|
409
|
+
keywords: ["ci", "workflow", "config", "docs", "readme", "changelog", "infrastructure"],
|
|
410
|
+
triggers: ["swarm infra", "infra audit and fix", "ci health", "docs health"],
|
|
411
|
+
excludeWhen: ["code only", "no ci"],
|
|
412
|
+
deliverableAffinity: ["Review"],
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
name: "Swarm Frontend Agent", pack: "swarm", phase: 3,
|
|
416
|
+
keywords: ["frontend", "ui", "component", "css", "html", "react", "view"],
|
|
417
|
+
triggers: ["swarm frontend", "frontend audit and fix", "ui health"],
|
|
418
|
+
excludeWhen: ["no frontend", "cli only", "backend only"],
|
|
419
|
+
deliverableAffinity: ["Code", "Review"],
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
name: "Swarm Synthesizer", pack: "swarm", phase: 5,
|
|
423
|
+
keywords: ["synthesis", "final report", "verification", "summary", "recommendation"],
|
|
424
|
+
triggers: ["swarm synthesis", "swarm final report", "swarm verification"],
|
|
425
|
+
excludeWhen: ["swarm still running", "no wave results"],
|
|
426
|
+
deliverableAffinity: ["Review"],
|
|
427
|
+
},
|
|
377
428
|
];
|
|
378
429
|
|
|
379
430
|
// ── Deliverable type → role affinity ──────────────────────────────────────────
|
package/src/run-cmd.mjs
CHANGED
|
@@ -109,13 +109,16 @@ export async function runCommand(args) {
|
|
|
109
109
|
// Strip flags from task description
|
|
110
110
|
const taskText = args.filter(a => !a.startsWith("--")).join(" ");
|
|
111
111
|
|
|
112
|
-
const run = createPersistentRun(taskText, cwd, opts);
|
|
112
|
+
const run = await createPersistentRun(taskText, cwd, opts);
|
|
113
113
|
|
|
114
114
|
console.log(`Created run: ${run.id}`);
|
|
115
115
|
console.log(`Entry: ${run.entryLevel.toUpperCase()}`);
|
|
116
116
|
if (run.missionKey) console.log(`Mission: ${run.missionKey}`);
|
|
117
117
|
if (run.packKey) console.log(`Pack: ${run.packKey}`);
|
|
118
118
|
console.log(`Steps: ${run.steps.length}`);
|
|
119
|
+
if (run.knowledge) {
|
|
120
|
+
console.log(`Knowledge: ${run.knowledge.status} (${run.knowledge.retrieval_bundle?.selected?.length ?? 0} chunks)`);
|
|
121
|
+
}
|
|
119
122
|
console.log("");
|
|
120
123
|
|
|
121
124
|
// Auto-start the first step
|
package/src/run.mjs
CHANGED
|
@@ -19,6 +19,7 @@ import { getMission } from "./mission.mjs";
|
|
|
19
19
|
import { TEAM_PACKS, getPack } from "./packs.mjs";
|
|
20
20
|
import { ROLE_CATALOG } from "./route.mjs";
|
|
21
21
|
import { ROLE_ARTIFACT_CONTRACTS, validateArtifact, getHandoffContract } from "./artifacts.mjs";
|
|
22
|
+
import { retrieveForDispatch, isKnowledgeConfigured } from "./knowledge/index.mjs";
|
|
22
23
|
|
|
23
24
|
// ── Run directory ────────────────────────────────────────────────────────────
|
|
24
25
|
|
|
@@ -88,7 +89,7 @@ let _counter = 0;
|
|
|
88
89
|
* @param {string} [opts.forcePack] - force a specific pack key
|
|
89
90
|
* @returns {PersistentRun}
|
|
90
91
|
*/
|
|
91
|
-
export function createPersistentRun(taskDescription, cwd, opts = {}) {
|
|
92
|
+
export async function createPersistentRun(taskDescription, cwd, opts = {}) {
|
|
92
93
|
if (!taskDescription || !taskDescription.trim()) {
|
|
93
94
|
throw new Error("Task description required");
|
|
94
95
|
}
|
|
@@ -125,6 +126,26 @@ export function createPersistentRun(taskDescription, cwd, opts = {}) {
|
|
|
125
126
|
|
|
126
127
|
const id = `run-${Date.now()}-${++_counter}`;
|
|
127
128
|
|
|
129
|
+
// Knowledge retrieval — automatic when corpus is configured
|
|
130
|
+
let knowledge = null;
|
|
131
|
+
if (isKnowledgeConfigured()) {
|
|
132
|
+
// Retrieve for the primary role in the chain (first step's role)
|
|
133
|
+
const primaryRole = steps[0]?.role;
|
|
134
|
+
if (primaryRole) {
|
|
135
|
+
try {
|
|
136
|
+
const roleId = primaryRole.toLowerCase().replace(/\s+/g, "-");
|
|
137
|
+
const result = await retrieveForDispatch({
|
|
138
|
+
roleId,
|
|
139
|
+
taskText: taskDescription.trim(),
|
|
140
|
+
});
|
|
141
|
+
knowledge = { retrieval_bundle: result.bundle, status: result.status };
|
|
142
|
+
} catch (e) {
|
|
143
|
+
// Retrieval failure is non-fatal — run proceeds without knowledge
|
|
144
|
+
knowledge = null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
128
149
|
const run = {
|
|
129
150
|
id,
|
|
130
151
|
taskDescription: taskDescription.trim(),
|
|
@@ -140,6 +161,7 @@ export function createPersistentRun(taskDescription, cwd, opts = {}) {
|
|
|
140
161
|
pausedAt: null,
|
|
141
162
|
completedAt: null,
|
|
142
163
|
completionReport: null,
|
|
164
|
+
knowledge,
|
|
143
165
|
};
|
|
144
166
|
|
|
145
167
|
// Persist
|
|
@@ -789,6 +811,13 @@ export function generateReport(run) {
|
|
|
789
811
|
artifactChain: artifacts,
|
|
790
812
|
escalationCount: run.escalations.length,
|
|
791
813
|
interventionCount: run.interventions.length,
|
|
814
|
+
knowledge: run.knowledge ? {
|
|
815
|
+
status: run.knowledge.status,
|
|
816
|
+
selected_count: run.knowledge.retrieval_bundle?.selected?.length ?? 0,
|
|
817
|
+
trust_posture: run.knowledge.retrieval_bundle?.provenance?.trust_posture ?? "unknown",
|
|
818
|
+
freshness_posture: run.knowledge.retrieval_bundle?.provenance?.freshness_posture ?? "unknown",
|
|
819
|
+
warning_codes: (run.knowledge.retrieval_bundle?.warnings ?? []).map((w) => w.code),
|
|
820
|
+
} : null,
|
|
792
821
|
honestPartial: (isPartial || isFailed) ? honestPartial : null,
|
|
793
822
|
verdict: isComplete
|
|
794
823
|
? "Run completed — all steps passed."
|
|
@@ -835,6 +864,18 @@ export function formatReport(report) {
|
|
|
835
864
|
lines.push(` ${icon} ${step.index}. ${step.role}${artifact}${note}`);
|
|
836
865
|
}
|
|
837
866
|
|
|
867
|
+
// Knowledge posture (Phase 5)
|
|
868
|
+
if (report.knowledge) {
|
|
869
|
+
lines.push("");
|
|
870
|
+
lines.push("## Knowledge");
|
|
871
|
+
lines.push(` Status: ${report.knowledge.status}`);
|
|
872
|
+
lines.push(` Evidence: ${report.knowledge.selected_count} chunks selected`);
|
|
873
|
+
lines.push(` Trust: ${report.knowledge.trust_posture} | Freshness: ${report.knowledge.freshness_posture}`);
|
|
874
|
+
if (report.knowledge.warning_codes.length > 0) {
|
|
875
|
+
lines.push(` Warnings: ${report.knowledge.warning_codes.join(", ")}`);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
838
879
|
if (report.honestPartial) {
|
|
839
880
|
lines.push("");
|
|
840
881
|
lines.push("## Honest Partial");
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build Gate — Detects build system and runs lint/typecheck/test verification.
|
|
3
|
+
*
|
|
4
|
+
* After every swarm wave, the build gate runs to ensure changes didn't break anything.
|
|
5
|
+
* Auto-detects the build system from project files and runs appropriate commands.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { execSync } from "node:child_process";
|
|
11
|
+
|
|
12
|
+
// ── Build system detection ──────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Detect the build system and available verification commands.
|
|
16
|
+
* @param {string} cwd - Repository root directory
|
|
17
|
+
* @returns {{ type: string, lintCmd: string|null, typecheckCmd: string|null, testCmd: string|null }}
|
|
18
|
+
*/
|
|
19
|
+
export function detectBuildSystem(cwd) {
|
|
20
|
+
// Node.js (package.json)
|
|
21
|
+
const pkgPath = join(cwd, "package.json");
|
|
22
|
+
if (existsSync(pkgPath)) {
|
|
23
|
+
try {
|
|
24
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
25
|
+
const scripts = pkg.scripts || {};
|
|
26
|
+
return {
|
|
27
|
+
type: "node",
|
|
28
|
+
lintCmd: scripts.lint ? "npm run lint" : null,
|
|
29
|
+
typecheckCmd: scripts.typecheck ? "npm run typecheck" : (scripts["type-check"] ? "npm run type-check" : null),
|
|
30
|
+
testCmd: scripts.test ? "npm test" : null,
|
|
31
|
+
};
|
|
32
|
+
} catch {
|
|
33
|
+
// Fall through
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Rust (Cargo.toml)
|
|
38
|
+
if (existsSync(join(cwd, "Cargo.toml"))) {
|
|
39
|
+
return {
|
|
40
|
+
type: "rust",
|
|
41
|
+
lintCmd: "cargo clippy --all-targets -- -D warnings",
|
|
42
|
+
typecheckCmd: "cargo check",
|
|
43
|
+
testCmd: "cargo test",
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Python (pyproject.toml or setup.py)
|
|
48
|
+
if (existsSync(join(cwd, "pyproject.toml")) || existsSync(join(cwd, "setup.py"))) {
|
|
49
|
+
const hasRuff = existsSync(join(cwd, "pyproject.toml")) &&
|
|
50
|
+
readFileSync(join(cwd, "pyproject.toml"), "utf8").includes("[tool.ruff]");
|
|
51
|
+
return {
|
|
52
|
+
type: "python",
|
|
53
|
+
lintCmd: hasRuff ? "ruff check ." : null,
|
|
54
|
+
typecheckCmd: null,
|
|
55
|
+
testCmd: "pytest",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Go (go.mod)
|
|
60
|
+
if (existsSync(join(cwd, "go.mod"))) {
|
|
61
|
+
return {
|
|
62
|
+
type: "go",
|
|
63
|
+
lintCmd: "golangci-lint run",
|
|
64
|
+
typecheckCmd: "go vet ./...",
|
|
65
|
+
testCmd: "go test ./...",
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return { type: "unknown", lintCmd: null, typecheckCmd: null, testCmd: null };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ── Build gate execution ────────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Run the build gate: lint → typecheck → test.
|
|
76
|
+
* @param {string} cwd - Repository root directory
|
|
77
|
+
* @param {object} [options]
|
|
78
|
+
* @param {object} [options.buildSystem] - Override auto-detected build system
|
|
79
|
+
* @param {number} [options.timeout] - Per-command timeout in ms (default: 120000)
|
|
80
|
+
* @returns {{ pass: boolean, lint: StepResult, typecheck: StepResult, test: StepResult, duration: number }}
|
|
81
|
+
*
|
|
82
|
+
* @typedef {{ status: "pass"|"fail"|"skip", output: string, duration: number }} StepResult
|
|
83
|
+
*/
|
|
84
|
+
export function runBuildGate(cwd, options = {}) {
|
|
85
|
+
const bs = options.buildSystem || detectBuildSystem(cwd);
|
|
86
|
+
const timeout = options.timeout || 120_000;
|
|
87
|
+
const start = Date.now();
|
|
88
|
+
|
|
89
|
+
const lint = runStep(bs.lintCmd, cwd, timeout);
|
|
90
|
+
const typecheck = runStep(bs.typecheckCmd, cwd, timeout);
|
|
91
|
+
const test = runStep(bs.testCmd, cwd, timeout);
|
|
92
|
+
|
|
93
|
+
const pass = lint.status !== "fail" && typecheck.status !== "fail" && test.status !== "fail";
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
pass,
|
|
97
|
+
lint,
|
|
98
|
+
typecheck,
|
|
99
|
+
test,
|
|
100
|
+
duration: Date.now() - start,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Run a single build step.
|
|
106
|
+
* @param {string|null} cmd
|
|
107
|
+
* @param {string} cwd
|
|
108
|
+
* @param {number} timeout
|
|
109
|
+
* @returns {StepResult}
|
|
110
|
+
*/
|
|
111
|
+
function runStep(cmd, cwd, timeout) {
|
|
112
|
+
if (!cmd) return { status: "skip", output: "", duration: 0 };
|
|
113
|
+
|
|
114
|
+
const start = Date.now();
|
|
115
|
+
try {
|
|
116
|
+
const output = execSync(cmd, {
|
|
117
|
+
cwd,
|
|
118
|
+
timeout,
|
|
119
|
+
encoding: "utf8",
|
|
120
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
121
|
+
});
|
|
122
|
+
return { status: "pass", output: output.slice(0, 2000), duration: Date.now() - start };
|
|
123
|
+
} catch (err) {
|
|
124
|
+
const output = (err.stdout || "") + "\n" + (err.stderr || "");
|
|
125
|
+
return { status: "fail", output: output.slice(0, 2000), duration: Date.now() - start };
|
|
126
|
+
}
|
|
127
|
+
}
|