@ionivetech/mugiwara 0.8.0 → 0.8.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/.kimi-plugin/plugin.json +1 -1
- package/README.md +2 -2
- package/content/agents/brook-healing.md +1 -1
- package/content/agents/memory-keeper.md +5 -0
- package/content/agents/usopp-brainstorm.md +3 -2
- package/content/agents/zoro-execution.md +4 -3
- package/content/skills/mugiwara-brainstorm/SKILL.md +5 -3
- package/content/skills/mugiwara-checkpoint/SKILL.md +2 -0
- package/content/skills/mugiwara-execution/SKILL.md +4 -3
- package/content/skills/mugiwara-execution/references/dispatch.md +1 -1
- package/content/skills/mugiwara-gates/SKILL.md +6 -0
- package/content/skills/mugiwara-healing/SKILL.md +5 -1
- package/content/skills/mugiwara-lessons/SKILL.md +3 -0
- package/content/skills/mugiwara-orchestration/SKILL.md +7 -6
- package/content/skills/mugiwara-planning/SKILL.md +2 -0
- package/content/skills/mugiwara-quality/SKILL.md +3 -14
- package/content/skills/mugiwara-quality/references/order-checklist.md +18 -0
- package/content/skills/mugiwara-resume/SKILL.md +3 -14
- package/content/skills/mugiwara-resume/references/resume-protocol.md +16 -0
- package/content/skills/mugiwara-review/SKILL.md +3 -15
- package/content/skills/mugiwara-review/references/red-flags-review.md +17 -0
- package/content/skills/mugiwara-security/SKILL.md +1 -0
- package/content/skills/mugiwara-ship/SKILL.md +2 -0
- package/content/skills/mugiwara-workflow/SKILL.md +28 -25
- package/dist/mugiwara.js +1323 -402
- package/gemini-extension.json +1 -1
- package/hooks/mugiwara-mode-tracker.js +24 -4
- package/hooks/mugiwara-mode-tracker.ts +36 -7
- package/hooks/session-start.js +6 -1
- package/hooks/session-start.ts +8 -1
- package/package.json +2 -2
- package/plugin.json +1 -1
- package/references/cost-governor.md +104 -0
- package/references/wave-banners.md +1 -2
- package/scripts/gate-selftest.ts +239 -21
- package/scripts/lane-base.ts +16 -0
- package/scripts/lane.sh +5 -1
- package/scripts/lib/lane-base.sh +1 -1
- package/scripts/savepoint.sh +48 -5
- package/scripts/validate-content.ts +60 -0
- package/scripts/verify-install.ts +20 -0
- package/scripts/write-metrics.ts +73 -0
- package/src/budget.ts +11 -0
- package/src/cli.ts +185 -28
- package/src/config.ts +6 -0
- package/src/continue.ts +36 -1
- package/src/cost.ts +4 -1
- package/src/installer.ts +27 -4
- package/src/integrity.ts +105 -25
- package/src/mission.ts +123 -7
- package/src/policy.ts +372 -4
- package/src/provenance.ts +29 -9
- package/src/sign.ts +45 -3
- package/content/skills/mugiwara-workflow/references/adaptive-budget-governor.md +0 -5
- package/content/skills/mugiwara-workflow/references/benchmark-governor.md +0 -53
- package/content/skills/mugiwara-workflow/references/cognitive-output-governor.md +0 -5
- package/content/skills/mugiwara-workflow/references/scope-code-governor.md +0 -14
- package/content/skills/mugiwara-workflow/references/stop-slop-governor.md +0 -14
package/scripts/lane-base.ts
CHANGED
|
@@ -107,6 +107,22 @@ for (const lane of lanes) {
|
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
// A lane whose base exceeds its budget is born in `warn` — the budget is then
|
|
111
|
+
// noise rather than a signal. (B5)
|
|
112
|
+
for (const lane of ['lean', 'standard', 'full', 'spike']) {
|
|
113
|
+
const base = constants[lane]?.base ?? 0;
|
|
114
|
+
const budget = constants[lane]?.budget ?? 0;
|
|
115
|
+
if (base >= budget) {
|
|
116
|
+
console.log(` ✗ LANE_BASE_${lane} (${base}) >= BUDGET_${lane} (${budget}) — every mission starts over budget`);
|
|
117
|
+
failures++;
|
|
118
|
+
}
|
|
119
|
+
const pct = budget ? Math.round((base / budget) * 100) : 100;
|
|
120
|
+
if (pct > 80) {
|
|
121
|
+
console.log(` ✗ LANE_BASE_${lane} is ${pct}% of BUDGET_${lane} — leaves no headroom (target ≤70%)`);
|
|
122
|
+
failures++;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
110
126
|
if (failures > 0) {
|
|
111
127
|
console.log(`\nlane-base: ${failures} constant(s) drifted from content load`);
|
|
112
128
|
process.exit(1);
|
package/scripts/lane.sh
CHANGED
|
@@ -11,7 +11,11 @@ BASE="${1:-main}"
|
|
|
11
11
|
JSON_OUT=0
|
|
12
12
|
[ "${2:-}" = "--json" ] && JSON_OUT=1
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
# Resolve the repo root: handles subdirectories and git worktrees, where .git
|
|
15
|
+
# is a file rather than a directory. (B4)
|
|
16
|
+
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) || {
|
|
17
|
+
echo "lane: not a git repository" >&2; exit 1; }
|
|
18
|
+
cd "$REPO_ROOT" || { echo "lane: cannot enter repo root" >&2; exit 1; }
|
|
15
19
|
|
|
16
20
|
# resolve base
|
|
17
21
|
if ! git rev-parse "$BASE" >/dev/null 2>&1; then
|
package/scripts/lib/lane-base.sh
CHANGED
package/scripts/savepoint.sh
CHANGED
|
@@ -8,6 +8,18 @@ set -u
|
|
|
8
8
|
|
|
9
9
|
die() { echo "savepoint: $*" >&2; exit 1; }
|
|
10
10
|
|
|
11
|
+
# count_boxes <file> <char-class> — count markdown checkboxes.
|
|
12
|
+
# Anchored so prose mentioning "- [x]" is not counted; skips fenced code blocks
|
|
13
|
+
# so documentation examples are not counted; matches [x] and [X] alike. (B3)
|
|
14
|
+
count_boxes() {
|
|
15
|
+
[ -f "$1" ] || { echo 0; return; }
|
|
16
|
+
awk -v pat="$2" '
|
|
17
|
+
/^[[:space:]]*```/ { inblock = !inblock; next }
|
|
18
|
+
!inblock && $0 ~ ("^[[:space:]]*-[[:space:]]*\\[" pat "\\]") { n++ }
|
|
19
|
+
END { print n+0 }
|
|
20
|
+
' "$1"
|
|
21
|
+
}
|
|
22
|
+
|
|
11
23
|
MUGIWARA_DIR="${MUGIWARA_DIR:-.mugiwara}"
|
|
12
24
|
|
|
13
25
|
# optional provider-reported tokens file (T4): --tokens-file <path> JSON {input_tokens, output_tokens}
|
|
@@ -154,6 +166,11 @@ esac
|
|
|
154
166
|
# (BSD/macOS-safe: no \+ BRE).
|
|
155
167
|
BRANCH_SLUG=$(echo "$BRANCH" | tr '/' '-' | tr -cd 'A-Za-z0-9._-' | sed 's/^\.\{1,\}$//' )
|
|
156
168
|
|
|
169
|
+
# Resolve the repo root: handles subdirectories and git worktrees, where .git
|
|
170
|
+
# is a file rather than a directory. (B4)
|
|
171
|
+
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) || die "not a git repository"
|
|
172
|
+
cd "$REPO_ROOT" || die "cannot enter repo root"
|
|
173
|
+
|
|
157
174
|
# state + continue live in the mission dir. Solo (member empty) → state.json
|
|
158
175
|
# + continue.json; team writes <member>.json + continue-<member>.json so
|
|
159
176
|
# parallel members never clobber each other.
|
|
@@ -167,7 +184,6 @@ else
|
|
|
167
184
|
fi
|
|
168
185
|
|
|
169
186
|
[ -z "$MISSION" ] && die "usage: savepoint.sh <mission> [member] [wave] [mode] [lane]"
|
|
170
|
-
[ -d .git ] || die "not a git repository"
|
|
171
187
|
|
|
172
188
|
# --- computed fields ---
|
|
173
189
|
BASE_SHA=$(git merge-base HEAD main 2>/dev/null || git merge-base HEAD master 2>/dev/null || git merge-base HEAD "$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|refs/remotes/origin/||')" 2>/dev/null || git rev-parse HEAD~1 2>/dev/null || echo "unknown")
|
|
@@ -328,9 +344,23 @@ if [ -n "$PLAN_FILE" ] && [ -f "$PLAN_FILE" ]; then
|
|
|
328
344
|
# total counts ALL task lines (checked + unchecked); done counts checked only.
|
|
329
345
|
# A fully-completed plan must read total=N done=N, never total=0 (the old
|
|
330
346
|
# unchecked-only grep degenerated a done plan to tasks.total=0).
|
|
331
|
-
TASKS_TOTAL=$(
|
|
332
|
-
TASKS_DONE=$(
|
|
347
|
+
TASKS_TOTAL=$(count_boxes "$PLAN_FILE" '[ xX]')
|
|
348
|
+
TASKS_DONE=$(count_boxes "$PLAN_FILE" '[xX]')
|
|
349
|
+
fi
|
|
350
|
+
# Fallback for large campaigns (>3 phases, >1500 lines) where master plan.md is an index
|
|
351
|
+
# and tasks live in sub-plan/*.md — only when plan.md has zero checkbox tasks to
|
|
352
|
+
# keep simple missions unchanged.
|
|
353
|
+
if [ "${TASKS_TOTAL:-0}" -eq 0 ] 2>/dev/null && [ -d "$MISSION_DIR/sub-plan" ]; then
|
|
354
|
+
TASKS_TOTAL=0; TASKS_DONE=0
|
|
355
|
+
for _sp in "$MISSION_DIR"/sub-plan/*.md; do
|
|
356
|
+
[ -f "$_sp" ] || continue
|
|
357
|
+
TASKS_TOTAL=$(( TASKS_TOTAL + $(count_boxes "$_sp" '[ xX]') ))
|
|
358
|
+
TASKS_DONE=$(( TASKS_DONE + $(count_boxes "$_sp" '[xX]') ))
|
|
359
|
+
done
|
|
333
360
|
fi
|
|
361
|
+
# done ≤ total is an invariant of the audit trail — never let a report show
|
|
362
|
+
# progress above 100%, whatever the plan file contains. (B3)
|
|
363
|
+
[ "${TASKS_DONE:-0}" -gt "${TASKS_TOTAL:-0}" ] 2>/dev/null && TASKS_DONE="$TASKS_TOTAL"
|
|
334
364
|
|
|
335
365
|
# blocker count
|
|
336
366
|
BLOCKERS_FILE="$MISSION_DIR/blockers.md"
|
|
@@ -365,6 +395,16 @@ if [ "$HEAL_CYCLE" -ge "$HEAL_MAX_CYCLES" ] 2>/dev/null; then
|
|
|
365
395
|
HEAL_HALT=true
|
|
366
396
|
fi
|
|
367
397
|
|
|
398
|
+
# slop — context (repeated reads) per cost-governor §§21-24,31-32 — T5 wire all crews Luffy/Nami/Zoro/Brook
|
|
399
|
+
REPEATED_READS=0
|
|
400
|
+
REPEATED_THRESHOLD=3
|
|
401
|
+
REGISTRY_FILE="$MISSION_DIR/context-registry.jsonl"
|
|
402
|
+
if [ -f "$REGISTRY_FILE" ]; then
|
|
403
|
+
REPEATED_READS=$(node -e "try{const fs=require('fs');const t=fs.readFileSync(process.argv[1],'utf8');let s=0;for(const l of t.split(/\r?\n/)){if(!l.trim())continue;try{const e=JSON.parse(l);if(typeof e.reads==='number'&&e.reads>=2)s+=Math.floor(e.reads)-1}catch{}}console.log(s)}catch(e){console.log(0)}" "$REGISTRY_FILE" 2>/dev/null || echo 0)
|
|
404
|
+
REPEATED_READS=$(( ${REPEATED_READS:-0} + 0 ))
|
|
405
|
+
fi
|
|
406
|
+
# repeated_reads > threshold → context slop — crew must skip re-read/compress before dispatch (§22,31); heal_cycle≥max → halt/escalate (§21.7/32) — cost-governor §§20,21-24
|
|
407
|
+
|
|
368
408
|
# depth flags — advisory → measured (roadmap v0.8 item 4). Read from config
|
|
369
409
|
# like the other keys; computed into state.json so enforcement is a fact the
|
|
370
410
|
# gates flow stage can read, not prose.
|
|
@@ -523,7 +563,9 @@ const data = {
|
|
|
523
563
|
budget_status: process.argv[19],
|
|
524
564
|
skill_version: process.argv[20],
|
|
525
565
|
evidence: process.argv[21] ? process.argv[21].split(',').filter(Boolean) : [],
|
|
526
|
-
updated_at: process.argv[22]
|
|
566
|
+
updated_at: process.argv[22],
|
|
567
|
+
schema_version: 2,
|
|
568
|
+
repeated_reads: parseInt(process.argv[41], 10) || 0
|
|
527
569
|
};
|
|
528
570
|
require('fs').writeFileSync(process.argv[23], JSON.stringify(data, null, 2) + '\n');
|
|
529
571
|
" \
|
|
@@ -536,7 +578,8 @@ require('fs').writeFileSync(process.argv[23], JSON.stringify(data, null, 2) + '\
|
|
|
536
578
|
"$STATE_FILE" "$LANE_PREV" "$LANE_ROSE" "$TOKENS_SOURCE" "$LANE_PEAK" \
|
|
537
579
|
"$LOC_INS" "$LOC_DEL" "$LOC_CHURN" "$MEMBER" "$VERBOSITY" \
|
|
538
580
|
"$HEAL_MAX_CYCLES" "$HEAL_HALT" "$DELEGATE_THRESHOLD" "$DELEGATE_DUE" \
|
|
539
|
-
"$MODEL" "$DEPTH_REVIEW" "$DEPTH_QUALITY" "$DEPTH_VERIFY"
|
|
581
|
+
"$MODEL" "$DEPTH_REVIEW" "$DEPTH_QUALITY" "$DEPTH_VERIFY" \
|
|
582
|
+
"$REPEATED_READS"
|
|
540
583
|
|
|
541
584
|
if [ "$LANE_ROSE" = true ]; then
|
|
542
585
|
echo "⚠ LANE ROSE: $LANE_PREV → $LANE ($LANE_REASON) — escalate per check-in protocol"
|
|
@@ -417,6 +417,66 @@ if (integrityArg !== -1) {
|
|
|
417
417
|
}
|
|
418
418
|
}
|
|
419
419
|
|
|
420
|
+
// --- README metrics gate (D3): README table must match .metrics/latest.json ---
|
|
421
|
+
if (process.argv.includes('--check-readme-metrics')) {
|
|
422
|
+
const metricsPath = join(import.meta.dirname, '..', '.metrics', 'latest.json');
|
|
423
|
+
if (!existsSync(metricsPath)) {
|
|
424
|
+
errors.push(`README metrics: ${metricsPath} not found — run bun scripts/write-metrics.ts`);
|
|
425
|
+
} else {
|
|
426
|
+
let metrics: any;
|
|
427
|
+
try { metrics = JSON.parse(readFileSync(metricsPath, 'utf8')); }
|
|
428
|
+
catch (e) { errors.push(`README metrics: invalid JSON in ${metricsPath}: ${e}`); }
|
|
429
|
+
if (metrics) {
|
|
430
|
+
const readmePath = join(import.meta.dirname, '..', 'README.md');
|
|
431
|
+
if (!existsSync(readmePath)) {
|
|
432
|
+
errors.push('README metrics: README.md not found');
|
|
433
|
+
} else {
|
|
434
|
+
const readme = readFileSync(readmePath, 'utf8');
|
|
435
|
+
// rank-1: **95.9%**, 216 probes
|
|
436
|
+
const rankMatch = readme.match(/Retrieval routing rank-1[^\n]*?(\d+\.\d+)%[^\n]*?(\d+)\s+probes/i);
|
|
437
|
+
if (!rankMatch) {
|
|
438
|
+
errors.push('README metrics: could not parse Retrieval routing rank-1 row (expected "**X.Y%**, N probes")');
|
|
439
|
+
} else {
|
|
440
|
+
const readmeRank = parseFloat(rankMatch[1]);
|
|
441
|
+
const readmeProbes = parseInt(rankMatch[2], 10);
|
|
442
|
+
const wantRank = Number(metrics.retrieval_rank1);
|
|
443
|
+
const wantProbes = Number(metrics.retrieval_probes);
|
|
444
|
+
if (Math.abs(readmeRank - wantRank) > 0.05) {
|
|
445
|
+
errors.push(`README metrics: rank-1 ${readmeRank}% != metrics ${wantRank}% (probes ${readmeProbes} vs ${wantProbes}) — run bun scripts/write-metrics.ts and update README`);
|
|
446
|
+
}
|
|
447
|
+
if (readmeProbes !== wantProbes) {
|
|
448
|
+
errors.push(`README metrics: probes ${readmeProbes} != metrics ${wantProbes} (rank ${readmeRank}% vs ${wantRank}%) — run bun scripts/write-metrics.ts and update README`);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
// pointers: **286/286**, 9 targets (or tiers)
|
|
452
|
+
const ptrMatch = readme.match(/Reference pointers resolve[^\n]*?\*\*(\d+)\/(\d+)\*\*[^\n]*?(\d+)\s+(tiers|targets)/i);
|
|
453
|
+
if (!ptrMatch) {
|
|
454
|
+
errors.push('README metrics: could not parse Reference pointers row (expected "**N/N**, M targets")');
|
|
455
|
+
} else {
|
|
456
|
+
const a = parseInt(ptrMatch[1], 10);
|
|
457
|
+
const b = parseInt(ptrMatch[2], 10);
|
|
458
|
+
const count = parseInt(ptrMatch[3], 10);
|
|
459
|
+
const wantTotal = Number(metrics.pointers_total);
|
|
460
|
+
const wantTargets = Number(metrics.pointers_targets);
|
|
461
|
+
if (a !== wantTotal || b !== wantTotal) {
|
|
462
|
+
errors.push(`README metrics: pointers ${a}/${b} != metrics ${wantTotal}/${wantTotal} — run bun scripts/write-metrics.ts and update README`);
|
|
463
|
+
}
|
|
464
|
+
if (count !== wantTargets) {
|
|
465
|
+
errors.push(`README metrics: targets/tiers ${count} != metrics ${wantTargets} — run bun scripts/write-metrics.ts and update README (expected ${wantTargets} targets)`);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
// sanity: table still claims "Nothing in this table is an estimate"
|
|
469
|
+
if (!readme.includes('Nothing in this table is an estimate')) {
|
|
470
|
+
errors.push('README metrics: missing "Nothing in this table is an estimate" line');
|
|
471
|
+
}
|
|
472
|
+
if (errors.filter(e => e.startsWith('README metrics:')).length === 0) {
|
|
473
|
+
console.log(`✓ README metrics match .metrics/latest.json (rank-1 ${metrics.retrieval_rank1}% ${metrics.retrieval_probes} probes, ${metrics.pointers_total}/${metrics.pointers_total} pointers, ${metrics.pointers_targets} targets)`);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
420
480
|
// Conditional-assertion guard: an expect() reachable only inside a truthiness
|
|
421
481
|
// check silently passes when the value is absent. This class produced 9 defects.
|
|
422
482
|
// Allowed: checks keyed on a declared invariant (tier, fixture keys).
|
|
@@ -18,6 +18,7 @@ import { targets, TARGET_IDS } from '../src/targets/index.ts';
|
|
|
18
18
|
|
|
19
19
|
const repoRoot = join(import.meta.dirname, '..');
|
|
20
20
|
const fail: string[] = [];
|
|
21
|
+
const isJson = process.argv.includes('--json');
|
|
21
22
|
|
|
22
23
|
function findMd(root: string, out: string[] = []): string[] {
|
|
23
24
|
if (!existsSync(root)) return out;
|
|
@@ -169,6 +170,25 @@ if (orphans.length > ORPHAN_BASELINE) {
|
|
|
169
170
|
}
|
|
170
171
|
|
|
171
172
|
// ---------------------------------------------------------------------------
|
|
173
|
+
if (isJson) {
|
|
174
|
+
const payload = {
|
|
175
|
+
pointers_total: pointers,
|
|
176
|
+
pointers_targets: TARGET_IDS.length,
|
|
177
|
+
pointers_broken: brokenPointers,
|
|
178
|
+
prose_paths: prosePaths,
|
|
179
|
+
prose_files: proseFiles.length,
|
|
180
|
+
orphans,
|
|
181
|
+
orphans_count: orphans.length,
|
|
182
|
+
ref_files: refFiles.length,
|
|
183
|
+
targets: TARGET_IDS.length,
|
|
184
|
+
pointers: pointers,
|
|
185
|
+
broken_pointers: brokenPointers,
|
|
186
|
+
};
|
|
187
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
188
|
+
if (fail.length) process.exit(1);
|
|
189
|
+
process.exit(0);
|
|
190
|
+
}
|
|
191
|
+
|
|
172
192
|
console.log(` ${pointers} pointers checked across ${TARGET_IDS.length} targets`);
|
|
173
193
|
console.log(` ${prosePaths} prose paths checked in ${proseFiles.length} files`);
|
|
174
194
|
console.log(` ${orphans.length}/${refFiles.length} reference files unreachable (baseline ${ORPHAN_BASELINE})`);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// scripts/write-metrics.ts — generate .metrics/latest.json from gate outputs
|
|
3
|
+
// Deterministic, no network. Runs retrieval-eval --json and verify-install --json.
|
|
4
|
+
|
|
5
|
+
import { execSync } from 'node:child_process';
|
|
6
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
|
|
9
|
+
const root = join(import.meta.dirname, '..');
|
|
10
|
+
|
|
11
|
+
function extractJson(output: string): any {
|
|
12
|
+
const idx = output.indexOf('{');
|
|
13
|
+
if (idx === -1) throw new Error('no JSON found in output: ' + output.slice(0, 200));
|
|
14
|
+
return JSON.parse(output.slice(idx));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function runJson(cmd: string): any {
|
|
18
|
+
// execSync returns stdout only; retrieval prints a rank line before JSON on stdout
|
|
19
|
+
// so we slice from first '{'
|
|
20
|
+
const out = execSync(cmd, { cwd: root, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
21
|
+
return extractJson(out);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// retrieval: need probes, rank1_rate, index_size
|
|
25
|
+
let ret: any;
|
|
26
|
+
let ver: any;
|
|
27
|
+
try {
|
|
28
|
+
ret = runJson('bun scripts/retrieval-eval.ts --json');
|
|
29
|
+
} catch (e: any) {
|
|
30
|
+
// if process exits non-zero, stdout still contains JSON + rank line; try to parse from error stdout
|
|
31
|
+
const out = e.stdout?.toString() ?? e.message ?? '';
|
|
32
|
+
if (out.includes('{')) ret = extractJson(out);
|
|
33
|
+
else throw e;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
ver = runJson('bun scripts/verify-install.ts --json');
|
|
38
|
+
} catch (e: any) {
|
|
39
|
+
const out = e.stdout?.toString() ?? e.message ?? '';
|
|
40
|
+
if (out.includes('{')) ver = extractJson(out);
|
|
41
|
+
else throw e;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const rank1Str: string = ret.rank1_rate ?? ret.rank1 ?? '';
|
|
45
|
+
const rank1Num = typeof rank1Str === 'string' ? parseFloat(rank1Str.replace('%', '')) : Number(rank1Str);
|
|
46
|
+
const probes = ret.probes ?? ret.totalProbes ?? 0;
|
|
47
|
+
const pointersTotal = ver.pointers_total ?? ver.pointers ?? 0;
|
|
48
|
+
const pointersTargets = ver.pointers_targets ?? ver.targets ?? 0;
|
|
49
|
+
const indexSize = ret.index_size ?? 0;
|
|
50
|
+
const updated = new Date().toISOString().split('T')[0];
|
|
51
|
+
|
|
52
|
+
const metrics = {
|
|
53
|
+
retrieval_rank1: rank1Num,
|
|
54
|
+
retrieval_rank1_rate: rank1Str,
|
|
55
|
+
retrieval_probes: probes,
|
|
56
|
+
retrieval_rank1_count: ret.rank1_count ?? null,
|
|
57
|
+
retrieval_positives: ret.positives ?? null,
|
|
58
|
+
retrieval_negatives: ret.negatives ?? null,
|
|
59
|
+
retrieval_index_size: indexSize,
|
|
60
|
+
retrieval_index_terms: ret.index_terms ?? null,
|
|
61
|
+
pointers_total: pointersTotal,
|
|
62
|
+
pointers_targets: pointersTargets,
|
|
63
|
+
pointers_broken: ver.pointers_broken ?? ver.broken_pointers ?? 0,
|
|
64
|
+
index_size: indexSize,
|
|
65
|
+
updated,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const outDir = join(root, '.metrics');
|
|
69
|
+
mkdirSync(outDir, { recursive: true });
|
|
70
|
+
const outPath = join(outDir, 'latest.json');
|
|
71
|
+
writeFileSync(outPath, JSON.stringify(metrics, null, 2) + '\n');
|
|
72
|
+
console.log(`✓ wrote ${outPath}`);
|
|
73
|
+
console.log(JSON.stringify(metrics, null, 2));
|
package/src/budget.ts
CHANGED
|
@@ -45,3 +45,14 @@ export function formatFootprint(chars: number, budget: number): string {
|
|
|
45
45
|
? `${base} — OVER budget ${budget}`
|
|
46
46
|
: `${base} (budget ${budget})`;
|
|
47
47
|
}
|
|
48
|
+
|
|
49
|
+
// ── Auto-compress threshold (T4) — 80% of budget ───────────────────────────
|
|
50
|
+
export const COMPRESS_THRESHOLD_PCT = 0.8;
|
|
51
|
+
|
|
52
|
+
export function shouldCompress(budget: number, chars: number): boolean {
|
|
53
|
+
return budget > 0 && chars > Math.floor(budget * COMPRESS_THRESHOLD_PCT);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function compressThreshold(budget: number): number {
|
|
57
|
+
return Math.floor(budget * COMPRESS_THRESHOLD_PCT);
|
|
58
|
+
}
|