@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,39 @@
1
+ // codeweb shared token-shingling primitives — lifted verbatim out of overlap.mjs so the
2
+ // body-similarity logic lives ONCE (dogfooding the anti-duplication mission). Used by overlap.mjs
3
+ // (duplicate confirmation) and find-similar.mjs (reuse-at-write-time). Pure, no I/O.
4
+ //
5
+ // NOTE: this KW stop-list is overlap.mjs's, and is DELIBERATELY DIFFERENT from the extractor's
6
+ // KEYWORDS set (extract-symbols.mjs) — the extractor's includes call-site noise like `print`/`super`
7
+ // for edge filtering; this one is tuned for body-shingle similarity. Do not unify the two.
8
+
9
+ export const KW = new Set(['if', 'for', 'while', 'switch', 'catch', 'return', 'function', 'typeof', 'await', 'new', 'const', 'let', 'var', 'async', 'else', 'try', 'finally', 'class', 'case', 'of', 'in', 'throw']);
10
+
11
+ // Tokenize source: strip line/block comments and string/template literals (→ ` STR `), lowercase,
12
+ // then keep identifiers and a fixed set of structural operators, dropping keywords.
13
+ export const tokenize = (src) => src
14
+ .replace(/\/\/[^\n]*/g, ' ')
15
+ .replace(/\/\*[\s\S]*?\*\//g, ' ')
16
+ // Linear-time string matcher: each char is consumed exactly one way (an escape pair XOR a
17
+ // non-backslash non-quote), so there is no backtracking ambiguity. The previous
18
+ // `(?:\\.|(?!\1).)*` let the engine re-partition backslash runs and went EXPONENTIAL on
19
+ // unterminated-quote content — a lone apostrophe in a big real-world body hung the whole
20
+ // overlap stage (found by the TypeScript-src scale test).
21
+ .replace(/(['"`])(?:\\[\s\S]|(?!\1)[^\\])*\1/g, ' STR ')
22
+ .toLowerCase()
23
+ .match(/[a-z_$][\w$]*|[{}();=><!+\-*/%]/g)?.filter((t) => !KW.has(t)) || [];
24
+
25
+ // K-gram shingle set of the tokenized source (default K=3 — must match overlap.mjs's K).
26
+ export const shingles = (src, k = 3) => {
27
+ const t = tokenize(src);
28
+ const s = new Set();
29
+ for (let i = 0; i + k <= t.length; i++) s.add(t.slice(i, i + k).join(' '));
30
+ return s;
31
+ };
32
+
33
+ // Jaccard similarity of two shingle sets. Empty either side → 0.
34
+ export const jaccard = (a, b) => {
35
+ if (!a.size || !b.size) return 0;
36
+ let i = 0;
37
+ for (const x of a) if (b.has(x)) i++;
38
+ return i / (a.size + b.size - i);
39
+ };
@@ -0,0 +1,41 @@
1
+ // codeweb structural-skeleton primitives (F6) — normalize a function body to a token stream where
2
+ // every IDENTIFIER collapses to a single `ID` placeholder, every number to `NUM`, every string to
3
+ // `STR`, while KEYWORDS and OPERATORS are preserved verbatim. Two functions that are the same up to
4
+ // variable renaming therefore have identical skeletons (Type-2 clones) — which the lexical shingler
5
+ // (lib/shingles.mjs, which KEEPS identifiers) cannot see. Pure, deterministic, zero-dependency.
6
+ //
7
+ // Intent locks (tests): rename-invariance (single placeholder, not positional ID0/ID1), literal
8
+ // normalization, but operator + keyword SENSITIVITY (`+`≠`*`, `&&`≠`||`, `if`≠`while`) so it does not
9
+ // over-collapse and call genuinely different logic a clone. Reuses jaccard from lib/shingles.mjs.
10
+
11
+ const JS_KW = new Set(['if', 'else', 'for', 'while', 'do', 'switch', 'case', 'default', 'break', 'continue', 'return', 'function', 'class', 'new', 'typeof', 'instanceof', 'const', 'let', 'var', 'of', 'in', 'try', 'catch', 'finally', 'throw', 'await', 'async', 'yield', 'extends', 'super', 'this', 'null', 'true', 'false', 'void', 'delete', 'import', 'export', 'from', 'static', 'get', 'set']);
12
+ const PY_KW = new Set(['if', 'elif', 'else', 'for', 'while', 'def', 'class', 'return', 'try', 'except', 'finally', 'raise', 'with', 'as', 'import', 'from', 'and', 'or', 'not', 'in', 'is', 'lambda', 'pass', 'yield', 'await', 'async', 'None', 'True', 'False', 'global', 'nonlocal', 'del', 'assert']);
13
+
14
+ // One pass, priority-ordered: string/template literals, numbers, identifiers, multi-char operators,
15
+ // then single chars. Comments are stripped first so their contents don't tokenize.
16
+ const TOKEN_RE = /`(?:\\.|[^`\\])*`|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\d+\.?\d*|[A-Za-z_$][\w$]*|===|!==|>>>|<<=|>>=|\*\*|\.\.\.|&&|\|\||\?\?|\?\.|=>|==|!=|<=|>=|\+\+|--|\+=|-=|\*=|\/=|&&|[-+*/%=<>!&|^~?:;,.(){}\[\]]/g;
17
+
18
+ export function skeletonTokens(src, lang = 'js') {
19
+ const kw = lang === 'py' ? PY_KW : JS_KW;
20
+ const stripped = lang === 'py'
21
+ ? (src || '').replace(/#[^\n]*/g, ' ')
22
+ : (src || '').replace(/\/\/[^\n]*/g, ' ').replace(/\/\*[\s\S]*?\*\//g, ' ');
23
+ const out = [];
24
+ for (const tok of stripped.match(TOKEN_RE) || []) {
25
+ const c = tok[0];
26
+ if (c === '`' || c === '"' || c === "'") out.push('STR');
27
+ else if (c >= '0' && c <= '9') out.push('NUM');
28
+ else if (/[A-Za-z_$]/.test(c)) out.push(kw.has(tok) ? tok : 'ID');
29
+ else out.push(tok);
30
+ }
31
+ return out;
32
+ }
33
+
34
+ export const skeleton = (src, lang = 'js') => skeletonTokens(src, lang).join(' ');
35
+
36
+ export const structuralShingles = (src, k = 3, lang = 'js') => {
37
+ const t = skeletonTokens(src, lang);
38
+ const set = new Set();
39
+ for (let i = 0; i + k <= t.length; i++) set.add(t.slice(i, i + k).join(' '));
40
+ return set;
41
+ };
@@ -0,0 +1,92 @@
1
+ // stats — the local outcome ledger: what codeweb actually DID during real work, counted where
2
+ // it happened. This is the value receipt ("3 regressions blocked before landing, 14 pre-edit
3
+ // cards, 87 queries served") — evidence from real use with a real denominator, not a lab.
4
+ //
5
+ // STRICTLY LOCAL: written beside the graph (<workspace>/stats.json), never transmitted,
6
+ // nothing identifying — counter names and integers only. CODEWEB_NO_STATS=1 disables all
7
+ // writes. Every operation is fail-open (a ledger must never break the tool it measures);
8
+ // concurrent writers are last-write-wins (counters may undercount under races — acceptable
9
+ // for a receipt, documented here).
10
+
11
+ import { readFileSync, writeFileSync, unlinkSync } from 'node:fs';
12
+ import { dirname, join } from 'node:path';
13
+
14
+ const statsPathOf = (graphPath) => join(dirname(graphPath), 'stats.json');
15
+ const monthNow = () => new Date().toISOString().slice(0, 7);
16
+
17
+ /** Read the ledger beside a graph.json, or null. Never throws. */
18
+ export function readStats(graphPath) {
19
+ try { return JSON.parse(readFileSync(statsPathOf(graphPath), 'utf8')); } catch { return null; }
20
+ }
21
+
22
+ /** Increment a counter in the current month's bucket. Silent no-op on opt-out or any error. */
23
+ export function bump(graphPath, counter, n = 1) {
24
+ if (process.env.CODEWEB_NO_STATS === '1' || !graphPath || !counter) return;
25
+ try {
26
+ const s = readStats(graphPath) || { since: monthNow(), months: {} };
27
+ const m = monthNow();
28
+ if (!s.months[m]) s.months[m] = {};
29
+ s.months[m][counter] = (s.months[m][counter] || 0) + n;
30
+ writeFileSync(statsPathOf(graphPath), JSON.stringify(s));
31
+ } catch { /* a receipt must never break the tool */ }
32
+ }
33
+
34
+ const LABELS = [
35
+ ['briefInjected', (v) => `${v} session brief(s)`],
36
+ ['cardsDelivered', (v) => `${v} pre-edit card(s)`],
37
+ ['cardCallersFollowed', (v) => `${v} card-named caller(s) followed`],
38
+ ['postEditChecks', (v) => `${v} post-edit check(s)`],
39
+ ['regressionsFlagged', (v) => `${v} regression(s) flagged before landing`],
40
+ ['queriesServed', (v) => `${v} quer${v === 1 ? 'y' : 'ies'} served`],
41
+ ['autoRefreshes', (v) => `${v} auto-refresh(es)`],
42
+ ];
43
+
44
+ // ---- pending card (docs/specs/card-correlation.md) ---------------------------------------------
45
+ // The pre-edit hook records which caller files the card WARNED about; when a later edit touches
46
+ // one, the ledger records that the advice steered real work. One pending card at a time (a new
47
+ // card replaces it), 30-minute expiry, each file counts once. Same rules as the ledger it feeds:
48
+ // local-only, fail-open, CODEWEB_NO_STATS=1 disables.
49
+ const PENDING_TTL_MS = 30 * 60 * 1000;
50
+ const pendingPathOf = (graphPath) => join(dirname(graphPath), 'pending-card.json');
51
+
52
+ /** Record the card's named caller files as the active pending set (replaces any previous). */
53
+ export function recordPendingCard(graphPath, symbol, files) {
54
+ if (process.env.CODEWEB_NO_STATS === '1' || !graphPath || !files || !files.length) return;
55
+ try { writeFileSync(pendingPathOf(graphPath), JSON.stringify({ t: Date.now(), symbol, files })); }
56
+ catch { /* receipt only */ }
57
+ }
58
+
59
+ /**
60
+ * An edit landed on `editedRel` — if the pending card named it (and the card is fresh, and the
61
+ * edit is not the card's own subject file), count it once and consume it. Never throws.
62
+ */
63
+ export function correlateEdit(graphPath, editedRel) {
64
+ if (process.env.CODEWEB_NO_STATS === '1' || !graphPath || !editedRel) return;
65
+ try {
66
+ const p = pendingPathOf(graphPath);
67
+ const card = JSON.parse(readFileSync(p, 'utf8'));
68
+ if (Date.now() - card.t > PENDING_TTL_MS) { try { unlinkSync(p); } catch {} return; }
69
+ const ix = (card.files || []).indexOf(editedRel);
70
+ if (ix === -1) return;
71
+ card.files.splice(ix, 1);
72
+ bump(graphPath, 'cardCallersFollowed');
73
+ if (card.files.length) writeFileSync(p, JSON.stringify(card));
74
+ else { try { unlinkSync(p); } catch {} }
75
+ } catch { /* no pending card / unreadable — fine */ }
76
+ }
77
+
78
+ /** One line for a month's counters, or null when the bucket is empty. */
79
+ export function monthLine(counters) {
80
+ if (!counters) return null;
81
+ const parts = LABELS.filter(([k]) => counters[k] > 0).map(([k, f]) => f(counters[k]));
82
+ for (const k of Object.keys(counters)) if (!LABELS.some(([l]) => l === k) && counters[k] > 0) parts.push(`${counters[k]} ${k}`);
83
+ return parts.length ? parts.join(' · ') : null;
84
+ }
85
+
86
+ /** Attach the current month's activity to a brief object (mutates + returns it). */
87
+ export function attachActivity(brief, graphPath) {
88
+ const s = readStats(graphPath);
89
+ const m = monthNow();
90
+ if (s?.months?.[m] && monthLine(s.months[m])) brief.activity = { month: m, counters: s.months[m] };
91
+ return brief;
92
+ }