@gcunharodrigues/wrxn 0.1.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 (102) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +38 -0
  3. package/bin/wrxn.cjs +342 -0
  4. package/lib/connect.cjs +216 -0
  5. package/lib/executor.cjs +238 -0
  6. package/lib/install.cjs +105 -0
  7. package/lib/manifest.cjs +67 -0
  8. package/lib/migrate.cjs +93 -0
  9. package/lib/onboard.cjs +84 -0
  10. package/lib/semver.cjs +14 -0
  11. package/lib/update.cjs +91 -0
  12. package/lib/worktree.cjs +217 -0
  13. package/manifest.json +451 -0
  14. package/migrations/README.md +21 -0
  15. package/package.json +23 -0
  16. package/payload/.claude/constitution.local.md +13 -0
  17. package/payload/.claude/constitution.md +28 -0
  18. package/payload/.claude/hooks/code-intel-push.cjs +108 -0
  19. package/payload/.claude/hooks/enforce-managed-guard.cjs +68 -0
  20. package/payload/.claude/hooks/enforce-managed-precommit.cjs +74 -0
  21. package/payload/.claude/hooks/enforce-push-authority.cjs +51 -0
  22. package/payload/.claude/hooks/enforce-review-marker.cjs +62 -0
  23. package/payload/.claude/hooks/enforce-tests-on-push.cjs +40 -0
  24. package/payload/.claude/hooks/recall-surface.cjs +127 -0
  25. package/payload/.claude/hooks/reference-detect.cjs +83 -0
  26. package/payload/.claude/hooks/session-end.cjs +132 -0
  27. package/payload/.claude/hooks/session-history.cjs +76 -0
  28. package/payload/.claude/hooks/session-start.cjs +117 -0
  29. package/payload/.claude/hooks/synapse-engine.cjs +351 -0
  30. package/payload/.claude/hooks/wiki-lint.cjs +104 -0
  31. package/payload/.claude/settings.json +60 -0
  32. package/payload/.claude/skills/audit/SKILL.md +23 -0
  33. package/payload/.claude/skills/diagnose/SKILL.md +117 -0
  34. package/payload/.claude/skills/diagnose/scripts/hitl-loop.template.sh +41 -0
  35. package/payload/.claude/skills/grill-me/SKILL.md +10 -0
  36. package/payload/.claude/skills/grill-with-docs/ADR-FORMAT.md +47 -0
  37. package/payload/.claude/skills/grill-with-docs/CONTEXT-FORMAT.md +60 -0
  38. package/payload/.claude/skills/grill-with-docs/SKILL.md +88 -0
  39. package/payload/.claude/skills/handoff/SKILL.md +19 -0
  40. package/payload/.claude/skills/improve-codebase-architecture/DEEPENING.md +37 -0
  41. package/payload/.claude/skills/improve-codebase-architecture/HTML-REPORT.md +123 -0
  42. package/payload/.claude/skills/improve-codebase-architecture/INTERFACE-DESIGN.md +44 -0
  43. package/payload/.claude/skills/improve-codebase-architecture/LANGUAGE.md +53 -0
  44. package/payload/.claude/skills/improve-codebase-architecture/SKILL.md +81 -0
  45. package/payload/.claude/skills/level-up/SKILL.md +28 -0
  46. package/payload/.claude/skills/memory/SKILL.md +79 -0
  47. package/payload/.claude/skills/onboard/SKILL.md +43 -0
  48. package/payload/.claude/skills/prototype/LOGIC.md +79 -0
  49. package/payload/.claude/skills/prototype/SKILL.md +30 -0
  50. package/payload/.claude/skills/prototype/UI.md +112 -0
  51. package/payload/.claude/skills/qa-walk/SKILL.md +227 -0
  52. package/payload/.claude/skills/qa-walk/references/cli-mode.md +28 -0
  53. package/payload/.claude/skills/qa-walk/references/finding-issue-template.md +48 -0
  54. package/payload/.claude/skills/qa-walk/references/walk-report-template.md +56 -0
  55. package/payload/.claude/skills/qa-walk/references/web-mode.md +112 -0
  56. package/payload/.claude/skills/setup-matt-pocock-skills/SKILL.md +121 -0
  57. package/payload/.claude/skills/setup-matt-pocock-skills/domain.md +51 -0
  58. package/payload/.claude/skills/setup-matt-pocock-skills/issue-tracker-github.md +22 -0
  59. package/payload/.claude/skills/setup-matt-pocock-skills/issue-tracker-gitlab.md +23 -0
  60. package/payload/.claude/skills/setup-matt-pocock-skills/issue-tracker-local.md +19 -0
  61. package/payload/.claude/skills/setup-matt-pocock-skills/triage-labels.md +15 -0
  62. package/payload/.claude/skills/skill-creator/LICENSE.txt +202 -0
  63. package/payload/.claude/skills/skill-creator/SKILL.md +209 -0
  64. package/payload/.claude/skills/skill-creator/scripts/init_skill.py +303 -0
  65. package/payload/.claude/skills/skill-creator/scripts/package_skill.py +110 -0
  66. package/payload/.claude/skills/skill-creator/scripts/quick_validate.py +65 -0
  67. package/payload/.claude/skills/synapse/SKILL.md +132 -0
  68. package/payload/.claude/skills/synapse/assets/README.md +50 -0
  69. package/payload/.claude/skills/synapse/references/brackets.md +100 -0
  70. package/payload/.claude/skills/synapse/references/commands.md +118 -0
  71. package/payload/.claude/skills/synapse/references/domains.md +126 -0
  72. package/payload/.claude/skills/synapse/references/layers.md +186 -0
  73. package/payload/.claude/skills/synapse/references/manifest.md +142 -0
  74. package/payload/.claude/skills/tdd/SKILL.md +22 -0
  75. package/payload/.claude/skills/tech-search/SKILL.md +431 -0
  76. package/payload/.claude/skills/tech-search/prompts/page-extract.md +133 -0
  77. package/payload/.claude/skills/to-issues/SKILL.md +83 -0
  78. package/payload/.claude/skills/to-prd/SKILL.md +74 -0
  79. package/payload/.claude/skills/triage/AGENT-BRIEF.md +168 -0
  80. package/payload/.claude/skills/triage/OUT-OF-SCOPE.md +101 -0
  81. package/payload/.claude/skills/triage/SKILL.md +103 -0
  82. package/payload/.claude/skills/write-a-skill/SKILL.md +117 -0
  83. package/payload/.recon.json +3 -0
  84. package/payload/.synapse/global +6 -0
  85. package/payload/.synapse/manifest +38 -0
  86. package/payload/.synapse/pipeline +6 -0
  87. package/payload/.synapse/routing +8 -0
  88. package/payload/.wrxn/continuity/.gitkeep +0 -0
  89. package/payload/.wrxn/history/.gitkeep +0 -0
  90. package/payload/.wrxn/wiki/.gitkeep +0 -0
  91. package/payload/.wrxn/wiki/concepts/.gitkeep +0 -0
  92. package/payload/.wrxn/wiki/decisions/.gitkeep +0 -0
  93. package/payload/.wrxn/wiki/gotchas/.gitkeep +0 -0
  94. package/payload/.wrxn/wiki/sessions/.gitkeep +0 -0
  95. package/payload/.wrxn/wiki.cjs +164 -0
  96. package/payload/aios-intake.md +32 -0
  97. package/payload/connections.md +15 -0
  98. package/payload/decisions/log.md +18 -0
  99. package/payload/docs/agents/domain.md +38 -0
  100. package/payload/docs/agents/issue-tracker.md +25 -0
  101. package/payload/docs/agents/triage-labels.md +15 -0
  102. package/payload/docs/workspace/operator-layer.md +14 -0
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // WRXN managed hook — protect MANAGED kernel files inside an install (the ring-0 reshape).
5
+ // PreToolUse:Edit|Write. Blocks an agent edit/write to a file classified `managed` in the
6
+ // install receipt unless WRXN_MANAGED_CONFIRM is set. Seeded + state files (and anything outside
7
+ // the install) edit freely. Self-contained: hooks ship into installs and cannot import the kernel lib.
8
+ //
9
+ // Contract: PreToolUse event JSON on stdin → decision JSON on stdout (exit 0).
10
+ // allow → {} block → { "decision": "block", "reason": "..." }
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+
15
+ function emit(decision) {
16
+ process.stdout.write(JSON.stringify(decision));
17
+ process.exit(0);
18
+ }
19
+
20
+ // Walk up from CLAUDE_PROJECT_DIR (or cwd) to the install root carrying wrxn.install.json.
21
+ function findInstallRoot() {
22
+ let dir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
23
+ for (let i = 0; i < 8; i++) {
24
+ if (fs.existsSync(path.join(dir, 'wrxn.install.json'))) return dir;
25
+ const up = path.dirname(dir);
26
+ if (up === dir) break;
27
+ dir = up;
28
+ }
29
+ return null;
30
+ }
31
+
32
+ function managedPaths(root) {
33
+ try {
34
+ const receipt = JSON.parse(fs.readFileSync(path.join(root, 'wrxn.install.json'), 'utf8'));
35
+ return (receipt.files || []).filter((f) => f.class === 'managed').map((f) => f.path);
36
+ } catch {
37
+ return [];
38
+ }
39
+ }
40
+
41
+ function main() {
42
+ let event;
43
+ try {
44
+ event = JSON.parse(fs.readFileSync(0, 'utf8') || '{}');
45
+ } catch {
46
+ return emit({}); // unparseable → fail open
47
+ }
48
+
49
+ const filePath = event.tool_input && event.tool_input.file_path;
50
+ if (!filePath) return emit({}); // not a file write
51
+
52
+ const root = findInstallRoot();
53
+ if (!root) return emit({}); // not inside a wrxn install → nothing to guard
54
+
55
+ const rel = path.relative(root, path.resolve(filePath));
56
+ if (rel.startsWith('..') || path.isAbsolute(rel)) return emit({}); // outside the install
57
+
58
+ if (!managedPaths(root).includes(rel)) return emit({}); // seeded/state/other → free
59
+
60
+ if (process.env.WRXN_MANAGED_CONFIRM) return emit({}); // explicit confirm → allowed
61
+
62
+ return emit({
63
+ decision: 'block',
64
+ reason: `"${rel}" is a MANAGED kernel file — kernel-owned, overwritten on \`wrxn update\`. To change it intentionally, set WRXN_MANAGED_CONFIRM. (Seeded + state files edit freely.)`,
65
+ });
66
+ }
67
+
68
+ main();
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // WRXN managed hook — the commit-side half of the managed-files guard.
5
+ // PreToolUse:Bash. Blocks a `git commit` that stages any MANAGED kernel file unless
6
+ // WRXN_MANAGED_CONFIRM is set. Seeded + state files commit freely. Self-contained.
7
+ //
8
+ // Contract: PreToolUse event JSON on stdin → decision JSON on stdout (exit 0).
9
+
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+ const { execFileSync } = require('child_process');
13
+
14
+ function emit(decision) {
15
+ process.stdout.write(JSON.stringify(decision));
16
+ process.exit(0);
17
+ }
18
+
19
+ function findInstallRoot() {
20
+ let dir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
21
+ for (let i = 0; i < 8; i++) {
22
+ if (fs.existsSync(path.join(dir, 'wrxn.install.json'))) return dir;
23
+ const up = path.dirname(dir);
24
+ if (up === dir) break;
25
+ dir = up;
26
+ }
27
+ return null;
28
+ }
29
+
30
+ function managedSet(root) {
31
+ try {
32
+ const receipt = JSON.parse(fs.readFileSync(path.join(root, 'wrxn.install.json'), 'utf8'));
33
+ return new Set((receipt.files || []).filter((f) => f.class === 'managed').map((f) => f.path));
34
+ } catch {
35
+ return new Set();
36
+ }
37
+ }
38
+
39
+ function main() {
40
+ let event;
41
+ try {
42
+ event = JSON.parse(fs.readFileSync(0, 'utf8') || '{}');
43
+ } catch {
44
+ return emit({});
45
+ }
46
+
47
+ const command = (event.tool_input && event.tool_input.command) || '';
48
+ if (!/\bgit\s+commit\b/.test(command)) return emit({}); // not a commit
49
+
50
+ const root = findInstallRoot();
51
+ if (!root) return emit({});
52
+
53
+ let staged = [];
54
+ try {
55
+ staged = execFileSync('git', ['diff', '--cached', '--name-only'], { cwd: root, encoding: 'utf8' })
56
+ .split('\n')
57
+ .filter(Boolean);
58
+ } catch {
59
+ return emit({}); // not a git repo / git unavailable → fail open
60
+ }
61
+
62
+ const managed = managedSet(root);
63
+ const hits = staged.filter((f) => managed.has(f));
64
+ if (hits.length === 0) return emit({});
65
+
66
+ if (process.env.WRXN_MANAGED_CONFIRM) return emit({});
67
+
68
+ return emit({
69
+ decision: 'block',
70
+ reason: `Commit stages MANAGED kernel file(s): ${hits.join(', ')}. Set WRXN_MANAGED_CONFIRM to confirm an intentional kernel change.`,
71
+ });
72
+ }
73
+
74
+ main();
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // WRXN managed hook — Constitution Article I (Agent Authority).
5
+ // PreToolUse:Bash gate: a remote git op (push / PR / tag push) is allowed only when
6
+ // the session declares the devops role via WRXN_ACTIVE_AGENT=devops. A bare push runs
7
+ // as @unknown and is denied. Fails OPEN on any internal error (never over-blocks).
8
+ //
9
+ // Contract: reads a PreToolUse hook event as JSON on stdin, writes a decision to stdout.
10
+ // allow → {} (exit 0)
11
+ // deny → { "decision": "block", "reason": "..." } (exit 0; the harness blocks the call)
12
+
13
+ const REMOTE_OP = /\bgit\s+push\b|\bgh\s+pr\s+(create|merge)\b|\bgit\s+push\s+.*--tags\b/;
14
+
15
+ function main() {
16
+ let input = '';
17
+ try {
18
+ input = require('fs').readFileSync(0, 'utf8');
19
+ } catch {
20
+ return emit({}); // no stdin → nothing to gate
21
+ }
22
+
23
+ let event;
24
+ try {
25
+ event = JSON.parse(input || '{}');
26
+ } catch {
27
+ return emit({}); // unparseable → fail open
28
+ }
29
+
30
+ const command = (event.tool_input && event.tool_input.command) || '';
31
+ if (!REMOTE_OP.test(command)) {
32
+ return emit({}); // not a remote op
33
+ }
34
+
35
+ if (process.env.WRXN_ACTIVE_AGENT === 'devops') {
36
+ return emit({}); // authorized
37
+ }
38
+
39
+ return emit({
40
+ decision: 'block',
41
+ reason:
42
+ 'Remote git op is devops-exclusive (Constitution Art. I). Re-run with WRXN_ACTIVE_AGENT=devops, or delegate to the devops role.',
43
+ });
44
+ }
45
+
46
+ function emit(decision) {
47
+ process.stdout.write(JSON.stringify(decision));
48
+ process.exit(0);
49
+ }
50
+
51
+ main();
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // WRXN managed hook — the review-gate: a push referencing an unreviewed issue BLOCKS.
5
+ // PreToolUse:Bash. On a `git push`, scans the pushed commit messages for bracketed issue ids
6
+ // `[id]` and requires a review marker `review-<id>.md` in the markers dir for each. Missing → block.
7
+ // Self-contained; shell-free git via execFileSync.
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const { execFileSync } = require('child_process');
12
+
13
+ function emit(decision) {
14
+ process.stdout.write(JSON.stringify(decision));
15
+ process.exit(0);
16
+ }
17
+
18
+ function pushedMessages(cwd) {
19
+ // Prefer an explicit range, then the upstream range, then the last commit as a floor.
20
+ const ranges = [process.env.WRXN_PUSH_RANGE, '@{u}..HEAD'].filter(Boolean);
21
+ for (const r of ranges) {
22
+ try {
23
+ return execFileSync('git', ['log', r, '--format=%B'], { cwd, encoding: 'utf8' });
24
+ } catch {
25
+ /* try next */
26
+ }
27
+ }
28
+ try {
29
+ return execFileSync('git', ['log', '-1', '--format=%B'], { cwd, encoding: 'utf8' });
30
+ } catch {
31
+ return '';
32
+ }
33
+ }
34
+
35
+ function main() {
36
+ let event;
37
+ try {
38
+ event = JSON.parse(fs.readFileSync(0, 'utf8') || '{}');
39
+ } catch {
40
+ return emit({});
41
+ }
42
+
43
+ const command = (event.tool_input && event.tool_input.command) || '';
44
+ if (!/\bgit\s+push\b/.test(command)) return emit({});
45
+
46
+ const cwd = process.env.CLAUDE_PROJECT_DIR || process.cwd();
47
+ const messages = pushedMessages(cwd);
48
+ const ids = [...new Set((messages.match(/\[([a-z0-9][a-z0-9._-]*)\]/gi) || []).map((s) => s.slice(1, -1)))];
49
+ if (ids.length === 0) return emit({}); // no issue referenced → nothing to gate
50
+
51
+ const markersRel = process.env.WRXN_REVIEW_MARKERS_DIR || '.claude/ai/output';
52
+ const dir = path.join(cwd, markersRel);
53
+ const missing = ids.filter((id) => !fs.existsSync(path.join(dir, `review-${id}.md`)));
54
+ if (missing.length === 0) return emit({});
55
+
56
+ return emit({
57
+ decision: 'block',
58
+ reason: `Push blocked: issue id(s) ${missing.join(', ')} in the pushed commits have no review marker (expected review-<id>.md in ${markersRel}). Run the review and write the marker first.`,
59
+ });
60
+ }
61
+
62
+ main();
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // WRXN managed hook — Quality-First (Art. III): a red suite blocks a push.
5
+ // PreToolUse:Bash. On a `git push`, runs the configured test command in the install root and
6
+ // blocks on a non-zero exit. WRXN_TEST_CMD (default "npm test") is OPERATOR config, not event
7
+ // data — it is intentionally a shell command line, so a shell is appropriate here.
8
+
9
+ const fs = require('fs');
10
+ const { execSync } = require('child_process');
11
+
12
+ function emit(decision) {
13
+ process.stdout.write(JSON.stringify(decision));
14
+ process.exit(0);
15
+ }
16
+
17
+ function main() {
18
+ let event;
19
+ try {
20
+ event = JSON.parse(fs.readFileSync(0, 'utf8') || '{}');
21
+ } catch {
22
+ return emit({});
23
+ }
24
+
25
+ const command = (event.tool_input && event.tool_input.command) || '';
26
+ if (!/\bgit\s+push\b/.test(command)) return emit({});
27
+
28
+ const testCmd = process.env.WRXN_TEST_CMD || 'npm test';
29
+ try {
30
+ execSync(testCmd, { cwd: process.env.CLAUDE_PROJECT_DIR || process.cwd(), stdio: 'ignore' });
31
+ return emit({});
32
+ } catch {
33
+ return emit({
34
+ decision: 'block',
35
+ reason: `Push blocked: the test suite is red (\`${testCmd}\` exited non-zero). Green it first (Constitution Art. III, Quality-First).`,
36
+ });
37
+ }
38
+ }
39
+
40
+ main();
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // WRXN recall-surface hook — per-prompt recall nudge (wrxn-kernel-11).
5
+ // UserPromptSubmit. The symmetric RECALL half of reference-detect's CAPTURE: on each prompt it
6
+ // matches the prompt's SALIENT terms against the wiki knowledge tiers (concepts/decisions/gotchas)
7
+ // and, ONLY when a page matches, injects a <recall-surface> nudge — "you already have a page on X,
8
+ // recall it before re-deriving / re-asking the operator". Gated → silent on non-matching prompts.
9
+ //
10
+ // Self-contained: ships into installs, MUST NOT import the kernel lib (node stdlib only). The kernel
11
+ // wiki engine is substring (not BM25) — recall here is a deliberately simple distinct-salient-term
12
+ // count, ranked, top-N. Fail-open: any fault emits {} — the hook NEVER blocks a prompt.
13
+ //
14
+ // Contract: UserPromptSubmit event JSON on stdin → envelope JSON on stdout (exit 0).
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+
19
+ const TIERS = ['concepts', 'decisions', 'gotchas']; // knowledge tiers; sessions (episodic) excluded
20
+ const TOP_N = 2;
21
+ const MIN_PROMPT_LEN = 8; // skip trivial prompts ("ok", "yes")
22
+ // A page must share >=2 DISTINCT salient terms with the prompt to surface — the substring engine has
23
+ // no BM25 score, so a single shared common word is too weak a signal (anti-noise). Tradeoff: a genuine
24
+ // single-strong-term recall is silenced (fail-silent — a missed nudge is safer than a false one).
25
+ const MIN_DISTINCT = 2;
26
+ const MAX_BLOCK_CHARS = 600;
27
+
28
+ // Drop stopwords + short tokens so common words don't match common page words (anti-noise).
29
+ const STOPWORDS = new Set(['about', 'after', 'again', 'against', 'because', 'before', 'being', 'between',
30
+ 'could', 'does', 'doing', 'down', 'during', 'each', 'from', 'further', 'have', 'having', 'here', 'how',
31
+ 'into', 'just', 'like', 'more', 'most', 'only', 'other', 'over', 'same', 'should', 'some', 'such',
32
+ 'than', 'that', 'their', 'them', 'then', 'there', 'these', 'they', 'this', 'those', 'through', 'under',
33
+ 'until', 'very', 'want', 'what', 'when', 'where', 'which', 'while', 'with', 'would', 'your', 'today',
34
+ 'tell', 'show', 'give', 'make', 'need', 'know', 'help', 'please', 'thing', 'stuff', 'really', 'going']);
35
+
36
+ function emit(envelope) {
37
+ process.stdout.write(JSON.stringify(envelope));
38
+ process.exit(0);
39
+ }
40
+
41
+ function findInstallRoot(startDir) {
42
+ let dir = startDir || process.env.CLAUDE_PROJECT_DIR || process.cwd();
43
+ for (let i = 0; i < 12; i++) {
44
+ if (fs.existsSync(path.join(dir, 'wrxn.install.json'))) return dir;
45
+ const up = path.dirname(dir);
46
+ if (up === dir) break;
47
+ dir = up;
48
+ }
49
+ return null;
50
+ }
51
+
52
+ // De-duped salient content tokens (lowercased, >=4 chars, non-stopword).
53
+ function salientTerms(prompt) {
54
+ const seen = new Set();
55
+ for (const tok of (String(prompt || '').toLowerCase().match(/[a-z][a-z0-9]{3,}/g) || [])) {
56
+ if (!STOPWORDS.has(tok)) seen.add(tok);
57
+ }
58
+ return [...seen];
59
+ }
60
+
61
+ // Score each wiki page by the count of DISTINCT salient terms it contains; keep pages with >=1.
62
+ function recall(root, terms) {
63
+ const hits = [];
64
+ for (const tier of TIERS) {
65
+ const dir = path.join(root, '.wrxn', 'wiki', tier);
66
+ let names;
67
+ try {
68
+ names = fs.readdirSync(dir).filter((n) => n.endsWith('.md'));
69
+ } catch {
70
+ continue; // tier absent → skip
71
+ }
72
+ for (const name of names) {
73
+ let text;
74
+ try {
75
+ text = fs.readFileSync(path.join(dir, name), 'utf8').toLowerCase();
76
+ } catch {
77
+ continue;
78
+ }
79
+ const matched = terms.filter((t) => text.includes(t)).length;
80
+ if (matched >= MIN_DISTINCT) hits.push({ slug: name.replace(/\.md$/, ''), tier, score: matched });
81
+ }
82
+ }
83
+ // Highest distinct-term count first; ties broken by slug for determinism.
84
+ hits.sort((a, b) => b.score - a.score || a.slug.localeCompare(b.slug));
85
+ return hits.slice(0, TOP_N);
86
+ }
87
+
88
+ function block(hits) {
89
+ const lines = [
90
+ '<recall-surface>',
91
+ 'You already have captured page(s) on this topic — READ before answering (do not re-derive, do',
92
+ 'not ask the operator to re-explain). Recall with: node .wrxn/wiki.cjs recall "<slug>"',
93
+ ...hits.map((h) => `- ${h.slug} (${h.tier})`),
94
+ '</recall-surface>',
95
+ ].join('\n');
96
+ return lines.length <= MAX_BLOCK_CHARS ? lines : `${lines.slice(0, MAX_BLOCK_CHARS - 18)}\n</recall-surface>`;
97
+ }
98
+
99
+ function main() {
100
+ let event = {};
101
+ try {
102
+ const stdin = fs.readFileSync(0, 'utf8');
103
+ if (stdin.trim()) event = JSON.parse(stdin);
104
+ } catch {
105
+ emit({});
106
+ }
107
+
108
+ const root = findInstallRoot();
109
+ if (!root) emit({});
110
+
111
+ const prompt = typeof event.prompt === 'string' ? event.prompt : '';
112
+ if (prompt.trim().length < MIN_PROMPT_LEN) emit({});
113
+
114
+ const terms = salientTerms(prompt);
115
+ if (!terms.length) emit({});
116
+
117
+ const hits = recall(root, terms);
118
+ if (!hits.length) emit({}); // no captured page matched → silent (the gate)
119
+
120
+ emit({ hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext: block(hits) } });
121
+ }
122
+
123
+ try {
124
+ main();
125
+ } catch {
126
+ emit({});
127
+ }
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // WRXN reference-detect hook — the capture-nudge (wrxn-kernel-11).
5
+ // UserPromptSubmit. Scans the prompt for reference SIGNALS (URLs + explicit source markers) and,
6
+ // ONLY when one is present, injects a <reference-candidate> nudge telling the agent to OFFER to
7
+ // capture the reference into the wiki WITH provenance. PROPOSE-then-CONFIRM — it never ingests
8
+ // anything itself (the operator's chosen mode). Gated → silent on the vast majority of prompts.
9
+ //
10
+ // Self-contained: ships into installs, MUST NOT import the kernel lib (node stdlib only).
11
+ // Fail-open: any fault emits {} — the hook NEVER blocks a prompt.
12
+ //
13
+ // Contract: UserPromptSubmit event JSON on stdin → envelope JSON on stdout (exit 0).
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+
18
+ const URL_RE = /\bhttps?:\/\/[^\s<>()[\]]+/gi;
19
+ // Single-word markers are colon-anchored so "open source" / "for reference" do NOT trigger.
20
+ const MARKER_RE = /(?:\b(?:source|reference|cite)\s*:|\b(?:per the|according to|this is from|based on)\b)/i;
21
+ const MAX_URLS = 5;
22
+ const MAX_BLOCK_CHARS = 500;
23
+
24
+ function emit(envelope) {
25
+ process.stdout.write(JSON.stringify(envelope));
26
+ process.exit(0);
27
+ }
28
+
29
+ function findInstallRoot(startDir) {
30
+ let dir = startDir || process.env.CLAUDE_PROJECT_DIR || process.cwd();
31
+ for (let i = 0; i < 12; i++) {
32
+ if (fs.existsSync(path.join(dir, 'wrxn.install.json'))) return dir;
33
+ const up = path.dirname(dir);
34
+ if (up === dir) break;
35
+ dir = up;
36
+ }
37
+ return null;
38
+ }
39
+
40
+ function detect(prompt) {
41
+ const text = String(prompt || '');
42
+ const urls = (text.match(URL_RE) || []).map((u) => u.replace(/[.,;:!?"']+$/, ''));
43
+ return { urls: [...new Set(urls)].slice(0, MAX_URLS), hasMarker: MARKER_RE.test(text) };
44
+ }
45
+
46
+ function nudge(sig) {
47
+ const parts = [];
48
+ if (sig.urls.length) parts.push(`URL(s): ${sig.urls.join(', ')}`);
49
+ if (sig.hasMarker) parts.push('an explicit source/reference marker');
50
+ const block = [
51
+ '<reference-candidate>',
52
+ `Detected ${parts.join(' + ')} in this prompt.`,
53
+ 'If the operator means these as references to KEEP, OFFER to capture them into the wiki WITH',
54
+ 'provenance (propose-then-confirm — NEVER auto-ingest). On confirmation:',
55
+ ' node .wrxn/wiki.cjs write-page concepts <slug> --description "<what>" --body "source: <url-or-origin>"',
56
+ '</reference-candidate>',
57
+ ].join('\n');
58
+ return block.length <= MAX_BLOCK_CHARS ? block : `${block.slice(0, MAX_BLOCK_CHARS - 24)}\n…\n</reference-candidate>`;
59
+ }
60
+
61
+ function main() {
62
+ let event = {};
63
+ try {
64
+ const stdin = fs.readFileSync(0, 'utf8');
65
+ if (stdin.trim()) event = JSON.parse(stdin);
66
+ } catch {
67
+ emit({});
68
+ }
69
+
70
+ if (!findInstallRoot()) emit({}); // install-scoped — silent outside an install
71
+
72
+ const prompt = typeof event.prompt === 'string' ? event.prompt : '';
73
+ const sig = detect(prompt);
74
+ if (!sig.urls.length && !sig.hasMarker) emit({}); // no signal → silent (the gate)
75
+
76
+ emit({ hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext: nudge(sig) } });
77
+ }
78
+
79
+ try {
80
+ main();
81
+ } catch {
82
+ emit({});
83
+ }
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // WRXN session-end hook — the episodic writer (wrxn-kernel-10).
5
+ // SessionEnd. Writes a dated session page into the install's own wiki sessions tier from the
6
+ // captured turn trail, then clears the trail. CONTINUITY DOCTRINE: this writer touches ONLY
7
+ // dated session pages — it NEVER writes the continuity baton (.wrxn/continuity/latest.md).
8
+ // That slot has a single writer (the handoff skill); keeping the paths disjoint is the
9
+ // structural fix for the clobber observed live 2026-06-12.
10
+ //
11
+ // Self-contained: ships into installs, MUST NOT import the kernel lib (node stdlib only).
12
+ // Fail-open + side-effect-only: emits nothing useful, never blocks; any fault exits 0 silently.
13
+ //
14
+ // Contract: SessionEnd event JSON on stdin → exit 0. Side effect: a sessions/<date>-<sid>.md page.
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+
19
+ function done() {
20
+ process.exit(0);
21
+ }
22
+
23
+ function findInstallRoot(startDir) {
24
+ let dir = startDir || process.env.CLAUDE_PROJECT_DIR || process.cwd();
25
+ for (let i = 0; i < 12; i++) {
26
+ if (fs.existsSync(path.join(dir, 'wrxn.install.json'))) return dir;
27
+ const up = path.dirname(dir);
28
+ if (up === dir) break;
29
+ dir = up;
30
+ }
31
+ return null;
32
+ }
33
+
34
+ function nowISO() {
35
+ return process.env.WRXN_NOW || new Date().toISOString();
36
+ }
37
+
38
+ // Sanitize a session id into a kebab slug fragment (the page + trail FILE name).
39
+ function safeId(sid) {
40
+ return String(sid || 'session')
41
+ .toLowerCase()
42
+ .replace(/[^a-z0-9]+/g, '-')
43
+ .replace(/^-+|-+$/g, '')
44
+ .slice(0, 48) || 'session';
45
+ }
46
+
47
+ // Collapse any whitespace run (incl. newlines/tabs) to a single space — keeps a value safe to
48
+ // embed on a single frontmatter line (the wiki adapter + synapse engine PARSE these pages, so
49
+ // a stray newline would corrupt the frontmatter and break wiki query).
50
+ function oneLine(s) {
51
+ return String(s == null ? '' : s).replace(/\s+/g, ' ').trim().slice(0, 120);
52
+ }
53
+
54
+ // Read + consume the captured turn trail for this session (one `<iso>\t<line>` per turn).
55
+ function readTrail(root, sid) {
56
+ const trail = path.join(root, '.wrxn', 'history', `${safeId(sid)}.trail`);
57
+ let raw;
58
+ try {
59
+ raw = fs.readFileSync(trail, 'utf8');
60
+ } catch {
61
+ return { turns: [], trail };
62
+ }
63
+ const turns = raw.split('\n').map((l) => l.trim()).filter(Boolean);
64
+ return { turns, trail };
65
+ }
66
+
67
+ function main() {
68
+ let event = {};
69
+ try {
70
+ const stdin = fs.readFileSync(0, 'utf8');
71
+ if (stdin.trim()) event = JSON.parse(stdin);
72
+ } catch {
73
+ /* no/garbled stdin → write a minimal page anyway */
74
+ }
75
+
76
+ const root = findInstallRoot();
77
+ if (!root) done();
78
+
79
+ const sid = oneLine(event.session_id || 'session');
80
+ const reason = oneLine(event.reason || 'unknown');
81
+ const date = nowISO().slice(0, 10); // YYYY-MM-DD
82
+ const slug = `${date}-${safeId(event.session_id)}`;
83
+
84
+ const { turns, trail } = readTrail(root, event.session_id);
85
+ const trailLines = turns.length
86
+ ? turns.map((t, i) => {
87
+ const tab = t.indexOf('\t');
88
+ const line = tab > -1 ? t.slice(tab + 1) : t;
89
+ return `${i + 1}. ${line}`;
90
+ })
91
+ : ['_(no turns captured)_'];
92
+
93
+ const page = [
94
+ '---',
95
+ `name: ${slug}`,
96
+ `description: Session ${sid} — ${turns.length} turn(s), ended ${reason}`,
97
+ 'tier: sessions',
98
+ 'source: session-end-hook',
99
+ '---',
100
+ '',
101
+ `# Session ${date} (${sid})`,
102
+ '',
103
+ `- Ended: ${reason}`,
104
+ `- Turns: ${turns.length}`,
105
+ '',
106
+ '## Turn trail',
107
+ ...trailLines,
108
+ '',
109
+ ].join('\n');
110
+
111
+ const dir = path.join(root, '.wrxn', 'wiki', 'sessions');
112
+ try {
113
+ fs.mkdirSync(dir, { recursive: true });
114
+ fs.writeFileSync(path.join(dir, `${slug}.md`), page);
115
+ // Consume the trail so the next session starts clean.
116
+ try {
117
+ fs.rmSync(trail, { force: true });
118
+ } catch {
119
+ /* trail cleanup is best-effort */
120
+ }
121
+ } catch {
122
+ /* page write failed → fail-open, never block session close */
123
+ }
124
+
125
+ done();
126
+ }
127
+
128
+ try {
129
+ main();
130
+ } catch {
131
+ done();
132
+ }