wei-wuwei 0.2.0 → 0.2.1
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/CHANGELOG.md +8 -0
- package/README.md +12 -3
- package/bin/wuwei.js +88 -1
- package/package.json +1 -1
- package/skills/wuwei-review/references/prep.md +1 -1
- package/skills/wuwei-review/skill.md +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@ One section per published version, newest first. `wuwei update` prints the
|
|
|
4
4
|
sections between the installed version and the new one before it asks;
|
|
5
5
|
`wuwei check` fails when package.json's version has no section here.
|
|
6
6
|
|
|
7
|
+
## 0.2.1
|
|
8
|
+
|
|
9
|
+
- `wuwei review-collect`: finds the latest Claude Code session that
|
|
10
|
+
dispatched a wuwei lens, copies each lens's transcript and report into
|
|
11
|
+
the review bundle's `reports/`, and prints what each one spent — turns,
|
|
12
|
+
context built, cached re-reads, and where the reading went. wuwei-review
|
|
13
|
+
§7 uses it instead of the collator re-typing each report.
|
|
14
|
+
|
|
7
15
|
## 0.2.0
|
|
8
16
|
|
|
9
17
|
Execute runs at a fixed tier; the model policy now covers review only.
|
package/README.md
CHANGED
|
@@ -67,6 +67,7 @@ node bin/wuwei.js status <project> # read-only: streams/claims from .wuwei, dr
|
|
|
67
67
|
node bin/wuwei.js update # git pull if behind, diff against what install wrote, apply on yes
|
|
68
68
|
node bin/wuwei.js review-prep <base> # wuwei-review's prep: dispatcher checks + scan bundle (or --pr <n>)
|
|
69
69
|
node bin/wuwei.js claim [stream] [NN] # list eligible phases, or claim one: cut, claim, push
|
|
70
|
+
node bin/wuwei.js review-collect # each lens's transcript + report into the bundle, and what each one spent
|
|
70
71
|
node bin/wuwei.js digest-check <f> # a review digest's arithmetic before it is published
|
|
71
72
|
node bin/wuwei.js plan-check [stream] # plan-phases step 11's checkable half, before sign-off
|
|
72
73
|
node bin/wuwei.js worktree <lens> <sha> # a throwaway worktree no other lens shares (--prune)
|
|
@@ -92,8 +93,16 @@ npm in the background, and when a newer version is out it says so on stderr
|
|
|
92
93
|
(at most hourly; never from a clone or in CI; `WUWEI_NO_UPDATE_CHECK=1` turns
|
|
93
94
|
it off).
|
|
94
95
|
|
|
95
|
-
Releasing
|
|
96
|
-
`
|
|
96
|
+
Releasing is merging. Every merge to main that changes what the package ships
|
|
97
|
+
(`bin`, `skills`, `agents`, `templates`, `wuwei.config.json`) is released by
|
|
98
|
+
`.github/workflows/publish.yml`: the version is bumped by the merged commits'
|
|
99
|
+
Conventional Commit types — `feat` minor, `type!:` or a `BREAKING CHANGE:`
|
|
100
|
+
footer major (minor while under 1.0), anything else patch — `CHANGELOG.md`
|
|
101
|
+
gains a section from the merged PR titles, and the release commit, the
|
|
102
|
+
`v<version>` tag, the npm publish and the GitHub Release follow. A docs- or
|
|
103
|
+
tests-only merge releases nothing; *Run workflow* (`workflow_dispatch`) with a
|
|
104
|
+
bump releases now. A version already set in `package.json` that npm has not
|
|
105
|
+
seen goes out as is.
|
|
97
106
|
|
|
98
107
|
## Daily use
|
|
99
108
|
|
|
@@ -189,7 +198,7 @@ wuwei/
|
|
|
189
198
|
├── agents/ # canonical subagent definitions
|
|
190
199
|
│ └── _template/
|
|
191
200
|
├── templates/ # PR / review templates, installed per-repo
|
|
192
|
-
├── bin/wuwei.js # CLI: build · check · list · init · install · update · status · review-prep · claim · digest-check · plan-check · worktree · mutate · sites · verify · uninstall
|
|
201
|
+
├── bin/wuwei.js # CLI: build · check · list · init · install · update · status · review-prep · review-collect · claim · digest-check · plan-check · worktree · mutate · sites · verify · uninstall
|
|
193
202
|
│ # the per-tool emitters live here too, not in adapters/
|
|
194
203
|
├── adapters/ # notes only — see bin/wuwei.js for the actual emitters
|
|
195
204
|
├── docs/
|
package/bin/wuwei.js
CHANGED
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
* wuwei claim [<stream>] [<phase>] [--take-over] [--by <id>] [--dry]
|
|
23
23
|
* wuwei-execute-phase step 1: list what is eligible,
|
|
24
24
|
* or claim one — cut, claim, push
|
|
25
|
+
* wuwei review-collect [--session <id>] each subagent's transcript and report into
|
|
26
|
+
* the bundle's reports/, and what each one spent
|
|
25
27
|
* wuwei digest-check <file> wuwei-review §7's arithmetic before publishing: count,
|
|
26
28
|
* rows, credits, file:line, Action names, redaction
|
|
27
29
|
* wuwei worktree <lens> [<sha>] a throwaway worktree no other lens shares; --prune
|
|
@@ -1842,6 +1844,90 @@ async function cmdVerify(argv) {
|
|
|
1842
1844
|
console.log('\nflags are leads, not findings: open the shot, and confirm against the spec what the page should say')
|
|
1843
1845
|
}
|
|
1844
1846
|
|
|
1847
|
+
// ── review-collect: the lenses' own record, from Claude Code's transcripts ──
|
|
1848
|
+
// Claude Code keeps every subagent's transcript under ~/.claude/projects/<cwd,
|
|
1849
|
+
// non-alphanumerics as '-'>/<session>/subagents/agent-<id>.jsonl, its type in
|
|
1850
|
+
// the .meta.json beside it. §7 used to have the collator re-type each report
|
|
1851
|
+
// into the bundle — skipped in practice, and paid for in tokens when not. This
|
|
1852
|
+
// copies them, and reads what each one spent
|
|
1853
|
+
function transcriptStats(file) {
|
|
1854
|
+
const usage = new Map(), uses = new Map(), reads = new Map()
|
|
1855
|
+
const t = { turns: 0, built: 0, reread: 0, input: 0, output: 0, first: null, last: null, report: '' }
|
|
1856
|
+
const kind = (name, inp = {}) => name === 'Read' ? (/\.wuwei-cache/.test(inp.file_path ?? '') ? 'Read:bundle' : 'Read:tree')
|
|
1857
|
+
: name === 'Bash' ? (/codegraph/.test(inp.command ?? '') ? 'Bash:codegraph' : /\b(git )?grep\b|\brg\b/.test(inp.command ?? '') ? 'Bash:grep' : 'Bash:other') : name
|
|
1858
|
+
for (const line of readFileSync(file, 'utf8').split('\n')) {
|
|
1859
|
+
let d; try { d = JSON.parse(line) } catch { continue }
|
|
1860
|
+
if (d.timestamp) { t.first ??= d.timestamp; t.last = d.timestamp }
|
|
1861
|
+
const m = d.message
|
|
1862
|
+
if (!m || typeof m !== 'object') continue
|
|
1863
|
+
// one message arrives as several lines while it streams: its last usage is its total
|
|
1864
|
+
if (d.type === 'assistant' && m.usage) usage.set(m.id, m.usage)
|
|
1865
|
+
for (const x of Array.isArray(m.content) ? m.content : []) {
|
|
1866
|
+
if (x.type === 'tool_use') {
|
|
1867
|
+
uses.set(x.id, kind(x.name, x.input))
|
|
1868
|
+
if (x.name === 'SubagentHandback' && x.input?.message) t.report = x.input.message
|
|
1869
|
+
} else if (x.type === 'tool_result') {
|
|
1870
|
+
const k = uses.get(x.tool_use_id) ?? '?', r = reads.get(k) ?? { n: 0, tokens: 0 }
|
|
1871
|
+
r.n++; r.tokens += Math.round(JSON.stringify(x.content ?? '').length / 4); reads.set(k, r)
|
|
1872
|
+
} else if (x.type === 'text' && d.type === 'assistant' && x.text?.trim()) t.lastText = x.text
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
for (const u of usage.values()) {
|
|
1876
|
+
t.turns++; t.built += u.cache_creation_input_tokens ?? 0; t.reread += u.cache_read_input_tokens ?? 0
|
|
1877
|
+
t.input += u.input_tokens ?? 0; t.output += u.output_tokens ?? 0
|
|
1878
|
+
}
|
|
1879
|
+
t.report ||= t.lastText ?? ''
|
|
1880
|
+
t.reads = [...reads].sort((a, b) => b[1].tokens - a[1].tokens)
|
|
1881
|
+
return t
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
function cmdReviewCollect(argv) {
|
|
1885
|
+
const flag = name => { const i = argv.indexOf(name); return i === -1 ? null : argv[i + 1] }
|
|
1886
|
+
const projects = join(process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), '.claude'), 'projects')
|
|
1887
|
+
const projDir = join(projects, process.cwd().replace(/[^A-Za-z0-9]/g, '-'))
|
|
1888
|
+
if (!existsSync(projDir)) { console.error(`✗ no Claude Code sessions recorded for ${process.cwd()} (${projDir}) — run it where the review ran (Codex has no subagent transcripts)`); process.exit(1) }
|
|
1889
|
+
const sessions = readdirSync(projDir, { withFileTypes: true }).filter(d => d.isDirectory() && existsSync(join(projDir, d.name, 'subagents')))
|
|
1890
|
+
.map(d => {
|
|
1891
|
+
const dir = join(projDir, d.name, 'subagents')
|
|
1892
|
+
const agents = readdirSync(dir).filter(f => f.endsWith('.meta.json')).map(f => {
|
|
1893
|
+
let meta = {}; try { meta = JSON.parse(readFileSync(join(dir, f), 'utf8')) } catch {}
|
|
1894
|
+
const jsonl = join(dir, f.replace(/\.meta\.json$/, '.jsonl'))
|
|
1895
|
+
return { meta, jsonl, mtime: existsSync(jsonl) ? statSync(jsonl).mtimeMs : 0 }
|
|
1896
|
+
}).filter(a => a.mtime)
|
|
1897
|
+
return { id: d.name, agents, wuwei: agents.some(a => /^wuwei-/.test(a.meta.agentType ?? '')), latest: Math.max(0, ...agents.map(a => a.mtime)) }
|
|
1898
|
+
})
|
|
1899
|
+
const session = flag('--session') ? sessions.find(s => s.id.startsWith(flag('--session')))
|
|
1900
|
+
: sessions.filter(s => s.wuwei).sort((a, b) => b.latest - a.latest)[0]
|
|
1901
|
+
if (!session) { console.error(`✗ ${flag('--session') ? `no session ${flag('--session')}` : 'no session here dispatched a wuwei lens'} under ${projDir}`); process.exit(1) }
|
|
1902
|
+
|
|
1903
|
+
const sha = flag('--sha') ?? quiet('git', ['rev-parse', '--verify', '--quiet', 'HEAD']).stdout?.trim().slice(0, 12) ?? 'nogit'
|
|
1904
|
+
const out = join(process.cwd(), '.wuwei-cache', 'review', sha, 'reports')
|
|
1905
|
+
mkdirSync(out, { recursive: true })
|
|
1906
|
+
const rows = [], used = new Map()
|
|
1907
|
+
for (const a of session.agents.map(a => ({ ...a, s: transcriptStats(a.jsonl) })).sort((x, y) => (x.s.first ?? '').localeCompare(y.s.first ?? ''))) {
|
|
1908
|
+
const type = a.meta.agentType ?? 'agent'
|
|
1909
|
+
const base = (type === 'general-purpose' && a.meta.description ? a.meta.description.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 30) : type).replace(/^wuwei-/, '')
|
|
1910
|
+
const n = (used.get(base) ?? 0) + 1; used.set(base, n)
|
|
1911
|
+
const name = n === 1 ? base : `${base}-${n}`
|
|
1912
|
+
writeFileSync(join(out, `${name}.jsonl`), readFileSync(a.jsonl))
|
|
1913
|
+
if (a.s.report) writeFileSync(join(out, `${name}.md`), a.s.report.trimEnd() + '\n')
|
|
1914
|
+
rows.push({ name, ...a.s })
|
|
1915
|
+
}
|
|
1916
|
+
const k = x => x >= 1e6 ? `${(x / 1e6).toFixed(1)}M` : x >= 1e3 ? `${Math.round(x / 1e3)}k` : String(x)
|
|
1917
|
+
const total = rows.reduce((t, r) => ({ built: t.built + r.built, reread: t.reread + r.reread }), { built: 0, reread: 0 })
|
|
1918
|
+
console.log(`session ${session.id} · ${rows.length} subagent(s) → ${out}\n`)
|
|
1919
|
+
// output tokens are left out: the transcript records a streaming chunk's count, not the message's
|
|
1920
|
+
console.log('| agent | turns | context built | re-read (cached) | where the reading went |\n|---|---|---|---|---|')
|
|
1921
|
+
for (const r of [...rows].sort((a, b) => (b.built + b.reread / 10) - (a.built + a.reread / 10)))
|
|
1922
|
+
console.log(`| ${r.name} | ${r.turns} | ${k(r.built)} | ${k(r.reread)} | ${r.reads.slice(0, 4).map(([kk, v]) => `${kk} ${v.n}× ≈${k(v.tokens)}`).join(' · ') || '—'} |`)
|
|
1923
|
+
console.log(`| **total** | | ${k(total.built)} | ${k(total.reread)} | |`)
|
|
1924
|
+
const byKind = new Map()
|
|
1925
|
+
for (const r of rows) for (const [kk, v] of r.reads) byKind.set(kk, (byKind.get(kk) ?? 0) + v.tokens)
|
|
1926
|
+
const top = [...byKind].sort((a, b) => b[1] - a[1]).slice(0, 3)
|
|
1927
|
+
if (top.length) console.log(`\nmost read, across the lenses: ${top.map(([kk, v]) => `${kk} ≈${k(v)}`).join(' · ')}`)
|
|
1928
|
+
console.log('re-read is billed at about a tenth of context built; a lens with many turns pays its context again on each')
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1845
1931
|
// ── digest-check: wuwei-review §7's arithmetic, before publishing ─────
|
|
1846
1932
|
// what the collator re-derives by hand: the header count against the table,
|
|
1847
1933
|
// reporter credits, every file:line against the reviewed tree, every
|
|
@@ -2818,6 +2904,7 @@ switch (cmd) {
|
|
|
2818
2904
|
case 'update': await cmdUpdate(dry, rest.includes('--no-fetch')); break
|
|
2819
2905
|
case 'status': cmdStatus(arg); break
|
|
2820
2906
|
case 'review-prep': cmdReviewPrep(rest); break
|
|
2907
|
+
case 'review-collect': cmdReviewCollect(rest); break
|
|
2821
2908
|
case 'verify': await cmdVerify(rest); break
|
|
2822
2909
|
case 'claim': cmdClaim(rest); break
|
|
2823
2910
|
case 'digest-check': cmdDigestCheck(rest); break
|
|
@@ -2827,6 +2914,6 @@ switch (cmd) {
|
|
|
2827
2914
|
case 'plan-check': cmdPlanCheck(rest); break
|
|
2828
2915
|
case 'uninstall': await cmdUninstall(arg); break
|
|
2829
2916
|
default:
|
|
2830
|
-
console.error(`unknown command '${cmd}'\nusage: wuwei [build|check|list|init [path]|install [path]|update|status [path]|review-prep [base] [--pr n] [--path p]|verify [page] [--phase NN]|claim [stream] [phase]|digest-check <digest.md>|worktree <lens> [sha]|mutate <file>:<line>|sites <pattern>|plan-check [stream]|uninstall [path]|--version] [--dry]`)
|
|
2917
|
+
console.error(`unknown command '${cmd}'\nusage: wuwei [build|check|list|init [path]|install [path]|update|status [path]|review-prep [base] [--pr n] [--path p]|review-collect|verify [page] [--phase NN]|claim [stream] [phase]|digest-check <digest.md>|worktree <lens> [sha]|mutate <file>:<line>|sites <pattern>|plan-check [stream]|uninstall [path]|--version] [--dry]`)
|
|
2831
2918
|
process.exit(1)
|
|
2832
2919
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wei-wuwei",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "為無為,則無不治 — a source-of-truth skills & agents framework that compiles to Claude Code and Codex (Cursor and Gemini CLI planned). Write once, run in every agent, govern without forcing.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -31,5 +31,5 @@ slices they need:
|
|
|
31
31
|
diff.patch git diff <base>...HEAD, three dots, verbatim
|
|
32
32
|
symbols.md definitions the diff references, from `codegraph node` — or "graph absent"
|
|
33
33
|
tools/<tool>.json each detected tool's read-only output over the change (§5), run once
|
|
34
|
-
reports/<lens>.md each lens's raw report
|
|
34
|
+
reports/<lens>.md each lens's raw report — `wuwei review-collect`, or written as returned
|
|
35
35
|
```
|
|
@@ -102,7 +102,7 @@ disk**; lenses pull the slices they need:
|
|
|
102
102
|
```
|
|
103
103
|
.wuwei-cache/review/<reviewed-sha>/ # gitignored — never inside the reviewed tree
|
|
104
104
|
PREP.md · INDEX.md (kind is a first pass by path — reclassify by content) · diff.patch
|
|
105
|
-
thread.json · tools/<tool>.json · reports/<lens>.md
|
|
105
|
+
thread.json · tools/<tool>.json · reports/<lens>.md + .jsonl (`wuwei review-collect`)
|
|
106
106
|
```
|
|
107
107
|
|
|
108
108
|
**No copies of the changed files** — each lens's context holds what it reads
|
|
@@ -346,9 +346,9 @@ finding closes.
|
|
|
346
346
|
|
|
347
347
|
## 7. Collate
|
|
348
348
|
|
|
349
|
-
The collator is the dispatching session.
|
|
350
|
-
|
|
351
|
-
|
|
349
|
+
The collator is the dispatching session. **`wuwei review-collect`** first
|
|
350
|
+
copies each lens's transcript and report into the bundle's `reports/` —
|
|
351
|
+
never re-typed (no subagents, as on Codex: write them yourself). Then one digest
|
|
352
352
|
against `.wuwei/templates/code-review.md`, opening with which
|
|
353
353
|
panels ran and why. **Whichever panels ran share one table** — code and
|
|
354
354
|
document on a mixed target, or the stream panel's five reporters plus §2 on
|