@ghostlygawd/codeweb 0.9.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.
Files changed (81) hide show
  1. package/.claude-plugin/plugin.json +30 -0
  2. package/CHANGELOG.md +480 -0
  3. package/LICENSE +21 -0
  4. package/README.md +573 -0
  5. package/agents/codeweb-dissector.md +56 -0
  6. package/agents/codeweb-domain-mapper.md +63 -0
  7. package/commands/codeweb.md +57 -0
  8. package/hooks/hooks.json +47 -0
  9. package/hooks/post-edit-diff.mjs +83 -0
  10. package/hooks/pre-edit-impact.mjs +94 -0
  11. package/hooks/session-brief.mjs +53 -0
  12. package/package.json +64 -0
  13. package/scripts/annotate.mjs +46 -0
  14. package/scripts/bench-ts-engine.mjs +59 -0
  15. package/scripts/bench.mjs +133 -0
  16. package/scripts/break-cycles.mjs +0 -0
  17. package/scripts/brief.mjs +28 -0
  18. package/scripts/build-report.mjs +182 -0
  19. package/scripts/campaign.mjs +61 -0
  20. package/scripts/check-consistency.mjs +45 -0
  21. package/scripts/ci-gate.mjs +86 -0
  22. package/scripts/cluster3.mjs +112 -0
  23. package/scripts/codemod.mjs +130 -0
  24. package/scripts/context-pack.mjs +118 -0
  25. package/scripts/deadcode.mjs +96 -0
  26. package/scripts/diff.mjs +0 -0
  27. package/scripts/explain.mjs +87 -0
  28. package/scripts/extract-symbols.mjs +1307 -0
  29. package/scripts/find-similar.mjs +86 -0
  30. package/scripts/find.mjs +50 -0
  31. package/scripts/fitness.mjs +90 -0
  32. package/scripts/grammars/PROVENANCE.md +36 -0
  33. package/scripts/grammars/tree-sitter-c-sharp.wasm +0 -0
  34. package/scripts/grammars/tree-sitter-go.wasm +0 -0
  35. package/scripts/grammars/tree-sitter-java.wasm +0 -0
  36. package/scripts/grammars/tree-sitter-python.wasm +0 -0
  37. package/scripts/grammars/tree-sitter-rust.wasm +0 -0
  38. package/scripts/grammars/tree-sitter-typescript.wasm +0 -0
  39. package/scripts/hotspots.mjs +53 -0
  40. package/scripts/lib/annotations.mjs +51 -0
  41. package/scripts/lib/bench-core.mjs +178 -0
  42. package/scripts/lib/brief-core.mjs +92 -0
  43. package/scripts/lib/campaign.mjs +73 -0
  44. package/scripts/lib/claims-check.mjs +44 -0
  45. package/scripts/lib/cli.mjs +140 -0
  46. package/scripts/lib/complexity.mjs +68 -0
  47. package/scripts/lib/dup-check.mjs +51 -0
  48. package/scripts/lib/find-core.mjs +85 -0
  49. package/scripts/lib/gate-md.mjs +50 -0
  50. package/scripts/lib/graph-ops.mjs +319 -0
  51. package/scripts/lib/hotspots.mjs +34 -0
  52. package/scripts/lib/query-core.mjs +85 -0
  53. package/scripts/lib/reading-order.mjs +53 -0
  54. package/scripts/lib/reliance.mjs +143 -0
  55. package/scripts/lib/risk.mjs +18 -0
  56. package/scripts/lib/shingles.mjs +39 -0
  57. package/scripts/lib/skeleton.mjs +41 -0
  58. package/scripts/lib/stats.mjs +92 -0
  59. package/scripts/lib/ts-engine.mjs +605 -0
  60. package/scripts/mcp-server.mjs +370 -0
  61. package/scripts/optimize.mjs +152 -0
  62. package/scripts/overlap.mjs +380 -0
  63. package/scripts/placement.mjs +93 -0
  64. package/scripts/query.mjs +94 -0
  65. package/scripts/reading-order.mjs +36 -0
  66. package/scripts/refresh.mjs +73 -0
  67. package/scripts/release-utils.mjs +158 -0
  68. package/scripts/release.mjs +62 -0
  69. package/scripts/report-template.html +730 -0
  70. package/scripts/review.mjs +112 -0
  71. package/scripts/risk.mjs +77 -0
  72. package/scripts/run.mjs +96 -0
  73. package/scripts/screenshot.mjs +104 -0
  74. package/scripts/simulate-edit.mjs +70 -0
  75. package/scripts/stats.mjs +31 -0
  76. package/scripts/trend.mjs +147 -0
  77. package/scripts/version-sync.mjs +19 -0
  78. package/skills/codebase-anatomy/SKILL.md +174 -0
  79. package/skills/codebase-anatomy/references/engine-detection.md +49 -0
  80. package/skills/codebase-anatomy/references/graph-schema.md +120 -0
  81. package/skills/codebase-anatomy/references/overlap-heuristics.md +52 -0
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+ // codeweb refresh (F0) — re-extract a graph's nodes+edges from current disk so an agent's mid-task
3
+ // queries (impact / context-pack / callers) reflect the working tree, not a stale snapshot. Uses the
4
+ // extractor's per-file scan cache (only changed files are re-scanned), re-attaches each surviving
5
+ // node's domain by id, and drops overlaps (they need the full pipeline; not needed by the query
6
+ // tools). meta is preserved. Read-only over the target source; never executes it.
7
+ //
8
+ // Usage: node refresh.mjs <graph.json> [--cache <path>] [--json]
9
+ // Exit: 0 ok, 2 usage / missing meta.root.
10
+
11
+ import { readFileSync, writeFileSync, existsSync } from 'node:fs';
12
+ import { spawnSync } from 'node:child_process';
13
+ import { resolve, dirname, join } from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
15
+
16
+ const HERE = dirname(fileURLToPath(import.meta.url));
17
+ const USAGE = 'usage: refresh.mjs <graph.json> [--cache <path>] [--json]';
18
+ import { die, emitJson, finish } from './lib/cli.mjs';
19
+
20
+ const argv = process.argv.slice(2);
21
+ let json = false, cache = null; const pos = [];
22
+ for (let i = 0; i < argv.length; i++) {
23
+ const t = argv[i];
24
+ if (t === '--json') json = true;
25
+ else if (t === '--cache') cache = argv[++i];
26
+ else if (!t.startsWith('-')) pos.push(t);
27
+ }
28
+ const graphPath = pos[0];
29
+ if (!graphPath) die(USAGE, 2);
30
+
31
+ const abs = resolve(graphPath);
32
+ if (!existsSync(abs)) die(`graph not found: ${abs}`, 2);
33
+ let graph;
34
+ try { graph = JSON.parse(readFileSync(abs, 'utf8')); }
35
+ catch (e) { die(`invalid JSON in ${abs}: ${e.message}`, 2); }
36
+
37
+ const root = graph.meta && graph.meta.root;
38
+ if (!root || !existsSync(root)) die(`cannot refresh: graph.meta.root is missing or not on disk (got ${root || 'none'}) — refresh re-extracts from the recorded target root`, 2);
39
+
40
+ // re-extract (cached) from the recorded root; the extractor emits the fragment on stdout
41
+ const cachePath = cache || join(dirname(abs), 'extract-cache.json'); // default cache beside the graph
42
+ const r = spawnSync(process.execPath, [join(HERE, 'extract-symbols.mjs'), root, '--cache', cachePath], { encoding: 'utf8', maxBuffer: 1 << 28 });
43
+ if (r.status !== 0) die(`extract failed: ${(r.stderr || '').trim() || r.status}`, 2);
44
+ let fresh;
45
+ try { fresh = JSON.parse(r.stdout); }
46
+ catch (e) { die(`extractor produced invalid JSON: ${e.message}`, 2); }
47
+
48
+ // re-attach each surviving node's domain by id (domains[] summaries kept; node domains carried over)
49
+ const oldDomainById = new Map(graph.nodes.filter((n) => n.domain && n.domain !== 'unassigned').map((n) => [n.id, n.domain]));
50
+ let reattached = 0;
51
+ for (const n of fresh.nodes) { const d = oldDomainById.get(n.id); if (d) { n.domain = d; reattached++; } else if (n.domain == null) n.domain = ''; }
52
+
53
+ const before = { nodes: graph.nodes.length, edges: graph.edges.length };
54
+ const updated = {
55
+ meta: { ...graph.meta, ...fresh.meta, target: graph.meta.target || fresh.meta.target },
56
+ nodes: fresh.nodes,
57
+ edges: fresh.edges,
58
+ domains: graph.domains || [], // domain summaries preserved (node assignments re-attached above)
59
+ overlaps: [], // stale after an edit — recompute via the full pipeline when needed
60
+ };
61
+ writeFileSync(abs, JSON.stringify(updated));
62
+
63
+ const payload = {
64
+ graph: abs, root,
65
+ before, after: { nodes: updated.nodes.length, edges: updated.edges.length },
66
+ domainsReattached: reattached, scanned: /scanned (\d+)/.exec(r.stderr)?.[1] ?? null,
67
+ };
68
+ if (json) { emitJson(payload); } else {
69
+ console.log(`codeweb refresh: ${root}`);
70
+ console.log(` nodes ${before.nodes} -> ${updated.nodes.length} edges ${before.edges} -> ${updated.edges.length} domains re-attached ${reattached}`);
71
+ console.log(` overlaps dropped (run the full pipeline to recompute). scanned ${payload.scanned ?? '?'} file(s).`);
72
+ finish();
73
+ }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Shared, dependency-free helpers for the codeweb release ecosystem.
3
+ *
4
+ * The single source of truth for the version is package.json; the single source
5
+ * of truth for the MCP tool count is the TOOLS table in scripts/mcp-server.mjs.
6
+ * Everything else is derived from or checked against those two facts.
7
+ *
8
+ * Pure functions are exported for unit testing (bumpVersion, rollChangelog,
9
+ * syncTargets); the file-touching helpers are thin wrappers over them.
10
+ */
11
+ import { readFileSync, writeFileSync, existsSync } from 'node:fs';
12
+ import { join } from 'node:path';
13
+
14
+ export const readText = (p) => readFileSync(p, 'utf8');
15
+ export const writeText = (p, s) => writeFileSync(p, s);
16
+
17
+ /** Canonical version (from package.json). */
18
+ export function getVersion(root) {
19
+ return JSON.parse(readText(join(root, 'package.json'))).version;
20
+ }
21
+
22
+ /** Count the MCP tools at the source: the TOOLS table in scripts/mcp-server.mjs. */
23
+ export function mcpToolCount(root) {
24
+ const src = readText(join(root, 'scripts', 'mcp-server.mjs'));
25
+ return (src.match(/name:\s*'codeweb_[a-z_]+'/g) || []).length;
26
+ }
27
+
28
+ /** Count the tools the website advertises (sum of toolPhases in product.json). */
29
+ export function productToolCount(root) {
30
+ const p = JSON.parse(readText(join(root, 'site', 'data', 'product.json')));
31
+ return p.toolPhases.reduce((n, ph) => n + ph.tools.length, 0);
32
+ }
33
+
34
+ /**
35
+ * Where the version + tool-count must be mirrored away from package.json.
36
+ * Each sub is [regExp, replacementString]; ${version}/${count} are interpolated,
37
+ * $1/$2 are honored backrefs so surrounding formatting is preserved.
38
+ */
39
+ export function syncTargets(version, count) {
40
+ return [
41
+ {
42
+ file: '.claude-plugin/plugin.json',
43
+ subs: [
44
+ [/("version":\s*")[^"]+(")/, `$1${version}$2`],
45
+ [/(\d+)(\s+deterministic read-only query tools)/, `${count}$2`],
46
+ ],
47
+ },
48
+ {
49
+ file: 'skills/codebase-anatomy/SKILL.md',
50
+ subs: [[/(^version:\s*).+$/m, `$1${version}`]],
51
+ },
52
+ {
53
+ file: 'README.md',
54
+ subs: [[/(badge\/version-)\d+\.\d+\.\d+(-)/, `$1${version}$2`]],
55
+ },
56
+ {
57
+ // no-op for the shipped server (version derives from package.json, no literal); repairs a
58
+ // hardcoded serverInfo literal if one is ever reintroduced.
59
+ file: 'scripts/mcp-server.mjs',
60
+ subs: [[/(version:\s*')\d+\.\d+\.\d+(')/, `$1${version}$2`]],
61
+ },
62
+ ];
63
+ }
64
+
65
+ /** Apply syncTargets to disk. Returns the list of files that changed. */
66
+ export function applySync(root, version, count) {
67
+ const changed = [];
68
+ for (const t of syncTargets(version, count)) {
69
+ const p = join(root, t.file);
70
+ if (!existsSync(p)) continue;
71
+ const before = readText(p);
72
+ let after = before;
73
+ for (const [re, rep] of t.subs) after = after.replace(re, rep);
74
+ if (after !== before) { writeText(p, after); changed.push(t.file); }
75
+ }
76
+ return changed;
77
+ }
78
+
79
+ /** Read-only consistency audit across the public-comms surface. */
80
+ export function checkConsistency(root) {
81
+ const version = getVersion(root);
82
+ const count = mcpToolCount(root);
83
+ const problems = [];
84
+
85
+ const plugin = JSON.parse(readText(join(root, '.claude-plugin', 'plugin.json')));
86
+ if (plugin.version !== version) problems.push(`plugin.json version ${plugin.version} != package.json ${version}`);
87
+ const advertised = (plugin.description.match(/(\d+)\s+deterministic read-only query tools/) || [])[1];
88
+ if (advertised && Number(advertised) !== count) problems.push(`plugin.json advertises ${advertised} tools; MCP server exposes ${count}`);
89
+
90
+ const skill = readText(join(root, 'skills', 'codebase-anatomy', 'SKILL.md'));
91
+ const skillVer = (skill.match(/^version:\s*(.+)$/m) || [])[1];
92
+ if (skillVer && skillVer.trim() !== version) problems.push(`SKILL.md version ${skillVer.trim()} != ${version}`);
93
+
94
+ const pc = productToolCount(root);
95
+ if (pc !== count) problems.push(`product.json lists ${pc} tools; MCP server exposes ${count}`);
96
+
97
+ const readmePath = join(root, 'README.md');
98
+ if (existsSync(readmePath)) {
99
+ const rb = (readText(readmePath).match(/badge\/version-(\d+\.\d+\.\d+)-/) || [])[1];
100
+ if (rb && rb !== version) problems.push(`README version badge ${rb} != ${version}`);
101
+ }
102
+
103
+ const clPath = join(root, 'CHANGELOG.md');
104
+ if (existsSync(clPath)) {
105
+ const cl = readText(clPath);
106
+ const verRe = new RegExp(`^##\\s*\\[${version.replace(/\./g, '\\.')}\\]`, 'm');
107
+ if (!verRe.test(cl)) problems.push(`CHANGELOG.md has no section for v${version}`);
108
+ } else {
109
+ problems.push('CHANGELOG.md is missing');
110
+ }
111
+
112
+ // The MCP handshake surface: serverInfo.version must agree with package.json. The shipped server
113
+ // derives it dynamically (no literal — nothing to drift); a hardcoded literal is tolerated only
114
+ // while it matches, and version-sync repairs it. (A literal '0.1.0' drifted for a whole release
115
+ // because nothing audited this file.)
116
+ const mcpPath = join(root, 'scripts', 'mcp-server.mjs');
117
+ if (existsSync(mcpPath)) {
118
+ const hard = readText(mcpPath).match(/version:\s*'(\d+\.\d+\.\d+)'/);
119
+ if (hard && hard[1] !== version) problems.push(`mcp-server.mjs hardcodes serverInfo version ${hard[1]} != package.json ${version}`);
120
+ }
121
+
122
+ return { ok: problems.length === 0, version, count, problems };
123
+ }
124
+
125
+ /** Semantic-version bump. */
126
+ export function bumpVersion(v, level) {
127
+ const [a, b, c] = v.split('.').map(Number);
128
+ if (level === 'major') return `${a + 1}.0.0`;
129
+ if (level === 'minor') return `${a}.${b + 1}.0`;
130
+ if (level === 'patch') return `${a}.${b}.${c + 1}`;
131
+ throw new Error(`bumpVersion: unknown level "${level}"`);
132
+ }
133
+
134
+ /**
135
+ * Roll a Keep-a-Changelog document for a release: the body currently under
136
+ * [Unreleased] becomes the new dated [version] section, and [Unreleased] is reset.
137
+ * Link-reference definitions for [Unreleased] and [version] are refreshed.
138
+ */
139
+ export function rollChangelog(md, version, date, repo = 'https://github.com/GhostlyGawd/codeweb') {
140
+ const PLACEHOLDER = '_Nothing yet. Open work lands here before it ships in the next tagged release._';
141
+ const unrelRe = /## \[Unreleased\]\s*([\s\S]*?)(?=\n## \[|\n\[Unreleased\]:|$)/;
142
+ const m = md.match(unrelRe);
143
+ if (!m) throw new Error('rollChangelog: no [Unreleased] section found');
144
+ const body = m[1].replace(/^\s+|\s+$/g, '');
145
+ if (!body || body === PLACEHOLDER) throw new Error('rollChangelog: [Unreleased] is empty — nothing to release');
146
+
147
+ const prev = (md.match(/## \[(\d+\.\d+\.\d+)\]/) || [])[1];
148
+ const replacement = `## [Unreleased]\n\n${PLACEHOLDER}\n\n## [${version}] - ${date}\n\n${body}\n`;
149
+ let out = md.replace(unrelRe, replacement);
150
+
151
+ // refresh link refs
152
+ out = out.replace(/^\[Unreleased\]:.*$/m, `[Unreleased]: ${repo}/compare/v${version}...HEAD`);
153
+ const verLink = `[${version}]: ${repo}/compare/${prev ? `v${prev}` : 'main'}...v${version}`;
154
+ if (!new RegExp(`^\\[${version.replace(/\./g, '\\.')}\\]:`, 'm').test(out)) {
155
+ out = out.replace(/^(\[Unreleased\]:.*\n)/m, `$1${verLink}\n`);
156
+ }
157
+ return out;
158
+ }
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * codeweb release prep — updates the whole file ecosystem in one motion, then
4
+ * stops and hands the gated git steps (commit, tag, push, GitHub release) to you.
5
+ *
6
+ * node scripts/release.mjs --minor # 0.2.0 -> 0.3.0
7
+ * node scripts/release.mjs --version=1.0.0
8
+ * node scripts/release.mjs --minor --dry-run # show the plan, change nothing
9
+ *
10
+ * What it does (non-dry): bump package.json -> roll CHANGELOG [Unreleased] into a
11
+ * dated version section -> version-sync plugin.json + SKILL.md -> rebuild the site
12
+ * -> re-check consistency. It never runs git; it prints the exact commands to finish.
13
+ */
14
+ import { dirname, join } from 'node:path';
15
+ import { fileURLToPath } from 'node:url';
16
+ import { execFileSync } from 'node:child_process';
17
+ import { getVersion, mcpToolCount, applySync, bumpVersion, rollChangelog, checkConsistency, readText, writeText } from './release-utils.mjs';
18
+
19
+ const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
20
+ const args = process.argv.slice(2);
21
+ const dry = args.includes('--dry-run');
22
+ const bumpLevel = (args.find((a) => ['--major', '--minor', '--patch'].includes(a)) || '').slice(2);
23
+ const explicit = (args.find((a) => a.startsWith('--version=')) || '').split('=')[1];
24
+ const today = new Date().toISOString().slice(0, 10);
25
+
26
+ const current = getVersion(ROOT);
27
+ const next = explicit || (bumpLevel ? bumpVersion(current, bumpLevel) : null);
28
+ if (!next) {
29
+ process.stderr.write('usage: release.mjs (--major|--minor|--patch | --version=X.Y.Z) [--dry-run]\n');
30
+ process.exit(2);
31
+ }
32
+ const count = mcpToolCount(ROOT);
33
+
34
+ console.log(`codeweb release: ${current} -> ${next} (${count} MCP tools)${dry ? ' [dry-run]' : ''}`);
35
+ ['bump package.json',
36
+ `roll CHANGELOG.md: [Unreleased] -> [${next}] - ${today}`,
37
+ 'version-sync plugin.json + SKILL.md',
38
+ 'rebuild site (node site/build.mjs)'].forEach((s, i) => console.log(` ${i + 1}. ${s}`));
39
+
40
+ if (dry) {
41
+ try {
42
+ const preview = rollChangelog(readText(join(ROOT, 'CHANGELOG.md')), next, today).split('\n').slice(0, 16).join('\n');
43
+ console.log('\n--- CHANGELOG preview ---\n' + preview);
44
+ } catch (e) { console.log(` (changelog: ${e.message})`); }
45
+ console.log('\nDry run — no files changed.');
46
+ process.exit(0);
47
+ }
48
+
49
+ const pkgPath = join(ROOT, 'package.json');
50
+ writeText(pkgPath, readText(pkgPath).replace(/("version":\s*")[^"]+(")/, `$1${next}$2`));
51
+ writeText(join(ROOT, 'CHANGELOG.md'), rollChangelog(readText(join(ROOT, 'CHANGELOG.md')), next, today));
52
+ const changed = applySync(ROOT, next, count);
53
+ execFileSync(process.execPath, [join(ROOT, 'site', 'build.mjs')], { stdio: 'inherit' });
54
+
55
+ const audit = checkConsistency(ROOT);
56
+ console.log(`\nupdated: package.json, CHANGELOG.md, ${changed.join(', ')}, docs/`);
57
+ console.log(audit.ok ? 'consistency: OK' : `consistency: ${audit.problems.length} problem(s) — ${audit.problems.join('; ')}`);
58
+ console.log('\nNext (gated — run when ready):');
59
+ console.log(` git add -A && git commit -m "release: v${next}"`);
60
+ console.log(` git tag -a v${next} -m "codeweb v${next}"`);
61
+ console.log(' git push origin main --tags');
62
+ console.log(` gh release create v${next} --title "codeweb v${next}" --notes-from-tag`);