sneakoscope 2.0.16 → 2.0.17

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.
Files changed (37) hide show
  1. package/README.md +5 -3
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/.sks-build-stamp.json +4 -4
  6. package/dist/bin/sks.js +1 -1
  7. package/dist/cli/command-registry.js +1 -1
  8. package/dist/commands/proof.js +21 -0
  9. package/dist/commands/zellij-slot-pane.js +7 -1
  10. package/dist/core/agents/agent-orchestrator.js +3 -1
  11. package/dist/core/agents/agent-scheduler.js +14 -1
  12. package/dist/core/agents/native-cli-session-swarm.js +11 -7
  13. package/dist/core/agents/native-cli-worker.js +56 -7
  14. package/dist/core/agents/parallel-runtime-proof.js +68 -9
  15. package/dist/core/agents/runtime-proof-summary.js +75 -0
  16. package/dist/core/commands/naruto-command.js +17 -3
  17. package/dist/core/commands/team-command.js +6 -311
  18. package/dist/core/commands/team-legacy-observe-command.js +182 -0
  19. package/dist/core/db-safety.js +15 -0
  20. package/dist/core/feature-registry.js +4 -2
  21. package/dist/core/fsx.js +1 -1
  22. package/dist/core/hooks-runtime.js +41 -4
  23. package/dist/core/init.js +1 -0
  24. package/dist/core/mad-db/mad-db-capability.js +9 -1
  25. package/dist/core/mad-db/mad-db-result-lifecycle.js +136 -0
  26. package/dist/core/release/release-gate-affected-selector.js +47 -5
  27. package/dist/core/release/release-gate-dag.js +5 -1
  28. package/dist/core/release/release-gate-scheduler.js +2 -1
  29. package/dist/core/routes.js +3 -1
  30. package/dist/core/version.js +1 -1
  31. package/dist/core/zellij/zellij-slot-pane-renderer.js +74 -1
  32. package/dist/core/zellij/zellij-slot-telemetry.js +29 -6
  33. package/dist/core/zellij/zellij-ui-mode.js +12 -2
  34. package/dist/scripts/prepublish-release-check-or-fast.js +3 -3
  35. package/dist/scripts/release-speed-summary.js +22 -2
  36. package/package.json +14 -3
  37. package/schemas/agents/parallel-runtime-proof.schema.json +31 -0
@@ -19,6 +19,7 @@ import { buildNarutoGptFinalPack } from '../naruto/naruto-gpt-final-pack.js';
19
19
  import { planNarutoZellijDashboard } from '../zellij/zellij-naruto-dashboard.js';
20
20
  import { checkPromptPlaceholders } from '../prompt/prompt-placeholder-guard.js';
21
21
  import { evaluateGitWorktreeCapability } from '../git/git-worktree-capability.js';
22
+ import { buildRuntimeProofSummary, renderRuntimeProofSummary } from '../agents/runtime-proof-summary.js';
22
23
  const NARUTO_RESULT_SCHEMA = 'sks.naruto-command-result.v1';
23
24
  const NARUTO_ROUTE = '$Naruto';
24
25
  // $Naruto — Shadow Clone Swarm (影分身 / Kage Bunshin no Jutsu).
@@ -37,6 +38,8 @@ export async function narutoCommand(commandOrArgs = 'naruto', maybeArgs = []) {
37
38
  return narutoDashboard(parsed);
38
39
  if (parsed.action === 'workers')
39
40
  return narutoWorkers(parsed);
41
+ if (parsed.action === 'proof')
42
+ return narutoProof(parsed);
40
43
  return narutoRun(parsed);
41
44
  }
42
45
  async function narutoRun(parsed) {
@@ -702,6 +705,16 @@ async function narutoWorkers(parsed) {
702
705
  console.log(`Active ${summary.active} · completed ${summary.completed} · failed ${summary.failed} · visible ${summary.visible_worker_panes.length} · headless ${summary.headless_workers.length}`);
703
706
  });
704
707
  }
