@oneie/claude 0.6.0 → 0.7.0
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/agents/w1-recon.md +9 -4
- package/agents/w2-decide.md +51 -28
- package/agents/w3-edit.md +70 -78
- package/agents/w4-verify.md +164 -69
- package/commands/browser.md +98 -35
- package/commands/cc-connect.md +6 -0
- package/commands/chat.md +13 -1
- package/commands/close.md +45 -19
- package/commands/create.md +20 -10
- package/commands/db-sync.md +5 -1
- package/commands/deploy.md +174 -87
- package/commands/do-autonomous.md +31 -20
- package/commands/do-improve.md +4 -4
- package/commands/do-show.md +4 -3
- package/commands/do.md +53 -13
- package/commands/go.md +75 -0
- package/commands/improve.md +4 -2
- package/commands/kill.md +6 -8
- package/commands/notify.md +15 -2
- package/commands/oo-push.md +24 -9
- package/commands/rag.md +6 -0
- package/commands/release.md +46 -25
- package/commands/restart.md +5 -4
- package/commands/see.md +45 -35
- package/commands/skill-create.md +38 -5
- package/commands/sync.md +21 -11
- package/hooks/scripts/auto-continue.sh +70 -0
- package/hooks/scripts/branch-pin.sh +85 -0
- package/hooks/scripts/git-add-guard.sh +16 -5
- package/hooks/scripts/post-edit-check.sh +16 -0
- package/hooks/scripts/session-start.sh +13 -8
- package/hooks/scripts/sync-priority-todo.sh +5 -0
- package/package.json +1 -1
- package/rules/api.md +9 -0
- package/rules/design.md +13 -3
- package/rules/documentation.md +30 -71
- package/rules/engine.md +8 -1
- package/rules/ui.md +5 -1
- package/scripts/__pycache__/do-rank.cpython-313.pyc +0 -0
- package/scripts/browser-check.mjs +44 -103
- package/scripts/cc-events-proof.sh +13 -12
- package/scripts/chrome.mjs +381 -0
- package/scripts/do-analyze.sh +109 -4
- package/scripts/do-auto.sh +350 -43
- package/scripts/do-consumer-sweep.sh +72 -0
- package/scripts/do-derives-check.sh +88 -0
- package/scripts/do-fleet.sh +50 -1
- package/scripts/do-killswitch-audit.py +33 -14
- package/scripts/do-promise-lint.sh +150 -4
- package/scripts/do-promise-settle.sh +131 -9
- package/scripts/do-prove.sh +197 -17
- package/scripts/do-rank.py +606 -20
- package/scripts/do-rubric.py +21 -2
- package/scripts/do-signal.sh +157 -17
- package/scripts/do-smoke.sh +9 -0
- package/scripts/do-substrate-check.sh +49 -0
- package/scripts/do-tasks-wire-check.sh +81 -0
- package/scripts/do-tier.sh +90 -5
- package/scripts/do-ui-gate.sh +332 -0
- package/scripts/do-untracked-gate.sh +231 -0
- package/scripts/do-walk.sh +344 -0
- package/scripts/factory-check.sh +747 -0
- package/scripts/factory-repo.sh +1361 -0
- package/scripts/factory-tasks-check.sh +85 -0
- package/scripts/outcome-pull.ts +279 -0
- package/scripts/promise-manifest.mjs +167 -0
- package/scripts/reconcile-allow.txt +5 -0
- package/scripts/rubric-weights.json +64 -0
- package/scripts/spine-canary.sh +20 -0
- package/scripts/typedb-cluster-status.sh +128 -0
- package/scripts/typedb-env.sh +182 -0
- package/scripts/typedb-flap-recorder.sh +85 -0
- package/scripts/typedb-probes/containment-probe.py +88 -0
- package/scripts/typedb-probes/panic-probe-version.py +92 -0
- package/scripts/typedb-probes/panic-probe.py +77 -0
- package/scripts/typedb-scratch.sh +73 -0
- package/scripts/urls-lint.sh +58 -0
- package/scripts/w4-rubric.ts +96 -12
- package/scripts/wf-check.mjs +75 -0
- package/skills/ai-ui/SKILL.md +1 -1
- package/skills/astro/SKILL.md +177 -132
- package/skills/astro/add-content-collection.md +29 -13
- package/skills/astro/check-build.md +19 -13
- package/skills/astro/create-component.md +36 -9
- package/skills/astro/create-page.md +39 -10
- package/skills/astro/optimize-performance.md +36 -8
- package/skills/cli/SKILL.md +254 -220
- package/skills/dev/SKILL.md +1 -1
- package/skills/directory-autofill/SKILL.md +68 -0
- package/skills/docs/SKILL.md +4 -4
- package/skills/mcp/SKILL.md +186 -156
- package/skills/promise-make/SKILL.md +2 -2
- package/skills/puck/SKILL.md +465 -250
- package/skills/react19/SKILL.md +196 -296
- package/skills/reactflow/SKILL.md +387 -412
- package/skills/sdk/SKILL.md +167 -106
- package/skills/shadcn/SKILL.md +245 -201
- package/skills/signal/SKILL.md +5 -0
- package/skills/sui/SKILL.md +6 -1
- package/skills/tutorial/SKILL.md +15 -0
- package/skills/typedb/SKILL.md +427 -217
- package/skills/typedb/examples/python-patterns.py +11 -4
- package/skills/typedb/examples/query-patterns.tql +8 -3
- package/skills/typedb/examples/schema-patterns.tql +4 -2
- package/skills/typedb/reference/migration-2x-3x.md +19 -1
- package/skills/typedb/reference/python-driver.md +17 -1
- package/skills/voice/SKILL.md +117 -0
- package/skills/writer/SKILL.md +29 -12
- package/templates/template-feature.md +26 -1
- package/templates/template-tests.md +4 -0
- package/templates/template-todo.md +65 -10
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# factory-tasks-check.sh — the LIVE half of text/factory-tasks.md.
|
|
3
|
+
#
|
|
4
|
+
# Every other check in that promise asserts an EMITTED TQL STRING. That is the right
|
|
5
|
+
# instrument for a query-builder change, and it is not enough on its own: all of them
|
|
6
|
+
# can be green while nothing ever came back from a substrate, because the production
|
|
7
|
+
# graph holds 6 `deliverable` rows against 783 written in promise markdown. So this
|
|
8
|
+
# check seeds a real objective -> deliverable -> task, reads it back through the
|
|
9
|
+
# widened path, and asserts the nesting survives AND that a deliverable with no task
|
|
10
|
+
# under it is visible as a gap.
|
|
11
|
+
#
|
|
12
|
+
# Modes:
|
|
13
|
+
# ladder-live seed a ladder in a SCRATCH db, read it back nested
|
|
14
|
+
#
|
|
15
|
+
# Exit: 0 = ok · 1 = RED (the wire is broken) · 3 = CANNOT RUN (no scratch db / no creds)
|
|
16
|
+
#
|
|
17
|
+
# 1 vs 3 is load-bearing, same as factory-check.sh: red sends someone to fix the build,
|
|
18
|
+
# cannot-run sends them to fix the environment. Never widen a cannot-run into a red.
|
|
19
|
+
#
|
|
20
|
+
# NEVER runs against `one`. The scratch db is named explicitly by TYPEDB_TEST_DB and
|
|
21
|
+
# the guard below refuses if it resolves to the production database — a seeding check
|
|
22
|
+
# pointed at production writes fabricated rungs into the graph the board reads.
|
|
23
|
+
set -uo pipefail
|
|
24
|
+
|
|
25
|
+
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
26
|
+
MODE="${1:-ladder-live}"
|
|
27
|
+
|
|
28
|
+
red() { echo "RED $1" >&2; return 1; }
|
|
29
|
+
cannot() { echo "CANNOT RUN: $1" >&2; return 3; }
|
|
30
|
+
|
|
31
|
+
# Same parser as factory-check.sh — `set -a && . .env` aborts on that file's
|
|
32
|
+
# hyphenated key + unquoted value.
|
|
33
|
+
eval "$(python3 - "$ROOT/one.ie/web/.env" <<'PY'
|
|
34
|
+
import re,sys,shlex
|
|
35
|
+
for line in open(sys.argv[1], errors="replace"):
|
|
36
|
+
m = re.match(r'^\s*(TYPEDB_(?:URL|DATABASE|USERNAME|PASSWORD))\s*=\s*(.*?)\s*$', line)
|
|
37
|
+
if m: print(f"{m.group(1)}={shlex.quote(m.group(2).strip(chr(34)+chr(39)))}")
|
|
38
|
+
PY
|
|
39
|
+
)"
|
|
40
|
+
: "${TYPEDB_URL:=}" "${TYPEDB_DATABASE:=}" "${TYPEDB_USERNAME:=}" "${TYPEDB_PASSWORD:=}" "${TYPEDB_TEST_DB:=}"
|
|
41
|
+
|
|
42
|
+
check_ladder_live() {
|
|
43
|
+
local t="$ROOT/one.ie/web/src/lib/resolvers/factory-ladder-live.test.ts"
|
|
44
|
+
[ -f "$t" ] || return $(red "no factory-ladder-live.test.ts — the ladder read is unproven against any substrate")
|
|
45
|
+
[ -n "$TYPEDB_URL" ] || return $(cannot "no TYPEDB_URL in one.ie/web/.env")
|
|
46
|
+
[ -n "$TYPEDB_TEST_DB" ] \
|
|
47
|
+
|| return $(cannot "no TYPEDB_TEST_DB scratch database — this check SEEDS rungs, so it must never point at the production graph the board reads")
|
|
48
|
+
|
|
49
|
+
# The production guard. TYPEDB_DATABASE is what the app talks to; if the scratch name
|
|
50
|
+
# equals it (or is the well-known prod name), refuse — seeding there would write
|
|
51
|
+
# fabricated rungs into the graph /factory and /u/<slug>/tasks both read.
|
|
52
|
+
if [ "$TYPEDB_TEST_DB" = "${TYPEDB_DATABASE:-}" ] || [ "$TYPEDB_TEST_DB" = "one" ]; then
|
|
53
|
+
return $(red "TYPEDB_TEST_DB is the PRODUCTION database — refusing to seed rungs into it")
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
local out
|
|
57
|
+
out=$(cd "$ROOT/one.ie/web" && \
|
|
58
|
+
TYPEDB_URL="$TYPEDB_URL" \
|
|
59
|
+
TYPEDB_USERNAME="$TYPEDB_USERNAME" \
|
|
60
|
+
TYPEDB_PASSWORD="$TYPEDB_PASSWORD" \
|
|
61
|
+
TYPEDB_TEST_DB="$TYPEDB_TEST_DB" \
|
|
62
|
+
bunx vitest run src/lib/resolvers/factory-ladder-live.test.ts 2>&1)
|
|
63
|
+
|
|
64
|
+
# An empty scratch db answers "Type label 'thing' not found" — the schema was never
|
|
65
|
+
# loaded there. That is an environment gap, not a broken ladder.
|
|
66
|
+
printf '%s' "$out" | grep -qE "Type label '(thing|containment|blocks)' not found" \
|
|
67
|
+
&& return $(cannot "scratch db $TYPEDB_TEST_DB has no schema loaded — load schema/one.tql into it first")
|
|
68
|
+
printf '%s' "$out" | grep -qE 'Failed to parse URL|ERR_INVALID_URL|signin failed' \
|
|
69
|
+
&& return $(cannot "scratch db named but the credentials did not reach vitest")
|
|
70
|
+
|
|
71
|
+
# A SKIPPED test is not a proof. The test is `describe.skipIf(!TYPEDB_TEST_DB)`, so
|
|
72
|
+
# without this guard vitest exits 0 having run nothing — the exact fail-open this
|
|
73
|
+
# check exists to forbid.
|
|
74
|
+
printf '%s' "$out" | grep -qE 'Tests[[:space:]]+.*skipped' \
|
|
75
|
+
&& return $(red "ladder-live tests SKIPPED — a skipped test is not a proof (fail-open)")
|
|
76
|
+
printf '%s' "$out" | grep -qE 'Tests[[:space:]]+[1-9][0-9]*[[:space:]]+passed' \
|
|
77
|
+
|| return $(red "ladder-live tests did not pass — a seeded objective->deliverable->task did not come back nested")
|
|
78
|
+
|
|
79
|
+
echo "ok ladder-live (scratch db $TYPEDB_TEST_DB)"
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
case "$MODE" in
|
|
83
|
+
ladder-live) check_ladder_live ;;
|
|
84
|
+
*) echo "usage: factory-tasks-check.sh ladder-live" >&2; exit 2 ;;
|
|
85
|
+
esac
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* outcome-pull.ts — pull production path weights into text/task-paths.json's `prod`
|
|
4
|
+
* field, provenance-tagged, never touching the local `strength`/`resistance`/`closed`
|
|
5
|
+
* fields `--mark`/`--sync-closes` own (do-rank.py). Part of text/self-improving-outcome.md.
|
|
6
|
+
*
|
|
7
|
+
* Reads LOCAL D1 only (`one-owners`, mirrored from prod by `bun run db:sync` — see
|
|
8
|
+
* hook:db-sync in .claude/settings.json, which runs this script right after). Never
|
|
9
|
+
* touches the network directly — do-rank.py's zero-network invariant is preserved by
|
|
10
|
+
* construction: this script is the only one that shells out, and only to local wrangler.
|
|
11
|
+
*
|
|
12
|
+
* Three prod sources, three mappers (text/self-improving-outcome-plan.md § Evidence):
|
|
13
|
+
* 1. a settled promise — claw_paths source="promise:<slug>" target="proof"
|
|
14
|
+
* 2. a /do cycle that closed — entity_tags tags ⊇ ["do:learn","slug:<slug>"]
|
|
15
|
+
* 3. a graded delivery — claw_paths source="tag:<board>:slug:<slug>"
|
|
16
|
+
* `shape→model` do-event marks are deliberately NOT mapped — verified slug-free.
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
* bun .claude/scripts/outcome-pull.ts # pull local D1 → text/task-paths.json
|
|
20
|
+
* bun .claude/scripts/outcome-pull.ts --selftest # offline fixture proof, no D1/network
|
|
21
|
+
*/
|
|
22
|
+
import { readFileSync, writeFileSync, existsSync } from 'node:fs'
|
|
23
|
+
import { join, dirname } from 'node:path'
|
|
24
|
+
|
|
25
|
+
const ROOT = join(dirname(new URL(import.meta.url).pathname), '..', '..')
|
|
26
|
+
const WEB_DIR = join(ROOT, 'one.ie', 'web')
|
|
27
|
+
const TASK_PATHS = join(ROOT, 'text', 'task-paths.json')
|
|
28
|
+
const D1_BIN = process.env.D1_BIN ?? 'one-owners'
|
|
29
|
+
|
|
30
|
+
export interface ClawPathRow {
|
|
31
|
+
source: string
|
|
32
|
+
target: string
|
|
33
|
+
strength: number
|
|
34
|
+
resistance: number
|
|
35
|
+
traversals: number
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface EntityTagRow {
|
|
39
|
+
entity_id: string
|
|
40
|
+
tags: string // JSON array string, e.g. '["do:learn","slug:foo"]'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface MappedOutcome {
|
|
44
|
+
slug: string
|
|
45
|
+
strength: number
|
|
46
|
+
resistance: number
|
|
47
|
+
traversals: number
|
|
48
|
+
source: string
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ─── the three pure mappers (fixture-tested by --selftest) ─────────────────
|
|
52
|
+
|
|
53
|
+
export function mapPromiseProof(row: ClawPathRow): MappedOutcome | null {
|
|
54
|
+
const m = /^promise:(.+)$/.exec(row.source)
|
|
55
|
+
if (!m || row.target !== 'proof') return null
|
|
56
|
+
return { slug: m[1]!, strength: row.strength, resistance: row.resistance, traversals: row.traversals, source: 'promise:proof' }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function mapDoEventLearn(row: EntityTagRow): MappedOutcome | null {
|
|
60
|
+
let tags: unknown
|
|
61
|
+
try {
|
|
62
|
+
tags = JSON.parse(row.tags)
|
|
63
|
+
} catch {
|
|
64
|
+
return null
|
|
65
|
+
}
|
|
66
|
+
if (!Array.isArray(tags)) return null
|
|
67
|
+
const has = (t: string) => tags.includes(t)
|
|
68
|
+
if (!has('do:learn')) return null
|
|
69
|
+
const slugTag = tags.find((t): t is string => typeof t === 'string' && t.startsWith('slug:'))
|
|
70
|
+
if (!slugTag) return null
|
|
71
|
+
// Presence of a prod-recorded close is the signal — the composite score embedded in
|
|
72
|
+
// doEventTitle's display text is UI prose, not a stable machine field (plan § Decisions).
|
|
73
|
+
return { slug: slugTag.slice('slug:'.length), strength: 0.5, resistance: 0, traversals: 1, source: 'do:learn' }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function mapWorldOutcome(row: ClawPathRow): MappedOutcome | null {
|
|
77
|
+
const m = /^tag:[^:]+:slug:(.+)$/.exec(row.source)
|
|
78
|
+
if (!m) return null
|
|
79
|
+
return { slug: m[1]!, strength: row.strength, resistance: row.resistance, traversals: row.traversals, source: 'world:outcome' }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ─── the merge law: `prod` is a full overwrite per pull, local fields untouched ────
|
|
83
|
+
|
|
84
|
+
type TaskPaths = Record<string, Record<string, unknown>>
|
|
85
|
+
|
|
86
|
+
export function mergeIntoTaskPaths(existing: TaskPaths, mapped: MappedOutcome[], pulledAt: string): TaskPaths {
|
|
87
|
+
const out: TaskPaths = JSON.parse(JSON.stringify(existing))
|
|
88
|
+
const bySlug = new Map<string, { strength: number; resistance: number; traversals: number; sources: Set<string> }>()
|
|
89
|
+
for (const m of mapped) {
|
|
90
|
+
const acc = bySlug.get(m.slug) ?? { strength: 0, resistance: 0, traversals: 0, sources: new Set<string>() }
|
|
91
|
+
acc.strength += m.strength
|
|
92
|
+
acc.resistance += m.resistance
|
|
93
|
+
acc.traversals += m.traversals
|
|
94
|
+
acc.sources.add(m.source)
|
|
95
|
+
bySlug.set(m.slug, acc)
|
|
96
|
+
}
|
|
97
|
+
for (const [slug, acc] of bySlug) {
|
|
98
|
+
const key = `task:${slug}`
|
|
99
|
+
const entry = out[key] ?? { closed: 0, strength: 0, resistance: 0, traversals: 0 }
|
|
100
|
+
entry.prod = {
|
|
101
|
+
strength: round3(acc.strength),
|
|
102
|
+
resistance: round3(acc.resistance),
|
|
103
|
+
traversals: acc.traversals,
|
|
104
|
+
sources: [...acc.sources].sort(),
|
|
105
|
+
pulled_at: pulledAt,
|
|
106
|
+
}
|
|
107
|
+
out[key] = entry
|
|
108
|
+
}
|
|
109
|
+
return out
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function round3(n: number): number {
|
|
113
|
+
return Math.round(n * 1000) / 1000
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ─── local D1 read (never network — reads the mirror db:sync already wrote) ────
|
|
117
|
+
|
|
118
|
+
async function d1Query(sql: string): Promise<Array<Record<string, unknown>>> {
|
|
119
|
+
const proc = Bun.spawn(
|
|
120
|
+
['bunx', 'wrangler', 'd1', 'execute', D1_BIN, '--local', '--json', '--command', sql],
|
|
121
|
+
{ cwd: WEB_DIR, stdout: 'pipe', stderr: 'pipe' },
|
|
122
|
+
)
|
|
123
|
+
const out = await new Response(proc.stdout).text()
|
|
124
|
+
if ((await proc.exited) !== 0) throw new Error(`d1 execute failed: ${await new Response(proc.stderr).text()}`)
|
|
125
|
+
const parsed = JSON.parse(out)
|
|
126
|
+
return (parsed?.[0]?.results ?? []) as Array<Record<string, unknown>>
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function pull(): Promise<void> {
|
|
130
|
+
let mapped: MappedOutcome[] = []
|
|
131
|
+
try {
|
|
132
|
+
const promiseRows = (await d1Query(
|
|
133
|
+
`SELECT source, target, strength, resistance, traversals FROM claw_paths WHERE source LIKE 'promise:%' AND target = 'proof'`,
|
|
134
|
+
)) as unknown as ClawPathRow[]
|
|
135
|
+
const outcomeRows = (await d1Query(
|
|
136
|
+
`SELECT source, target, strength, resistance, traversals FROM claw_paths WHERE source LIKE 'tag:%:slug:%'`,
|
|
137
|
+
)) as unknown as ClawPathRow[]
|
|
138
|
+
const learnRows = (await d1Query(
|
|
139
|
+
`SELECT entity_id, tags FROM entity_tags WHERE tags LIKE '%"do:learn"%' AND tags LIKE '%"slug:%'`,
|
|
140
|
+
)) as unknown as EntityTagRow[]
|
|
141
|
+
mapped = [
|
|
142
|
+
...promiseRows.map(mapPromiseProof),
|
|
143
|
+
...outcomeRows.map(mapWorldOutcome),
|
|
144
|
+
...learnRows.map(mapDoEventLearn),
|
|
145
|
+
].filter((m): m is MappedOutcome => m !== null)
|
|
146
|
+
} catch (err) {
|
|
147
|
+
// Best-effort — matches hook:db-sync's own posture (session start must never block
|
|
148
|
+
// on an offline/unreachable local D1). No crash, no partial write.
|
|
149
|
+
console.error(`[outcome-pull] local D1 unreachable, skipping: ${(err as Error).message}`)
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
const existing: TaskPaths = existsSync(TASK_PATHS) ? JSON.parse(readFileSync(TASK_PATHS, 'utf8')) : {}
|
|
153
|
+
const merged = mergeIntoTaskPaths(existing, mapped, new Date().toISOString())
|
|
154
|
+
writeFileSync(TASK_PATHS, JSON.stringify(sortKeys(merged), null, 2) + '\n')
|
|
155
|
+
console.log(`[outcome-pull] merged prod outcomes for ${new Set(mapped.map((m) => m.slug)).size} slug(s) from ${mapped.length} row(s)`)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Recursive — matches do-rank.py's `json.dumps(sort_keys=True)`, which sorts every nested
|
|
159
|
+
// level. A top-level-only sort would make the two writers emit different byte order for
|
|
160
|
+
// the same data (nested `prod`/etc. keys), causing spurious diffs on every session.
|
|
161
|
+
function sortKeys<T>(value: T): T {
|
|
162
|
+
if (Array.isArray(value)) return value.map(sortKeys) as unknown as T
|
|
163
|
+
if (value !== null && typeof value === 'object') {
|
|
164
|
+
const out: Record<string, unknown> = {}
|
|
165
|
+
for (const k of Object.keys(value as Record<string, unknown>).sort()) {
|
|
166
|
+
out[k] = sortKeys((value as Record<string, unknown>)[k])
|
|
167
|
+
}
|
|
168
|
+
return out as T
|
|
169
|
+
}
|
|
170
|
+
return value
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ─── offline selftest — the only mode the promise's proof: runs ────────────
|
|
174
|
+
|
|
175
|
+
function selftest(): void {
|
|
176
|
+
let pass = 0
|
|
177
|
+
let fail = 0
|
|
178
|
+
const check = (name: string, cond: boolean) => {
|
|
179
|
+
if (cond) {
|
|
180
|
+
pass++
|
|
181
|
+
} else {
|
|
182
|
+
fail++
|
|
183
|
+
console.error(`FAIL: ${name}`)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 1. mapPromiseProof — direct slug extraction, rejects non-proof targets.
|
|
188
|
+
check(
|
|
189
|
+
'mapPromiseProof: extracts slug from promise:<slug>→proof',
|
|
190
|
+
mapPromiseProof({ source: 'promise:self-improving-outcome', target: 'proof', strength: 4, resistance: 0, traversals: 1 })?.slug === 'self-improving-outcome',
|
|
191
|
+
)
|
|
192
|
+
check(
|
|
193
|
+
'mapPromiseProof: rejects non-proof target',
|
|
194
|
+
mapPromiseProof({ source: 'promise:foo', target: 'other', strength: 1, resistance: 0, traversals: 1 }) === null,
|
|
195
|
+
)
|
|
196
|
+
check(
|
|
197
|
+
'mapPromiseProof: rejects non-promise source',
|
|
198
|
+
mapPromiseProof({ source: 'shape:foo', target: 'proof', strength: 1, resistance: 0, traversals: 1 }) === null,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
// 2. mapDoEventLearn — requires BOTH do:learn and a slug: tag; ignores other stages.
|
|
202
|
+
check(
|
|
203
|
+
'mapDoEventLearn: extracts slug from a learn-tagged row',
|
|
204
|
+
mapDoEventLearn({ entity_id: 'conv:do-foo-c1', tags: JSON.stringify(['do:learn', 'do:cycle', 'tier:feature', 'slug:foo', 's:security']) })?.slug === 'foo',
|
|
205
|
+
)
|
|
206
|
+
check(
|
|
207
|
+
'mapDoEventLearn: rejects a non-learn stage (do:wave)',
|
|
208
|
+
mapDoEventLearn({ entity_id: 'conv:do-foo-c1', tags: JSON.stringify(['do:wave', 'slug:foo']) }) === null,
|
|
209
|
+
)
|
|
210
|
+
check(
|
|
211
|
+
'mapDoEventLearn: rejects a learn row with no slug tag',
|
|
212
|
+
mapDoEventLearn({ entity_id: 'conv:x', tags: JSON.stringify(['do:learn']) }) === null,
|
|
213
|
+
)
|
|
214
|
+
check(
|
|
215
|
+
'mapDoEventLearn: tolerates malformed tags JSON',
|
|
216
|
+
mapDoEventLearn({ entity_id: 'conv:x', tags: 'not-json' }) === null,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
// 3. mapWorldOutcome — tag-node source, world:outcome's tagNode() shape.
|
|
220
|
+
check(
|
|
221
|
+
'mapWorldOutcome: extracts slug from tag:<board>:slug:<slug>',
|
|
222
|
+
mapWorldOutcome({ source: 'tag:acme:slug:foo', target: 'model:sonnet', strength: 1, resistance: 0, traversals: 1 })?.slug === 'foo',
|
|
223
|
+
)
|
|
224
|
+
check(
|
|
225
|
+
'mapWorldOutcome: rejects a shape→model row (verified slug-free, plan § Evidence)',
|
|
226
|
+
mapWorldOutcome({ source: 'shape:do:feature', target: 'model:sonnet', strength: 1, resistance: 0, traversals: 1 }) === null,
|
|
227
|
+
)
|
|
228
|
+
check(
|
|
229
|
+
'mapWorldOutcome: rejects a non-slug tag',
|
|
230
|
+
mapWorldOutcome({ source: 'tag:acme:vip', target: 'actor:x', strength: 1, resistance: 0, traversals: 1 }) === null,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
// 4. mergeIntoTaskPaths — the merge law: outcome-weight (marked contributes, warned
|
|
234
|
+
// contributes to resistance), provenance (sources tagged), idempotent re-pull,
|
|
235
|
+
// and — the load-bearing one — LOCAL fields are never touched by a pull.
|
|
236
|
+
const local: TaskPaths = {
|
|
237
|
+
'task:foo': { closed: 1.2, strength: 0.4, resistance: 0.1, traversals: 3 },
|
|
238
|
+
}
|
|
239
|
+
const mapped: MappedOutcome[] = [
|
|
240
|
+
{ slug: 'foo', strength: 2, resistance: 0, traversals: 1, source: 'promise:proof' },
|
|
241
|
+
{ slug: 'foo', strength: 0.5, resistance: 0, traversals: 1, source: 'do:learn' },
|
|
242
|
+
]
|
|
243
|
+
const merged = mergeIntoTaskPaths(local, mapped, '2026-07-08T00:00:00.000Z')
|
|
244
|
+
check('merge: prod.strength sums same-slug rows within one pull', (merged['task:foo'] as any).prod.strength === 2.5)
|
|
245
|
+
check('merge: prod.sources carries provenance for both mappers', JSON.stringify((merged['task:foo'] as any).prod.sources) === JSON.stringify(['do:learn', 'promise:proof']))
|
|
246
|
+
check('merge: LOCAL strength untouched by the pull', (merged['task:foo'] as any).strength === 0.4)
|
|
247
|
+
check('merge: LOCAL resistance untouched by the pull', (merged['task:foo'] as any).resistance === 0.1)
|
|
248
|
+
check('merge: LOCAL closed untouched by the pull', (merged['task:foo'] as any).closed === 1.2)
|
|
249
|
+
|
|
250
|
+
// merge law, other direction: a re-pull with different rows OVERWRITES prod, never adds.
|
|
251
|
+
const rePulled = mergeIntoTaskPaths(merged, [{ slug: 'foo', strength: 1, resistance: 0, traversals: 1, source: 'promise:proof' }], '2026-07-09T00:00:00.000Z')
|
|
252
|
+
check('merge: re-pull overwrites prod.strength, does not accumulate across pulls', (rePulled['task:foo'] as any).prod.strength === 1)
|
|
253
|
+
check(
|
|
254
|
+
'merge: two identical pulls are idempotent (byte-identical prod block)',
|
|
255
|
+
JSON.stringify(mergeIntoTaskPaths(local, mapped, 'T')['task:foo']) === JSON.stringify(mergeIntoTaskPaths(local, mapped, 'T')['task:foo']),
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
// warned outcome (resistance-only) — the "warned lowers rank" half of outcome-weight.
|
|
259
|
+
const warned = mergeIntoTaskPaths({}, [{ slug: 'bar', strength: 0, resistance: 2, traversals: 1, source: 'promise:proof' }], 'T')
|
|
260
|
+
check('merge: a warned outcome lands entirely in prod.resistance, not prod.strength', (warned['task:bar'] as any).prod.resistance === 2 && (warned['task:bar'] as any).prod.strength === 0)
|
|
261
|
+
|
|
262
|
+
// outcome-weight: a slug the world marked outranks an identical one the world warned —
|
|
263
|
+
// proven here at the merge layer (do-rank.py's own selftest proves it again at the
|
|
264
|
+
// path_momentum layer — two independent proofs of the same law, per plan § Pre-mortem).
|
|
265
|
+
const markedNet = ((merged['task:foo'] as any).closed) + ((merged['task:foo'] as any).strength) + ((merged['task:foo'] as any).prod.strength) - ((merged['task:foo'] as any).resistance) - ((merged['task:foo'] as any).prod.resistance)
|
|
266
|
+
const warnedNet = ((warned['task:bar'] as any).closed ?? 0) + ((warned['task:bar'] as any).strength ?? 0) + ((warned['task:bar'] as any).prod.strength) - ((warned['task:bar'] as any).resistance ?? 0) - ((warned['task:bar'] as any).prod.resistance)
|
|
267
|
+
check('outcome-weight: a marked prod outcome nets strictly above a warned one', markedNet > warnedNet)
|
|
268
|
+
|
|
269
|
+
console.log(`SELFTEST: ${pass}/${pass + fail} pass (mapPromiseProof · mapDoEventLearn · mapWorldOutcome · merge-law · provenance · outcome-weight)`)
|
|
270
|
+
if (fail > 0) process.exit(1)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ─── entry ───────────────────────────────────────────────────────────────
|
|
274
|
+
|
|
275
|
+
if (process.argv.includes('--selftest')) {
|
|
276
|
+
selftest()
|
|
277
|
+
} else {
|
|
278
|
+
await pull()
|
|
279
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// promise-manifest.mjs — project text/<slug>.md promise frontmatter into the two
|
|
3
|
+
// JSON reads the Promise view needs (text/factory-promise-view.md §6).
|
|
4
|
+
//
|
|
5
|
+
// The promise files live in git and the web app is a CF Worker with no filesystem,
|
|
6
|
+
// so the contract has to be projected at build time. Same shape as the precedent
|
|
7
|
+
// at src/data/factory-check.json, which /factory imports.
|
|
8
|
+
//
|
|
9
|
+
// Two outputs, split so the bundle cost is bounded:
|
|
10
|
+
// src/data/promises-index.json slug · title · counts. imported by the route.
|
|
11
|
+
// src/data/promises.json full detail. imported ONLY by the API route.
|
|
12
|
+
//
|
|
13
|
+
// NOT written to public/ — a public JSON carrying accept: bodies would make the
|
|
14
|
+
// shared lens's redaction (§4) cosmetic, and would be unauthenticated for every
|
|
15
|
+
// workspace forever the moment a tenant writes a promise.
|
|
16
|
+
//
|
|
17
|
+
// Usage: node .claude/scripts/promise-manifest.mjs [--check]
|
|
18
|
+
// --check exits 3 if the committed output is stale (no write).
|
|
19
|
+
|
|
20
|
+
import { readFileSync, readdirSync, writeFileSync, existsSync } from 'node:fs'
|
|
21
|
+
import { resolve, dirname } from 'node:path'
|
|
22
|
+
import { fileURLToPath } from 'node:url'
|
|
23
|
+
import { parse as parseYaml } from '../../one.ie/web/node_modules/yaml/dist/index.js'
|
|
24
|
+
|
|
25
|
+
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../..')
|
|
26
|
+
const TEXT = resolve(ROOT, 'text')
|
|
27
|
+
const OUT = resolve(ROOT, 'one.ie/web/src/data')
|
|
28
|
+
|
|
29
|
+
// A promise is the bare slug — text/<feature>.md. Every suffixed sibling is derived
|
|
30
|
+
// from it (text/CLAUDE.md § File naming) and carries its own frontmatter that is NOT
|
|
31
|
+
// a contract; a -todo.md's `outcome:` is the promise's proof, not a second promise.
|
|
32
|
+
const DERIVED = /-(plan|todo|docs|ui|features|tutorial|how-to|reference|agents|agents-docs|humans|lifecycle)\.md$/
|
|
33
|
+
|
|
34
|
+
function splitFrontmatter(src) {
|
|
35
|
+
if (!src.startsWith('---\n')) return null
|
|
36
|
+
const end = src.indexOf('\n---', 4)
|
|
37
|
+
if (end === -1) return null
|
|
38
|
+
return { fm: src.slice(4, end), body: src.slice(end + 4) }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// The one-sentence claim — the largest thing on the rendered page, so a wrong pick
|
|
42
|
+
// is loud. Prefer the section written to BE the claim (`## The promise`, or a
|
|
43
|
+
// `## Who this is for` where the promise family uses that heading); otherwise the
|
|
44
|
+
// first prose paragraph anywhere after the H1. Headings are skipped, not bailed on:
|
|
45
|
+
// 56 of 79 promises open with a section heading immediately under the H1, so
|
|
46
|
+
// stopping at the first heading returned an empty lead for most of the corpus.
|
|
47
|
+
function firstProse(lines, from) {
|
|
48
|
+
for (let i = from; i < lines.length; i++) {
|
|
49
|
+
const l = lines[i].trim()
|
|
50
|
+
if (!l) continue
|
|
51
|
+
if (/^(#{1,6}\s|>\s|[-*+]\s|\d+\.\s|```|\||---|\*\*Status|<)/.test(l)) continue
|
|
52
|
+
// A one-line paragraph is the shape we want; join a wrapped one up to its break.
|
|
53
|
+
const buf = [l]
|
|
54
|
+
for (let j = i + 1; j < lines.length && lines[j].trim(); j++) {
|
|
55
|
+
const nxt = lines[j].trim()
|
|
56
|
+
if (/^(#{1,6}\s|>\s|[-*+]\s|\d+\.\s|```|\|)/.test(nxt)) break
|
|
57
|
+
buf.push(nxt)
|
|
58
|
+
}
|
|
59
|
+
return buf.join(' ')
|
|
60
|
+
}
|
|
61
|
+
return ''
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Only from a section written to BE the claim. Measured over the 79-file corpus:
|
|
65
|
+
// falling back to "first prose after the H1" returned a lead for all 79, but the
|
|
66
|
+
// leads were wrong — `affiliate-escrow-multichain` yielded "`resolveAffiliateLeg`
|
|
67
|
+
// in pay/backend/src/routes/escrow.ts computes an AffiliateSplit…", which is
|
|
68
|
+
// implementation detail presented as the headline claim. A confident wrong sentence
|
|
69
|
+
// at the top of a contract is worse than no sentence, so the fallback is gone: the
|
|
70
|
+
// TITLE carries the claim (they are written that way — "Affiliate escrow, every
|
|
71
|
+
// chain — EVM and Solana pay the referral for real") and this is the optional
|
|
72
|
+
// second line under it.
|
|
73
|
+
function leadSentence(body) {
|
|
74
|
+
const lines = body.split('\n')
|
|
75
|
+
// `## The promise` ONLY. `## Who this is for` was tried and dropped: it is an
|
|
76
|
+
// audience section, and its opening prose is routinely implementation detail
|
|
77
|
+
// (affiliate-escrow-multichain opens it with a function name and a file path).
|
|
78
|
+
const at = lines.findIndex((l) => /^##+\s+The promise\b/i.test(l.trim()))
|
|
79
|
+
if (at === -1) return ''
|
|
80
|
+
const lead = firstProse(lines, at + 1)
|
|
81
|
+
// One sentence, not the whole section.
|
|
82
|
+
return lead ? (lead.match(/^.*?[.!?](?=\s|$)/)?.[0] ?? lead).slice(0, 320) : ''
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// "Out of scope — not promised, by law" — the block the doc says gets equal weight
|
|
86
|
+
// and must survive to the Delivery state. Bullets only; prose in the section is
|
|
87
|
+
// context for us, not a scope line for the reader.
|
|
88
|
+
function outOfScope(body) {
|
|
89
|
+
const m = body.match(/^##+\s+Out of scope[^\n]*\n([\s\S]*?)(?=\n##\s|\n---\s*\n|$)/m)
|
|
90
|
+
if (!m) return []
|
|
91
|
+
return m[1].split('\n')
|
|
92
|
+
.map((l) => l.trim())
|
|
93
|
+
.filter((l) => /^[-*+]\s+/.test(l))
|
|
94
|
+
.map((l) => l.replace(/^[-*+]\s+/, '').replace(/^\*\*(.+?)\*\*\s*/, '$1 — ').trim())
|
|
95
|
+
.filter(Boolean)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function toList(v) {
|
|
99
|
+
if (!v) return []
|
|
100
|
+
return Array.isArray(v) ? v.filter((x) => typeof x === 'string') : []
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const promises = []
|
|
104
|
+
for (const file of readdirSync(TEXT).sort()) {
|
|
105
|
+
if (!file.endsWith('.md') || DERIVED.test(file)) continue
|
|
106
|
+
if (file.startsWith('template-')) continue
|
|
107
|
+
const src = readFileSync(resolve(TEXT, file), 'utf8')
|
|
108
|
+
const split = splitFrontmatter(src)
|
|
109
|
+
if (!split) continue
|
|
110
|
+
|
|
111
|
+
let fm
|
|
112
|
+
try { fm = parseYaml(split.fm) } catch { continue } // an unparseable head is not a contract
|
|
113
|
+
if (!fm || typeof fm !== 'object') continue
|
|
114
|
+
const rows = Array.isArray(fm.deliverables) ? fm.deliverables : []
|
|
115
|
+
if (rows.length === 0) continue // no schedule = not a promise
|
|
116
|
+
|
|
117
|
+
const slug = typeof fm.slug === 'string' && fm.slug ? fm.slug : file.replace(/\.md$/, '')
|
|
118
|
+
promises.push({
|
|
119
|
+
slug,
|
|
120
|
+
title: typeof fm.title === 'string' ? fm.title : slug,
|
|
121
|
+
status: typeof fm.status === 'string' ? fm.status.split('\n')[0].trim() : '',
|
|
122
|
+
lead: leadSentence(split.body),
|
|
123
|
+
deliverables: rows
|
|
124
|
+
.filter((r) => r && typeof r.item === 'string')
|
|
125
|
+
.map((r) => ({ item: r.item, accept: typeof r.accept === 'string' ? r.accept : '' })),
|
|
126
|
+
assumes: toList(fm.assumes),
|
|
127
|
+
// `proof:` is deliberately NOT carried. By the schedule ⊆ proof law it is the
|
|
128
|
+
// verbatim `&&`-join of every accept: above (do-promise-lint.sh enforces the
|
|
129
|
+
// inclusion), so shipping it doubles the payload to say nothing new — it cost
|
|
130
|
+
// 190 KB of the first cut. The view renders verdicts, never the command.
|
|
131
|
+
outOfScope: outOfScope(split.body),
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// The index carries the claim and the count and nothing else — it is imported by
|
|
136
|
+
// the Astro route, so every field costs worker bundle on every tasks request.
|
|
137
|
+
const index = promises.map((p) => ({
|
|
138
|
+
slug: p.slug,
|
|
139
|
+
title: p.title,
|
|
140
|
+
count: p.deliverables.length,
|
|
141
|
+
}))
|
|
142
|
+
|
|
143
|
+
const detail = Object.fromEntries(promises.map((p) => [p.slug, p]))
|
|
144
|
+
|
|
145
|
+
const files = [
|
|
146
|
+
[resolve(OUT, 'promises-index.json'), index],
|
|
147
|
+
[resolve(OUT, 'promises.json'), detail],
|
|
148
|
+
]
|
|
149
|
+
|
|
150
|
+
if (process.argv.includes('--check')) {
|
|
151
|
+
for (const [path, data] of files) {
|
|
152
|
+
const want = JSON.stringify(data, null, 2) + '\n'
|
|
153
|
+
const have = existsSync(path) ? readFileSync(path, 'utf8') : ''
|
|
154
|
+
if (want !== have) {
|
|
155
|
+
console.error(`[promise-manifest] STALE: ${path.replace(ROOT + '/', '')} — run node .claude/scripts/promise-manifest.mjs`)
|
|
156
|
+
process.exit(3)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
console.log(`[promise-manifest] fresh — ${promises.length} promises`)
|
|
160
|
+
process.exit(0)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
for (const [path, data] of files) {
|
|
164
|
+
writeFileSync(path, JSON.stringify(data, null, 2) + '\n')
|
|
165
|
+
console.log(`[promise-manifest] ${path.replace(ROOT + '/', '')} — ${(JSON.stringify(data).length / 1024).toFixed(1)} KB`)
|
|
166
|
+
}
|
|
167
|
+
console.log(`[promise-manifest] ${promises.length} promises · ${promises.reduce((n, p) => n + p.deliverables.length, 0)} deliverables`)
|
|
@@ -23,3 +23,8 @@ knowledge supplies
|
|
|
23
23
|
# runs one), not the substrate "node". text/tokens.md, text/tokens-plan.md.
|
|
24
24
|
lightning node
|
|
25
25
|
node to run
|
|
26
|
+
|
|
27
|
+
# playbook-node — a real one.tql attribute (ELEVATE playbook position marker),
|
|
28
|
+
# a kebab-case compound where grep -w treats the hyphen as a word boundary and
|
|
29
|
+
# false-positives on "node". Not the substrate dead name. schema/one.tql:385.
|
|
30
|
+
playbook-node
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_": "Single source for every rubric weight. Docs and scripts READ this; none restate the numbers. Edit here, nowhere else.",
|
|
3
|
+
"_why": "Four files once carried four different formulas because two legitimately different rubrics were never named. They are named here: `cycle` gates a /do cycle and carries goal-fit; `task` scores a diff that has no plan context and deliberately does not. A file that computes one must never be read as the other.",
|
|
4
|
+
"_canon": "text/rubrics.md is the design source of truth. The `migration` block below records where this file deliberately lags it.",
|
|
5
|
+
|
|
6
|
+
"gate": 0.65,
|
|
7
|
+
"goalFitGate": 0.50,
|
|
8
|
+
|
|
9
|
+
"cycle": {
|
|
10
|
+
"_use": "The in-cycle W4 gate — the verdict that closes a /do cycle. Spec: .claude/commands/do.md § W4. Consumers: w4-verify agent, /close.",
|
|
11
|
+
"_gate": "composite >= gate AND goal-fit >= goalFitGate (hard) AND no adversarial > 0.5 AND delta_tsc <= 0. All four conjuncts, or the cycle does not close.",
|
|
12
|
+
"weights": {
|
|
13
|
+
"goal-fit": 0.30,
|
|
14
|
+
"security": 0.18,
|
|
15
|
+
"stability": 0.18,
|
|
16
|
+
"simplicity": 0.12,
|
|
17
|
+
"integration": 0.12,
|
|
18
|
+
"speed": 0.10
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
"task": {
|
|
23
|
+
"_use": "The 5-axis task rubric — scores a diff WITHOUT plan context, so goal-fit is unavailable and its absence is by design, not drift. Spec: .claude/commands/do-autonomous.md § CLOSE. Consumer: w4-rubric.ts.",
|
|
24
|
+
"_not": "NOT the cycle gate. A task composite has no goal-fit term and must never be compared to the cycle gate or written where a cycle composite is expected.",
|
|
25
|
+
"_copies": "one.ie/web/src/engine/rubric.ts CODE_RUBRIC_WEIGHTS is a pinned copy of this block (worker code cannot read this file at runtime). Change one, change both.",
|
|
26
|
+
"weights": {
|
|
27
|
+
"security": 0.30,
|
|
28
|
+
"stability": 0.25,
|
|
29
|
+
"simplicity": 0.20,
|
|
30
|
+
"integration": 0.15,
|
|
31
|
+
"speed": 0.10
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
"package": {
|
|
36
|
+
"_use": "Static scoring of a package source tree (no diff, no plan). Consumer: do-rubric.py, which writes the W0 baseline.",
|
|
37
|
+
"_partial": "goal-fit is unavailable here too. do-rubric.py normalises over these five and labels its output composite_partial. It is a BASELINE, never a gate verdict.",
|
|
38
|
+
"_sum": "These DELIBERATELY sum to 0.75, not 1.00 — they are the five code axes lifted from a larger set with goal-fit (0.30) removed, and do-rubric.py divides by their sum. Do not 'fix' them to 1.00; that would silently reweight the baseline. `cycle` and `task` DO sum to 1.00.",
|
|
39
|
+
"weights": {
|
|
40
|
+
"security": 0.20,
|
|
41
|
+
"stability": 0.20,
|
|
42
|
+
"simplicity": 0.15,
|
|
43
|
+
"integration": 0.10,
|
|
44
|
+
"speed": 0.10
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
"definitions": {
|
|
49
|
+
"_": "Every weighted axis needs a definition or the LLM judge improvises one per run. `stability` carried 0.25 undefined until 2026-08-02.",
|
|
50
|
+
"goal-fit": "Does the diff deliver the cycle's stated goal? Scored against the todo's outcome line, not against general quality.",
|
|
51
|
+
"security": "Zero vulnerabilities. Every boundary validated. No secrets. Authorization off ctx, never off a request body field.",
|
|
52
|
+
"stability": "Errors are handled, not swallowed: no empty catch, no bare Promise.all, no silent console-only catch. Every handler closes its loop with mark/warn/dissolve. TS strict holds.",
|
|
53
|
+
"simplicity": "Minimum code for maximum feature. Every line earns its place.",
|
|
54
|
+
"integration": "Wired, never orphaned — imports/exports used, routes registered, links added, docs synced. Scored deterministically from .w2-surface-checklist.json when available.",
|
|
55
|
+
"speed": "No added latency on a hot path; bundle and query cost do not regress."
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
"migration": {
|
|
59
|
+
"_status": "PENDING — design only, not executed. See text/rubric-migration-plan.md.",
|
|
60
|
+
"_target": "text/rubrics.md defines EIGHT code dims: security .20, structure .15, simplicity .15, speed .10, integration .15, code-reuse .10, test-coverage .10, ux .05, with in-cycle W4 = 0.30*goal-fit + 0.70*(the eight reweighted).",
|
|
61
|
+
"_gap": "`structure`, `code-reuse`, `test-coverage` and `ux` are scored by NOTHING today. `stability` is not a rename of `structure` — do-rubric.py's score_stability() conflates error-handling with test presence, which canon splits into `structure` and `test-coverage`. Landing the eight changes every score and breaks comparability with the 262 historical composites in text/learnings.md.",
|
|
62
|
+
"_do_not": "Do not silently substitute the eight-dim formula in any consumer. It lands as one edit to this file plus judge-prompt changes, with a dated changeover marker in learnings.md."
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# spine-canary.sh — C7 nightly home. Walks a client spine in canary mode.
|
|
3
|
+
# The spine's data/actors/<kind>.toml lives in a SEPARATE git repo
|
|
4
|
+
# (apps/vespio/clients/<slug>/) that is never bundled into the one-prod
|
|
5
|
+
# Cloudflare Worker, so this cannot run as a CF cron — it runs here, on the
|
|
6
|
+
# machine that has both checkouts, same pattern as backup/scripts/pull-backup.ts.
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
REPO_ROOT="/Users/toc/Server/one-ie"
|
|
10
|
+
CLIENT_SLUG="${SPINE_CANARY_SLUG:-elitemoversca}"
|
|
11
|
+
ACTOR_KIND="${SPINE_CANARY_KIND:-customer}"
|
|
12
|
+
|
|
13
|
+
export SPINE_CLIENT_DIR="/Users/toc/Server/apps/vespio/clients/${CLIENT_SLUG}"
|
|
14
|
+
export ONE_API_URL="${ONE_API_URL:-https://one.ie}"
|
|
15
|
+
if [ -f "$REPO_ROOT/one.ie/web/.env" ]; then
|
|
16
|
+
export GATEWAY_SERVICE_SECRET="$(grep -m1 '^GATEWAY_SERVICE_SECRET=' "$REPO_ROOT/one.ie/web/.env" | cut -d= -f2-)"
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
cd "$REPO_ROOT/one.ie/web"
|
|
20
|
+
exec bun scripts/spine-walk.ts "$ACTOR_KIND" --mode=canary
|