@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,73 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# typedb-scratch.sh — create (or refresh) a LOCAL scratch database carrying the
|
|
3
|
+
# live `one` schema, for integration tests that must never touch `one`.
|
|
4
|
+
#
|
|
5
|
+
# Why this exists: `factory-check.sh belief-guard` runs a real-substrate test
|
|
6
|
+
# (repo rule: never mock TypeDB) and needs TYPEDB_TEST_DB pointing at a database
|
|
7
|
+
# that already has the schema — the test defines none of its own. Hand-building
|
|
8
|
+
# that db is a step nobody remembers, and an empty db reports as `cannot run`, so
|
|
9
|
+
# the check silently never proves anything.
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# bash .claude/scripts/typedb-scratch.sh [name] # default: belieftest
|
|
13
|
+
# export TYPEDB_TEST_DB=belieftest # then run the checks
|
|
14
|
+
#
|
|
15
|
+
# Refuses anything but a local substrate: this DROPS and recreates the database.
|
|
16
|
+
set -uo pipefail
|
|
17
|
+
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
18
|
+
NAME="${1:-belieftest}"
|
|
19
|
+
|
|
20
|
+
if [ "$NAME" = "one" ]; then
|
|
21
|
+
echo "refusing: 'one' is the working database, not a scratch db" >&2; exit 2
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# An explicitly-exported TYPEDB_URL wins over .env. Without this the eval below
|
|
25
|
+
# silently overwrites it, which makes the local-only guard untestable and means a
|
|
26
|
+
# caller who thinks they are pointing somewhere else is not.
|
|
27
|
+
_ENV_URL="${TYPEDB_URL:-}"
|
|
28
|
+
eval "$(python3 - "$ROOT/one.ie/web/.env" <<'PY'
|
|
29
|
+
import re,sys,shlex
|
|
30
|
+
for line in open(sys.argv[1], errors="replace"):
|
|
31
|
+
m = re.match(r'^\s*(TYPEDB_(?:URL|DATABASE|USERNAME|PASSWORD))\s*=\s*(.*?)\s*$', line)
|
|
32
|
+
if m: print(f"{m.group(1)}={shlex.quote(m.group(2).strip(chr(34)+chr(39)))}")
|
|
33
|
+
PY
|
|
34
|
+
)"
|
|
35
|
+
: "${TYPEDB_URL:=}" "${TYPEDB_DATABASE:=one}" "${TYPEDB_USERNAME:=admin}" "${TYPEDB_PASSWORD:=}"
|
|
36
|
+
[ -n "$_ENV_URL" ] && TYPEDB_URL="$_ENV_URL"
|
|
37
|
+
|
|
38
|
+
case "$TYPEDB_URL" in
|
|
39
|
+
*127.0.0.1*|*localhost*) ;;
|
|
40
|
+
*) echo "refusing: TYPEDB_URL is not local ($TYPEDB_URL). Run typedb-env.sh dev first." >&2; exit 2 ;;
|
|
41
|
+
esac
|
|
42
|
+
|
|
43
|
+
TOK=$(curl -s -m 15 -X POST "$TYPEDB_URL/v1/signin" -H 'Content-Type: application/json' \
|
|
44
|
+
-d "{\"username\":\"$TYPEDB_USERNAME\",\"password\":\"$TYPEDB_PASSWORD\"}" \
|
|
45
|
+
| python3 -c 'import sys,json;print(json.load(sys.stdin).get("token",""))' 2>/dev/null)
|
|
46
|
+
[ -z "$TOK" ] && { echo "cannot run: local TypeDB signin failed at $TYPEDB_URL" >&2; exit 3; }
|
|
47
|
+
|
|
48
|
+
SCHEMA=$(mktemp); trap 'rm -f "$SCHEMA" "$REQ"' EXIT
|
|
49
|
+
curl -s -m 30 -H "Authorization: Bearer $TOK" "$TYPEDB_URL/v1/databases/$TYPEDB_DATABASE/schema" > "$SCHEMA"
|
|
50
|
+
[ -s "$SCHEMA" ] || { echo "cannot run: could not read the $TYPEDB_DATABASE schema" >&2; exit 3; }
|
|
51
|
+
|
|
52
|
+
curl -s -o /dev/null -X DELETE "$TYPEDB_URL/v1/databases/$NAME" -H "Authorization: Bearer $TOK"
|
|
53
|
+
curl -s -o /dev/null -X POST "$TYPEDB_URL/v1/databases/$NAME" -H "Authorization: Bearer $TOK"
|
|
54
|
+
|
|
55
|
+
# ensure_ascii=False is load-bearing: json.dumps' default escapes non-ASCII to
|
|
56
|
+
# \uXXXX, and a \uXXXX inside TypeQL PANICS TypeDB 3.8.3 — it kills the server
|
|
57
|
+
# process rather than returning an error. See
|
|
58
|
+
# text/typedb-production-problem-solutions.md § Two queries that kill a 3.8.3 server.
|
|
59
|
+
REQ=$(mktemp)
|
|
60
|
+
python3 - "$SCHEMA" "$NAME" > "$REQ" <<'PY'
|
|
61
|
+
import json,sys
|
|
62
|
+
print(json.dumps({"databaseName": sys.argv[2], "transactionType": "schema",
|
|
63
|
+
"query": open(sys.argv[1]).read()}, ensure_ascii=False))
|
|
64
|
+
PY
|
|
65
|
+
RES=$(curl -s -m 60 -X POST "$TYPEDB_URL/v1/query" -H "Authorization: Bearer $TOK" \
|
|
66
|
+
-H 'Content-Type: application/json' --data-binary @"$REQ")
|
|
67
|
+
|
|
68
|
+
case "$RES" in
|
|
69
|
+
*'"answerType":"ok"'*)
|
|
70
|
+
echo "scratch db '$NAME' ready — $(wc -c < "$SCHEMA" | tr -d ' ') chars of schema"
|
|
71
|
+
echo " export TYPEDB_TEST_DB=$NAME" ;;
|
|
72
|
+
*) echo "failed to define schema in '$NAME': ${RES:0:300}" >&2; exit 1 ;;
|
|
73
|
+
esac
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# urls-lint.sh — shrink-only root lint (text/urls.md § reserved prefix set).
|
|
3
|
+
#
|
|
4
|
+
# one.ie's root-level src/pages/*.astro is frozen at the set below (verified
|
|
5
|
+
# 2026-07-20). A new top-level page must be added to this allowlist by hand —
|
|
6
|
+
# an amendment to the law, never a silent file drop. The set can only shrink
|
|
7
|
+
# (a deletion needs no allowlist change); any file NOT on the list fails.
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
|
|
10
|
+
PAGES_DIR="one.ie/web/src/pages"
|
|
11
|
+
|
|
12
|
+
# The frozen baseline — every root .astro page that existed at promise-making,
|
|
13
|
+
# grouped by text/urls-plan.md's route-classification buckets (platform /
|
|
14
|
+
# console-slugless / identity / system / redirect-pending-deletion / dead).
|
|
15
|
+
FROZEN_ROOT_PAGES=(
|
|
16
|
+
# platform — marketing/product
|
|
17
|
+
index pricing agents agency affiliates brand chatbots components contracts
|
|
18
|
+
crm-platform design developers "enterprise-license" "free-license" "get-yours"
|
|
19
|
+
join leaderboard license marketing "marketing-studio" marketplace models
|
|
20
|
+
partners playbook proof scale security showcase speed sui teams tools
|
|
21
|
+
upgrade "thankyou-playbook"
|
|
22
|
+
# console-slugless
|
|
23
|
+
dashboard settings analytics chat skills build create editor in memory
|
|
24
|
+
profile credits payments rewards tracking watch stream meet classroom
|
|
25
|
+
learning activity do cc device motion "self-improving"
|
|
26
|
+
# identity
|
|
27
|
+
signin signup recover "recovery-codes" "auth-security" unsubscribe
|
|
28
|
+
# system
|
|
29
|
+
404 500
|
|
30
|
+
# redirect — superseded, C4 deletes these; kept on the allowlist so the lint
|
|
31
|
+
# is green both before and after that deletion (shrinking, never growing)
|
|
32
|
+
"index-new" "index-open" home
|
|
33
|
+
# dead — unreferenced, flagged for a future lever-4 cycle, not this promise
|
|
34
|
+
dotsdemo
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
is_allowed() {
|
|
38
|
+
local name="$1"
|
|
39
|
+
for allowed in "${FROZEN_ROOT_PAGES[@]}"; do
|
|
40
|
+
[ "$name" = "$allowed" ] && return 0
|
|
41
|
+
done
|
|
42
|
+
return 1
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
fail=0
|
|
46
|
+
for file in "$PAGES_DIR"/*.astro; do
|
|
47
|
+
[ -e "$file" ] || continue
|
|
48
|
+
base="$(basename "$file" .astro)"
|
|
49
|
+
if ! is_allowed "$base"; then
|
|
50
|
+
echo "urls-lint: FAIL — '$base.astro' is not on the frozen root allowlist (text/urls.md § reserved prefix set). Root can only shrink." >&2
|
|
51
|
+
fail=1
|
|
52
|
+
fi
|
|
53
|
+
done
|
|
54
|
+
|
|
55
|
+
if [ "$fail" -eq 0 ]; then
|
|
56
|
+
echo "urls-lint: OK — every root page is accounted for."
|
|
57
|
+
fi
|
|
58
|
+
exit "$fail"
|
package/scripts/w4-rubric.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
// w4-rubric.ts —
|
|
2
|
+
// w4-rubric.ts — the TASK rubric judge (5 axes, no goal-fit).
|
|
3
|
+
//
|
|
4
|
+
// This computes the `task` rubric, NOT the in-cycle W4 cycle gate. The two are
|
|
5
|
+
// different by design: a diff handed to this script carries no plan context, so
|
|
6
|
+
// goal-fit cannot be scored here. The cycle gate adds goal-fit at 0.30 and gates
|
|
7
|
+
// on it at >= 0.50 — see .claude/commands/do.md § W4. Its output is labelled
|
|
8
|
+
// `task-composite` so it can never be read as a cycle verdict.
|
|
9
|
+
//
|
|
10
|
+
// Weights and axis definitions come from rubric-weights.json — the single
|
|
11
|
+
// source. Do not restate them here.
|
|
12
|
+
//
|
|
3
13
|
// Usage: w4-rubric.ts [edge] [--self-test] [--gate 0.65]
|
|
4
14
|
// Reads diff from stdin. Exits 0=pass, 1=fail, 2=defer-to-agent.
|
|
5
15
|
import { readFileSync, existsSync } from 'fs'
|
|
@@ -7,30 +17,76 @@ import { readFileSync, existsSync } from 'fs'
|
|
|
7
17
|
const args = process.argv.slice(2)
|
|
8
18
|
const selfTest = args.includes('--self-test')
|
|
9
19
|
const edge = args.find(a => !a.startsWith('--')) ?? (process.env.DO_SLUG ? `do/${process.env.DO_SLUG}` : 'do/unknown')
|
|
10
|
-
|
|
20
|
+
|
|
21
|
+
// The weights file sits beside this script. A missing or malformed file is a
|
|
22
|
+
// hard failure, never a fallback to inlined numbers — a silent divergence
|
|
23
|
+
// between the file and a hardcoded copy is the exact drift this file removes.
|
|
24
|
+
const WEIGHTS_PATH = new URL('./rubric-weights.json', import.meta.url).pathname
|
|
25
|
+
type Rubric = {
|
|
26
|
+
gate: number
|
|
27
|
+
task: { weights: Record<string, number> }
|
|
28
|
+
definitions: Record<string, string>
|
|
29
|
+
}
|
|
30
|
+
function loadRubric(): Rubric {
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(readFileSync(WEIGHTS_PATH, 'utf8')) as Rubric
|
|
33
|
+
} catch (e) {
|
|
34
|
+
process.stderr.write(`[w4-rubric] cannot read ${WEIGHTS_PATH}: ${(e as Error).message}\n`)
|
|
35
|
+
process.exit(1)
|
|
36
|
+
throw e // unreachable — process.exit never returns; satisfies the checker without @types/node
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const RUBRIC = loadRubric()
|
|
40
|
+
const W = RUBRIC.task.weights
|
|
41
|
+
const AXES = Object.keys(W)
|
|
42
|
+
const wSum = AXES.reduce((n, k) => n + W[k], 0)
|
|
43
|
+
if (Math.abs(wSum - 1) > 1e-9) {
|
|
44
|
+
process.stderr.write(`[w4-rubric] task weights sum to ${wSum}, not 1.00 — refusing to score\n`)
|
|
45
|
+
process.exit(1)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const gate = parseFloat(args.find(a => a.startsWith('--gate='))?.slice(7) ?? String(RUBRIC.gate))
|
|
11
49
|
// integration is deterministic when the caller knows it (W4 computes it from
|
|
12
50
|
// .w2-surface-checklist.json + do-reconcile.sh sdk|navigation|docs and passes
|
|
13
51
|
// --integration=0.NN); only when absent does the LLM score it from the diff.
|
|
14
52
|
const integrationArg = args.find(a => a.startsWith('--integration='))?.slice(14)
|
|
15
|
-
const W = { security: 0.30, stability: 0.25, simplicity: 0.20, integration: 0.15, speed: 0.10 }
|
|
16
53
|
|
|
17
54
|
function composite(s: Record<string,number>) {
|
|
18
|
-
return
|
|
55
|
+
return AXES.reduce((n, k) => n + s[k] * W[k], 0)
|
|
19
56
|
}
|
|
20
57
|
|
|
58
|
+
// --self-test vectors. `pass` is the historical fixture; `fail` exists so the
|
|
59
|
+
// gate can be shown going RED. A gate that has only ever been observed passing
|
|
60
|
+
// is indistinguishable from a gate that cannot fail.
|
|
61
|
+
const SELF_TEST_SCORES: Record<string, number> =
|
|
62
|
+
{ security: 0.90, stability: 1.00, simplicity: 0.80, integration: 0.85, speed: 0.75 }
|
|
63
|
+
const SELF_TEST_FAIL: Record<string, number> =
|
|
64
|
+
{ security: 0.40, stability: 0.40, simplicity: 0.40, integration: 0.40, speed: 0.40 }
|
|
65
|
+
|
|
21
66
|
async function judge(diff: string): Promise<Record<string,number>> {
|
|
22
|
-
const envFile = process.env.DO_ENV_FILE ?? 'one.ie/web/.env'
|
|
67
|
+
const envFile = process.env.ONE_ENV_FILE ?? process.env.DO_ENV_FILE ?? 'one.ie/web/.env'
|
|
23
68
|
let key = process.env.OPENROUTER_API_KEY ?? ''
|
|
24
69
|
if (!key && existsSync(envFile)) {
|
|
25
70
|
const m = readFileSync(envFile,'utf8').split('\n').find(l => l.startsWith('OPENROUTER_API_KEY='))
|
|
26
71
|
if (m) key = m.slice(m.indexOf('=')+1).replace(/^["']|["']$/g,'')
|
|
27
72
|
}
|
|
28
73
|
if (!key) { process.stderr.write('[w4-rubric] no OPENROUTER_API_KEY — deferring\n'); process.exit(2) }
|
|
29
|
-
const
|
|
74
|
+
const gwAccount = process.env.CF_AI_GATEWAY_ACCOUNT_ID
|
|
75
|
+
const gwId = process.env.CF_AI_GATEWAY_ID
|
|
76
|
+
const orBase = gwAccount && gwId
|
|
77
|
+
? `https://gateway.ai.cloudflare.com/v1/${gwAccount}/${gwId}/openrouter`
|
|
78
|
+
: 'https://openrouter.ai/api/v1'
|
|
79
|
+
const res = await fetch(`${orBase}/chat/completions`, {
|
|
30
80
|
method:'POST', headers:{'Authorization':`Bearer ${key}`,'Content-Type':'application/json'},
|
|
31
81
|
body:JSON.stringify({ model:'anthropic/claude-haiku-4-5', max_tokens:80,
|
|
32
82
|
messages:[
|
|
33
|
-
|
|
83
|
+
// Every axis carries its definition from rubric-weights.json. An axis
|
|
84
|
+
// scored without one is scored against whatever the model invents that
|
|
85
|
+
// run — `stability` carried 0.25 weight undefined until 2026-08-02.
|
|
86
|
+
{role:'system',content:
|
|
87
|
+
`Score this diff on ${AXES.join('/')} (0-1). Definitions:\n`
|
|
88
|
+
+ AXES.map(k => `- ${k}: ${RUBRIC.definitions[k] ?? '(UNDEFINED — score 0 and say so)'}`).join('\n')
|
|
89
|
+
+ `\nJSON only, exactly these keys: {${AXES.map(k => `"${k}":0.8`).join(',')}}`},
|
|
34
90
|
{role:'user',content:diff.slice(0,3000)}
|
|
35
91
|
]})
|
|
36
92
|
})
|
|
@@ -39,21 +95,49 @@ async function judge(diff: string): Promise<Record<string,number>> {
|
|
|
39
95
|
if (!m2) { process.stderr.write('[w4-rubric] bad LLM response — deferring\n'); process.exit(2) }
|
|
40
96
|
const d = JSON.parse(m2[0]) as Record<string,unknown>
|
|
41
97
|
const c = (v:unknown) => Math.max(0, Math.min(1, Number(v)||0))
|
|
42
|
-
|
|
98
|
+
// An axis the model omitted must not silently become 0 inside a weighted sum —
|
|
99
|
+
// that reads as "scored badly" when it means "not scored at all".
|
|
100
|
+
const missing = AXES.filter(k => d[k] === undefined)
|
|
101
|
+
if (missing.length) {
|
|
102
|
+
process.stderr.write(`[w4-rubric] LLM omitted ${missing.join(',')} — deferring\n`)
|
|
103
|
+
process.exit(2)
|
|
104
|
+
}
|
|
105
|
+
return Object.fromEntries(AXES.map(k => [k, c(d[k])])) as Record<string, number>
|
|
43
106
|
}
|
|
44
107
|
|
|
45
108
|
async function main() {
|
|
46
|
-
const
|
|
47
|
-
|
|
109
|
+
const failFixture = args.includes('--self-test-fail')
|
|
110
|
+
const scores: Record<string, number> = (selfTest || failFixture)
|
|
111
|
+
? Object.fromEntries(AXES.map(k => [k, (failFixture ? SELF_TEST_FAIL : SELF_TEST_SCORES)[k] ?? 0.80]))
|
|
48
112
|
: await judge(await new Promise<string>(r => { let s=''; process.stdin.on('data',c=>s+=c); process.stdin.on('end',()=>r(s)) }))
|
|
49
113
|
// deterministic override wins: the surface checklist is truth, not vibes
|
|
50
114
|
if (integrationArg !== undefined) scores.integration = Math.max(0, Math.min(1, parseFloat(integrationArg)||0))
|
|
51
115
|
const comp = composite(scores)
|
|
52
116
|
const baseUrl = process.env.DO_SIGNAL_URL ?? 'https://one.ie'
|
|
53
117
|
if (!selfTest && edge !== 'do/unknown') {
|
|
54
|
-
|
|
118
|
+
// /api/mark-dims is gate()-guarded (security-gates #5) — it authenticates a service
|
|
119
|
+
// caller by Bearer === GATEWAY_API_KEY. Resolve it the same way judge() resolves the
|
|
120
|
+
// OpenRouter key (process.env → DO_ENV_FILE). Without it the marks silently 401 and
|
|
121
|
+
// the /do rubric-learning signal stops landing — a break the #5 gate introduced.
|
|
122
|
+
const envFile = process.env.ONE_ENV_FILE ?? process.env.DO_ENV_FILE ?? 'one.ie/web/.env'
|
|
123
|
+
let gwKey = process.env.GATEWAY_API_KEY ?? ''
|
|
124
|
+
if (!gwKey && existsSync(envFile)) {
|
|
125
|
+
const m = readFileSync(envFile,'utf8').split('\n').find(l => l.startsWith('GATEWAY_API_KEY='))
|
|
126
|
+
if (m) gwKey = m.slice(m.indexOf('=')+1).replace(/^["']|["']$/g,'')
|
|
127
|
+
}
|
|
128
|
+
await fetch(`${baseUrl}/api/mark-dims`, {
|
|
129
|
+
method:'POST',
|
|
130
|
+
headers:{'Content-Type':'application/json', ...(gwKey ? {'Authorization':`Bearer ${gwKey}`} : {})},
|
|
131
|
+
body:JSON.stringify({edge,dims:scores}),
|
|
132
|
+
}).catch(()=>{})
|
|
55
133
|
}
|
|
56
|
-
|
|
134
|
+
// `task-composite`, never bare `composite` — the cycle gate carries goal-fit
|
|
135
|
+
// at 0.30 and this number does not. Anything that logs, compares, or ratchets
|
|
136
|
+
// on it is comparing a task score, and the label has to say so.
|
|
137
|
+
const dims = AXES.map(k => `${k}=${scores[k].toFixed(2)}`).join(' ')
|
|
138
|
+
process.stdout.write(
|
|
139
|
+
`[w4-rubric] task-composite=${comp.toFixed(2)} ${dims} gate=${gate} ${comp>=gate?'PASS':'FAIL'}`
|
|
140
|
+
+ ` (task rubric — no goal-fit; NOT the in-cycle W4 verdict)\n`)
|
|
57
141
|
process.exit(comp >= gate ? 0 : 1)
|
|
58
142
|
}
|
|
59
143
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// wf-check — syntax-check dynamic workflow scripts the way the RUNTIME parses them.
|
|
3
|
+
//
|
|
4
|
+
// Why this exists: a syntax error in a workflow script doesn't fail loudly, it stops
|
|
5
|
+
// the workflow from launching at all. do-engine.js already carries a scar comment
|
|
6
|
+
// about one such case (a second top-level `export` is illegal in a workflow body).
|
|
7
|
+
//
|
|
8
|
+
// Why the obvious checkers are wrong for this file shape:
|
|
9
|
+
// node --check <f>.js → silently exits 0 for ANY file starting with `export`
|
|
10
|
+
// (verified on node v24.15.0 — the ESM path is a no-op).
|
|
11
|
+
// node --check <f>.mjs → real ESM parse, but rejects the top-level `return` and
|
|
12
|
+
// `await` that every workflow body legitimately uses.
|
|
13
|
+
// The runtime wraps the body in an async function, so that is what we parse it as.
|
|
14
|
+
//
|
|
15
|
+
// Usage: node .claude/scripts/wf-check.mjs [file ...]
|
|
16
|
+
// (no args → every .js under .claude/workflows/)
|
|
17
|
+
// Exit 0 = all green. Exit 1 = at least one script would fail to launch.
|
|
18
|
+
|
|
19
|
+
import { readFileSync, readdirSync, existsSync } from 'node:fs'
|
|
20
|
+
import { join } from 'node:path'
|
|
21
|
+
|
|
22
|
+
const WF_DIR = '.claude/workflows'
|
|
23
|
+
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor
|
|
24
|
+
const HOOKS = ['args', 'budget', 'agent', 'parallel', 'pipeline', 'log', 'phase', 'workflow']
|
|
25
|
+
|
|
26
|
+
function check(file) {
|
|
27
|
+
let src
|
|
28
|
+
try {
|
|
29
|
+
src = readFileSync(file, 'utf8')
|
|
30
|
+
} catch (e) {
|
|
31
|
+
return { file, ok: false, msg: `unreadable: ${e.message}` }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// The loader permits exactly one top-level export: the leading `export const meta`.
|
|
35
|
+
const exports = src.match(/^export\s/gm) ?? []
|
|
36
|
+
if (exports.length > 1) {
|
|
37
|
+
return { file, ok: false, msg: `${exports.length} top-level exports — only the leading \`export const meta\` is permitted` }
|
|
38
|
+
}
|
|
39
|
+
if (!/^export\s+const\s+meta\s*=/m.test(src)) {
|
|
40
|
+
return { file, ok: false, msg: 'missing the required leading `export const meta = {...}`' }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
new AsyncFunction(...HOOKS, src.replace(/^export\s+const\s+meta/m, 'const meta'))
|
|
45
|
+
return { file, ok: true }
|
|
46
|
+
} catch (e) {
|
|
47
|
+
return { file, ok: false, msg: e.message }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const args = process.argv.slice(2)
|
|
52
|
+
const files = args.length
|
|
53
|
+
? args
|
|
54
|
+
: existsSync(WF_DIR)
|
|
55
|
+
? readdirSync(WF_DIR).filter((f) => f.endsWith('.js')).map((f) => join(WF_DIR, f))
|
|
56
|
+
: []
|
|
57
|
+
|
|
58
|
+
if (!files.length) {
|
|
59
|
+
console.log('wf-check: no workflow scripts found')
|
|
60
|
+
process.exit(0)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let failed = 0
|
|
64
|
+
for (const f of files) {
|
|
65
|
+
const r = check(f)
|
|
66
|
+
if (r.ok) {
|
|
67
|
+
console.log(` ok ${r.file}`)
|
|
68
|
+
} else {
|
|
69
|
+
failed++
|
|
70
|
+
console.log(` FAIL ${r.file}: ${r.msg}`)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
console.log(`wf-check: ${files.length - failed}/${files.length} green`)
|
|
75
|
+
process.exit(failed ? 1 : 0)
|
package/skills/ai-ui/SKILL.md
CHANGED