708
+ async function narutoProof(parsed) {
709
+ const root = await sksRoot();
710
+ const id = parsed.missionId && parsed.missionId !== 'latest' ? parsed.missionId : await findLatestMission(root);
711
+ if (!id)
712
+ return emit(parsed, { schema: NARUTO_RESULT_SCHEMA, ok: false, action: 'proof', status: 'missing_mission' }, () => console.log('No Naruto mission found.'));
713
+ const summary = await buildRuntimeProofSummary(root, id);
714
+ return emit(parsed, { ...summary, action: 'proof' }, () => {
715
+ console.log(renderRuntimeProofSummary(summary));
716
+ });
717
+ }
705
718
  async function narutoHelp(parsed) {
706
719
  const help = {
707
720
  schema: NARUTO_RESULT_SCHEMA,
@@ -711,7 +724,8 @@ async function narutoHelp(parsed) {
711
724
  description: 'Shadow Clone Swarm: fan out up to ' + MAX_NARUTO_AGENT_COUNT + ' parallel clone sessions.',
712
725
  usage: [
713
726
  'sks naruto run "<task>" [--clones N] [--backend codex-sdk|fake|ollama] [--local-model|--no-ollama] [--work-items N] [--real] [--readonly] [--json]',
714
- 'sks naruto status [--mission <id>] [--json]'
727
+ 'sks naruto status [--mission <id>] [--json]',
728
+ 'sks naruto proof latest [--json]'
715
729
  ],
716
730
  defaults: { clones: DEFAULT_NARUTO_CLONES, max_clones: MAX_NARUTO_AGENT_COUNT, backend: 'codex-sdk' }
717
731
  };
@@ -726,7 +740,7 @@ function parseNarutoArgs(args = []) {
726
740
  if (hasFlag(args, '--help') || hasFlag(args, '-h'))
727
741
  args = ['help', ...args.filter((arg) => arg !== '--help' && arg !== '-h')];
728
742
  const first = args[0] && !String(args[0]).startsWith('--') ? String(args[0]) : '';
729
- const actions = new Set(['run', 'status', 'help', 'dashboard', 'workers']);
743
+ const actions = new Set(['run', 'status', 'help', 'dashboard', 'workers', 'proof']);
730
744
  const action = (actions.has(first) ? first : 'run');
731
745
  const rest = action === first ? args.slice(1) : args;
732
746
  const json = hasFlag(args, '--json');
@@ -743,7 +757,7 @@ function parseNarutoArgs(args = []) {
743
757
  const readonly = hasFlag(args, '--readonly') || hasFlag(args, '--read-only');
744
758
  const writeModeRaw = String(readOption(args, '--write-mode', hasFlag(args, '--parallel-write') ? 'parallel' : '') || '');
745
759
  const writeMode = (['proof-safe', 'parallel', 'serial', 'off'].includes(writeModeRaw) ? writeModeRaw : null);
746
- const positionalMission = action === 'dashboard' || action === 'workers' || action === 'status'
760
+ const positionalMission = action === 'dashboard' || action === 'workers' || action === 'status' || action === 'proof'
747
761
  ? positionalArgs(rest, new Set()).find((arg) => /^latest$|^M-/.test(arg))
748
762
  : null;
749
763
  const missionId = String(readOption(args, '--mission', readOption(args, '--mission-id', positionalMission || 'latest')));
@@ -1,29 +1,12 @@
1
1
  import path from 'node:path';
2
- import { appendJsonlBounded, exists, nowIso, readJson, sksRoot, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
3
- import { initProject } from '../init.js';
4
- import { createMission, findLatestMission, loadMission, setCurrent } from '../mission.js';
5
- import { buildQuestionSchema, writeQuestions } from '../questions.js';
6
- import { CODEX_COMPUTER_USE_ONLY_POLICY, CODEX_WEB_VERIFICATION_POLICY, FROM_CHAT_IMG_CHECKLIST_ARTIFACT, FROM_CHAT_IMG_COVERAGE_ARTIFACT, FROM_CHAT_IMG_QA_LOOP_ARTIFACT, FROM_CHAT_IMG_SOURCE_INVENTORY_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT, FROM_CHAT_IMG_VISUAL_MAP_ARTIFACT, FROM_CHAT_IMG_WORK_ORDER_ARTIFACT, ROUTES, hasFromChatImgSignal, routePrompt, routeReasoning, triwikiContextTracking } from '../routes.js';
7
- import { TEAM_DECOMPOSITION_ARTIFACT, TEAM_GRAPH_ARTIFACT, TEAM_INBOX_DIR, TEAM_RUNTIME_TASKS_ARTIFACT, teamRuntimePlanMetadata, teamRuntimeRequiredArtifacts, writeTeamRuntimeArtifacts } from '../team-dag.js';
8
- import { SSOT_GUARD_ARTIFACT, buildSsotGuard, ssotGuardPolicyText } from '../safety/ssot-guard.js';
9
- import { appendTeamEvent, formatAgentReasoning, formatRoleCounts, initTeamLive, isTerminalTeamAgentStatus, normalizeTeamSpec, parseTeamSpecArgs, readTeamControl, readTeamDashboard, readTeamLive, readTeamTranscriptTail, renderTeamAgentLane, renderTeamCleanupSummary, renderTeamWatch, requestTeamSessionCleanup, teamCleanupRequested, teamReasoningPolicy } from '../team-live.js';
10
- import { evaluateTeamReviewPolicyGate, MIN_TEAM_REVIEWER_LANES, MIN_TEAM_REVIEW_POLICY_TEXT, teamReviewPolicy } from '../team-review-policy.js';
11
- import { ARTIFACT_FILES } from '../artifact-schemas.js';
12
- import { writeEffortDecision } from '../effort-orchestrator.js';
13
- import { createWorkOrderLedger, writeWorkOrderLedger } from '../work-order-ledger.js';
14
- import { writeFromChatImgArtifacts } from '../from-chat-img-forensics.js';
15
- import { renderTeamDashboardState, writeTeamDashboardState } from '../team-dashboard-renderer.js';
16
- import { PIPELINE_PLAN_ARTIFACT, validatePipelinePlan, writePipelinePlan } from '../pipeline.js';
17
- import { attachZellijSessionInteractive, launchTeamZellijView } from '../zellij/zellij-launcher.js';
18
- import { maybeFinalizeRoute } from '../proof/auto-finalize.js';
19
- import { runNativeAgentOrchestrator } from '../agents/agent-orchestrator.js';
20
- import { ambientGoalContinuation, flag, readBoundedIntegerFlag, readFlagValue } from './command-utils.js';
2
+ import { nowIso, sksRoot, writeJsonAtomic } from '../fsx.js';
3
+ import { findLatestMission } from '../mission.js';
21
4
  import { narutoCommand } from './naruto-command.js';
22
- const TEAM_SESSION_CLEANUP_ARTIFACT = 'team-session-cleanup.json';
5
+ import { teamLegacyObserveCommand, teamLegacySubcommands } from './team-legacy-observe-command.js';
23
6
  export async function team(args = []) {
24
- const teamSubcommands = new Set(['log', 'tail', 'watch', 'lane', 'status', 'dashboard', 'event', 'message', 'open-zellij', 'attach-zellij', 'cleanup-zellij', 'open-tmux', 'attach-tmux', 'cleanup-tmux']);
25
- if (teamSubcommands.has(args[0]))
26
- return teamCommand(args[0], args.slice(1));
7
+ if (teamLegacySubcommands.has(String(args[0] || ''))) {
8
+ return teamLegacyObserveCommand(String(args[0]), args.slice(1));
9
+ }
27
10
  return redirectTeamCreateToNaruto(args);
28
11
  }
29
12
  async function redirectTeamCreateToNaruto(args = []) {
@@ -48,292 +31,4 @@ async function redirectTeamCreateToNaruto(args = []) {
48
31
  }
49
32
  return result;
50
33
  }
51
- export function parseTeamCreateArgs(args) {
52
- const spec = parseTeamSpecArgs(args);
53
- const prompt = spec.cleanArgs.join(' ').trim();
54
- const normalized = normalizeTeamSpec({ agentSessions: spec.agentSessions, roleCounts: spec.roleCounts, prompt });
55
- return { prompt, agentSessions: normalized.agentSessions, roleCounts: normalized.roleCounts, roster: normalized.roster };
56
- }
57
- function stripTeamCreateControlArgs(args = []) {
58
- const booleanFlags = new Set([
59
- '--open-zellij', '--zellij-open', '--no-open-zellij', '--no-zellij', '--no-attach',
60
- '--mock', '--ollama', '--local-model', '--no-ollama', '--no-local-model'
61
- ]);
62
- const valueFlags = new Set(['--ollama-model', '--local-model-model', '--ollama-base-url', '--local-model-base-url']);
63
- const out = [];
64
- for (let i = 0; i < args.length; i += 1) {
65
- const arg = String(args[i]);
66
- if (booleanFlags.has(arg))
67
- continue;
68
- if (valueFlags.has(arg)) {
69
- i += 1;
70
- continue;
71
- }
72
- if ([...valueFlags].some((flagName) => arg.startsWith(flagName + '=')))
73
- continue;
74
- out.push(args[i]);
75
- }
76
- return out;
77
- }
78
- export function buildTeamPlan(id, prompt, opts = {}) {
79
- const spec = normalizeTeamSpec({ ...opts, prompt });
80
- const { agentSessions, roleCounts, roster } = spec;
81
- const fromChatImgRequired = hasFromChatImgSignal(prompt);
82
- const ssotGuard = buildSsotGuard({ route: 'Team', mode: 'TEAM', task: prompt });
83
- const requiredArtifacts = ['team-roster.json', 'work-order-ledger.json', 'effort-decision.json', 'team-dashboard-state.json', 'agents/agent-proof-evidence.json', 'team-analysis.md', ...(fromChatImgRequired ? [FROM_CHAT_IMG_WORK_ORDER_ARTIFACT, FROM_CHAT_IMG_SOURCE_INVENTORY_ARTIFACT, FROM_CHAT_IMG_VISUAL_MAP_ARTIFACT, FROM_CHAT_IMG_COVERAGE_ARTIFACT, FROM_CHAT_IMG_CHECKLIST_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT, FROM_CHAT_IMG_QA_LOOP_ARTIFACT] : []), SSOT_GUARD_ARTIFACT, 'team-consensus.md', ...teamRuntimeRequiredArtifacts(), 'team-review.md', 'team-gate.json', TEAM_SESSION_CLEANUP_ARTIFACT, 'team-live.md', 'team-transcript.jsonl', 'team-dashboard.json', '.sneakoscope/wiki/context-pack.json', 'context7-evidence.jsonl'];
84
- return {
85
- schema_version: 1,
86
- mission_id: id,
87
- mode: 'team',
88
- prompt,
89
- agent_session_count: agentSessions,
90
- default_agent_session_count: MIN_TEAM_REVIEWER_LANES,
91
- target_active_slots: opts.targetActiveSlots || agentSessions,
92
- role_counts: roleCounts,
93
- session_policy: `Use at most ${opts.targetActiveSlots || agentSessions} native multi-session lanes at a time; parent orchestrator is not counted.`,
94
- review_policy: teamReviewPolicy(),
95
- review_gate: evaluateTeamReviewPolicyGate({ roleCounts, agentSessions, roster }),
96
- bundle_size: roster.bundle_size,
97
- roster,
98
- goal_continuation: ambientGoalContinuation(),
99
- team_model: {
100
- phases: ['native_agent_intake', 'triwiki_stage_refresh', 'debate_team', 'triwiki_stage_refresh', 'runtime_task_graph', 'development_team', 'triwiki_stage_refresh', 'review', 'session_cleanup'],
101
- analysis_team: `Read-only native analysis with exactly ${roster.bundle_size} native_agent_N agents.`,
102
- debate_team: `Read-only role debate with exactly ${roster.bundle_size} participants.`,
103
- development_team: `Fresh parallel development bundle with exactly ${roster.bundle_size} executor_N developers implementing disjoint slices.`,
104
- review_team: `Validation runs at least ${MIN_TEAM_REVIEWER_LANES} independent reviewer/QA lanes before integration or final.`
105
- },
106
- team_runtime: teamRuntimePlanMetadata(),
107
- persona_axioms: [
108
- 'Final users are intentionally low-context, impatient, self-interested, stubborn, and hostile to inconvenience.',
109
- 'Executors are capable developers and must receive disjoint write ownership.',
110
- 'Reviewers are strict, skeptical, and block unsupported correctness, DB safety, test, or evidence claims.',
111
- MIN_TEAM_REVIEW_POLICY_TEXT
112
- ],
113
- reasoning: teamReasoningPolicy(prompt, roster),
114
- ssot_guard: ssotGuard,
115
- context_tracking: triwikiContextTracking(),
116
- phases: [
117
- { id: 'team_roster_confirmation', goal: 'Materialize Team roster and write team-roster.json.', agents: ['parent_orchestrator'], output: 'team-roster.json' },
118
- { id: 'native_agent_intake', goal: fromChatImgRequired ? `Complete From-Chat-IMG source inventory and coverage artifacts. Web/browser/webapp screenshots require Codex Chrome Extension readiness first; native Mac/non-web surfaces may use Codex Computer Use. ${CODEX_WEB_VERIFICATION_POLICY} ${CODEX_COMPUTER_USE_ONLY_POLICY}` : 'Read relevant TriWiki context and run read-only native agent intake agents before debate.', agents: roster.analysis_team.map((agent) => agent.id), max_parallel_native_sessions: opts.targetActiveSlots || agentSessions, write_policy: 'read-only', output: 'team-analysis.md' },
119
- { id: 'triwiki_refresh', goal: 'Refresh and validate TriWiki from agent intake findings.', agents: ['parent_orchestrator'], commands: ['sks wiki refresh', 'sks wiki validate .sneakoscope/wiki/context-pack.json'], output: '.sneakoscope/wiki/context-pack.json' },
120
- { id: 'ssot_guard', goal: ssotGuardPolicyText(), agents: ['parent_orchestrator'], output: SSOT_GUARD_ARTIFACT },
121
- { id: 'planning_debate', goal: 'Debate risks and viable approaches with refreshed context.', agents: roster.debate_team.map((agent) => agent.id), max_parallel_native_sessions: opts.targetActiveSlots || agentSessions, write_policy: 'read-only' },
122
- { id: 'runtime_task_graph_compile', goal: `Compile ${TEAM_GRAPH_ARTIFACT}, ${TEAM_RUNTIME_TASKS_ARTIFACT}, and ${TEAM_DECOMPOSITION_ARTIFACT}.`, agents: ['parent_orchestrator'] },
123
- { id: 'parallel_implementation', goal: 'Fresh executor developers implement disjoint slices.', agents: roster.development_team.map((agent) => agent.id), max_parallel_native_sessions: opts.targetActiveSlots || agentSessions, write_policy: 'workspace-write with explicit ownership' },
124
- { id: 'review_and_integrate', goal: `Review with at least ${MIN_TEAM_REVIEWER_LANES} independent lanes.`, agents: roster.validation_team.map((agent) => agent.id).concat(['parent_orchestrator']), min_reviewer_lanes: MIN_TEAM_REVIEWER_LANES },
125
- { id: 'session_cleanup', goal: `Write ${TEAM_SESSION_CLEANUP_ARTIFACT}.`, agents: ['parent_orchestrator'], output: TEAM_SESSION_CLEANUP_ARTIFACT }
126
- ],
127
- invariants: ['The parent thread remains the orchestrator and owns final integration.', 'Native agent intake are read-only.', 'Implementation workers receive disjoint ownership scopes.', 'SSOT guard blocks source-of-truth drift before implementation and final gate pass.', MIN_TEAM_REVIEW_POLICY_TEXT],
128
- live_visibility: { markdown: 'team-live.md', transcript: 'team-transcript.jsonl', dashboard: 'team-dashboard.json' },
129
- required_artifacts: requiredArtifacts,
130
- prompt_command: fromChatImgRequired ? '$From-Chat-IMG' : '$Team'
131
- };
132
- }
133
- export function teamWorkflowMarkdown(plan) {
134
- const ctx = plan.context_tracking || triwikiContextTracking();
135
- return `# SKS Team Mission
136
-
137
- Mission: ${plan.mission_id}
138
-
139
- Prompt:
140
- ${plan.prompt}
141
-
142
- ## Codex App Prompt
143
-
144
- \`\`\`text
145
- ${plan.prompt_command || '$Team'} ${plan.prompt}
146
-
147
- Use at most ${plan.target_active_slots || plan.agent_session_count || MIN_TEAM_REVIEWER_LANES} native multi-session lanes at a time; the parent orchestrator is not counted. ${plan.review_policy?.text || MIN_TEAM_REVIEW_POLICY_TEXT}
148
- \`\`\`
149
-
150
- ## Context Tracking
151
-
152
- - SSOT: ${ctx.ssot}
153
- - Pack: ${ctx.default_pack}
154
- - Refresh: \`${ctx.pack_command}\`
155
- - Validate: \`${ctx.validate_command}\`
156
-
157
- ## Analysis Agents
158
-
159
- ${plan.roster.analysis_team.map((agent) => `- ${agent.id}: ${agent.persona} [reasoning: ${formatAgentReasoning(agent)}]`).join('\n')}
160
-
161
- ## Debate Team
162
-
163
- ${plan.roster.debate_team.map((agent) => `- ${agent.id}: ${agent.persona} [reasoning: ${formatAgentReasoning(agent)}]`).join('\n')}
164
-
165
- ## Development Team
166
-
167
- ${plan.roster.development_team.map((agent) => `- ${agent.id}: ${agent.persona} [reasoning: ${formatAgentReasoning(agent)}]`).join('\n')}
168
-
169
- ## Validation Team
170
-
171
- ${plan.roster.validation_team.map((agent) => `- ${agent.id}: ${agent.persona} [reasoning: ${formatAgentReasoning(agent)}]`).join('\n')}
172
-
173
- ## Phases
174
-
175
- ${plan.phases.map((phase, idx) => `${idx + 1}. ${phase.id}: ${phase.goal}`).join('\n')}
176
-
177
- ## Invariants
178
-
179
- ${plan.invariants.map((x) => `- ${x}`).join('\n')}
180
- `;
181
- }
182
- async function teamCommand(sub, args) {
183
- const root = await sksRoot();
184
- const missionArg = args[0] && !String(args[0]).startsWith('--') ? args[0] : 'latest';
185
- const { resolveMissionId } = await import('./command-utils.js');
186
- const id = await resolveMissionId(root, missionArg);
187
- if (!id) {
188
- console.error(`Usage: sks team ${sub} [mission-id|latest]`);
189
- process.exitCode = 1;
190
- return;
191
- }
192
- const { dir } = await loadMission(root, id);
193
- if (sub === 'open-tmux' || sub === 'attach-tmux' || sub === 'cleanup-tmux') {
194
- const result = { ok: false, status: 'removed_runtime', runtime: 'tmux', replacement: 'zellij', operator_actions: ['Use `sks team open-zellij`, `attach-zellij`, or `cleanup-zellij`.'] };
195
- if (flag(args, '--json'))
196
- return console.log(JSON.stringify(result, null, 2));
197
- console.error('tmux runtime has been removed from SKS Team. Use Zellij commands instead.');
198
- process.exitCode = 2;
199
- return;
200
- }
201
- if (sub === 'open-zellij' || sub === 'attach-zellij') {
202
- const plan = await readJson(path.join(dir, 'team-plan.json'), null);
203
- if (!plan) {
204
- console.error(`Team plan missing for ${id}; cannot open Zellij Team view.`);
205
- process.exitCode = 2;
206
- return;
207
- }
208
- const slotCount = await inferTeamZellijSlotCount(dir, plan);
209
- const zellij = await launchTeamZellijView({ root, missionId: id, ledgerRoot: path.join(dir, 'agents'), slotCount, dryRun: flag(args, '--json'), attach: false });
210
- if (flag(args, '--json'))
211
- return console.log(JSON.stringify(zellij, null, 2));
212
- if (!zellij.ok) {
213
- console.error(`Zellij Team view blocked for ${id}: ${(zellij.blockers || []).join('; ') || 'Zellij launch failed'}`);
214
- process.exitCode = 2;
215
- return;
216
- }
217
- if (zellij.capability?.status === 'ok')
218
- console.log(`Zellij: prepared Team lane(s) in ${zellij.session_name}`);
219
- else
220
- console.log(`Zellij: optional live panes unavailable (${(zellij.warnings || []).join('; ') || zellij.capability?.status || 'unknown'})`);
221
- if (zellij.capability?.status === 'ok' && (sub === 'attach-zellij' || shouldAutoAttachTeamZellij(args))) {
222
- attachZellijSessionInteractive(zellij.session_name, { cwd: root, configPath: zellij.clipboard_config_path });
223
- }
224
- return;
225
- }
226
- if (sub === 'event') {
227
- const message = readFlagValue(args, '--message', '');
228
- if (!message) {
229
- console.error('Usage: sks team event [mission-id|latest] --agent <name> --phase <phase> --message "..."');
230
- process.exitCode = 1;
231
- return;
232
- }
233
- const phase = readFlagValue(args, '--phase', 'general');
234
- const plan = await readJson(path.join(dir, 'team-plan.json'), null).catch(() => null);
235
- const record = await appendTeamEvent(dir, { agent: readFlagValue(args, '--agent', 'parent_orchestrator'), phase, type: readFlagValue(args, '--type', 'status'), artifact: readFlagValue(args, '--artifact', ''), message });
236
- if (flag(args, '--json'))
237
- return console.log(JSON.stringify(record, null, 2));
238
- console.log(`${record.ts} [${record.phase}] ${record.agent}: ${record.message}`);
239
- return;
240
- }
241
- if (sub === 'message') {
242
- const message = readFlagValue(args, '--message', '');
243
- if (!message) {
244
- console.error('Usage: sks team message [mission-id|latest] --from <agent> --to <agent|all> --message "..."');
245
- process.exitCode = 1;
246
- return;
247
- }
248
- const record = await appendTeamEvent(dir, { agent: readFlagValue(args, '--from', readFlagValue(args, '--agent', 'parent_orchestrator')), to: readFlagValue(args, '--to', 'all'), phase: readFlagValue(args, '--phase', 'communication'), type: 'message', message });
249
- if (flag(args, '--json'))
250
- return console.log(JSON.stringify(record, null, 2));
251
- console.log(`${record.ts} [${record.phase}] ${record.agent} -> ${record.to}: ${record.message}`);
252
- return;
253
- }
254
- if (sub === 'cleanup-zellij') {
255
- const control = await requestTeamSessionCleanup(dir, { missionId: id, agent: readFlagValue(args, '--agent', 'parent_orchestrator'), reason: readFlagValue(args, '--reason', 'Team session ended; clean up live follow panes.'), finalMessage: 'Team session ended.' });
256
- await appendTeamEvent(dir, { agent: readFlagValue(args, '--agent', 'parent_orchestrator'), phase: 'session_cleanup', type: 'cleanup', message: control.cleanup_reason || 'Team session cleanup requested.' });
257
- const cleanup = { ok: true, runtime: 'zellij', mission_id: id, control, close_requested: flag(args, '--close-session') || flag(args, '--close') };
258
- await writeJsonAtomic(path.join(dir, 'zellij-session-cleanup.json'), cleanup);
259
- if (flag(args, '--json'))
260
- return console.log(JSON.stringify(cleanup, null, 2));
261
- console.log('Zellij cleanup: marked complete.');
262
- console.log(renderTeamCleanupSummary(control));
263
- return;
264
- }
265
- if (sub === 'status') {
266
- const dashboard = await readTeamDashboard(dir);
267
- if (flag(args, '--json'))
268
- return console.log(JSON.stringify(dashboard || {}, null, 2));
269
- if (!dashboard) {
270
- console.error(`Team dashboard missing for ${id}.`);
271
- process.exitCode = 2;
272
- return;
273
- }
274
- console.log(`Team mission: ${id}`);
275
- console.log(`Updated: ${dashboard.updated_at || 'unknown'}`);
276
- console.log(`Agent sessions: ${dashboard.agent_session_count || MIN_TEAM_REVIEWER_LANES}`);
277
- if (dashboard.role_counts)
278
- console.log(`Role counts: ${formatRoleCounts(dashboard.role_counts)}`);
279
- return;
280
- }
281
- if (sub === 'dashboard') {
282
- await writeTeamDashboardState(dir, { missionId: id });
283
- const state = await readJson(path.join(dir, ARTIFACT_FILES.team_dashboard_state), {});
284
- if (flag(args, '--json'))
285
- return console.log(JSON.stringify(state, null, 2));
286
- console.log(renderTeamDashboardState(state));
287
- return;
288
- }
289
- if (sub === 'log')
290
- return console.log(await readTeamLive(dir));
291
- if (sub === 'lane') {
292
- const agent = readFlagValue(args, '--agent', 'parent_orchestrator');
293
- const phase = readFlagValue(args, '--phase', '');
294
- const lines = Number(readFlagValue(args, '--lines', '12'));
295
- const text = await renderTeamAgentLane(dir, { missionId: id, agent, phase, lines });
296
- if (flag(args, '--json'))
297
- return console.log(JSON.stringify({ mission_id: id, agent, phase, lane: text }, null, 2));
298
- console.log(text);
299
- if (flag(args, '--follow') && !teamCleanupRequested(await readTeamControl(dir)) && !isTerminalTeamAgentStatus((await readTeamDashboard(dir).catch(() => null))?.agents?.[agent]?.status || '')) {
300
- // Follow mode intentionally falls through only for interactive terminals in the full Zellij lane.
301
- }
302
- return;
303
- }
304
- if (sub === 'tail' || sub === 'watch') {
305
- const lines = readFlagValue(args, '--lines', '20');
306
- if (sub === 'watch' && !flag(args, '--raw'))
307
- console.log(await renderTeamWatch(dir, { missionId: id, lines: Number(lines) }));
308
- else
309
- for (const line of await readTeamTranscriptTail(dir, Number(lines)))
310
- console.log(line);
311
- }
312
- }
313
- async function inferTeamZellijSlotCount(dir, plan = {}) {
314
- const scheduler = await readJson(path.join(dir, 'agents', 'agent-scheduler-state.json'), null);
315
- const lanes = await readJson(path.join(dir, 'agents', 'agent-zellij-lanes.json'), null);
316
- const candidates = [
317
- plan?.bundle_size,
318
- plan?.agent_session_count,
319
- lanes?.lane_count,
320
- plan?.target_active_slots,
321
- scheduler?.target_active_slots
322
- ].map((value) => Number(value)).filter((value) => Number.isFinite(value) && value > 0);
323
- return Math.max(1, Math.min(100, Math.floor(candidates[0] || 5)));
324
- }
325
- function shouldAutoAttachTeamZellij(args = []) {
326
- const list = (args || []).map((arg) => String(arg));
327
- if (list.includes('--no-attach'))
328
- return false;
329
- if (list.includes('--json'))
330
- return false;
331
- if (process.env.SKS_NO_ZELLIJ_ATTACH === '1')
332
- return false;
333
- if (process.env.ZELLIJ)
334
- return false;
335
- if (list.includes('--attach'))
336
- return true;
337
- return Boolean(process.stdout.isTTY && process.stdin.isTTY);
338
- }
339
34
  //# sourceMappingURL=team-command.js.map
@@ -0,0 +1,182 @@
1
+ import path from 'node:path';
2
+ import { ARTIFACT_FILES } from '../artifact-schemas.js';
3
+ import { readJson, sksRoot, writeJsonAtomic } from '../fsx.js';
4
+ import { loadMission } from '../mission.js';
5
+ import { MIN_TEAM_REVIEWER_LANES } from '../team-review-policy.js';
6
+ import { renderTeamDashboardState, writeTeamDashboardState } from '../team-dashboard-renderer.js';
7
+ import { appendTeamEvent, formatRoleCounts, isTerminalTeamAgentStatus, readTeamControl, readTeamDashboard, readTeamLive, readTeamTranscriptTail, renderTeamAgentLane, renderTeamCleanupSummary, renderTeamWatch, requestTeamSessionCleanup, teamCleanupRequested } from '../team-live.js';
8
+ import { attachZellijSessionInteractive, launchTeamZellijView } from '../zellij/zellij-launcher.js';
9
+ import { flag, readFlagValue } from './command-utils.js';
10
+ export const teamLegacySubcommands = new Set([
11
+ 'log',
12
+ 'tail',
13
+ 'watch',
14
+ 'lane',
15
+ 'status',
16
+ 'dashboard',
17
+ 'event',
18
+ 'message',
19
+ 'open-zellij',
20
+ 'attach-zellij',
21
+ 'cleanup-zellij',
22
+ 'open-tmux',
23
+ 'attach-tmux',
24
+ 'cleanup-tmux'
25
+ ]);
26
+ export async function teamLegacyObserveCommand(sub, args = []) {
27
+ const root = await sksRoot();
28
+ const missionArg = args[0] && !String(args[0]).startsWith('--') ? args[0] : 'latest';
29
+ const { resolveMissionId } = await import('./command-utils.js');
30
+ const id = await resolveMissionId(root, missionArg);
31
+ if (!id) {
32
+ console.error(`Usage: sks team ${sub} [mission-id|latest]`);
33
+ process.exitCode = 1;
34
+ return;
35
+ }
36
+ const { dir } = await loadMission(root, id);
37
+ if (sub === 'open-tmux' || sub === 'attach-tmux' || sub === 'cleanup-tmux') {
38
+ const result = { ok: false, status: 'removed_runtime', runtime: 'tmux', replacement: 'zellij', operator_actions: ['Use `sks team open-zellij`, `attach-zellij`, or `cleanup-zellij`.'] };
39
+ if (flag(args, '--json'))
40
+ return console.log(JSON.stringify(result, null, 2));
41
+ console.error('tmux runtime has been removed from SKS Team. Use Zellij commands instead.');
42
+ process.exitCode = 2;
43
+ return;
44
+ }
45
+ if (sub === 'open-zellij' || sub === 'attach-zellij') {
46
+ const plan = await readJson(path.join(dir, 'team-plan.json'), null);
47
+ if (!plan) {
48
+ console.error(`Team plan missing for ${id}; cannot open Zellij Team view.`);
49
+ process.exitCode = 2;
50
+ return;
51
+ }
52
+ const slotCount = await inferTeamZellijSlotCount(dir, plan);
53
+ const zellij = await launchTeamZellijView({ root, missionId: id, ledgerRoot: path.join(dir, 'agents'), slotCount, dryRun: flag(args, '--json'), attach: false });
54
+ if (flag(args, '--json'))
55
+ return console.log(JSON.stringify(zellij, null, 2));
56
+ if (!zellij.ok) {
57
+ console.error(`Zellij Team view blocked for ${id}: ${(zellij.blockers || []).join('; ') || 'Zellij launch failed'}`);
58
+ process.exitCode = 2;
59
+ return;
60
+ }
61
+ if (zellij.capability?.status === 'ok')
62
+ console.log(`Zellij: prepared Team lane(s) in ${zellij.session_name}`);
63
+ else
64
+ console.log(`Zellij: optional live panes unavailable (${(zellij.warnings || []).join('; ') || zellij.capability?.status || 'unknown'})`);
65
+ if (zellij.capability?.status === 'ok' && (sub === 'attach-zellij' || shouldAutoAttachTeamZellij(args))) {
66
+ attachZellijSessionInteractive(zellij.session_name, { cwd: root, configPath: zellij.clipboard_config_path });
67
+ }
68
+ return;
69
+ }
70
+ if (sub === 'event') {
71
+ const message = readFlagValue(args, '--message', '');
72
+ if (!message) {
73
+ console.error('Usage: sks team event [mission-id|latest] --agent <name> --phase <phase> --message "..."');
74
+ process.exitCode = 1;
75
+ return;
76
+ }
77
+ const phase = readFlagValue(args, '--phase', 'general');
78
+ const record = await appendTeamEvent(dir, { agent: readFlagValue(args, '--agent', 'parent_orchestrator'), phase, type: readFlagValue(args, '--type', 'status'), artifact: readFlagValue(args, '--artifact', ''), message });
79
+ if (flag(args, '--json'))
80
+ return console.log(JSON.stringify(record, null, 2));
81
+ console.log(`${record.ts} [${record.phase}] ${record.agent}: ${record.message}`);
82
+ return;
83
+ }
84
+ if (sub === 'message') {
85
+ const message = readFlagValue(args, '--message', '');
86
+ if (!message) {
87
+ console.error('Usage: sks team message [mission-id|latest] --from <agent> --to <agent|all> --message "..."');
88
+ process.exitCode = 1;
89
+ return;
90
+ }
91
+ const record = await appendTeamEvent(dir, { agent: readFlagValue(args, '--from', readFlagValue(args, '--agent', 'parent_orchestrator')), to: readFlagValue(args, '--to', 'all'), phase: readFlagValue(args, '--phase', 'communication'), type: 'message', message });
92
+ if (flag(args, '--json'))
93
+ return console.log(JSON.stringify(record, null, 2));
94
+ console.log(`${record.ts} [${record.phase}] ${record.agent} -> ${record.to}: ${record.message}`);
95
+ return;
96
+ }
97
+ if (sub === 'cleanup-zellij') {
98
+ const control = await requestTeamSessionCleanup(dir, { missionId: id, agent: readFlagValue(args, '--agent', 'parent_orchestrator'), reason: readFlagValue(args, '--reason', 'Team session ended; clean up live follow panes.'), finalMessage: 'Team session ended.' });
99
+ await appendTeamEvent(dir, { agent: readFlagValue(args, '--agent', 'parent_orchestrator'), phase: 'session_cleanup', type: 'cleanup', message: control.cleanup_reason || 'Team session cleanup requested.' });
100
+ const cleanup = { ok: true, runtime: 'zellij', mission_id: id, control, close_requested: flag(args, '--close-session') || flag(args, '--close') };
101
+ await writeJsonAtomic(path.join(dir, 'zellij-session-cleanup.json'), cleanup);
102
+ if (flag(args, '--json'))
103
+ return console.log(JSON.stringify(cleanup, null, 2));
104
+ console.log('Zellij cleanup: marked complete.');
105
+ console.log(renderTeamCleanupSummary(control));
106
+ return;
107
+ }
108
+ if (sub === 'status') {
109
+ const dashboard = await readTeamDashboard(dir);
110
+ if (flag(args, '--json'))
111
+ return console.log(JSON.stringify(dashboard || {}, null, 2));
112
+ if (!dashboard) {
113
+ console.error(`Team dashboard missing for ${id}.`);
114
+ process.exitCode = 2;
115
+ return;
116
+ }
117
+ console.log(`Team mission: ${id}`);
118
+ console.log(`Updated: ${dashboard.updated_at || 'unknown'}`);
119
+ console.log(`Agent sessions: ${dashboard.agent_session_count || MIN_TEAM_REVIEWER_LANES}`);
120
+ if (dashboard.role_counts)
121
+ console.log(`Role counts: ${formatRoleCounts(dashboard.role_counts)}`);
122
+ return;
123
+ }
124
+ if (sub === 'dashboard') {
125
+ await writeTeamDashboardState(dir, { missionId: id });
126
+ const state = await readJson(path.join(dir, ARTIFACT_FILES.team_dashboard_state), {});
127
+ if (flag(args, '--json'))
128
+ return console.log(JSON.stringify(state, null, 2));
129
+ console.log(renderTeamDashboardState(state));
130
+ return;
131
+ }
132
+ if (sub === 'log')
133
+ return console.log(await readTeamLive(dir));
134
+ if (sub === 'lane') {
135
+ const agent = readFlagValue(args, '--agent', 'parent_orchestrator');
136
+ const phase = readFlagValue(args, '--phase', '');
137
+ const lines = Number(readFlagValue(args, '--lines', '12'));
138
+ const text = await renderTeamAgentLane(dir, { missionId: id, agent, phase, lines });
139
+ if (flag(args, '--json'))
140
+ return console.log(JSON.stringify({ mission_id: id, agent, phase, lane: text }, null, 2));
141
+ console.log(text);
142
+ if (flag(args, '--follow') && !teamCleanupRequested(await readTeamControl(dir)) && !isTerminalTeamAgentStatus((await readTeamDashboard(dir).catch(() => null))?.agents?.[agent]?.status || '')) {
143
+ // Follow mode intentionally falls through only for interactive terminals in the full Zellij lane.
144
+ }
145
+ return;
146
+ }
147
+ if (sub === 'tail' || sub === 'watch') {
148
+ const lines = readFlagValue(args, '--lines', '20');
149
+ if (sub === 'watch' && !flag(args, '--raw'))
150
+ console.log(await renderTeamWatch(dir, { missionId: id, lines: Number(lines) }));
151
+ else
152
+ for (const line of await readTeamTranscriptTail(dir, Number(lines)))
153
+ console.log(line);
154
+ }
155
+ }
156
+ async function inferTeamZellijSlotCount(dir, plan = {}) {
157
+ const scheduler = await readJson(path.join(dir, 'agents', 'agent-scheduler-state.json'), null);
158
+ const lanes = await readJson(path.join(dir, 'agents', 'agent-zellij-lanes.json'), null);
159
+ const candidates = [
160
+ plan?.bundle_size,
161
+ plan?.agent_session_count,
162
+ lanes?.lane_count,
163
+ plan?.target_active_slots,
164
+ scheduler?.target_active_slots
165
+ ].map((value) => Number(value)).filter((value) => Number.isFinite(value) && value > 0);
166
+ return Math.max(1, Math.min(100, Math.floor(candidates[0] || 5)));
167
+ }
168
+ function shouldAutoAttachTeamZellij(args = []) {
169
+ const list = (args || []).map((arg) => String(arg));
170
+ if (list.includes('--no-attach'))
171
+ return false;
172
+ if (list.includes('--json'))
173
+ return false;
174
+ if (process.env.SKS_NO_ZELLIJ_ATTACH === '1')
175
+ return false;
176
+ if (process.env.ZELLIJ)
177
+ return false;
178
+ if (list.includes('--attach'))
179
+ return true;
180
+ return Boolean(process.stdout.isTTY && process.stdin.isTTY);
181
+ }
182
+ //# sourceMappingURL=team-legacy-observe-command.js.map
@@ -5,6 +5,7 @@ import { evaluateMadSksPermissionGate, isMadSksRouteState } from './permission-g
5
5
  import { resolveMadDbMutationPolicy } from './mad-db/mad-db-policy-resolver.js';
6
6
  import { recordMadDbOperation } from './mad-db/mad-db-capability.js';
7
7
  import { appendMadDbLedgerEvent, appendMadDbOperationLifecycle } from './mad-db/mad-db-ledger.js';
8
+ import { lifecycleHookFromUnknown, recordPendingMadDbLifecycleHook } from './mad-db/mad-db-result-lifecycle.js';
8
9
  export const DEFAULT_DB_SAFETY_POLICY = Object.freeze({
9
10
  schema_version: 1,
10
11
  mode: 'read_only_default',
@@ -520,6 +521,14 @@ export async function checkDbOperation(root, state, payload, { duringNoQuestion
520
521
  sqlHash,
521
522
  destructive: classification.level === 'destructive'
522
523
  });
524
+ const lifecycleHook = {
525
+ mission_id: String(state.mission_id),
526
+ operation_id: operationId,
527
+ cycle_id: madDbDecision.cycle_id || null,
528
+ tool_name: classification.toolName || null,
529
+ sql_hash: sqlHash,
530
+ destructive: classification.level === 'destructive'
531
+ };
523
532
  const decision = {
524
533
  allowed: true,
525
534
  action: 'allow',
@@ -534,6 +543,8 @@ export async function checkDbOperation(root, state, payload, { duringNoQuestion
534
543
  capability_file: 'mad-db-capability.json',
535
544
  consumed: false,
536
545
  operation_id: operationId,
546
+ lifecycle_result_pending: true,
547
+ ledger_result_hook: lifecycleHook,
537
548
  operation_count: Number(madDbDecision.operation_count || 0) + 1,
538
549
  max_operations: madDbDecision.max_operations || 20
539
550
  }
@@ -555,6 +566,7 @@ export async function checkDbOperation(root, state, payload, { duringNoQuestion
555
566
  });
556
567
  decision.mad_db.consumed = updatedCapability?.consumed === true;
557
568
  decision.mad_db.operation_count = updatedCapability?.operation_count ?? decision.mad_db.operation_count;
569
+ await recordPendingMadDbLifecycleHook(root, state.mission_id, lifecycleHook);
558
570
  await appendJsonlBounded(path.join(missionDir(root, state.mission_id), 'db-safety.jsonl'), { ts: nowIso(), decision });
559
571
  return decision;
560
572
  }
@@ -567,6 +579,9 @@ export async function checkDbOperation(root, state, payload, { duringNoQuestion
567
579
  }
568
580
  return decision;
569
581
  }
582
+ export function madDbLifecycleHookFromDecision(decision) {
583
+ return lifecycleHookFromUnknown(decision);
584
+ }
570
585
  export async function checkSqlFile(file) {
571
586
  const sql = await readText(file);
572
587
  return classifySql(sql);
@@ -830,7 +830,7 @@ function commandMaturity(name) {
830
830
  function routeMaturity(command) {
831
831
  if (['$Answer', '$DFix', '$SKS', '$Fast-Mode', '$Wiki', '$Help'].includes(command))
832
832
  return 'stable';
833
- if (['$Team', '$Goal', '$DB', '$Computer-Use', '$CU', '$QA-LOOP', '$MAD-SKS'].includes(command))
833
+ if (['$Team', '$Goal', '$DB', '$Computer-Use', '$CU', '$QA-LOOP', '$MAD-SKS', '$MAD-DB'].includes(command))
834
834
  return 'beta';
835
835
  return 'labs';
836
836
  }
@@ -858,7 +858,7 @@ function knownGapsForCommand(name) {
858
858
  function routeVoxelContract(command) {
859
859
  if (['$Image-UX-Review', '$UX-Review', '$PPT', '$From-Chat-IMG', '$GX'].includes(command))
860
860
  return 'image/source/bbox voxel required';
861
- if (command === '$DB' || command === '$MAD-SKS')
861
+ if (command === '$DB' || command === '$MAD-SKS' || command === '$MAD-DB')
862
862
  return 'DB policy voxel required';
863
863
  return 'TriWiki anchors required';
864
864
  }
@@ -867,6 +867,8 @@ function routeKnownGaps(command) {
867
867
  return ['live imagegen/CU evidence required'];
868
868
  if (command === '$MAD-SKS')
869
869
  return ['permission closed by owning gate'];
870
+ if (command === '$MAD-DB')
871
+ return ['one-cycle capability must be explicitly enabled, consumed, or revoked'];
870
872
  return [];
871
873
  }
872
874
  function checkRow(id, ok, blockers = []) {