sneakoscope 2.0.12 → 2.0.13

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 (43) hide show
  1. package/README.md +5 -1
  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/build-manifest.json +24 -8
  8. package/dist/core/codex-control/codex-sdk-adapter.js +10 -0
  9. package/dist/core/codex-control/codex-task-runner.js +4 -2
  10. package/dist/core/commands/research-command.js +43 -4
  11. package/dist/core/fsx.js +1 -1
  12. package/dist/core/research/claim-evidence-matrix.js +160 -0
  13. package/dist/core/research/experiment-plan.js +53 -0
  14. package/dist/core/research/falsification.js +18 -0
  15. package/dist/core/research/implementation-blueprint-markdown.js +31 -0
  16. package/dist/core/research/implementation-blueprint.js +66 -0
  17. package/dist/core/research/replication-pack.js +50 -0
  18. package/dist/core/research/research-cycle-runner.js +25 -0
  19. package/dist/core/research/research-final-reviewer.js +58 -0
  20. package/dist/core/research/research-handoff.js +51 -0
  21. package/dist/core/research/research-prompt-contract.js +24 -0
  22. package/dist/core/research/research-quality-contract.js +61 -0
  23. package/dist/core/research/research-report-quality.js +67 -0
  24. package/dist/core/research/research-stage-runner.js +16 -0
  25. package/dist/core/research/research-work-graph.js +75 -0
  26. package/dist/core/research/source-quality-report.js +94 -0
  27. package/dist/core/research.js +344 -44
  28. package/dist/core/version.js +1 -1
  29. package/dist/core/zellij/zellij-slot-column-anchor.js +5 -3
  30. package/dist/core/zellij/zellij-slot-pane-renderer.js +259 -16
  31. package/dist/scripts/codex-sdk-research-pipeline-check.js +7 -0
  32. package/dist/scripts/packlist-performance-check.js +1 -1
  33. package/dist/scripts/research-quality-gate-check.js +86 -0
  34. package/dist/scripts/zellij-slot-column-anchor-check.js +26 -5
  35. package/dist/scripts/zellij-slot-pane-renderer-check.js +73 -5
  36. package/package.json +13 -1
  37. package/schemas/research/claim-evidence-matrix.schema.json +37 -0
  38. package/schemas/research/experiment-plan.schema.json +17 -0
  39. package/schemas/research/implementation-blueprint.schema.json +30 -0
  40. package/schemas/research/replication-pack.schema.json +17 -0
  41. package/schemas/research/research-final-review.schema.json +16 -0
  42. package/schemas/research/research-quality-contract.schema.json +37 -0
  43. package/schemas/research/source-quality-report.schema.json +18 -0
