ruvnet-brain 4.3.20 → 4.3.25
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 +5 -5
- package/bin/install.mjs +383 -78
- package/console/app.js +141 -9
- package/console/index.html +51 -24
- package/console/scope.css +137 -0
- package/console/scope.html +144 -0
- package/console/scope.js +209 -0
- package/console/tips.html +1 -0
- package/kb/corpus-release-identity.mjs +239 -0
- package/kb/update-storage-transaction.mjs +20 -3
- package/package.json +9 -2
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/commands/checkpoint.md +61 -0
- package/plugin/hooks/codex-hooks.json +64 -1
- package/plugin/hooks/hook-contracts.json +299 -6
- package/plugin/hooks/hooks.json +81 -1
- package/plugin/mcp/server.mjs +23 -0
- package/plugin/scripts/advocacy-catalog.mjs +245 -0
- package/plugin/scripts/advocacy-route.mjs +460 -0
- package/plugin/scripts/continuation-gate.mjs +25 -2
- package/plugin/scripts/continuation-objective.mjs +7 -1
- package/plugin/scripts/continuity-hook-policy.mjs +190 -15
- package/plugin/scripts/coverage-integrity.mjs +7 -0
- package/plugin/scripts/gates.mjs +113 -10
- package/plugin/scripts/grounding-turn-gate.mjs +167 -0
- package/plugin/scripts/grounding-turn-mark.mjs +91 -0
- package/plugin/scripts/hook-shim.mjs +14 -0
- package/plugin/scripts/host-shell-boundary.mjs +43 -0
- package/plugin/scripts/nightly-scheduler.mjs +37 -4
- package/plugin/scripts/project-progression-checkpoint.mjs +145 -0
- package/plugin/scripts/project-progression-contract.mjs +16 -0
- package/plugin/scripts/project-progression-hook.mjs +3 -0
- package/plugin/scripts/project-progression-producer.mjs +252 -0
- package/plugin/scripts/project-progression-reader.mjs +271 -0
- package/plugin/scripts/project-progression-session-start.mjs +93 -16
- package/plugin/scripts/project-progression-sources.mjs +220 -0
- package/plugin/scripts/project-progression-store.mjs +106 -13
- package/plugin/scripts/ruvnet-gate1-pattern.mjs +29 -0
- package/plugin/scripts/session-snapshot-hook.mjs +115 -7
- package/plugin/scripts/session-start-budget.mjs +59 -0
- package/plugin/scripts/session-start-core.mjs +234 -457
- package/plugin/scripts/session-start-fsutil.mjs +61 -0
- package/plugin/scripts/session-start-health.mjs +64 -0
- package/plugin/scripts/session-start-hook-description.mjs +45 -0
- package/plugin/scripts/session-start-issue-alert.mjs +77 -0
- package/plugin/scripts/session-start-repo-identity.mjs +54 -0
- package/plugin/scripts/session-start-signals.mjs +73 -0
- package/plugin/scripts/session-start-trace.mjs +86 -0
- package/plugin/scripts/session-start-update-plane.mjs +104 -0
- package/plugin/scripts/unprompted-runtime.mjs +32 -2
- package/plugin/scripts/update-apply.mjs +2 -32
- package/plugin/skills/ruvnet-brain/PLAYBOOK.md +26 -2
- package/plugin/skills/ruvnet-brain/SKILL.md +67 -2
- package/scripts/adr-072-completion.mjs +1 -1
- package/scripts/agentdb-fleet-doctor.mjs +5 -1
- package/scripts/approved-runtime.mjs +197 -0
- package/scripts/brain-novice-50.mjs +16 -1
- package/scripts/brain-score.mjs +23 -5
- package/scripts/build-bundle.mjs +971 -530
- package/scripts/build-concepts.mjs +36 -116
- package/scripts/console-engine.test.mjs +8 -7
- package/scripts/console-runtime-identity.mjs +4 -0
- package/scripts/corpus-aggregates.mjs +94 -77
- package/scripts/corpus-candidate.mjs +475 -222
- package/scripts/corpus-next-seed.mjs +225 -0
- package/scripts/corpus-promotion.mjs +58 -0
- package/scripts/corpus-reconcile.mjs +411 -105
- package/scripts/doc-currency.mjs +16 -1
- package/scripts/dual-host-deliberation.mjs +25 -2
- package/scripts/dual-host-suggest.mjs +17 -1
- package/scripts/falsify.mjs +13 -3
- package/scripts/gist-receipts.mjs +482 -87
- package/scripts/github-health-watch.mjs +12 -2
- package/scripts/handoff-asset.mjs +34 -0
- package/scripts/hook-retirement-check.mjs +8 -1
- package/scripts/host-registry.mjs +1 -1
- package/scripts/ingest-gists.mjs +74 -101
- package/scripts/job-heartbeat.sh +77 -14
- package/scripts/learning-replay-execution.mjs +10 -4
- package/scripts/nightly-gists.sh +27 -13
- package/scripts/nightly-two-run-proof.mjs +1 -1
- package/scripts/nightly-watchdog.mjs +61 -4
- package/scripts/onboarding-console.mjs +319 -27
- package/scripts/oracle/produce-questions.mjs +293 -0
- package/scripts/oracle/producer-hosts.mjs +235 -0
- package/scripts/oracle/repo-recall.mjs +448 -0
- package/scripts/oracle/retrieval-accuracy.mjs +818 -0
- package/scripts/oracle/source-tree.mjs +165 -0
- package/scripts/oracle/source-units.mjs +391 -0
- package/scripts/oracle/spike-run.mjs +98 -0
- package/scripts/oracle/unit-inventory.mjs +141 -0
- package/scripts/oracle/unit-sampling.mjs +128 -0
- package/scripts/oracle/validate-labels.mjs +250 -0
- package/scripts/private-overlay.mjs +248 -0
- package/scripts/product-integrity-contract.mjs +1 -1
- package/scripts/proxy/claude-proxied.sh +6 -0
- package/scripts/proxy/proxy-revert.sh +5 -0
- package/scripts/proxy/proxy-up.sh +6 -0
- package/scripts/proxy/proxy-verify.mjs +4 -0
- package/scripts/public-inputs.mjs +409 -0
- package/scripts/public-verification-inputs.mjs +112 -26
- package/scripts/public-verification-lane.mjs +1 -1
- package/scripts/published-surface-probe.mjs +34 -4
- package/scripts/qe/card-lane-gate.mjs +16 -1
- package/scripts/qe/session-start-gate.mjs +16 -1
- package/scripts/rebuild-gists-from-receipts.mjs +58 -78
- package/scripts/record-lesson.mjs +4 -1
- package/scripts/rehearse-corpus-pipeline.mjs +994 -0
- package/scripts/release-abort-stale.mjs +5 -1
- package/scripts/release-authority.mjs +104 -12
- package/scripts/release-channel-kind.mjs +86 -0
- package/scripts/release-convergence-watchdog.mjs +7 -2
- package/scripts/release-projection.mjs +177 -72
- package/scripts/release-transaction-provider.mjs +23 -6
- package/scripts/release.mjs +252 -17
- package/scripts/retrieval-canary.mjs +87 -0
- package/scripts/rvf-index-audit.mjs +573 -13
- package/scripts/rvf-wire.mjs +269 -0
- package/scripts/seal-gist-receipt.mjs +65 -0
- package/scripts/selfcheck.mjs +42 -21
- package/scripts/source-coverage.mjs +253 -24
- package/scripts/status-honesty.mjs +25 -0
- package/scripts/sync-census.mjs +0 -0
- package/scripts/sync-version.mjs +2 -0
- package/scripts/trismart.mjs +42 -0
- package/scripts/updater-manifest.mjs +162 -0
- package/scripts/verify-channels.mjs +17 -5
- package/scripts/wired-check.mjs +48 -10
- package/tri-smart-skill/QUICKSTART.md +37 -0
- package/tri-smart-skill/README.md +92 -0
- package/tri-smart-skill/install.cmd +14 -0
- package/tri-smart-skill/install.command +13 -0
- package/tri-smart-skill/install.mjs +51 -0
- package/tri-smart-skill/install.sh +9 -0
- package/tri-smart-skill/tri-smart/SKILL.md +90 -0
- package/tri-smart-skill/tri-smart/evals/evals.json +25 -0
- package/tri-smart-skill/tri-smart/references/protocol.md +25 -0
- package/tri-smart-skill/tri-smart/references/provider-cli.md +18 -0
- package/tri-smart-skill/tri-smart/scripts/review.mjs +154 -0
- package/tri-smart-skill/tri-smart/scripts/setup.mjs +97 -0
- package/tri-smart-skill/tri-smart/scripts/verify-access.mjs +107 -0
- package/scripts/corpus-seed-publish.mjs +0 -110
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scripts/updater-manifest.mjs — every shipped repository store gets a complete, honestly-provenanced
|
|
3
|
+
* updater entry in SOURCE.json.
|
|
4
|
+
*
|
|
5
|
+
* WHY (measured 2026-09-15). A full local build reached bundle assembly and was refused:
|
|
6
|
+
* "2bottalk: sealed corpus SOURCE.json carries no updater entry for this repository store". The corpus
|
|
7
|
+
* carried 194 repository stores; SOURCE.json carried entries for 94, and of those, 90 were exactly the
|
|
8
|
+
* stores refreshed that run. Zero of the 100 missing had been refreshed. The worker path is correct —
|
|
9
|
+
* corpus-reconcile's validateWorkerOutput requires an entry for each store a worker builds — so the gap
|
|
10
|
+
* is purely INHERITANCE from the pinned bootstrap seed, whose own SOURCE.json never covered them.
|
|
11
|
+
* build-bundle refuses rather than ship a store with no self-update configuration, which is right.
|
|
12
|
+
*
|
|
13
|
+
* THE RULE (Dual, decision A, verifier APPROVE_WITH_REQUIRED_IMPLEMENTATION_CONDITIONS): backfill
|
|
14
|
+
* deterministically from the corpus's OWN authenticated records, and never let a migration masquerade
|
|
15
|
+
* as a build.
|
|
16
|
+
* - `builtUtc` is the ORIGINAL artifact build time from the ledger. Never the migration time.
|
|
17
|
+
* - `builder` and `sourceDescribe` are preserved when evidenced and otherwise explicitly null.
|
|
18
|
+
* A fallback that fabricated "rvf-kb-forge" or reconstructed a git describe string is forbidden.
|
|
19
|
+
* - `contentOrigin` (inherited | built-this-generation) and `metadataOrigin` (worker-produced |
|
|
20
|
+
* inherited-existing | backfilled-from-seed-ledger) are recorded SEPARATELY for every store,
|
|
21
|
+
* because metadata written now does not make the content new.
|
|
22
|
+
* - Pinned is not automatically authenticated and unrefreshed is not automatically byte-identical:
|
|
23
|
+
* an entry is only synthesized after the artifact on disk matches the ledger's digest and size.
|
|
24
|
+
* A store that fails that check is reported, never quietly backfilled — it needs a real rebuild.
|
|
25
|
+
* - Only REPOSITORY stores require an entry; aggregates and derived stores are classified out, using
|
|
26
|
+
* the same rule build-bundle applies.
|
|
27
|
+
* - `updateManaged: false` is preserved exactly: a store deliberately excluded from self-update stays
|
|
28
|
+
* excluded.
|
|
29
|
+
* Deterministic and idempotent: keys are sorted and a second run over its own output changes nothing.
|
|
30
|
+
*/
|
|
31
|
+
import crypto from 'node:crypto';
|
|
32
|
+
import fs from 'node:fs';
|
|
33
|
+
import path from 'node:path';
|
|
34
|
+
|
|
35
|
+
export const UPDATER_MANIFEST_VERSION = 'updater-manifest-normalization/1';
|
|
36
|
+
export const GIST_AGGREGATE_STORE = 'ruv-gists';
|
|
37
|
+
const SAFE_STORE = /^[a-z0-9][a-z0-9._-]*$/i;
|
|
38
|
+
|
|
39
|
+
const sha256File = (file) => crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
|
|
40
|
+
const readJson = (file) => JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
41
|
+
|
|
42
|
+
/** Same classification build-bundle uses: the gist aggregate, the derived registry, else a repository. */
|
|
43
|
+
export function classifyStore(store, derivedStores) {
|
|
44
|
+
const folded = String(store).toLowerCase();
|
|
45
|
+
if (folded === GIST_AGGREGATE_STORE) return 'gist-aggregate';
|
|
46
|
+
return derivedStores.has(folded) ? 'derived' : 'repository';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function derivedStoreSet(assetsDir) {
|
|
50
|
+
const file = path.join(assetsDir, 'public-store-classes.json');
|
|
51
|
+
if (!fs.existsSync(file)) return new Set();
|
|
52
|
+
const classes = readJson(file);
|
|
53
|
+
return new Set((classes?.derived || []).map((entry) => String(entry?.store || entry || '').toLowerCase()).filter(Boolean));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Normalize SOURCE.json in place. Returns a summary; it never throws for a missing entry — the caller
|
|
58
|
+
* decides, because a store whose artifact cannot be verified needs a rebuild rather than a synthesized row.
|
|
59
|
+
*/
|
|
60
|
+
export function normalizeUpdaterManifest({ assetsDir, coverage, refreshedStores = [], seedIdentity = null }) {
|
|
61
|
+
const assets = path.resolve(assetsDir || '');
|
|
62
|
+
const sourceFile = path.join(assets, 'SOURCE.json');
|
|
63
|
+
const ledgerFile = path.join(assets, 'RVF-GENERATIONS.json');
|
|
64
|
+
if (!fs.existsSync(ledgerFile)) throw new Error(`updater normalization needs the generation ledger (${ledgerFile})`);
|
|
65
|
+
const source = fs.existsSync(sourceFile) ? readJson(sourceFile) : { builder: 'rvf-kb-forge', stores: {} };
|
|
66
|
+
if (!source.stores || typeof source.stores !== 'object' || Array.isArray(source.stores)) {
|
|
67
|
+
throw new Error('SOURCE.json is malformed (expected an object with a stores object)');
|
|
68
|
+
}
|
|
69
|
+
const ledger = readJson(ledgerFile);
|
|
70
|
+
const ledgerSha256 = sha256File(ledgerFile);
|
|
71
|
+
const derived = derivedStoreSet(assets);
|
|
72
|
+
const refreshed = new Set(refreshedStores.map((s) => String(s).toLowerCase()));
|
|
73
|
+
const urlByStore = new Map();
|
|
74
|
+
for (const row of coverage?.rows || []) {
|
|
75
|
+
const store = String(row?.artifact?.store || '').toLowerCase();
|
|
76
|
+
if (store && row?.url && !urlByStore.has(store)) urlByStore.set(store, row.url);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const stores = fs.readdirSync(assets).filter((f) => f.endsWith('.big.rvf')).map((f) => f.slice(0, -'.big.rvf'.length)).sort();
|
|
80
|
+
const normalized = {};
|
|
81
|
+
const backfilled = [];
|
|
82
|
+
const unverified = [];
|
|
83
|
+
const classification = { repository: 0, 'gist-aggregate': 0, derived: 0 };
|
|
84
|
+
|
|
85
|
+
for (const store of stores) {
|
|
86
|
+
const kind = classifyStore(store, derived);
|
|
87
|
+
classification[kind] += 1;
|
|
88
|
+
const existing = source.stores[store] ?? source.stores[Object.keys(source.stores).find((k) => k.toLowerCase() === store.toLowerCase()) ?? ''];
|
|
89
|
+
const contentOrigin = refreshed.has(store.toLowerCase()) ? 'built-this-generation' : 'inherited';
|
|
90
|
+
if (existing && typeof existing === 'object') {
|
|
91
|
+
// An entry that was BACKFILLED keeps that provenance forever: re-running normalization must not
|
|
92
|
+
// quietly relabel synthesized metadata as though the seed had shipped it. Only an entry with no
|
|
93
|
+
// recorded metadataOrigin gets one derived, which is also what makes this idempotent.
|
|
94
|
+
const metadataOrigin = typeof existing.metadataOrigin === 'string' && existing.metadataOrigin
|
|
95
|
+
? existing.metadataOrigin
|
|
96
|
+
: (contentOrigin === 'built-this-generation' ? 'worker-produced' : 'inherited-existing');
|
|
97
|
+
normalized[store] = sortKeys({ ...existing, contentOrigin, metadataOrigin });
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (kind !== 'repository') continue; // only repository stores require an updater entry
|
|
101
|
+
if (!SAFE_STORE.test(store)) { unverified.push({ store, reason: 'unsafe store name' }); continue; }
|
|
102
|
+
|
|
103
|
+
// Pinned is not authenticated, and unrefreshed is not byte-identical: verify before synthesizing.
|
|
104
|
+
const generation = ledger?.stores?.[store]
|
|
105
|
+
?? Object.entries(ledger?.stores || {}).find(([k]) => k.toLowerCase() === store.toLowerCase())?.[1];
|
|
106
|
+
if (!generation) { unverified.push({ store, reason: 'no generation record in the corpus ledger' }); continue; }
|
|
107
|
+
const rvf = path.join(assets, `${store}.big.rvf`);
|
|
108
|
+
const bytes = fs.statSync(rvf).size;
|
|
109
|
+
const digest = sha256File(rvf);
|
|
110
|
+
if (generation.sha256 !== digest || generation.bytes !== bytes) {
|
|
111
|
+
unverified.push({ store, reason: 'artifact on disk does not match its ledger digest or size' });
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const url = urlByStore.get(store.toLowerCase()) ?? null;
|
|
115
|
+
if (!url) { unverified.push({ store, reason: 'no coverage row supplies an upstream repository URL' }); continue; }
|
|
116
|
+
|
|
117
|
+
normalized[store] = sortKeys({
|
|
118
|
+
kbName: store,
|
|
119
|
+
sourceRepo: url,
|
|
120
|
+
sourceCommit: generation.sourceCommit ?? null,
|
|
121
|
+
// Preserved, never reconstructed. An absent historical value is stated as unknown.
|
|
122
|
+
sourceDescribe: generation.sourceDescribe ?? null,
|
|
123
|
+
builtUtc: generation.builtUtc ?? null,
|
|
124
|
+
builder: generation.builder ?? null,
|
|
125
|
+
// Per-store nulls are correct while the TOP-LEVEL discovery contract supplies the endpoint.
|
|
126
|
+
canonicalManifestUrl: null,
|
|
127
|
+
canonicalBundleUrl: null,
|
|
128
|
+
selfUpdate: `node forge-update.mjs ${store}`,
|
|
129
|
+
contentOrigin: 'inherited',
|
|
130
|
+
metadataOrigin: 'backfilled-from-seed-ledger',
|
|
131
|
+
inheritanceEvidence: sortKeys({
|
|
132
|
+
ledgerSha256,
|
|
133
|
+
artifactSha256: digest,
|
|
134
|
+
artifactBytes: bytes,
|
|
135
|
+
sourceCommit: generation.sourceCommit ?? null,
|
|
136
|
+
builtUtc: generation.builtUtc ?? null,
|
|
137
|
+
seedTag: seedIdentity?.tag ?? null,
|
|
138
|
+
seedSha256: seedIdentity?.sha256 ?? null,
|
|
139
|
+
}),
|
|
140
|
+
migrationIdentity: UPDATER_MANIFEST_VERSION,
|
|
141
|
+
});
|
|
142
|
+
backfilled.push(store);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const out = { ...source, stores: sortKeys(normalized) };
|
|
146
|
+
fs.writeFileSync(sourceFile, `${JSON.stringify(out, null, 2)}\n`);
|
|
147
|
+
const required = stores.filter((s) => classifyStore(s, derived) === 'repository');
|
|
148
|
+
const missing = required.filter((s) => !normalized[s]);
|
|
149
|
+
return {
|
|
150
|
+
version: UPDATER_MANIFEST_VERSION,
|
|
151
|
+
classification,
|
|
152
|
+
requiredRepositoryStores: required.length,
|
|
153
|
+
entries: Object.keys(normalized).length,
|
|
154
|
+
backfilled,
|
|
155
|
+
unverified,
|
|
156
|
+
missing,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function sortKeys(value) {
|
|
161
|
+
return Object.fromEntries(Object.keys(value).sort().map((k) => [k, value[k]]));
|
|
162
|
+
}
|
|
@@ -29,6 +29,7 @@ import path from 'node:path';
|
|
|
29
29
|
import os from 'node:os';
|
|
30
30
|
import { fileURLToPath } from 'node:url';
|
|
31
31
|
import { execFileSync } from 'node:child_process';
|
|
32
|
+
import { describeLatestPointer, latestCodeReleaseTag } from './release-channel-kind.mjs';
|
|
32
33
|
|
|
33
34
|
const ROOT = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
34
35
|
const JSON_OUT = process.argv.includes('--json');
|
|
@@ -133,20 +134,31 @@ async function run() {
|
|
|
133
134
|
// place of the thing (receipt-count for "is routing running", backlog-remaining for "is promotion in
|
|
134
135
|
// force", cache-exists for "is this measurement current"). Reachability is the proxy; version is the
|
|
135
136
|
// thing. Users updating via the release bundle get whatever THIS says, not whatever npm says.
|
|
137
|
+
// ADR-086 S1: `releases/latest` is the CUSTOMER download pointer and may legitimately be a corpus
|
|
138
|
+
// generation (`corpus-sha256-<digest>`), which can never equal a semver. Asking that pointer "are
|
|
139
|
+
// you the shipping version?" therefore stopped being the right question the moment corpus
|
|
140
|
+
// promotion existed — and because release.mjs runs this script as `runOrDie` in step E, the wrong
|
|
141
|
+
// question would have failed the OWNER'S OWN preflight every time a corpus night succeeded. The
|
|
142
|
+
// code generation is a separate fact, so it gets asked separately.
|
|
136
143
|
{
|
|
137
|
-
let tag = null, err = null;
|
|
144
|
+
let releases = null, tag = null, latestPointer = null, err = null;
|
|
138
145
|
try {
|
|
139
|
-
const r = await fetch(`https://api.github.com/repos/${REPO}/releases
|
|
146
|
+
const r = await fetch(`https://api.github.com/repos/${REPO}/releases?per_page=30`, {
|
|
140
147
|
headers: { 'User-Agent': 'ruvnet-brain-verify-channels' },
|
|
141
148
|
});
|
|
142
|
-
if (r.ok)
|
|
149
|
+
if (r.ok) {
|
|
150
|
+
releases = await r.json();
|
|
151
|
+
tag = latestCodeReleaseTag(releases);
|
|
152
|
+
latestPointer = (Array.isArray(releases) ? releases.find((row) => row && !row.draft) : null)?.tag_name || null;
|
|
153
|
+
} else err = `HTTP ${r.status}`;
|
|
143
154
|
} catch (e) { err = String(e && e.message || e); }
|
|
144
155
|
// Tags carry a leading `v`; the version does not. Compare the bare versions.
|
|
145
156
|
const bare = (s) => String(s || '').replace(/^v/, '');
|
|
157
|
+
const pointerNote = latestPointer ? ` · ${describeLatestPointer(latestPointer)}` : '';
|
|
146
158
|
check('GitHub Release IS the shipping version', tag != null && bare(tag) === bare(V),
|
|
147
159
|
tag == null
|
|
148
|
-
? `could not read
|
|
149
|
-
: `release=${tag} · shipping=${V}${bare(tag) === bare(V) ? '' :
|
|
160
|
+
? `could not read the latest code release (${err || 'no semver-tagged release found'}) — release currency NOT verified`
|
|
161
|
+
: `code release=${tag} · shipping=${V}${pointerNote}${bare(tag) === bare(V) ? '' : ' ← STALE: the release channel is behind npm. Cut the release, or users on --update stay on ' + tag}`);
|
|
150
162
|
}
|
|
151
163
|
|
|
152
164
|
// 4) release signature present (auto-apply verifies against it)
|
package/scripts/wired-check.mjs
CHANGED
|
@@ -51,7 +51,7 @@ import fs from 'node:fs';
|
|
|
51
51
|
import path from 'node:path';
|
|
52
52
|
import os from 'node:os';
|
|
53
53
|
import { loadLessons, TRIGGERS, STATUS } from './lesson-store.mjs';
|
|
54
|
-
import { isAllowedContinuityRegistration } from '../plugin/scripts/continuity-hook-policy.mjs';
|
|
54
|
+
import { continuityRegistrations, isAllowedContinuityRegistration } from '../plugin/scripts/continuity-hook-policy.mjs';
|
|
55
55
|
|
|
56
56
|
const REPO = path.resolve(import.meta.dirname, '..');
|
|
57
57
|
const argv = process.argv.slice(2);
|
|
@@ -70,11 +70,23 @@ const argv = process.argv.slice(2);
|
|
|
70
70
|
* every entry on every run, below, so they cannot rot unseen.
|
|
71
71
|
*/
|
|
72
72
|
const STANDALONE = [
|
|
73
|
+
['spike-run', 'ADR-086 Step 14 oracle feasibility-spike driver, run by hand against disposable snapshots outside the checkout. '
|
|
74
|
+
+ 'It is deliberately NOT imported by the pipeline: Step 14 only proves or disproves that a trustworthy unattended label '
|
|
75
|
+
+ 'producer exists, and Step 15 owns any prepareCorpusCandidate wiring. The three modules it drives (source-units, '
|
|
76
|
+
+ 'produce-questions, validate-labels) are each wired to a real caller; this driver is the human entry point.'],
|
|
73
77
|
['gate', 'retired automatic-hook helper and manual benchmark retained for explicit human use; no workflow or scheduler invokes this expensive command'],
|
|
74
78
|
['dream-issue-gate', 'pure Dream Cycle disposition policy; invoked by the external issue adapter, never a GitHub writer'],
|
|
75
79
|
['sync-census', 'explicit maintainer census writer; a destructive source-to-surface refresh is never scheduled'],
|
|
76
80
|
['sync-commands', 'explicit maintainer alias synchronizer; run deliberately before release, never from a lifecycle hook'],
|
|
77
81
|
['version-bump-gate', 'retired automatic interceptor; explicit version checks own release validation'],
|
|
82
|
+
['project-progression-checkpoint', 'the body of the shipped `/ruvnet-brain:checkpoint` command '
|
|
83
|
+
+ '(plugin/commands/checkpoint.md:39; added a7167b6b 2026-09-11). The command host executes that '
|
|
84
|
+
+ 'instruction, and this check does not scan command markdown — the same shape as '
|
|
85
|
+
+ '`onboarding-console` below. Recorded 2026-09-11 when wired-check first named it.'],
|
|
86
|
+
['handoff-asset', 'client explainer handoff to a named host, run by a human per docs/CLIENT-ASSET-HANDOFF.md '
|
|
87
|
+
+ '(added 32b7b7ca 2026-09-11 with the TriSmart sub-product); nothing may schedule a copy to a client '
|
|
88
|
+
+ 'machine. Surfaced 2026-09-11 the first time wired-check ran with no agent worktrees under '
|
|
89
|
+
+ '.claude/worktrees/ — their package.json copies had been counted as callers all day.'],
|
|
78
90
|
['lesson-seed', 'one-shot seeding, run deliberately by a human'],
|
|
79
91
|
['lesson-ratify', 'the human control surface — a CLI is its entire purpose'],
|
|
80
92
|
['stamp-sweep', 'ADR-056 §2 — the ONE-TIME backfill half of the stamp rule. A human runs it once '
|
|
@@ -101,10 +113,6 @@ const STANDALONE = [
|
|
|
101
113
|
+ 'RVFs to RVF-GENERATIONS.json and optionally prunes legacy sidecars; build-bundle.mjs consumes '
|
|
102
114
|
+ 'and validates the resulting manifest, so scheduling this destructive migration would be wrong'],
|
|
103
115
|
['release', 'the ship path, run by a human'],
|
|
104
|
-
['corpus-seed-publish', 'privileged manual corpus publication wrapper; corpus-seed.yml explicitly '
|
|
105
|
-
+ 'stops at sealed preparation with contents:read. This entry validates receipt/archive inputs '
|
|
106
|
-
+ 'and delegates mutation to release.mjs protected authority. No operational caller is registered; '
|
|
107
|
-
+ 'classification does not claim publication works or has occurred'],
|
|
108
116
|
['execution-preflight', 'external orchestration boundary — invoked by the host before consequential Ruflo/Codex execution; no in-repo caller exists because the host supplies the live Brain and AgentDB receipts'],
|
|
109
117
|
['fix-workstream', 'session-supervised coordination CLI run explicitly by the integration owner or an '
|
|
110
118
|
+ 'isolated writing agent to start and hand off a fix lane. Scheduling it would violate its safety '
|
|
@@ -316,11 +324,31 @@ const CALLER_ROOTS = [
|
|
|
316
324
|
const CALLER_EXTS = new Set(['.mjs', '.js', '.sh', '.json', '.html', '.yml', '.yaml']);
|
|
317
325
|
export const REQUIRED_OPERATIONAL_EXPORTS = [
|
|
318
326
|
{ rel: 'scripts/corpus-reconcile.mjs', symbol: 'syncCorpusInputs' },
|
|
319
|
-
{ rel: 'scripts/corpus-reconcile.mjs', symbol: 'materializeGistReceipts' },
|
|
320
|
-
{ rel: 'scripts/corpus-reconcile.mjs', symbol: 'observeAndMaterializeGistReceipts' },
|
|
321
327
|
{ rel: 'scripts/corpus-aggregates.mjs', symbol: 'rebuildCorpusAggregates' },
|
|
322
|
-
{ rel: 'scripts/corpus-reconcile.mjs', symbol: '
|
|
328
|
+
{ rel: 'scripts/corpus-reconcile.mjs', symbol: 'acquireCorpusGeneration' },
|
|
323
329
|
{ rel: 'scripts/corpus-reconcile.mjs', symbol: 'reconcileAndPrepareCorpusCandidate' },
|
|
330
|
+
{ rel: 'scripts/gist-receipts.mjs', symbol: 'buildGistAggregate' },
|
|
331
|
+
// Step 3 (2026-09-13): the single canonical public-prose selection entry point, same rigor as
|
|
332
|
+
// buildGistAggregate above.
|
|
333
|
+
{ rel: 'scripts/public-inputs.mjs', symbol: 'materializePublicInputs' },
|
|
334
|
+
// Step 5 (2026-09-13): the single canonical assembly entry point (replaces the old ad hoc
|
|
335
|
+
// discover/copy/rebind logic previously scattered across build-bundle.mjs's module top level) and
|
|
336
|
+
// the single canonical release-coverage-projection entry point it calls in-process. Both are wired
|
|
337
|
+
// from build-bundle.mjs's own CLI wrapper at the bottom of the same file.
|
|
338
|
+
{ rel: 'scripts/build-bundle.mjs', symbol: 'assembleBundle' },
|
|
339
|
+
{ rel: 'scripts/release-projection.mjs', symbol: 'createReleaseProjection' },
|
|
340
|
+
// Step 13 (2026-09-13): the C2 deep store audit — measured recall, hash-verified segments, id-map /
|
|
341
|
+
// passage / source-map correspondence — that corpus-candidate.mjs fails closed on.
|
|
342
|
+
{ rel: 'scripts/rvf-index-audit.mjs', symbol: 'auditCorpusStores' },
|
|
343
|
+
// Step 15 (2026-09-14): the C3 retrieval-accuracy gate. prepareCorpusCandidate shells out to its
|
|
344
|
+
// CLI after single-pass assembly; corpus-candidate.mjs and release.mjs both import its
|
|
345
|
+
// readAccuracyReport reader so candidate acceptance and publication can never drift apart.
|
|
346
|
+
{ rel: 'scripts/oracle/retrieval-accuracy.mjs', symbol: 'runRetrievalAccuracy' },
|
|
347
|
+
// ADR-086 amendment (2026-09-15): the BLOCKING retrieval gate. C3 above still runs and still ships,
|
|
348
|
+
// but as a published diagnostic; this is the module that can refuse a candidate. prepareCorpusCandidate
|
|
349
|
+
// shells out to its CLI after single-pass assembly, and corpus-candidate.mjs and release.mjs both
|
|
350
|
+
// import its readRecallReport reader so candidate acceptance and publication can never drift apart.
|
|
351
|
+
{ rel: 'scripts/oracle/repo-recall.mjs', symbol: 'runRepoRecall' },
|
|
324
352
|
];
|
|
325
353
|
|
|
326
354
|
const isTestFile = (f) => /\.(test|spec)\.(mjs|js)$/.test(path.basename(f))
|
|
@@ -369,6 +397,14 @@ function callerFiles(repo = REPO) {
|
|
|
369
397
|
// the remedy: "an invoker outside the roots proves the ROOTS are incomplete — the fix is to add
|
|
370
398
|
// the root, never to" special-case the module. Same rule, applied to the extension filter.
|
|
371
399
|
for (const f of walk(abs, true)) {
|
|
400
|
+
// `.claude/` is a caller root because `.claude/settings.json` genuinely invokes hooks. But
|
|
401
|
+
// `.claude/worktrees/<agent>/` holds FULL COPIES of this repository (Claude Code agent
|
|
402
|
+
// worktrees), and a copy of the repo is not a caller of the repo. Measured 2026-09-11: with any
|
|
403
|
+
// worktree present, card-from-source read `wired` via `.claude/worktrees/…/package.json`
|
|
404
|
+
// (`manual` with none present), handoff-asset read `wired` while invoked by nothing, and the
|
|
405
|
+
// MANUAL class read 0 all day. Same rule as the `kb/` exclusion in this file's header.
|
|
406
|
+
const rel = path.relative(repo, f).split(path.sep).join('/');
|
|
407
|
+
if (rel.startsWith('.claude/worktrees/')) continue;
|
|
372
408
|
if (CALLER_EXTS.has(path.extname(f))) { out.push(f); continue; }
|
|
373
409
|
if (path.extname(f) === '' && path.basename(path.dirname(f)) === 'git-hooks') out.push(f);
|
|
374
410
|
}
|
|
@@ -790,8 +826,10 @@ export function hookWiringAudit({
|
|
|
790
826
|
const packageRegistry = file.endsWith('/plugin/hooks/hooks.json') || file.endsWith('/plugin/hooks/codex-hooks.json');
|
|
791
827
|
const rows = Object.entries(doc.hooks).flatMap(([event, groups]) => (groups ?? []).flatMap((group) =>
|
|
792
828
|
(group?.hooks ?? []).map((hook) => ({ event, matcher: group.matcher ?? '', command: hook.command }))));
|
|
829
|
+
const host = file.endsWith('/plugin/hooks/codex-hooks.json') ? 'codex' : 'claude';
|
|
793
830
|
return packageRegistry
|
|
794
|
-
? rows.length ===
|
|
831
|
+
? rows.length === continuityRegistrations(host).length
|
|
832
|
+
&& rows.every((row) => isAllowedContinuityRegistration({ ...row, host }))
|
|
795
833
|
: rows.length === 0;
|
|
796
834
|
});
|
|
797
835
|
const table = hookShimTable(repo);
|
|
@@ -850,7 +888,7 @@ export function hookWiringAudit({
|
|
|
850
888
|
else if (automaticHooksConstrained && declared) rows.push({
|
|
851
889
|
file: f,
|
|
852
890
|
state: 'retired',
|
|
853
|
-
why: 'legacy automatic Brain hooks remain retired; only the
|
|
891
|
+
why: 'legacy automatic Brain hooks remain retired; only the declared continuity plane (see plugin/hooks/hook-contracts.json) is wired',
|
|
854
892
|
});
|
|
855
893
|
else if (held[f]) rows.push({ file: f, state: 'held', why: held[f] });
|
|
856
894
|
else rows.push({ file: f, state: 'unwired' });
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# TriSmart Skill quick start
|
|
2
|
+
|
|
3
|
+
Version 1.1.0
|
|
4
|
+
|
|
5
|
+
TriSmart Skill lets two or three subscription developer tools think through an important design together before ordinary coding begins.
|
|
6
|
+
|
|
7
|
+
1. Install the official developer CLIs you want to use: Claude Code, Codex, and/or Grok Code.
|
|
8
|
+
|
|
9
|
+
Official starting points: [Claude Code setup](https://docs.anthropic.com/en/docs/claude-code/getting-started), [OpenAI Developers](https://developers.openai.com/), and [Grok CLI reference](https://docs.x.ai/build/cli/reference). Follow each provider's current instructions; ModelMesh does not install third-party wrappers.
|
|
10
|
+
2. Unzip this package.
|
|
11
|
+
3. Start the installer for your operating system: double-click `install.command` on macOS, double-click `install.cmd` on Windows, or run `./install.sh` on Linux. If you prefer a terminal, run `node install.mjs` from the extracted folder. It installs to your user-level host directories by default; use `--project=/path/to/project` for a project-local install.
|
|
12
|
+
4. The installer copies the skill into the host-specific directory and starts the guided OAuth walkthrough:
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
node install.mjs
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Prefer a customized setup? Skip the installer and drag the extracted `tri-smart` folder into the skill directory for your host: `.claude/skills/tri-smart/`, `.agents/skills/tri-smart/`, or `.grok/skills/tri-smart/`. Reload the host afterward.
|
|
19
|
+
|
|
20
|
+
5. Follow each provider's official browser login. TriSmart never asks for an API key and never stores login tokens.
|
|
21
|
+
|
|
22
|
+
On macOS, the first double-click may show a Gatekeeper warning because the launcher is unsigned. Control-click `install.command`, choose **Open**, then choose **Open** again. The terminal fallback is `./install.sh`.
|
|
23
|
+
6. When setup finishes, ask your current coding assistant:
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
Use ModelMesh to review this architecture before implementation: [describe the decision]
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
TriSmart chooses **TriSmart** when all three providers are available. With any two it chooses **Dual** and tells you which pair is active. It uses the highest model your account can actually run, or that provider's verified default when your plan does not include the preferred model.
|
|
30
|
+
|
|
31
|
+
You will see a plain-language update for every stage: independent proposals, adversarial challenge, one shared synthesis, and independent verification. If the reviewers identify an unresolved critical issue, the result is **blocked** and nothing is handed to implementation. That is an intentional safety result.
|
|
32
|
+
|
|
33
|
+
Preview installation without changing files or logging in:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
node install.mjs --dry-run
|
|
37
|
+
```
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# TriSmart Skill
|
|
2
|
+
|
|
3
|
+
**Version 1.1.0** · semantic versioning (`major.minor.patch`)
|
|
4
|
+
|
|
5
|
+
For a non-technical first run, start with [QUICKSTART.md](QUICKSTART.md).
|
|
6
|
+
|
|
7
|
+
TriSmart Skill is a portable subscription-only, cross-provider architectural review skill. It uses native Claude Code, Codex, and Grok CLIs with their top verified models, performs adversarial proposal and critique stages, persists only non-secret receipts in AgentDB, and hands accepted work to lower-cost implementation agents. It selects TriSmart when all three are available and Dual when any two are available; Claude + OpenAI is the preferred pair, but the available pair is reported explicitly.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
The installer is a Node program. Double-clicking `install.mjs` opens its source text; that is expected. Use the launcher for your operating system:
|
|
12
|
+
|
|
13
|
+
- **macOS:** double-click `install.command`, or run `./install.sh` in Terminal.
|
|
14
|
+
- **Windows:** double-click `install.cmd`, or run `install.cmd` in PowerShell or Command Prompt.
|
|
15
|
+
- **Linux:** run `./install.sh` in a terminal.
|
|
16
|
+
|
|
17
|
+
All launchers run the same installer. Node.js 18+ is required. The direct command is:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
node install.mjs
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### macOS first run
|
|
24
|
+
|
|
25
|
+
Because this ZIP contains an unsigned local script, macOS may show “Apple could not verify install.command.” In Finder, **Control-click (or right-click) `install.command`, choose Open, then choose Open again**. That approves this copy. If you prefer Terminal, use the shell launcher instead:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
cd /path/to/tri-smart-skill
|
|
29
|
+
chmod +x install.sh
|
|
30
|
+
./install.sh
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
If macOS has already quarantined the file and the right-click path is unavailable, remove quarantine from this extracted copy and run it:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
xattr -d com.apple.quarantine install.command install.sh 2>/dev/null || true
|
|
37
|
+
./install.sh
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
There are two supported setup styles:
|
|
41
|
+
|
|
42
|
+
- **Guided / high-tech:** run the launcher or `node install.mjs`. It creates the host folders, copies the skill, and walks you through OAuth setup.
|
|
43
|
+
- **Manual / low-tech:** open the extracted ZIP and drag the `tri-smart` folder into the host's skill directory. Use `.claude/skills/tri-smart/` for Claude Code, `.agents/skills/tri-smart/` for Codex, or `.grok/skills/tri-smart/` for Grok Code. Then restart or reload that host so it discovers the skill.
|
|
44
|
+
|
|
45
|
+
It installs the same host-neutral skill into your user-level Claude, Codex, and Grok skill directories by default, then starts the guided OAuth walkthrough. Existing installations are preserved; use `--force` only when deliberately upgrading one. Preview it with `node install.mjs --dry-run`; target one host with `--host=claude`, `--host=codex`, or `--host=grok`; target a project-local install with `--project=/path/to/project`.
|
|
46
|
+
|
|
47
|
+
If you prefer to install manually, copy the `tri-smart` directory into the skill directory used by your host. This is a valid setup path; the installer only automates the copy and guided login:
|
|
48
|
+
|
|
49
|
+
- Claude Code: `.claude/skills/tri-smart/` or `~/.claude/skills/tri-smart/`
|
|
50
|
+
- Codex: `.agents/skills/tri-smart/` or `~/.codex/skills/tri-smart/`
|
|
51
|
+
- Grok Code: the skills directory shown by `grok inspect` (commonly `.grok/skills/tri-smart/`)
|
|
52
|
+
|
|
53
|
+
The same `SKILL.md` is host-neutral and can be loaded by all three hosts. The active host must be able to discover and invoke the other two native CLIs to execute the full workflow; otherwise ModelMesh returns a clearly labeled degraded result. Account tiers vary: ModelMesh tries the preferred top model, then uses the provider's own highest/default model if the account does not include it, and reports the actual model used. It never silently labels a fallback as the top model. Use the host's documented skill directory and verify discovery from that host before relying on it.
|
|
54
|
+
|
|
55
|
+
## Verify access
|
|
56
|
+
|
|
57
|
+
For a guided first setup, run:
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
node tri-smart/scripts/setup.mjs
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
It detects the CLIs, offers each official OAuth login, explains the selected Dual/TriSmart mode, and asks before spending subscription allowance on a live probe. Use `--dry-run` to see the walkthrough without logging in.
|
|
64
|
+
|
|
65
|
+
After setup, run the bounded orchestration directly from any of the three hosts:
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
node tri-smart/scripts/review.mjs --task-file=architecture-question.md
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The runner performs independent proposals in bounded parallel waves (two native sessions at a time to avoid provider throttling), pairwise challenge, deterministic synthesis, and independent verification. It prints only stage explanations and a structured decision; it stays read-only and reports the actual model or provider-default fallback used.
|
|
72
|
+
Before Stage 1 it prints the exact host/provider/model mapping and native authentication mode. “Dual” or “TriSmart” alone is never the complete model status.
|
|
73
|
+
It also prints: “native provider subscription/OAuth CLIs; API-key variables unset.” That proves the access path used by the runner; it does not claim visibility into a provider's internal billing ledger.
|
|
74
|
+
|
|
75
|
+
From the unzipped directory:
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
node tri-smart/scripts/verify-access.mjs
|
|
79
|
+
node tri-smart/scripts/verify-access.mjs --probe
|
|
80
|
+
node tri-smart/scripts/verify-access.mjs --mode=dual --probe
|
|
81
|
+
node tri-smart/scripts/verify-access.mjs --mode=tri --probe
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The second command performs real single-turn probes only after the selected mode's OAuth/subscription checks pass and may consume subscription allowance. It strips provider API, cloud, helper, and custom-routing variables, validates the exact OAuth mode and model marker, and emits allowlisted metadata only; raw provider output is never printed.
|
|
85
|
+
|
|
86
|
+
## Trigger
|
|
87
|
+
|
|
88
|
+
Say **“use ModelMesh”** / **“model mesh”** for automatic mode selection, **“use TriSmart”** / **“tri smart”** / **“trip smart”** for the three-way review, or **“use Dual”** / **“dual smart”** for a two-provider review. “Brock” is accepted as a casual name for Grok. The skill recalls AgentDB, RVF, the North Star, active hooks, release contracts, and prior lessons before starting the review.
|
|
89
|
+
|
|
90
|
+
## Scope and safety
|
|
91
|
+
|
|
92
|
+
The package contains instructions and a deterministic access verifier; it does not contain credentials or a hidden API proxy. Native CLI authentication remains in each vendor's secure user store. A provider outage produces a degraded result and never silently substitutes another model. The skill does not claim a production release from a local pass or draft receipt. The verifier proves access prerequisites and marker probes; it does not by itself prove host skill discovery, a complete architectural review, or production readiness.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
setlocal
|
|
3
|
+
where node >nul 2>nul
|
|
4
|
+
if errorlevel 1 (
|
|
5
|
+
echo TriSmart Skill needs Node.js 18 or newer. Install it from https://nodejs.org/ and run this file again.
|
|
6
|
+
pause
|
|
7
|
+
exit /b 2
|
|
8
|
+
)
|
|
9
|
+
set "HAS_PROJECT="
|
|
10
|
+
for %%A in (%*) do if /I "%%~A"=="--project" set "HAS_PROJECT=1"
|
|
11
|
+
for %%A in (%*) do echo %%~A | findstr /b /i "--project=" >nul && set "HAS_PROJECT=1"
|
|
12
|
+
if defined HAS_PROJECT (node "%~dp0install.mjs" %*) else (node "%~dp0install.mjs" %* --project="%USERPROFILE%")
|
|
13
|
+
if errorlevel 1 pause
|
|
14
|
+
endlocal
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
set -eu
|
|
3
|
+
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
4
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
5
|
+
printf '%s\n' 'TriSmart Skill needs Node.js 18 or newer. Install it from https://nodejs.org/ and run this file again.'
|
|
6
|
+
printf '%s\n' 'Press Return to close.'
|
|
7
|
+
read -r _
|
|
8
|
+
exit 2
|
|
9
|
+
fi
|
|
10
|
+
case " $* " in *" --project="*|*" --project "*) ;; *) set -- "$@" "--project=$HOME" ;; esac
|
|
11
|
+
node "$SCRIPT_DIR/install.mjs" "$@"
|
|
12
|
+
printf '%s\n' 'TriSmart setup finished. Press Return to close.'
|
|
13
|
+
read -r _
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Install TriSmart into one or all supported project-local skill directories. */
|
|
3
|
+
import { cpSync, existsSync } from 'node:fs';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import process from 'node:process';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
|
|
9
|
+
const VERSION = '1.1.0';
|
|
10
|
+
|
|
11
|
+
const args = process.argv.slice(2);
|
|
12
|
+
if (Number.parseInt(process.versions.node.split('.')[0], 10) < 18) {
|
|
13
|
+
console.error(`TriSmart Skill requires Node.js 18 or newer. Found ${process.version}. Install Node from https://nodejs.org/ and run this installer again.`);
|
|
14
|
+
process.exit(2);
|
|
15
|
+
}
|
|
16
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
17
|
+
console.log('Usage: node install.mjs [--host=claude|codex|grok|all] [--project=PATH] [--dry-run] [--no-setup] [--force]');
|
|
18
|
+
console.log('Guided path: installs the same TriSmart Skill for the selected host(s), then starts guided OAuth setup.');
|
|
19
|
+
console.log('Manual path: drag the tri-smart folder into the host skill directory shown in README.md.');
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}
|
|
22
|
+
if (args.includes('--version')) { console.log(`TriSmart Skill ${VERSION}`); process.exit(0); }
|
|
23
|
+
const host = (args.find((a) => a.startsWith('--host='))?.slice(7) || 'all').toLowerCase();
|
|
24
|
+
const defaultProject = process.env.HOME || process.env.USERPROFILE || process.cwd();
|
|
25
|
+
const project = path.resolve(args.find((a) => a.startsWith('--project='))?.slice(10) || defaultProject);
|
|
26
|
+
const dryRun = args.includes('--dry-run');
|
|
27
|
+
const noSetup = args.includes('--no-setup');
|
|
28
|
+
const force = args.includes('--force');
|
|
29
|
+
const roots = { claude: '.claude/skills', codex: '.agents/skills', grok: '.grok/skills' };
|
|
30
|
+
const hosts = host === 'all' ? Object.keys(roots) : [host];
|
|
31
|
+
if (!hosts.every((item) => Object.hasOwn(roots, item))) { console.error('host must be claude, codex, grok, or all'); process.exit(2); }
|
|
32
|
+
if (!existsSync(project)) { console.error(`Project directory does not exist: ${project}`); process.exit(2); }
|
|
33
|
+
const source = path.resolve(path.dirname(fileURLToPath(import.meta.url)), 'tri-smart');
|
|
34
|
+
console.log(`TriSmart Skill ${VERSION} installation options:`);
|
|
35
|
+
console.log(' Guided: continue and this program copies the skill into the selected host directories.');
|
|
36
|
+
console.log(' Manual: stop here and drag the tri-smart folder into .claude/skills, .agents/skills, or .grok/skills.');
|
|
37
|
+
console.log('');
|
|
38
|
+
for (const item of hosts) {
|
|
39
|
+
const destination = path.join(project, roots[item], 'tri-smart');
|
|
40
|
+
if (existsSync(destination) && !force) {
|
|
41
|
+
console.log(`Already installed for ${item}; leaving it unchanged. Use --force to replace it.`);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
console.log(`${dryRun ? 'Would install' : 'Installing'} TriSmart for ${item}: ${destination}`);
|
|
45
|
+
if (!dryRun) cpSync(source, destination, { recursive: true, force: true });
|
|
46
|
+
}
|
|
47
|
+
if (!dryRun && !noSetup) {
|
|
48
|
+
console.log('\nStarting the guided TriSmart login walkthrough...');
|
|
49
|
+
const setup = spawnSync(process.execPath, [path.join(source, 'scripts', 'setup.mjs')], { stdio: 'inherit' });
|
|
50
|
+
process.exitCode = setup.status ?? 1;
|
|
51
|
+
} else if (dryRun) console.log('\nDry run only: no files changed and no login started.');
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
set -eu
|
|
3
|
+
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
4
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
5
|
+
printf '%s\n' 'TriSmart Skill needs Node.js 18 or newer. Install it from https://nodejs.org/ and run this file again.' >&2
|
|
6
|
+
exit 2
|
|
7
|
+
fi
|
|
8
|
+
case " $* " in *" --project="*|*" --project "*) ;; *) set -- "$@" "--project=$HOME" ;; esac
|
|
9
|
+
exec node "$SCRIPT_DIR/install.mjs" "$@"
|