futura-scion 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +205 -0
- package/bin/scion.js +253 -11
- package/knowledge/architecture.yaml +98 -0
- package/knowledge/core.yaml +99 -0
- package/knowledge/git-sdlc.yaml +82 -0
- package/knowledge/javascript.yaml +89 -0
- package/knowledge/lexicon.de.yaml +24 -0
- package/knowledge/lexicon.ja.yaml +25 -0
- package/knowledge/lexicon.tr.yaml +24 -0
- package/knowledge/performance.yaml +85 -0
- package/knowledge/python.yaml +85 -0
- package/knowledge/security.yaml +97 -0
- package/knowledge/testing.yaml +85 -0
- package/package.json +5 -1
- package/src/brain/conversations.js +84 -0
- package/src/brain/db.js +174 -1
- package/src/brain/embedding.js +120 -0
- package/src/brain/forgetting.js +222 -0
- package/src/brain/memory-graph.js +242 -0
- package/src/brain/memory.js +102 -9
- package/src/config.js +47 -3
- package/src/http.js +387 -0
- package/src/index.js +3 -1
- package/src/kernel/maintain.js +22 -2
- package/src/kernel/trail.js +37 -4
- package/src/kernel/watchdog.js +79 -0
- package/src/mind/brief.js +385 -0
- package/src/mind/calltree-context.js +129 -0
- package/src/mind/claims.js +348 -0
- package/src/mind/completeness.js +182 -0
- package/src/mind/generator.js +9 -1
- package/src/mind/grade.js +184 -0
- package/src/mind/harness-gate.js +235 -0
- package/src/mind/harness-miner.js +196 -0
- package/src/mind/harness-propose.js +163 -0
- package/src/mind/interventions.js +186 -0
- package/src/mind/knowledge.js +165 -0
- package/src/mind/langs.js +270 -0
- package/src/mind/learn-loop.js +204 -0
- package/src/mind/nlu.js +117 -4
- package/src/mind/provider.js +109 -0
- package/src/mind/tools.js +46 -0
- package/src/ui/app.js +434 -0
- package/src/ui/index.html +156 -0
- package/src/ui/style.css +175 -0
- package/src/worker.js +21 -0
package/README.md
CHANGED
|
@@ -53,6 +53,63 @@ scion run "fix var in src/legacy.js" --intent fix # fix + gate-verify + certi
|
|
|
53
53
|
scion brain --all # fleet-brain memory inventory
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
+
## The brief organ — context in, verified contract out
|
|
57
|
+
|
|
58
|
+
FS's rungs act on commands, but real work arrives as CONTEXT — tickets,
|
|
59
|
+
specs, conversations. The brief organ (`scion brief`, `docs/brief.md`)
|
|
60
|
+
ingests a context document and produces a structured problem model:
|
|
61
|
+
entities, business rules (`MUST`/`never` sentences), acceptance criteria
|
|
62
|
+
(Gherkin `Given/When/Then`, bullets). Each requirement is classified
|
|
63
|
+
**checkable** (compiles to a Gate verifier command — the same oracle that
|
|
64
|
+
judges code judges the business rules) or **judgment** (kept visible for
|
|
65
|
+
the clarify/review loop, never dropped). A failing requirement **vetoes
|
|
66
|
+
completeness**, so FS cannot declare work done while the contract is
|
|
67
|
+
broken.
|
|
68
|
+
|
|
69
|
+
## Temporal memory: forgetting, supersession, semantic recall (cortex lineage)
|
|
70
|
+
|
|
71
|
+
The brain never grows stale or noisy — two deterministic organs (ported from
|
|
72
|
+
agentic-cortex, LLM-free throughout) keep it sharp:
|
|
73
|
+
|
|
74
|
+
- **Temporal expiry** — a memory whose text carries a deadline (`2026-12-25`,
|
|
75
|
+
`in 3 days`, `tomorrow`, or an explicit `ttlDays`) gets that lifespan on
|
|
76
|
+
save; `scion brain --sweep` soft-deletes everything whose time has passed.
|
|
77
|
+
- **Supersession** — saving a statement-like fact with a near-identical title
|
|
78
|
+
(`deploy is Monday` over `deploy is Friday`) marks the old fact
|
|
79
|
+
`superseded_by` the new one and excludes it from retrieval. Gate-verified
|
|
80
|
+
memories are append-only evidence and never auto-supersede.
|
|
81
|
+
- **Semantic recall** — every memory carries a deterministic 512-dim
|
|
82
|
+
hashed-feature vector (no model, no download, byte-identical on every
|
|
83
|
+
machine). Hybrid search blends FTS keyword rank with vector similarity;
|
|
84
|
+
when FTS cannot match at all (morphological near-misses like `pools
|
|
85
|
+
exhausting` vs `pool exhaustion`), retrieval falls back to vector-only.
|
|
86
|
+
`scion brain` reports the forgetting ledger (`superseded` / `expired`).
|
|
87
|
+
|
|
88
|
+
## FS Desktop (the UI)
|
|
89
|
+
|
|
90
|
+
The web UI ships inside the npm package — one command after install:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
scion ui # boots the kernel + opens FS Desktop in the browser
|
|
94
|
+
# or: scion serve, then open http://127.0.0.1:5107/ui
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Windows installer (two-tier)
|
|
98
|
+
|
|
99
|
+
A native Windows app is also built from this repo:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
cd desktop && npm install && npm run dist # → dist-desktop/FS-Desktop-Setup-<ver>.exe
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The shell is a thin Electron host (~78 MB installer) that **detects the npm kernel at
|
|
106
|
+
launch**: if `futura-scion` is installed globally (or `SCION_BIN` is set) it spawns
|
|
107
|
+
`scion serve` itself and opens the UI window; otherwise it shows a guided setup screen
|
|
108
|
+
with the one-line install command and a Retry button. The kernel always runs under the
|
|
109
|
+
user's own Node — updates flow through `npm update -g futura-scion` without
|
|
110
|
+
re-installing the shell. Silent install: `FS-Desktop-Setup-<ver>.exe /S`.
|
|
111
|
+
|
|
112
|
+
|
|
56
113
|
`better-sqlite3` ships prebuilt binaries for common platforms (Linux/macOS/Windows,
|
|
57
114
|
x64/arm64); exotic platforms need build tools. FS keeps its brain per project
|
|
58
115
|
(`.scion/`), reads config from `config/scion.config.yaml` (or shipped defaults),
|
|
@@ -617,6 +674,154 @@ default) keeps it fully inert even for tasks that opt in with
|
|
|
617
674
|
`payload.generate: true`. The doctrine is unchanged — the LLM proposes,
|
|
618
675
|
the Gate disposes, the certificate proves it.
|
|
619
676
|
|
|
677
|
+
### Self-improvement — the harness evolves under its own Gate (`scion evolve`)
|
|
678
|
+
|
|
679
|
+
FS applies its verification doctrine to ITSELF. The nightly evolution loop
|
|
680
|
+
(imp_doc adoption, zero-LLM) is: **mine → propose → gate → apply**.
|
|
681
|
+
|
|
682
|
+
- **Weakness miner** — failures from the queue and trail are normalized to
|
|
683
|
+
signature hashes (`failureSignatureOf`); a signature with ≥3 occurrences
|
|
684
|
+
across ≥2 goal families is a HARNESS weakness (not one goal's bad luck),
|
|
685
|
+
classified into the 4-layer taxonomy (environment-contract /
|
|
686
|
+
procedural-skill / action-realization / trajectory-regulation).
|
|
687
|
+
- **Proposals** — deterministic per layer (bounds-field, verifier-recipe,
|
|
688
|
+
tool-filter-rule, spec-note), hard 20-line diff budget, targets must
|
|
689
|
+
exist. No LLM anywhere; a generator can be attached later but is still
|
|
690
|
+
gate-checked.
|
|
691
|
+
- **The harness-edit Gate** — SICA utility scoring
|
|
692
|
+
(`U = 0.5·passRate + 0.25·(1−cost) + 0.25·(1−time)`) over the replay
|
|
693
|
+
window: accept only if utility improves AND pass-rate does not regress.
|
|
694
|
+
Every decision lands in the `harness_edits` audit table; rejected
|
|
695
|
+
edit-hashes are remembered (never re-proposed); bounded to ≤3 accepted
|
|
696
|
+
edits per batch; addressed signatures skip (idempotent). Gate-passing but
|
|
697
|
+
utility-unproven edits ARCHIVE in `harness_variants` for monthly re-scoring
|
|
698
|
+
(`scion evolve variants`) — population thinking, nothing promising is lost.
|
|
699
|
+
|
|
700
|
+
```bash
|
|
701
|
+
scion evolve # one bounded, idempotent batch
|
|
702
|
+
scion evolve --dry-run # mine + propose only
|
|
703
|
+
scion evolve variants # re-score the archive (G4)
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
### Stuck-trajectory watchdog + completeness + grading (the guardrails)
|
|
707
|
+
|
|
708
|
+
- **Watchdog** (`src/kernel/watchdog.js`) — hash consecutive `(action,
|
|
709
|
+
state)` pairs; 3 identical → stuck, abort before burning the bounds.
|
|
710
|
+
Key-order-insensitive, pure, also usable as an audit (`findStuckPoint`).
|
|
711
|
+
- **Completeness** (`src/mind/completeness.js`) — every research bundle now
|
|
712
|
+
carries a deterministic completeness verdict: entity coverage,
|
|
713
|
+
subquestion corroboration (stem-folded), source-quality floor. Incomplete
|
|
714
|
+
results produce gap-targeted `replan_hints` and a bounded replan loop
|
|
715
|
+
(`verifyWithReplan`, ≤2 replans) — the VMAO orchestration-level check.
|
|
716
|
+
- **Run records + grading** (`scion eval`) — every task appends a portable
|
|
717
|
+
JSONL run record to `eval/runs.jsonl` (trajectory, cost, outcome); the
|
|
718
|
+
3-scorer grader (outcome 0.5 / trajectory 0.3 / economy 0.2) emits
|
|
719
|
+
per-run grades and a trend file. `scion eval regression` gates on
|
|
720
|
+
per-family pass-rate drops; `scion eval capability` trends pass-rate per
|
|
721
|
+
difficulty tier.
|
|
722
|
+
|
|
723
|
+
### Graph-linked memory + interventions (the brain connects and travels)
|
|
724
|
+
|
|
725
|
+
- **Memory graph** — every save links into `memory_links`:
|
|
726
|
+
token-overlap → `related` (weight = Jaccard), same family + opposite
|
|
727
|
+
outcome polarity → `contradicts` (the older side ranks lower —
|
|
728
|
+
suppression, never deletion), newer-higher-confidence → `supersedes`.
|
|
729
|
+
Recall does 1-hop expansion (neighbors at ×0.5) and trust ranking:
|
|
730
|
+
gate-verified memories weigh 1.0, observed 0.95, web 0.7, LLM 0.6.
|
|
731
|
+
- **Interventions** (`scion interventions import [dir]`) — accepted harness
|
|
732
|
+
edits export as `interventions/<hash>.intervention.yaml` artifacts
|
|
733
|
+
(signature, layer, edit, evidence, provenance) and import into another
|
|
734
|
+
FS instance ONLY if that instance's own history shows the same failure
|
|
735
|
+
signature ≥2 times: no blind cross-pollination. This is the fleet brain's
|
|
736
|
+
transport format for learned fixes.
|
|
737
|
+
|
|
738
|
+
### Call-tree context + indexed actions (Jev/LLM-as-code discipline)
|
|
739
|
+
|
|
740
|
+
- **Call-tree context** (`src/mind/calltree-context.js`) — the generate
|
|
741
|
+
rung's context is the task's ANCESTOR CHAIN from the trail, each ancestor
|
|
742
|
+
budget-capped by depth (deeper → less), total ≤4000 chars — replacing
|
|
743
|
+
flat truncation. Flat inputs stay backward compatible.
|
|
744
|
+
- **Indexed action space** — clarification options render as numbered
|
|
745
|
+
ACTIONS with executable consequences (`[2] analyze the named file and
|
|
746
|
+
apply a gate-verified fix`); answering `2` is a complete decision.
|
|
747
|
+
- **Pre-execution revalidation** — tool calls re-check freshness before
|
|
748
|
+
spawning: file targets that vanished since the decision refuse loudly
|
|
749
|
+
(`stale decision`) instead of executing into a world that moved.
|
|
750
|
+
|
|
751
|
+
### The learn → build → improve → repeat layer (`scion learn`)
|
|
752
|
+
|
|
753
|
+
The mandate: FS must get BETTER at every kind of problem it takes on —
|
|
754
|
+
software, research, finding things — from its own mistakes, without anyone
|
|
755
|
+
remembering to run a command. This layer is the always-on circulatory
|
|
756
|
+
system connecting every learning organ:
|
|
757
|
+
|
|
758
|
+
- **LEARN** — every task outcome (verified fix, gate failure, escalation,
|
|
759
|
+
error) is observed automatically by the worker and written as a graded
|
|
760
|
+
run record. Failures are the raw material; nothing escapes the loop.
|
|
761
|
+
- **BUILD** — repeated verified fixes forge into seeds (precision-floored);
|
|
762
|
+
clarifications teach the NLU lexicon; interventions export to the fleet.
|
|
763
|
+
- **IMPROVE** — the weakness miner finds recurring failure patterns across
|
|
764
|
+
ALL domains; the harness gate utility-verifies bounded self-edits; the
|
|
765
|
+
variant archive re-scores what didn't win yet.
|
|
766
|
+
- **REPEAT** — every cycle is measured into the `learn_cycles` ledger
|
|
767
|
+
(pass rate, replay hit rate, deterministic share, seeds, edits accepted);
|
|
768
|
+
improvement is a TREND over rows, never a claim.
|
|
769
|
+
|
|
770
|
+
```bash
|
|
771
|
+
scion learn # one full cycle now
|
|
772
|
+
scion learn trend # is FS improving? (ledger-backed, ≥2 cycles)
|
|
773
|
+
scion learn vitals # current vital signs
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
With `learn: { auto: true }` in config, `scion serve` runs a cycle every
|
|
777
|
+
`learn.interval_ms` (default 6h) in-process — the harness improves itself
|
|
778
|
+
while it works. Zero-LLM by construction.
|
|
779
|
+
|
|
780
|
+
### FS Desktop — the standalone UI (chat / agent / plan / architect)
|
|
781
|
+
|
|
782
|
+
FS ships a real UI in two forms, both zero-build:
|
|
783
|
+
|
|
784
|
+
- **Web console (ships in the npm package):** `scion serve` now serves the
|
|
785
|
+
FS Desktop SPA at `http://127.0.0.1:5107/ui` — open it in any browser.
|
|
786
|
+
Nothing extra to install; works over SSH tunnels and on remote machines.
|
|
787
|
+
- **Electron desktop shell (`desktop/`):** a thin host that boots the kernel
|
|
788
|
+
as a child process and renders the same UI in a native window.
|
|
789
|
+
```bash
|
|
790
|
+
npm run desktop # dev (needs: cd desktop && npm install)
|
|
791
|
+
npm run desktop:build # installers (NSIS / DMG / AppImage) via electron-builder
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
**Surfaces** (every action goes through the same kernel routes the CLI and
|
|
795
|
+
MCP use — the UI can never do more than the kernel allows):
|
|
796
|
+
|
|
797
|
+
- **Chat with streaming + history** — replies stream over SSE with live
|
|
798
|
+
NLU/agent progress events; every conversation persists in the brain with a
|
|
799
|
+
sidebar (new / open / rename / delete), auto-titling, and secret redaction
|
|
800
|
+
at write time — history survives restarts and travels with the DB.
|
|
801
|
+
- **Chat mode** — plain language in, state-grounded answers out: status,
|
|
802
|
+
brain/economy/queue lookups, brain search. Deterministic NLU; no LLM.
|
|
803
|
+
- **Agent mode** — real work: your utterance is NLU-interpreted, slotted,
|
|
804
|
+
enqueued, and run through the full ladder→Gate pipeline with a rung+
|
|
805
|
+
certificate summary in the transcript.
|
|
806
|
+
- **Plan mode** — research with tools + the deterministic completeness
|
|
807
|
+
verdict (entity coverage, corroboration, source floor) as evidence.
|
|
808
|
+
- **Architect mode** — one-click declared-architecture scan with violations.
|
|
809
|
+
- **Review queue** — the human gate in the loop: escalations with the
|
|
810
|
+
ladder's last proposal, Approve/Reject/Defer, badge counts live.
|
|
811
|
+
- **Trail / Brain / Economy dashboards** — the journaled decision trail,
|
|
812
|
+
brain stats, and the token-economy report as live views.
|
|
813
|
+
- **Autonomy toggle** — semi-autonomous (default: each agent action asks
|
|
814
|
+
through the Gate + Review flow) vs autonomous (apply directly; the Gate
|
|
815
|
+
still verifies everything, destructive work still escalates).
|
|
816
|
+
|
|
817
|
+
**LLM attach (optional):** Settings → Provider covers Ollama, LM Studio,
|
|
818
|
+
OpenAI, Anthropic, OpenRouter, and any OpenAI-compatible endpoint, with a
|
|
819
|
+
connection test. Saving sets `llm.provider/baseUrl/model/apiKey` +
|
|
820
|
+
`llm.daily_tokens` in config, which arms rung G (the governance-shell
|
|
821
|
+
generator): the model PROPOSES patches, the Gate verifies them by exit code,
|
|
822
|
+
and nothing unverified is learned or shipped. `daily_tokens: 0` keeps FS
|
|
823
|
+
fully deterministic — a local model (Ollama/LM Studio) works offline.
|
|
824
|
+
|
|
620
825
|
### Semi-autonomous auto-fix (`scion watch` + `daemon.auto_fix`)
|
|
621
826
|
|
|
622
827
|
By default the daemon only **detects and remembers**. With `daemon.auto_fix: true`
|
package/bin/scion.js
CHANGED
|
@@ -16,8 +16,10 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
import { createInterface } from 'node:readline';
|
|
19
|
-
import { existsSync } from 'node:fs';
|
|
19
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
20
20
|
import { resolve } from 'node:path';
|
|
21
|
+
import { fileURLToPath } from 'node:url';
|
|
22
|
+
import { spawnSync } from 'node:child_process';
|
|
21
23
|
import * as trail from '../src/kernel/trail.js';
|
|
22
24
|
import {
|
|
23
25
|
enqueue, runSwarm, economy, trailList, runTask, claim as claimTask,
|
|
@@ -90,7 +92,7 @@ async function oneShot(text, opts = {}, depth = 0) {
|
|
|
90
92
|
// persisted scopes (brain config). interpret() strips the @tokens itself.
|
|
91
93
|
const projectScopes = opts.noProjectScopes ? [] : getProjectScopes();
|
|
92
94
|
const mergedScopes = resolveScopes(text, { flag: opts.scope ?? null, projectScopes });
|
|
93
|
-
|
|
95
|
+
let interp = interpret(text, { existsSync: (p) => existsSync(p), intentOverride: opts.intentOverride, scopes: mergedScopes.length ? mergedScopes : undefined });
|
|
94
96
|
trail.journal('nlu.interpret', {
|
|
95
97
|
utterance: String(text).slice(0, 200),
|
|
96
98
|
intent: interp.intent,
|
|
@@ -102,6 +104,26 @@ async function oneShot(text, opts = {}, depth = 0) {
|
|
|
102
104
|
override: opts.intentOverride ?? null,
|
|
103
105
|
});
|
|
104
106
|
|
|
107
|
+
// Tier 3 — the System-1 language-parse rung: ONLY when the deterministic
|
|
108
|
+
// interpreter found no evidence at all, and an llm provider is configured
|
|
109
|
+
// AND budgeted (daily_tokens > 0 — off by default). The model parses the
|
|
110
|
+
// utterance into intent JSON; the SAME schema check validates it; an
|
|
111
|
+
// abstention falls through to the clarify loop. Never a guess.
|
|
112
|
+
if (interp.intentEvidence.length === 0 && interp.confidence === 0 && !interp.question && !opts.intentOverride) {
|
|
113
|
+
const cfg = loadConfig();
|
|
114
|
+
if (cfg.llm?.daily_tokens > 0 && cfg.llm?.baseUrl && cfg.llm?.model) {
|
|
115
|
+
const { llmInterpret } = await import('../src/mind/nlu.js');
|
|
116
|
+
const parsed = await llmInterpret(text, { llmCfg: cfg.llm });
|
|
117
|
+
trail.journal('nlu.llm-parse', {
|
|
118
|
+
utterance: String(text).slice(0, 200),
|
|
119
|
+
accepted: !!parsed,
|
|
120
|
+
intent: parsed?.intent ?? null,
|
|
121
|
+
confidence: parsed?.confidence ?? null,
|
|
122
|
+
});
|
|
123
|
+
if (parsed) interp = parsed;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
105
127
|
// Route pure service intents to their kernel surface — a question about
|
|
106
128
|
// state should not become a queue task.
|
|
107
129
|
if (interp.intent === 'show') {
|
|
@@ -158,7 +180,7 @@ async function oneShot(text, opts = {}, depth = 0) {
|
|
|
158
180
|
// Unresolved answer: fall through and surface the question.
|
|
159
181
|
}
|
|
160
182
|
}
|
|
161
|
-
return { ok: false, intent: 'clarify', prompt: cl.prompt, options: cl.options, open: cl.open, gap_id: gap.id };
|
|
183
|
+
return { ok: false, intent: 'clarify', prompt: cl.prompt, options: cl.options, open: cl.open, gap_id: gap.id, lang_notice: interp.lang_notice ?? null };
|
|
162
184
|
}
|
|
163
185
|
|
|
164
186
|
// Ordinary task — enriched with extracted slots.
|
|
@@ -310,17 +332,130 @@ switch (cmd || '') {
|
|
|
310
332
|
const db = getDb();
|
|
311
333
|
const scope = restArgs[0] === '--all' ? 'all'
|
|
312
334
|
: restArgs[0] === '--project' ? resolve(restArgs[1] || '.').replace(/\\/g, '/') : null;
|
|
335
|
+
// Temporal forgetting (cortex lineage): --sweep expires every memory
|
|
336
|
+
// whose lifespan has passed; --sweep --dry-run previews it.
|
|
337
|
+
if (arg === '--sweep' || restArgs.includes('--sweep')) {
|
|
338
|
+
const { sweepExpires } = await import('../src/brain/forgetting.js');
|
|
339
|
+
console.log(JSON.stringify(sweepExpires({ dryRun: arg === '--dry-run' || restArgs.includes('--dry-run') }), null, 2));
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
313
342
|
const perProject = db.prepare(
|
|
314
343
|
'SELECT project, COUNT(*) as memories, SUM(is_active) as active FROM memories GROUP BY project ORDER BY memories DESC'
|
|
315
344
|
).all();
|
|
345
|
+
const forgetting = db.prepare(
|
|
346
|
+
`SELECT SUM(CASE WHEN is_active = 0 AND superseded_by IS NOT NULL THEN 1 ELSE 0 END) as superseded,
|
|
347
|
+
SUM(CASE WHEN is_active = 0 AND expires_at IS NOT NULL THEN 1 ELSE 0 END) as expired,
|
|
348
|
+
SUM(CASE WHEN is_active = 1 AND expires_at IS NOT NULL THEN 1 ELSE 0 END) as expiring_future
|
|
349
|
+
FROM memories`
|
|
350
|
+
).get();
|
|
351
|
+
// Claims (YOINK lineage): open bets, settled record, the overconfidence
|
|
352
|
+
// gap, the Brier score, and the dead-memory census.
|
|
353
|
+
const { calibration, deadMemories } = await import('../src/mind/claims.js');
|
|
354
|
+
const claims = db.prepare(
|
|
355
|
+
'SELECT COUNT(*) as total, SUM(CASE WHEN outcome IS NULL THEN 1 ELSE 0 END) as open, SUM(CASE WHEN outcome IS NOT NULL THEN 1 ELSE 0 END) as settled FROM claims'
|
|
356
|
+
).get();
|
|
316
357
|
console.log(JSON.stringify({
|
|
317
358
|
...brainStats(),
|
|
318
359
|
...(scope ? { scope } : {}),
|
|
319
360
|
projects: perProject,
|
|
361
|
+
forgetting,
|
|
362
|
+
claims: { ...claims, record: calibration() },
|
|
363
|
+
dead_memories: deadMemories().length,
|
|
320
364
|
replay_conflicts: listConflicts(20),
|
|
321
365
|
}, null, 2));
|
|
322
366
|
break;
|
|
323
367
|
}
|
|
368
|
+
case 'brief': {
|
|
369
|
+
// The problem-understanding stage: ingest a context document (file or
|
|
370
|
+
// inline text), compile its requirements to Gate checks, optionally
|
|
371
|
+
// verify now. scion brief <file> [--verify] | scion brief show <id>
|
|
372
|
+
const briefs = await import('../src/mind/brief.js');
|
|
373
|
+
if (arg === 'show') {
|
|
374
|
+
try { console.log(JSON.stringify(briefs.getBrief(Number(restArgs[0])), null, 2)); }
|
|
375
|
+
catch (e) { console.error('✗ ' + e.message); process.exitCode = 1; }
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
if (arg === 'list') {
|
|
379
|
+
console.log(JSON.stringify(briefs.listBriefs(), null, 2));
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
382
|
+
try {
|
|
383
|
+
const { readFileSync } = await import('node:fs');
|
|
384
|
+
const text = arg && arg !== '-' && existsSync(arg) ? readFileSync(arg, 'utf8') : (arg || '');
|
|
385
|
+
if (!text.trim()) { console.error('✗ brief: provide a file path or inline text'); process.exitCode = 1; break; }
|
|
386
|
+
const b = briefs.ingest({ text, title: restArgs.find(x => !x.startsWith('--')), source: 'cli' });
|
|
387
|
+
const c = briefs.compileBrief(b.brief.id);
|
|
388
|
+
const out = { brief: b.brief, entities: b.entities.length, requirements: b.requirements.length, compiled: c.compiled, judgment: c.judgment };
|
|
389
|
+
if (process.argv.includes('--verify')) {
|
|
390
|
+
const v = await briefs.verifyBrief(b.brief.id);
|
|
391
|
+
out.verification = { passed: v.passed, failed: v.failed, coverage: v.coverage, uncheckable: v.uncheckable };
|
|
392
|
+
out.results = v.results;
|
|
393
|
+
}
|
|
394
|
+
console.log(JSON.stringify(out, null, 2));
|
|
395
|
+
} catch (e) { console.error('✗ ' + e.message); process.exitCode = 1; }
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
case 'lang': {
|
|
399
|
+
// The multilingual layer: list configured languages, or set the active
|
|
400
|
+
// one (persisted per brain, like project scopes).
|
|
401
|
+
const langs = await import('../src/mind/langs.js');
|
|
402
|
+
if (!arg || arg === 'list') {
|
|
403
|
+
const packs = langs.loadPacks();
|
|
404
|
+
console.log(JSON.stringify({
|
|
405
|
+
active: langs.activeLanguage(),
|
|
406
|
+
languages: ['en', ...langs.configuredLanguages()],
|
|
407
|
+
packs: Object.values(packs).map(p => ({ lang: p.lang, name: p.name, script: p.script, entries: p.entries.length })),
|
|
408
|
+
}, null, 2));
|
|
409
|
+
} else if (arg === 'set') {
|
|
410
|
+
try { console.log(JSON.stringify(langs.setActiveLanguage(restArgs[0]), null, 2)); }
|
|
411
|
+
catch (e) { console.error('✗ ' + e.message); process.exitCode = 1; }
|
|
412
|
+
} else {
|
|
413
|
+
try { console.log(JSON.stringify(langs.setActiveLanguage(arg), null, 2)); }
|
|
414
|
+
catch (e) { console.error('✗ ' + e.message); process.exitCode = 1; }
|
|
415
|
+
}
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
case 'claim': {
|
|
419
|
+
// YOINK lineage: turn a memory into a bet with a date on it.
|
|
420
|
+
// scion claim <memoryId> --claim "..." --settles 2027-01-01 --reads file:...#json.path --test "gte 10" --confidence 0.7
|
|
421
|
+
const { declare } = await import('../src/mind/claims.js');
|
|
422
|
+
const flag = (name) => {
|
|
423
|
+
const i = restArgs.indexOf('--' + name);
|
|
424
|
+
return i >= 0 ? restArgs[i + 1] : undefined;
|
|
425
|
+
};
|
|
426
|
+
try {
|
|
427
|
+
const r = declare({
|
|
428
|
+
memoryId: Number(arg),
|
|
429
|
+
claim: flag('claim'),
|
|
430
|
+
settles: flag('settles'),
|
|
431
|
+
reads: flag('reads'),
|
|
432
|
+
test: flag('test'),
|
|
433
|
+
confidence: Number(flag('confidence')),
|
|
434
|
+
});
|
|
435
|
+
console.log(JSON.stringify(r, null, 2));
|
|
436
|
+
} catch (e) {
|
|
437
|
+
console.error('✗ ' + e.message); process.exitCode = 1;
|
|
438
|
+
}
|
|
439
|
+
break;
|
|
440
|
+
}
|
|
441
|
+
case 'settle': {
|
|
442
|
+
// Settle everything due: read the sources, apply the tests, write the
|
|
443
|
+
// outcomes back beside the thoughts. Unreadable sources stay open.
|
|
444
|
+
const { settleAll } = await import('../src/mind/claims.js');
|
|
445
|
+
const r = await settleAll();
|
|
446
|
+
console.log(JSON.stringify(r, null, 2));
|
|
447
|
+
break;
|
|
448
|
+
}
|
|
449
|
+
case 'calibrate': {
|
|
450
|
+
// The record: said vs right — the gap and the Brier score.
|
|
451
|
+
const { calibration, deadMemories } = await import('../src/mind/claims.js');
|
|
452
|
+
console.log(JSON.stringify({
|
|
453
|
+
record: calibration(),
|
|
454
|
+
dead: deadMemories({ minAgeDays: Number(restArgs[0]) || 14 }),
|
|
455
|
+
trail_chain: (await import('../src/kernel/trail.js')).verifyChain(),
|
|
456
|
+
}, null, 2));
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
324
459
|
case 'conflicts': {
|
|
325
460
|
const { listConflicts, clearConflicts } = await import('../src/mind/replay.js');
|
|
326
461
|
if (arg === '--clear') {
|
|
@@ -441,6 +576,54 @@ switch (cmd || '') {
|
|
|
441
576
|
console.log(JSON.stringify({ dry_run: dryRun, ...result }, null, 2));
|
|
442
577
|
break;
|
|
443
578
|
}
|
|
579
|
+
case 'eval': {
|
|
580
|
+
// Portable run records + grading (workstream F):
|
|
581
|
+
// scion eval regression per-family pass-rate drop check (gate-able)
|
|
582
|
+
// scion eval capability pass-rate per difficulty tier (trend only)
|
|
583
|
+
const { regressionCheck, capabilityReport } = await import('../src/mind/grade.js');
|
|
584
|
+
if (arg === 'capability') console.log(JSON.stringify(capabilityReport(), null, 2));
|
|
585
|
+
else console.log(JSON.stringify(regressionCheck(), null, 2));
|
|
586
|
+
const reg = regressionCheck();
|
|
587
|
+
if (arg !== 'capability' && Object.values(reg).some(f => f.ok === false)) process.exitCode = 1;
|
|
588
|
+
break;
|
|
589
|
+
}
|
|
590
|
+
case 'interventions': {
|
|
591
|
+
// Shared failure-intervention library (workstream E):
|
|
592
|
+
// scion interventions import [dir] import artifacts with the local-occurrence floor
|
|
593
|
+
const { importInterventions } = await import('../src/mind/interventions.js');
|
|
594
|
+
const dir = arg && arg !== 'import' ? arg : 'interventions';
|
|
595
|
+
console.log(JSON.stringify(importInterventions(dir), null, 2));
|
|
596
|
+
break;
|
|
597
|
+
}
|
|
598
|
+
case 'learn': {
|
|
599
|
+
// THE learn → build → improve → repeat layer:
|
|
600
|
+
// scion learn one full cycle (mine → gate → forge → rescore → ledger)
|
|
601
|
+
// scion learn trend is FS improving? (ledger-backed, ≥2 cycles)
|
|
602
|
+
// scion learn vitals the current vital signs
|
|
603
|
+
const loop = await import('../src/mind/learn-loop.js');
|
|
604
|
+
if (arg === 'trend') console.log(JSON.stringify(loop.trend(), null, 2));
|
|
605
|
+
else if (arg === 'vitals') console.log(JSON.stringify(loop.vitals(), null, 2));
|
|
606
|
+
else console.log(JSON.stringify(loop.cycle(), null, 2));
|
|
607
|
+
break;
|
|
608
|
+
}
|
|
609
|
+
case 'evolve': {
|
|
610
|
+
// The nightly harness-evolution pass (A4): mine weaknesses → propose
|
|
611
|
+
// harness edits → gate them (SICA utility) → apply the accepted. Bounded
|
|
612
|
+
// (≤3 accepted per batch), idempotent (addressed signatures skip),
|
|
613
|
+
// zero-LLM. `scion evolve --dry-run` mines+proposes but applies nothing.
|
|
614
|
+
// scion evolve one bounded batch
|
|
615
|
+
// scion evolve --dry-run mine + propose only
|
|
616
|
+
// scion evolve variants re-score archived harness variants (G4)
|
|
617
|
+
const hGate = await import('../src/mind/harness-gate.js');
|
|
618
|
+
if (arg === 'variants') {
|
|
619
|
+
console.log(JSON.stringify(hGate.rescoreVariants({}), null, 2));
|
|
620
|
+
break;
|
|
621
|
+
}
|
|
622
|
+
const dryRun = arg === '--dry-run';
|
|
623
|
+
const result = hGate.evolveBatch(dryRun ? { applyAfter: undefined } : {});
|
|
624
|
+
console.log(JSON.stringify({ dry_run: dryRun, ...result }, null, 2));
|
|
625
|
+
break;
|
|
626
|
+
}
|
|
444
627
|
case 'recipes': {
|
|
445
628
|
// The recipe library: gate verifier bundles per stack.
|
|
446
629
|
// scion recipes list the library
|
|
@@ -565,9 +748,21 @@ switch (cmd || '') {
|
|
|
565
748
|
// scion conventions <file> deviations of one file (evidence rows)
|
|
566
749
|
const c = await import('../src/mind/conventions.js');
|
|
567
750
|
if (arg && !arg.startsWith('--')) {
|
|
568
|
-
const { readFileSync } = await import('node:fs');
|
|
569
|
-
|
|
570
|
-
|
|
751
|
+
const { readFileSync, statSync } = await import('node:fs');
|
|
752
|
+
let st = null;
|
|
753
|
+
try { st = statSync(arg); } catch { /* handled below */ }
|
|
754
|
+
if (st?.isDirectory()) {
|
|
755
|
+
// A directory argument means: discover + persist over that tree.
|
|
756
|
+
const result = c.discoverConventions(arg);
|
|
757
|
+
c.saveConventions(result);
|
|
758
|
+
console.log(JSON.stringify({ ok: true, files: result.files, conventions: result.conventions }, null, 2));
|
|
759
|
+
} else if (st?.isFile()) {
|
|
760
|
+
const source = readFileSync(arg, 'utf8');
|
|
761
|
+
console.log(JSON.stringify({ file: arg, deviations: c.deviationsFor(source) }, null, 2));
|
|
762
|
+
} else {
|
|
763
|
+
console.error(`conventions: no such file or directory: ${arg}`);
|
|
764
|
+
process.exitCode = 1;
|
|
765
|
+
}
|
|
571
766
|
} else {
|
|
572
767
|
const result = c.discoverConventions(arg && arg !== '--all' ? arg : 'src');
|
|
573
768
|
c.saveConventions(result);
|
|
@@ -641,13 +836,37 @@ switch (cmd || '') {
|
|
|
641
836
|
});
|
|
642
837
|
const api = await startApi({ port: Number(arg) || undefined, leader });
|
|
643
838
|
leader.start();
|
|
839
|
+
// FS Desktop support: provider-backed generator (rung G) from config,
|
|
840
|
+
// and the default agent-mode runTask over the real oracle.
|
|
841
|
+
const { makeGenerator } = await import('../src/mind/provider.js');
|
|
842
|
+
const { configureLlm } = await import('../src/ladder.js');
|
|
843
|
+
const generator = makeGenerator(cfg.llm);
|
|
844
|
+
configureLlm({ generator, daily_tokens: cfg.llm?.daily_tokens ?? 0 });
|
|
845
|
+
const { setRunTask } = await import('../src/http.js');
|
|
846
|
+
setRunTask(async (task) => runTask(claimTask('ui', task.id), oracle));
|
|
847
|
+
// The always-on learn→build→improve→repeat layer (config learn.auto).
|
|
848
|
+
if (cfg.learn?.auto) {
|
|
849
|
+
const { startAuto } = await import('../src/mind/learn-loop.js');
|
|
850
|
+
startAuto();
|
|
851
|
+
console.log(` learn-loop: auto-cycle every ${Math.round((cfg.learn.interval_ms ?? 21600000) / 60000)} min`);
|
|
852
|
+
}
|
|
644
853
|
// Observability (env-gated): OTLP log export + ntfy push.
|
|
645
854
|
const { startObservers } = await import('../src/kernel/observe.js');
|
|
646
855
|
startObservers();
|
|
647
856
|
console.log(`scion serve: http://127.0.0.1:${api.port} role=${leader.role}${leader.primaryUrl ? ` primary=${leader.primaryUrl}` : ''} (SCION_TOKEN guards routes when set)`);
|
|
648
|
-
console.log(
|
|
857
|
+
console.log(` UI: http://127.0.0.1:${api.port}/ui ← FS Desktop (chat, agent, dashboards)`);
|
|
858
|
+
console.log(' flags: --follower --lease-ms N --leader-poll-ms N --primary-url URL [--open]');
|
|
859
|
+
// --open: launch the default browser at the UI (best-effort, every OS).
|
|
860
|
+
if (process.argv.includes('--open')) {
|
|
861
|
+
const { spawn } = await import('node:child_process');
|
|
862
|
+
const url = `http://127.0.0.1:${api.port}/ui`;
|
|
863
|
+
const opener = process.platform === 'win32' ? spawn('cmd', ['/c', 'start', '', url], { detached: true, stdio: 'ignore' })
|
|
864
|
+
: process.platform === 'darwin' ? spawn('open', [url], { detached: true, stdio: 'ignore' })
|
|
865
|
+
: spawn('xdg-open', [url], { detached: true, stdio: 'ignore' });
|
|
866
|
+
opener.unref();
|
|
867
|
+
}
|
|
649
868
|
const sweep = setInterval(() => {
|
|
650
|
-
|
|
869
|
+
dailyMaintenance().catch(() => { /* maintenance never kills the server */ });
|
|
651
870
|
}, 60_000);
|
|
652
871
|
for (const sig of ['SIGINT', 'SIGTERM']) {
|
|
653
872
|
process.on(sig, async () => {
|
|
@@ -659,6 +878,16 @@ switch (cmd || '') {
|
|
|
659
878
|
}
|
|
660
879
|
break;
|
|
661
880
|
}
|
|
881
|
+
case 'ui': {
|
|
882
|
+
// `scion ui` — the one-command desktop experience: serve + open the UI.
|
|
883
|
+
// Identical to `scion serve --open`; a separate word because that's what
|
|
884
|
+
// people type after `npm i -g futura-scion`.
|
|
885
|
+
const self = process.argv[1];
|
|
886
|
+
const rest = process.argv.slice(2).filter(a => a !== 'ui');
|
|
887
|
+
const r = spawnSync(process.execPath, [self, 'serve', ...rest, '--open'], { stdio: 'inherit', env: process.env });
|
|
888
|
+
process.exitCode = r.status ?? 0;
|
|
889
|
+
break;
|
|
890
|
+
}
|
|
662
891
|
case 'remote': {
|
|
663
892
|
// Distributed tier: drain a remote scion's queue from THIS machine.
|
|
664
893
|
// scion remote --url http://host:5107 --workerId rw-1 [--max N] [--token T]
|
|
@@ -687,9 +916,13 @@ switch (cmd || '') {
|
|
|
687
916
|
}
|
|
688
917
|
case 'dash': {
|
|
689
918
|
// Live TUI dashboard — one ANSI refresh loop over kernel state.
|
|
919
|
+
// `scion dash <frames> <intervalMs>` runs N frames then exits (CI/trial
|
|
920
|
+
// friendly); bare `scion dash` loops until Ctrl+C as before.
|
|
690
921
|
const { runDashboard } = await import('../src/kernel/dash.js');
|
|
691
|
-
const
|
|
692
|
-
|
|
922
|
+
const nums = [arg, ...restArgs].filter(a => /^\d+$/.test(a)).map(Number);
|
|
923
|
+
const frames = nums[0] ?? null;
|
|
924
|
+
const intervalMs = nums[1] ?? 2000;
|
|
925
|
+
await runDashboard({ intervalMs, ...(frames ? { maxFrames: frames } : {}) });
|
|
693
926
|
break;
|
|
694
927
|
}
|
|
695
928
|
case 'mcp': {
|
|
@@ -777,7 +1010,16 @@ switch (cmd || '') {
|
|
|
777
1010
|
}
|
|
778
1011
|
break;
|
|
779
1012
|
}
|
|
1013
|
+
case '--version':
|
|
1014
|
+
case '-v': {
|
|
1015
|
+
// Self-reported version — the FS Desktop shell probes this to detect the
|
|
1016
|
+
// kernel and warn on shell/kernel version skew.
|
|
1017
|
+
let kernelPkg = { version: 'dev' };
|
|
1018
|
+
try { kernelPkg = JSON.parse(readFileSync(resolve(fileURLToPath(import.meta.url), '../../package.json'), 'utf8')); } catch { /* dev checkout without package.json */ }
|
|
1019
|
+
console.log(kernelPkg.version);
|
|
1020
|
+
break;
|
|
1021
|
+
}
|
|
780
1022
|
default:
|
|
781
|
-
console.error(`unknown command: ${cmd}\nusage: scion [run "<task>" | swarm [n] | plan <target> [--out m.json] [--run] | workflow <manifest.json> | analyze [dir] | fix <file> | watch [dir] [--auto-fix] [--concurrency N] | economy | brain | remember "<c>" | search "<q>" | reason "<topic>" | forge [--dry-run] | review [-i] | resolve <id> approve "<fix>"|reject|defer | gaps [teach <id> <intent> | forget <id> | --all] | scopes [list|add <name>|rm <name>] | serve [port] [--follower --lease-ms N --leader-poll-ms N --primary-url URL] | remote --url URL --workerId ID | leader | recipes [doctor | show <name>]`);
|
|
1023
|
+
console.error(`unknown command: ${cmd}\nusage: scion [run "<task>" | swarm [n] | plan <target> [--out m.json] [--run] | workflow <manifest.json> | analyze [dir] | fix <file> | watch [dir] [--auto-fix] [--concurrency N] | economy | brain | remember "<c>" | search "<q>" | reason "<topic>" | forge [--dry-run] | review [-i] | resolve <id> approve "<fix>"|reject|defer | gaps [teach <id> <intent> | forget <id> | --all] | scopes [list|add <name>|rm <name>] | ui | serve [port] [--follower --lease-ms N --leader-poll-ms N --primary-url URL] | remote --url URL --workerId ID | leader | recipes [doctor | show <name>]`);
|
|
782
1024
|
process.exitCode = 1;
|
|
783
1025
|
}
|