package/README.md CHANGED
@@ -16,7 +16,7 @@ Set up this agent project with Sneakoscope Codex. Use [[mandarange/Sneakoscope-C
16
16
 
17
17
  ## Current Release
18
18
 
19
- SKS **2.0.12** is the public-ready parallel runtime stabilization release. It closes release DAG coverage around Zellij slot renderer proof semantics, wires Naruto allocation/rebalance into the production scheduler, keeps pre-run worker smoke opt-in, and requires GPT Final approval before local/worktree candidate output can apply.
19
+ SKS **2.0.13** is the public-ready research pipeline stabilization release. It closes the Research quality contract, claim-evidence matrix, source-quality report, implementation handoff, experiment/replication pack, final-reviewer, and read-only stage-aware work graph path while preserving the 2.0.12 parallel runtime stabilization work.
20
20
 
21
21
  What changed:
22
22
 
@@ -466,6 +466,8 @@ sks code-structure scan --json
466
466
 
467
467
  `sks research` prepares a named genius-lens agent council, requires every agent to run at `xhigh`, records one literal `Eureka!` idea per agent, runs an evidence-bound debate, and creates `research-source-skill.md` as a route-local source collection skill before synthesis. Research is not a code-change route: real runs may write only their own mission artifacts under `.sneakoscope/missions/<id>/`, and source/package/docs/config mutations block the run with `research-code-mutation-blocker.json`. The required Research persona lenses are Einstein Agent, Feynman Agent, Turing Agent, von Neumann Agent, and Skeptic Agent; they are cognitive roles, not impersonations, and `agent-ledger.json` must include `display_name`, `persona`, `persona_boundary`, `reasoning_effort`, falsifiers, cheap probes, and `challenge_or_response`. Normal Research is not a fixed three-cycle flow: it repeats source gathering, Eureka ideas, debate, falsification, and synthesis pressure until every agent records final agreement, or pauses at the explicit max-cycle safety cap with an unpassed gate. `debate-ledger.json` must include `consensus_iterations`, `unanimous_consensus`, and per-agent agreements; `research-gate.json` cannot pass until unanimous consensus is true for all agents. Normal Research is intentionally allowed to take one or two hours when the problem needs it; `--mock` is only for selftests or dry harness checks, and a real run blocks with `research-blocker.json` instead of silently substituting mock output when the Codex execution path is unavailable. The source layer contract separates latest papers, official/government or leading-institution sources, standards/primary docs, current news such as BBC/CNN/GDELT-style sources, public discourse such as X/Reddit, developer/practitioner knowledge such as Stack Overflow/GitHub, traditional background sources, and counterevidence/fact-checking; `source-ledger.json` must record layer coverage, source quality, blockers, citations, and cross-layer triangulation. Context7 is optional for `$Research` and only becomes relevant when the research topic specifically depends on package, API, framework, or SDK documentation. Research runs require `research-report.md`, `research-paper.md`, `genius-opinion-summary.md`, `research-source-skill.md`, `source-ledger.json`, `agent-ledger.json`, `debate-ledger.json`, `novelty-ledger.json`, `falsification-ledger.json`, and `research-gate.json` so they stay source-backed, adversarially checked, falsifiable, paper-ready, and clear about every agent lens opinion. `research status` reports source entries, source-layer coverage, triangulation checks, counterevidence, xhigh agent count, Eureka moments, debate exchanges, consensus iterations, unanimous consensus, paper presence/sections, genius-opinion summary coverage, agent findings, and falsification cases alongside the gate.
468
468
 
469
+ In 2.0.13, Research also writes a quality contract and handoff package: `research-quality-contract.json`, `claim-evidence-matrix.json`, `source-quality-report.json`, `implementation-blueprint.json`/`.md`, `experiment-plan.json`/`.md`, `replication-pack.json`, `research-work-graph.json`, and `research-final-review.json`. The default gate requires 12 total sources, 5 source layers, 2 counterevidence sources, 8 key claims, 6 triangulated claims, 8 blueprint sections, 4 falsification cases, 5 experiment steps, and a 2200-word report before `research-gate.json` can pass. See `docs/research-pipeline.md`, `docs/research-artifacts.md`, and `docs/research-implementation-handoff.md`.
470
+
469
471
  `sks recallpulse` is the 0.8.0 report-only RecallPulse utility. It writes `recallpulse-decision.json`, `mission-status-ledger.json`, `route-proof-capsule.json`, `evidence-envelope.json`, `recallpulse-governance-report.json`, `recallpulse-task-goal-ledger.json`, and `recallpulse-eval-report.json` for the current mission. RecallPulse does not replace route gates, Honest Mode, DB safety, imagegen evidence, or TriWiki validation; it records cache hits, hydration needs, duplicate suppression, route-governance risks, and final-summary-ready durable status so later releases can promote only measured improvements. Checklist updates are sequential: every `Txxx` row is treated as a child `$Goal` checkpoint, and `sks recallpulse checklist ... --task T001 --apply` refuses out-of-order checks unless explicitly overridden.
470
472
 
471
473
  `sks pipeline plan` shows the active route lane, kept/skipped stages, verification commands, and no-unrequested-fallback invariant. The 0.9.0 Decision Lattice augments this planning surface with report-only A*/proof-debt evidence: frontier paths considered, the selected path, and rejected paths with rejection reasons. `sks proof-field scan` remains the lightweight rubric for small changes; risky or broad signals return to the full Team/Honest path, and no speedup claim is valid without replay or eval evidence.
@@ -531,6 +533,8 @@ $DB inspect this migration for destructive risk
531
533
 
532
534
  Local model workers are off by default, so SKS stays GPT-only unless you explicitly enable them. Use the Codex App prompt commands:
533
535
 
536
+ ![SKS Local LLM mode workflow](docs/sks-local-llm-mode/assets/sks-local-llm-flow.png)
537
+
534
538
  ```text
535
539
  $with-local-llm-on
536
540
  $with-local-llm-off
@@ -76,7 +76,7 @@ dependencies = [
76
76
 
77
77
  [[package]]
78
78
  name = "sks-core"
79
- version = "2.0.12"
79
+ version = "2.0.13"
80
80
  dependencies = [
81
81
  "serde_json",
82
82
  ]
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "2.0.12"
3
+ version = "2.0.13"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -4,7 +4,7 @@ use std::io::{self, Read, Seek, SeekFrom};
4
4
  fn main() {
5
5
  let mut args = std::env::args().skip(1);
6
6
  match args.next().as_deref() {
7
- Some("--version") => println!("sks-rs 2.0.12"),
7
+ Some("--version") => println!("sks-rs 2.0.13"),
8
8
  Some("compact-info") => {
9
9
  let mut input = String::new();
10
10
  let _ = io::stdin().read_to_string(&mut input);
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schema": "sks.dist-build-stamp.v1",
3
3
  "package_name": "sneakoscope",
4
- "package_version": "2.0.12",
5
- "source_digest": "6f9f20ed184ebe714b41b9176d8414b7fded251a30591ada3c3bac53e49fcf61",
6
- "source_file_count": 2053,
7
- "built_at_source_time": 1780833723608
4
+ "package_version": "2.0.13",
5
+ "source_digest": "4e6d812c7572cee4b51a8e1d0374fff3b908c58e2173b6255122b87ce9e8b83e",
6
+ "source_file_count": 2090,
7
+ "built_at_source_time": 1780843454792
8
8
  }
package/dist/bin/sks.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const FAST_PACKAGE_VERSION = '2.0.12';
2
+ const FAST_PACKAGE_VERSION = '2.0.13';
3
3
  const args = process.argv.slice(2);
4
4
  try {
5
5
  if (args[0] === '--agent' && args[1] === 'worker') {
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "schema": "sks.dist-build.v2",
3
- "version": "2.0.12",
4
- "package_version": "2.0.12",
3
+ "version": "2.0.13",
4
+ "package_version": "2.0.13",
5
5
  "typescript": true,
6
6
  "mjs_runtime_files": 0,
7
- "compiled_file_count": 1111,
8
- "compiled_js_count": 1111,
7
+ "compiled_file_count": 1127,
8
+ "compiled_js_count": 1127,
9
9
  "compiled_dts_count": 0,
10
- "source_digest": "6f9f20ed184ebe714b41b9176d8414b7fded251a30591ada3c3bac53e49fcf61",
11
- "source_file_count": 2053,
12
- "source_files_hash": "cf94f337fb28d923dd91214e01c2b80794f0d24659e7aaac0baf5c3110f1eb7a",
13
- "source_list_hash": "cf94f337fb28d923dd91214e01c2b80794f0d24659e7aaac0baf5c3110f1eb7a",
10
+ "source_digest": "4e6d812c7572cee4b51a8e1d0374fff3b908c58e2173b6255122b87ce9e8b83e",
11
+ "source_file_count": 2090,
12
+ "source_files_hash": "07ac9249be0189c641861e02515d575578156357d6a92cd33869ae8cc4a38691",
13
+ "source_list_hash": "07ac9249be0189c641861e02515d575578156357d6a92cd33869ae8cc4a38691",
14
14
  "src_mjs_runtime_files": 0,
15
15
  "dist_stamp_schema": "sks.dist-build-stamp.v1",
16
16
  "files": [
@@ -538,6 +538,21 @@
538
538
  "core/release/release-gate-scheduler.js",
539
539
  "core/reporting/markdown-table.js",
540
540
  "core/research.js",
541
+ "core/research/claim-evidence-matrix.js",
542
+ "core/research/experiment-plan.js",
543
+ "core/research/falsification.js",
544
+ "core/research/implementation-blueprint-markdown.js",
545
+ "core/research/implementation-blueprint.js",
546
+ "core/research/replication-pack.js",
547
+ "core/research/research-cycle-runner.js",
548
+ "core/research/research-final-reviewer.js",
549
+ "core/research/research-handoff.js",
550
+ "core/research/research-prompt-contract.js",
551
+ "core/research/research-quality-contract.js",
552
+ "core/research/research-report-quality.js",
553
+ "core/research/research-stage-runner.js",
554
+ "core/research/research-work-graph.js",
555
+ "core/research/source-quality-report.js",
541
556
  "core/responses-retry-policy.js",
542
557
  "core/retention.js",
543
558
  "core/router/capability-card.js",
@@ -1038,6 +1053,7 @@
1038
1053
  "scripts/repo-audit.js",
1039
1054
  "scripts/research-actual-route-backfill-check.js",
1040
1055
  "scripts/research-backfill-route-blackbox.js",
1056
+ "scripts/research-quality-gate-check.js",
1041
1057
  "scripts/responses-retry-policy-centralized-check.js",
1042
1058
  "scripts/retention-cleanup-safety-check.js",
1043
1059
  "scripts/route-blackbox-realism-check.js",
@@ -1,5 +1,8 @@
1
+ import path from 'node:path';
2
+ import { appendJsonl } from '../fsx.js';
1
3
  import { buildCodexSdkConfig } from './codex-sdk-config-policy.js';
2
4
  import { buildCodexSdkEnv } from './codex-sdk-env-policy.js';
5
+ import { translateCodexSdkEvent } from './codex-event-translator.js';
3
6
  export async function runRealCodexSdkTask(input, policy) {
4
7
  const mod = await import('@openai/codex-sdk');
5
8
  const Codex = mod.Codex || mod.default?.Codex || mod.default;
@@ -22,9 +25,15 @@ export async function runRealCodexSdkTask(input, policy) {
22
25
  const thread = resumeId ? codex.resumeThread(resumeId, threadOptions) : codex.startThread(threadOptions);
23
26
  const events = [];
24
27
  let finalResponse = '';
28
+ let liveEventsWritten = false;
29
+ const liveEventPath = input.mutationLedgerRoot ? path.join(input.mutationLedgerRoot, 'codex-sdk-events.jsonl') : null;
25
30
  const streamed = await thread.runStreamed(buildSdkInput(input), { outputSchema: input.outputSchema });
26
31
  for await (const event of streamed.events) {
27
32
  events.push(event);
33
+ if (liveEventPath) {
34
+ await appendJsonl(liveEventPath, translateCodexSdkEvent(event));
35
+ liveEventsWritten = true;
36
+ }
28
37
  if (event?.type === 'item.completed' && event?.item?.type === 'agent_message')
29
38
  finalResponse = String(event.item.text || '');
30
39
  }
@@ -37,6 +46,7 @@ export async function runRealCodexSdkTask(input, policy) {
37
46
  finalResponse,
38
47
  structuredOutput,
39
48
  blockers: [],
49
+ liveEventsWritten,
40
50
  raw: { item_count: events.filter((event) => String(event?.type || '').startsWith('item.')).length }
41
51
  };
42
52
  }
@@ -67,8 +67,10 @@ export async function runCodexTask(input) {
67
67
  }
68
68
  const events = Array.isArray(adapterResult?.events) ? adapterResult.events : [];
69
69
  const translatedEvents = translateCodexSdkEvents(events);
70
- for (const event of translatedEvents)
71
- await appendJsonl(path.join(root, 'codex-sdk-events.jsonl'), event);
70
+ if (adapterResult?.liveEventsWritten !== true) {
71
+ for (const event of translatedEvents)
72
+ await appendJsonl(path.join(root, 'codex-sdk-events.jsonl'), event);
73
+ }
72
74
  if (adapterResult?.reliabilityShield)
73
75
  await writeJsonAtomic(path.join(root, 'codex-reliability-shield.json'), adapterResult.reliabilityShield);
74
76
  const structuredOutput = adapterResult?.structuredOutput;
@@ -14,6 +14,15 @@ import { scanDbSafety } from '../db-safety.js';
14
14
  import { maybeFinalizeRoute } from '../proof/auto-finalize.js';
15
15
  import { runNativeAgentOrchestrator } from '../agents/agent-orchestrator.js';
16
16
  import { flag, positionalArgs, readFlagValue, readMaxCycles, readBoundedIntegerFlag, resolveMissionId, safeReadTextFile } from './command-utils.js';
17
+ import { writeResearchWorkGraph } from '../research/research-work-graph.js';
18
+ import { runResearchCycle } from '../research/research-cycle-runner.js';
19
+ import { readResearchQualityContract } from '../research/research-quality-contract.js';
20
+ import { readClaimEvidenceMatrix } from '../research/claim-evidence-matrix.js';
21
+ import { readSourceQualityReport } from '../research/source-quality-report.js';
22
+ import { readImplementationBlueprint, validateImplementationBlueprint } from '../research/implementation-blueprint.js';
23
+ import { readExperimentPlan, validateExperimentPlan } from '../research/experiment-plan.js';
24
+ import { readReplicationPack, validateReplicationPack } from '../research/replication-pack.js';
25
+ import { readResearchFinalReview } from '../research/research-final-reviewer.js';
17
26
  const RESEARCH_DEFAULT_MAX_CYCLES = 12;
18
27
  const RESEARCH_DEFAULT_CYCLE_TIMEOUT_MINUTES = 120;
19
28
  const RESEARCH_MIN_CYCLE_TIMEOUT_MINUTES = 15;
@@ -128,9 +137,12 @@ async function researchRun(args) {
128
137
  const dryRunPatches = flag(args, '--dry-run-patches') || flag(args, '--dryrun-patches');
129
138
  const maxWriteAgents = readBoundedIntegerFlag(args, '--max-write-agents', Math.min(requestedAgents, 5), 1, 20);
130
139
  const mock = flag(args, '--mock');
140
+ const researchWorkGraph = await writeResearchWorkGraph(dir, plan);
141
+ const graphWorkItemCount = Math.max(1, Number(researchWorkGraph.total_work_items || researchWorkGraph.work_items?.length || 0));
142
+ await runResearchCycle(dir, researchWorkGraph, { cycle: 0, status: mock ? 'mock_native_orchestrator_planned' : 'native_orchestrator_planned' });
131
143
  await setCurrent(root, { mission_id: id, mode: 'RESEARCH', phase: 'RESEARCH_RUNNING_NO_QUESTIONS', questions_allowed: false, implementation_allowed: false, research_real_run_required: !mock, research_cycle_timeout_minutes: cycleTimeoutMinutes });
132
144
  await appendJsonlBounded(path.join(dir, 'events.jsonl'), { ts: nowIso(), type: 'research.run.started', maxCycles, mock, cycleTimeoutMinutes, real_run_required: !mock });
133
- const nativeAgentRun = await runNativeAgentOrchestrator({ root, missionId: id, route: flag(args, '--autoresearch') ? '$AutoResearch' : '$Research', prompt: mission.prompt || plan.prompt || 'Research run', backend: mock ? 'fake' : 'codex-sdk', mock, agents: requestedAgents, targetActiveSlots, desiredWorkItemCount, minimumWorkItems, maxQueueExpansion, concurrency: Math.min(requestedAgents, 5), readonly: !(applyPatches && writeMode !== 'off'), profile, writeMode: writeMode, applyPatches, dryRunPatches, maxWriteAgents, roster: plan.native_agent_plan, routeCommand: 'sks research run', routeBlackboxKind: 'actual_research_command' });
145
+ const nativeAgentRun = await runNativeAgentOrchestrator({ root, missionId: id, route: flag(args, '--autoresearch') ? '$AutoResearch' : '$Research', prompt: mission.prompt || plan.prompt || 'Research run', backend: mock ? 'fake' : 'codex-sdk', mock, agents: requestedAgents, targetActiveSlots, desiredWorkItemCount: Math.max(desiredWorkItemCount, graphWorkItemCount), minimumWorkItems: Math.max(minimumWorkItems, Math.min(graphWorkItemCount, targetActiveSlots)), maxQueueExpansion, concurrency: Math.min(requestedAgents, 5), readonly: true, profile, writeMode: writeMode, applyPatches: false, dryRunPatches, maxWriteAgents, roster: plan.native_agent_plan, routeCommand: 'sks research run', routeBlackboxKind: 'actual_research_command', narutoWorkGraph: researchWorkGraph });
134
146
  await writeJsonAtomic(path.join(dir, 'research-native-agent-run.json'), nativeAgentRun);
135
147
  await appendJsonlBounded(path.join(dir, 'events.jsonl'), { ts: nowIso(), type: 'research.native_agents.completed', backend: nativeAgentRun.backend, ok: nativeAgentRun.ok, proof: nativeAgentRun.proof?.status });
136
148
  if (mock) {
@@ -141,7 +153,7 @@ async function researchRun(args) {
141
153
  const proof = await maybeFinalizeRoute(root, { missionId: id, route: '$Research', gateFile: 'research-gate.json', gate: gate.gate || gate, artifacts: ['agents/agent-proof-evidence.json', 'research-native-agent-run.json', 'research-gate.json', 'research-report.md', researchPaperArtifactForPlan(plan), 'source-ledger.json', 'agent-ledger.json', 'debate-ledger.json', 'completion-proof.json'], mock, command: { cmd: `sks research run ${id} --mock`, status: 0 } });
142
154
  await setCurrent(root, { mission_id: id, mode: 'RESEARCH', phase: gate.passed ? 'RESEARCH_DONE' : 'RESEARCH_PAUSED', questions_allowed: true, implementation_allowed: false });
143
155
  if (flag(args, '--json'))
144
- return console.log(JSON.stringify({ schema: flag(args, '--autoresearch') ? 'sks.autoresearch-run.v1' : 'sks.research-run.v1', ok: proof.ok, mission_id: id, gate, proof: proof.validation, native_agent_run: nativeAgentRun, agent_batches: plan.agent_batches, autoresearch_cycle_policy: plan.autoresearch_cycle_policy }, null, 2));
156
+ return console.log(JSON.stringify({ schema: flag(args, '--autoresearch') ? 'sks.autoresearch-run.v1' : 'sks.research-run.v1', ok: proof.ok, mission_id: id, gate, quality_metrics: gate.metrics || null, proof: proof.validation, native_agent_run: nativeAgentRun, research_work_graph: researchWorkGraph, agent_batches: plan.agent_batches, autoresearch_cycle_policy: plan.autoresearch_cycle_policy }, null, 2));
145
157
  console.log(`Mock research done: ${id}`);
146
158
  console.log(`Gate: ${gate.passed ? 'passed' : 'blocked'}`);
147
159
  return;
@@ -178,6 +190,7 @@ async function researchRun(args) {
178
190
  const cycleDir = path.join(dir, 'research', `cycle-${cycle}`);
179
191
  const outputFile = path.join(cycleDir, 'final.md');
180
192
  await appendJsonlBounded(path.join(dir, 'events.jsonl'), { ts: nowIso(), type: 'research.cycle.start', cycle, timeoutMinutes: cycleTimeoutMinutes, profile, enforced_reasoning_effort: 'xhigh' });
193
+ await runResearchCycle(dir, researchWorkGraph, { cycle, status: 'codex_research_cycle_started' });
181
194
  const prompt = buildResearchPrompt({ id, mission, plan, cycle, previous: last });
182
195
  const result = await runCodexExec({ root, prompt, outputFile, json: true, profile, extraArgs: researchCodexArgs, logDir: cycleDir, timeoutMs: cycleTimeoutMs });
183
196
  await writeJsonAtomic(path.join(cycleDir, 'process.json'), { code: result.code, stdout_tail: result.stdout, stderr_tail: result.stderr, stdout_bytes: result.stdoutBytes, stderr_bytes: result.stderrBytes, truncated: result.truncated, timed_out: result.timedOut });
@@ -212,7 +225,7 @@ async function researchRun(args) {
212
225
  await appendJsonlBounded(path.join(dir, 'events.jsonl'), { ts: nowIso(), type: 'research.done', cycle });
213
226
  await enforceRetention(root).catch(() => { });
214
227
  if (flag(args, '--json'))
215
- return console.log(JSON.stringify({ schema: flag(args, '--autoresearch') ? 'sks.autoresearch-run.v1' : 'sks.research-run.v1', ok: proof.ok, mission_id: id, gate, proof: proof.validation, agent_batches: plan.agent_batches, autoresearch_cycle_policy: plan.autoresearch_cycle_policy }, null, 2));
228
+ return console.log(JSON.stringify({ schema: flag(args, '--autoresearch') ? 'sks.autoresearch-run.v1' : 'sks.research-run.v1', ok: proof.ok, mission_id: id, gate, quality_metrics: gate.metrics || null, proof: proof.validation, research_work_graph: researchWorkGraph, agent_batches: plan.agent_batches, autoresearch_cycle_policy: plan.autoresearch_cycle_policy }, null, 2));
216
229
  console.log(`Research done: ${id}`);
217
230
  return;
218
231
  }
@@ -246,6 +259,16 @@ async function researchStatus(args) {
246
259
  const agentRows = Array.isArray(agentLedger?.agents) ? agentLedger.agents : [];
247
260
  const sourceLayerRows = Array.isArray(sourceLedger?.source_layers) ? sourceLedger.source_layers : [];
248
261
  const sourceLayersCovered = sourceLayerRows.filter((layer) => layer.status === 'covered' && ((Array.isArray(layer.source_ids) && layer.source_ids.length) || (Array.isArray(layer.counterevidence_ids) && layer.counterevidence_ids.length))).length;
262
+ const qualityContract = await readResearchQualityContract(dir);
263
+ const claimMatrix = await readClaimEvidenceMatrix(dir);
264
+ const sourceQualityReport = await readSourceQualityReport(dir);
265
+ const implementationBlueprint = await readImplementationBlueprint(dir);
266
+ const experimentPlan = await readExperimentPlan(dir);
267
+ const replicationPack = await readReplicationPack(dir);
268
+ const finalReview = await readResearchFinalReview(dir);
269
+ const blueprintValidation = validateImplementationBlueprint(implementationBlueprint, qualityContract);
270
+ const experimentValidation = validateExperimentPlan(experimentPlan, qualityContract);
271
+ const replicationValidation = validateReplicationPack(replicationPack);
249
272
  console.log(JSON.stringify({
250
273
  mission,
251
274
  state,
@@ -275,7 +298,23 @@ async function researchStatus(args) {
275
298
  research_paper_artifact: paperArtifact.name,
276
299
  paper_present: Boolean(paperText.trim()),
277
300
  paper_sections: countResearchPaperSections(paperText),
278
- falsification_cases: falsificationLedger?.cases?.length ?? null
301
+ falsification_cases: falsificationLedger?.cases?.length ?? null,
302
+ research_quality: {
303
+ contract: qualityContract,
304
+ report_word_count: gate?.metrics?.report_word_count ?? null,
305
+ claim_evidence_matrix_present: claimMatrix.present,
306
+ key_claims: claimMatrix.key_claim_ids.length,
307
+ triangulated_claims: claimMatrix.triangulated_claim_count,
308
+ claim_matrix_blockers: claimMatrix.blockers,
309
+ source_quality_report_ok: sourceQualityReport?.ok === true,
310
+ implementation_blueprint_sections: Array.isArray(implementationBlueprint?.sections) ? implementationBlueprint.sections.length : null,
311
+ implementation_blueprint_ok: blueprintValidation.ok,
312
+ experiment_steps: Array.isArray(experimentPlan?.steps) ? experimentPlan.steps.length : null,
313
+ experiment_plan_ok: experimentValidation.ok,
314
+ replication_pack_ok: replicationValidation.ok,
315
+ final_review_approved: finalReview?.approved === true,
316
+ final_review_blockers: finalReview?.blockers || []
317
+ }
279
318
  }, null, 2));
280
319
  }
281
320
  async function researchCodeMutationSnapshot(root, missionId = null) {
package/dist/core/fsx.js CHANGED
@@ -5,7 +5,7 @@ import os from 'node:os';
5
5
  import crypto from 'node:crypto';
6
6
  import { spawn } from 'node:child_process';
7
7
  import { fileURLToPath } from 'node:url';
8
- export const PACKAGE_VERSION = '2.0.12';
8
+ export const PACKAGE_VERSION = '2.0.13';
9
9
  export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
10
10
  export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
11
11
  export function nowIso() {
@@ -0,0 +1,160 @@
1
+ import path from 'node:path';
2
+ import { exists, nowIso, readJson, writeJsonAtomic } from '../fsx.js';
3
+ export const CLAIM_EVIDENCE_MATRIX_ARTIFACT = 'claim-evidence-matrix.json';
4
+ export function defaultClaimEvidenceMatrix(missionId = '') {
5
+ return {
6
+ schema: 'sks.claim-evidence-matrix.v1',
7
+ mission_id: missionId,
8
+ claims: [],
9
+ key_claim_ids: [],
10
+ unsupported_claims: [],
11
+ triangulated_claim_count: 0,
12
+ blockers: []
13
+ };
14
+ }
15
+ export async function readClaimEvidenceMatrix(dir) {
16
+ const file = path.join(dir, CLAIM_EVIDENCE_MATRIX_ARTIFACT);
17
+ const present = await exists(file);
18
+ const matrix = normalizeClaimEvidenceMatrix(await readJson(file, null));
19
+ return {
20
+ present,
21
+ matrix,
22
+ key_claim_ids: matrix.key_claim_ids,
23
+ unsupported_claims: matrix.unsupported_claims,
24
+ triangulated_claim_count: matrix.triangulated_claim_count,
25
+ blockers: present ? matrix.blockers : ['claim_evidence_matrix_missing']
26
+ };
27
+ }
28
+ export async function writeClaimEvidenceMatrix(dir, matrix) {
29
+ const normalized = normalizeClaimEvidenceMatrix(matrix);
30
+ await writeJsonAtomic(path.join(dir, CLAIM_EVIDENCE_MATRIX_ARTIFACT), normalized);
31
+ return normalized;
32
+ }
33
+ export function validateClaimEvidenceMatrix(matrix, sourceLedger = null, falsificationLedger = null) {
34
+ const normalized = normalizeClaimEvidenceMatrix(matrix);
35
+ const claimIds = new Set(normalized.claims.map((claim) => claim.id));
36
+ const sourceIds = sourceIdSet(sourceLedger);
37
+ const counterIds = new Set([
38
+ ...Array.from(sourceIds).filter((id) => /counter/i.test(id)),
39
+ ...(Array.isArray(sourceLedger?.counterevidence_sources) ? sourceLedger.counterevidence_sources.map((row) => String(row?.id || '')).filter(Boolean) : []),
40
+ ...(Array.isArray(falsificationLedger?.cases) ? falsificationLedger.cases.flatMap((row) => [row?.id, ...(Array.isArray(row?.counterevidence_source_ids) ? row.counterevidence_source_ids : []), ...(Array.isArray(row?.source_ids) ? row.source_ids : [])]).map(String).filter(Boolean) : [])
41
+ ]);
42
+ const blockers = [];
43
+ for (const id of normalized.key_claim_ids)
44
+ if (!claimIds.has(id))
45
+ blockers.push(`key_claim_missing:${id}`);
46
+ for (const claim of normalized.claims) {
47
+ const important = claim.importance === 'high' || claim.importance === 'critical';
48
+ if (important && !claim.source_ids.length)
49
+ blockers.push(`claim_source_missing:${claim.id}`);
50
+ if (claim.importance === 'critical' && !claim.counterevidence_ids.length)
51
+ blockers.push(`critical_claim_counterevidence_missing:${claim.id}`);
52
+ for (const sourceId of claim.source_ids)
53
+ if (!sourceIds.has(sourceId))
54
+ blockers.push(`claim_source_unknown:${claim.id}:${sourceId}`);
55
+ for (const counterId of claim.counterevidence_ids)
56
+ if (!counterIds.has(counterId))
57
+ blockers.push(`claim_counterevidence_unknown:${claim.id}:${counterId}`);
58
+ if (claim.claim_type === 'hypothesis' && !claim.test_or_probe.trim())
59
+ blockers.push(`hypothesis_probe_missing:${claim.id}`);
60
+ }
61
+ for (const id of normalized.unsupported_claims) {
62
+ const claim = normalized.claims.find((row) => row.id === id);
63
+ if (claim?.importance === 'high' || claim?.importance === 'critical')
64
+ blockers.push(`unsupported_important_claim:${id}`);
65
+ }
66
+ return { ok: blockers.length === 0, blockers: [...new Set(blockers)] };
67
+ }
68
+ export function normalizeClaimEvidenceMatrix(value) {
69
+ const raw = value && typeof value === 'object' ? value : {};
70
+ const claims = (Array.isArray(raw.claims) ? raw.claims : []).map(normalizeClaim).filter((claim) => claim.id);
71
+ const keyClaimIds = normalizeStringList(raw.key_claim_ids).filter((id) => claims.some((claim) => claim.id === id));
72
+ const unsupported = normalizeStringList(raw.unsupported_claims);
73
+ return {
74
+ schema: 'sks.claim-evidence-matrix.v1',
75
+ mission_id: String(raw.mission_id || ''),
76
+ claims,
77
+ key_claim_ids: keyClaimIds,
78
+ unsupported_claims: unsupported,
79
+ triangulated_claim_count: Number.isFinite(Number(raw.triangulated_claim_count))
80
+ ? Math.max(0, Math.floor(Number(raw.triangulated_claim_count)))
81
+ : claims.filter((claim) => claim.triangulation.independent_confirmation_count >= 2 && claim.triangulation.source_layers.length >= 2).length,
82
+ blockers: normalizeStringList(raw.blockers)
83
+ };
84
+ }
85
+ export function buildClaimEvidenceMatrixFromLedgers(input = {}) {
86
+ const entries = Array.isArray(input.noveltyLedger?.entries) ? input.noveltyLedger.entries : [];
87
+ const sources = Array.isArray(input.sourceLedger?.sources) ? input.sourceLedger.sources : [];
88
+ const counterSources = Array.isArray(input.sourceLedger?.counterevidence_sources) ? input.sourceLedger.counterevidence_sources : [];
89
+ const fallbackSourceIds = sources.map((row) => String(row?.id || '')).filter(Boolean);
90
+ const fallbackCounterIds = counterSources.map((row) => String(row?.id || '')).filter(Boolean);
91
+ const claims = entries.map((entry, index) => {
92
+ const id = String(entry.id || `claim-${index + 1}`);
93
+ const sourceIds = normalizeStringList(entry.source_ids || entry.evidence).filter((sourceId) => fallbackSourceIds.includes(sourceId));
94
+ const counterIds = normalizeStringList(entry.counterevidence_ids || entry.falsifiers).filter((sourceId) => fallbackCounterIds.includes(sourceId));
95
+ return normalizeClaim({
96
+ id,
97
+ claim: entry.claim || entry.title || id,
98
+ claim_type: 'hypothesis',
99
+ importance: index < 2 ? 'critical' : 'high',
100
+ source_ids: sourceIds.length ? sourceIds : fallbackSourceIds.slice(0, 2),
101
+ counterevidence_ids: counterIds.length ? counterIds : fallbackCounterIds.slice(0, 1),
102
+ triangulation: {
103
+ source_layers: sourceLayersForSourceIds(input.sourceLedger, sourceIds.length ? sourceIds : fallbackSourceIds),
104
+ independent_confirmation_count: Math.max(1, sourceIds.length || fallbackSourceIds.length),
105
+ conflicts: []
106
+ },
107
+ confidence: entry.confidence >= 2 ? 'high' : 'medium',
108
+ falsifiable: true,
109
+ test_or_probe: entry.next_experiment || entry.test_or_probe || 'Run the proposed replication probe.'
110
+ });
111
+ });
112
+ return normalizeClaimEvidenceMatrix({
113
+ schema: 'sks.claim-evidence-matrix.v1',
114
+ mission_id: input.missionId || '',
115
+ claims,
116
+ key_claim_ids: claims.slice(0, 8).map((claim) => claim.id),
117
+ unsupported_claims: [],
118
+ triangulated_claim_count: claims.filter((claim) => claim.triangulation.source_layers.length >= 2).length,
119
+ blockers: []
120
+ });
121
+ }
122
+ function normalizeClaim(value) {
123
+ const importance = ['low', 'medium', 'high', 'critical'].includes(value?.importance) ? value.importance : 'medium';
124
+ const claimType = ['fact', 'inference', 'hypothesis', 'recommendation', 'implementation_guidance'].includes(value?.claim_type) ? value.claim_type : 'hypothesis';
125
+ const confidence = ['low', 'medium', 'high'].includes(value?.confidence) ? value.confidence : 'medium';
126
+ return {
127
+ id: String(value?.id || '').trim(),
128
+ claim: String(value?.claim || '').trim(),
129
+ claim_type: claimType,
130
+ importance,
131
+ source_ids: normalizeStringList(value?.source_ids),
132
+ local_evidence_ids: normalizeStringList(value?.local_evidence_ids),
133
+ counterevidence_ids: normalizeStringList(value?.counterevidence_ids),
134
+ triangulation: {
135
+ source_layers: normalizeStringList(value?.triangulation?.source_layers),
136
+ independent_confirmation_count: Math.max(0, Math.floor(Number(value?.triangulation?.independent_confirmation_count || 0))),
137
+ conflicts: normalizeStringList(value?.triangulation?.conflicts)
138
+ },
139
+ confidence,
140
+ falsifiable: value?.falsifiable !== false,
141
+ test_or_probe: String(value?.test_or_probe || '').trim()
142
+ };
143
+ }
144
+ function sourceIdSet(sourceLedger) {
145
+ return new Set([
146
+ ...(Array.isArray(sourceLedger?.sources) ? sourceLedger.sources : []),
147
+ ...(Array.isArray(sourceLedger?.counterevidence_sources) ? sourceLedger.counterevidence_sources : [])
148
+ ].map((row) => String(row?.id || '')).filter(Boolean));
149
+ }
150
+ function sourceLayersForSourceIds(sourceLedger, ids) {
151
+ const idSet = new Set(ids);
152
+ return [...new Set([
153
+ ...(Array.isArray(sourceLedger?.sources) ? sourceLedger.sources : []),
154
+ ...(Array.isArray(sourceLedger?.counterevidence_sources) ? sourceLedger.counterevidence_sources : [])
155
+ ].filter((row) => idSet.has(String(row?.id || ''))).map((row) => String(row?.layer || row?.source_layer || '')).filter(Boolean))];
156
+ }
157
+ function normalizeStringList(value) {
158
+ return [...new Set((Array.isArray(value) ? value : value == null ? [] : [value]).map((item) => String(item || '').trim()).filter(Boolean))];
159
+ }
160
+ //# sourceMappingURL=claim-evidence-matrix.js.map
@@ -0,0 +1,53 @@
1
+ import path from 'node:path';
2
+ import { nowIso, readJson, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
3
+ export const EXPERIMENT_PLAN_JSON_ARTIFACT = 'experiment-plan.json';
4
+ export const EXPERIMENT_PLAN_MARKDOWN_ARTIFACT = 'experiment-plan.md';
5
+ export function defaultExperimentPlan(plan = null) {
6
+ const prompt = String(plan?.prompt || 'research mission');
7
+ return {
8
+ schema: 'sks.research-experiment-plan.v1',
9
+ generated_at: nowIso(),
10
+ prompt,
11
+ hypothesis: 'The surviving research claim should produce a measurable improvement over a summary-only baseline.',
12
+ steps: [
13
+ { id: 'E1', action: 'Select one baseline output and one research-pipeline output for the same prompt.', expected_evidence: ['research-report.md'] },
14
+ { id: 'E2', action: 'Score cited key claims, triangulation, counterevidence, and unsupported claims.', expected_evidence: ['claim-evidence-matrix.json'] },
15
+ { id: 'E3', action: 'Run or design the smallest probe implied by the implementation blueprint.', expected_evidence: ['implementation-blueprint.json'] },
16
+ { id: 'E4', action: 'Compare failure cases and falsification outcomes.', expected_evidence: ['falsification-ledger.json'] },
17
+ { id: 'E5', action: 'Record replication commands, artifacts, and acceptance thresholds.', expected_evidence: ['replication-pack.json'] }
18
+ ],
19
+ metrics: ['key_claims_supported', 'triangulated_claims', 'counterevidence_sources', 'falsification_cases', 'experiment_steps'],
20
+ controls: ['summary_only_baseline', 'same_prompt_same_context'],
21
+ acceptance_threshold: 'All quality-contract thresholds are met and the final reviewer approves the run.'
22
+ };
23
+ }
24
+ export function validateExperimentPlan(experimentPlan = null, contract = null) {
25
+ const minSteps = Number(contract?.min_experiment_steps || 5);
26
+ const steps = Array.isArray(experimentPlan?.steps) ? experimentPlan.steps : [];
27
+ const completeSteps = steps.filter((step) => String(step?.id || '').trim() && String(step?.action || '').trim());
28
+ const blockers = [
29
+ ...(experimentPlan ? [] : ['experiment_plan_missing']),
30
+ ...(steps.length < minSteps ? ['experiment_plan_steps_below_contract'] : []),
31
+ ...(completeSteps.length < minSteps ? ['experiment_plan_too_thin'] : [])
32
+ ];
33
+ return { ok: blockers.length === 0, blockers, steps: steps.length, complete_steps: completeSteps.length, min_steps: minSteps };
34
+ }
35
+ export function renderExperimentPlanMarkdown(experimentPlan = null) {
36
+ const lines = ['# Research Experiment Plan', '', `Hypothesis: ${experimentPlan?.hypothesis || ''}`, '', '## Steps'];
37
+ for (const step of Array.isArray(experimentPlan?.steps) ? experimentPlan.steps : []) {
38
+ lines.push(`- ${step.id}: ${step.action}`);
39
+ }
40
+ lines.push('', '## Metrics');
41
+ for (const metric of Array.isArray(experimentPlan?.metrics) ? experimentPlan.metrics : [])
42
+ lines.push(`- ${metric}`);
43
+ return `${lines.join('\n')}\n`;
44
+ }
45
+ export async function readExperimentPlan(dir) {
46
+ return readJson(path.join(dir, EXPERIMENT_PLAN_JSON_ARTIFACT), null);
47
+ }
48
+ export async function writeExperimentPlan(dir, experimentPlan) {
49
+ await writeJsonAtomic(path.join(dir, EXPERIMENT_PLAN_JSON_ARTIFACT), experimentPlan);
50
+ await writeTextAtomic(path.join(dir, EXPERIMENT_PLAN_MARKDOWN_ARTIFACT), renderExperimentPlanMarkdown(experimentPlan));
51
+ return experimentPlan;
52
+ }
53
+ //# sourceMappingURL=experiment-plan.js.map
@@ -0,0 +1,18 @@
1
+ export function validateFalsificationCoverage(falsificationLedger = null, contract = null) {
2
+ const minCases = Number(contract?.min_falsification_cases || 4);
3
+ const cases = Array.isArray(falsificationLedger?.cases) ? falsificationLedger.cases : [];
4
+ const completeCases = cases.filter((entry) => {
5
+ return String(entry?.id || '').trim()
6
+ && String(entry?.target_claim || entry?.claim_id || '').trim()
7
+ && String(entry?.attack || entry?.counterexample || '').trim()
8
+ && Array.isArray(entry?.source_ids)
9
+ && entry.source_ids.length > 0
10
+ && String(entry?.next_decisive_test || '').trim();
11
+ });
12
+ const blockers = [
13
+ ...(cases.length < minCases ? ['falsification_cases_below_contract'] : []),
14
+ ...(completeCases.length < minCases ? ['falsification_cases_incomplete'] : [])
15
+ ];
16
+ return { ok: blockers.length === 0, blockers, cases: cases.length, complete_cases: completeCases.length, min_cases: minCases };
17
+ }
18
+ //# sourceMappingURL=falsification.js.map
@@ -0,0 +1,31 @@
1
+ export const IMPLEMENTATION_BLUEPRINT_MARKDOWN_ARTIFACT = 'implementation-blueprint.md';
2
+ export function renderImplementationBlueprintMarkdown(blueprint = null) {
3
+ const lines = [];
4
+ lines.push('# Research Implementation Blueprint');
5
+ lines.push('');
6
+ lines.push(`Prompt: ${blueprint?.prompt || ''}`);
7
+ lines.push(`Handoff route: ${blueprint?.handoff_route || '$Team'}`);
8
+ lines.push(`Implementation allowed in Research: ${blueprint?.implementation_allowed_in_research === true ? 'yes' : 'no'}`);
9
+ lines.push('');
10
+ lines.push('## Sections');
11
+ for (const section of Array.isArray(blueprint?.sections) ? blueprint.sections : []) {
12
+ lines.push(`### ${section.title || section.id}`);
13
+ lines.push('');
14
+ lines.push(String(section.detail || ''));
15
+ if (Array.isArray(section.acceptance_checks) && section.acceptance_checks.length) {
16
+ lines.push('');
17
+ lines.push('Acceptance checks:');
18
+ for (const check of section.acceptance_checks)
19
+ lines.push(`- ${check}`);
20
+ }
21
+ lines.push('');
22
+ }
23
+ if (Array.isArray(blueprint?.risks) && blueprint.risks.length) {
24
+ lines.push('## Risks');
25
+ for (const risk of blueprint.risks)
26
+ lines.push(`- ${risk}`);
27
+ lines.push('');
28
+ }
29
+ return `${lines.join('\n')}\n`;
30
+ }
31
+ //# sourceMappingURL=implementation-blueprint-markdown.js.map
@@ -0,0 +1,66 @@
1
+ import path from 'node:path';
2
+ import { nowIso, readJson, writeJsonAtomic } from '../fsx.js';
3
+ export const IMPLEMENTATION_BLUEPRINT_ARTIFACT = 'implementation-blueprint.json';
4
+ const DEFAULT_SECTION_IDS = Object.freeze([
5
+ 'problem',
6
+ 'decision',
7
+ 'architecture',
8
+ 'interfaces',
9
+ 'data_contracts',
10
+ 'execution_plan',
11
+ 'verification_plan',
12
+ 'risks_and_rollbacks'
13
+ ]);
14
+ export function defaultImplementationBlueprint(plan = null) {
15
+ const prompt = String(plan?.prompt || 'research mission');
16
+ return {
17
+ schema: 'sks.research-implementation-blueprint.v1',
18
+ generated_at: nowIso(),
19
+ prompt,
20
+ implementation_allowed_in_research: false,
21
+ handoff_route: '$Team',
22
+ sections: DEFAULT_SECTION_IDS.map((id, index) => ({
23
+ id,
24
+ title: id.split('_').map((part) => part[0]?.toUpperCase() + part.slice(1)).join(' '),
25
+ order: index + 1,
26
+ detail: `Research handoff detail for ${id} on: ${prompt}`,
27
+ evidence_claim_ids: [],
28
+ target_paths: [],
29
+ acceptance_checks: [`${id} is reviewed against cited research artifacts before implementation.`]
30
+ })),
31
+ dependencies: [],
32
+ out_of_scope: ['Repository source mutation during $Research runs.'],
33
+ open_questions: []
34
+ };
35
+ }
36
+ export function validateImplementationBlueprint(blueprint = null, contract = null) {
37
+ const minSections = Number(contract?.min_implementation_blueprint_sections || contract?.min_blueprint_sections || 8);
38
+ const sections = Array.isArray(blueprint?.sections) ? blueprint.sections : [];
39
+ const completeSections = sections.filter((section) => {
40
+ return String(section?.id || '').trim()
41
+ && String(section?.title || '').trim()
42
+ && String(section?.detail || '').trim()
43
+ && Array.isArray(section?.acceptance_checks)
44
+ && section.acceptance_checks.length > 0;
45
+ });
46
+ const blockers = [
47
+ ...(blueprint ? [] : ['implementation_blueprint_missing']),
48
+ ...(sections.length < minSections ? ['implementation_blueprint_sections_below_contract'] : []),
49
+ ...(completeSections.length < minSections ? ['implementation_blueprint_incomplete_sections'] : [])
50
+ ];
51
+ return {
52
+ ok: blockers.length === 0,
53
+ blockers,
54
+ sections: sections.length,
55
+ complete_sections: completeSections.length,
56
+ min_sections: minSections
57
+ };
58
+ }
59
+ export async function readImplementationBlueprint(dir) {
60
+ return readJson(path.join(dir, IMPLEMENTATION_BLUEPRINT_ARTIFACT), null);
61
+ }
62
+ export async function writeImplementationBlueprint(dir, blueprint) {
63
+ await writeJsonAtomic(path.join(dir, IMPLEMENTATION_BLUEPRINT_ARTIFACT), blueprint);
64
+ return blueprint;
65
+ }
66
+ //# sourceMappingURL=implementation-blueprint.js.map