hypomnema 1.0.1 → 1.2.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 (76) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.ko.md +12 -5
  4. package/README.md +12 -5
  5. package/commands/audit.md +46 -0
  6. package/commands/crystallize.md +113 -23
  7. package/commands/feedback.md +40 -26
  8. package/commands/ingest.md +31 -9
  9. package/commands/upgrade.md +2 -2
  10. package/docs/ARCHITECTURE.md +83 -9
  11. package/docs/CONTRIBUTING.md +2 -2
  12. package/hooks/hooks.json +39 -1
  13. package/hooks/hypo-auto-commit.mjs +23 -4
  14. package/hooks/hypo-auto-minimal-crystallize.mjs +145 -0
  15. package/hooks/hypo-auto-stage.mjs +9 -5
  16. package/hooks/hypo-compact-guard.mjs +33 -24
  17. package/hooks/hypo-cwd-change.mjs +107 -24
  18. package/hooks/hypo-file-watch.mjs +23 -10
  19. package/hooks/hypo-first-prompt.mjs +37 -23
  20. package/hooks/hypo-hot-rebuild.mjs +31 -8
  21. package/hooks/hypo-lookup.mjs +171 -65
  22. package/hooks/hypo-personal-check.mjs +207 -112
  23. package/hooks/hypo-pre-commit.mjs +46 -0
  24. package/hooks/hypo-session-end.mjs +58 -0
  25. package/hooks/hypo-session-record.mjs +60 -0
  26. package/hooks/hypo-session-start.mjs +312 -44
  27. package/hooks/hypo-shared.mjs +880 -28
  28. package/hooks/hypo-web-fetch-ingest.mjs +121 -0
  29. package/hooks/version-check-fetch.mjs +74 -0
  30. package/hooks/version-check.mjs +184 -0
  31. package/package.json +17 -3
  32. package/scripts/crystallize.mjs +623 -18
  33. package/scripts/doctor.mjs +739 -46
  34. package/scripts/feedback-sync.mjs +974 -0
  35. package/scripts/feedback.mjs +253 -44
  36. package/scripts/graph.mjs +35 -22
  37. package/scripts/ingest.mjs +89 -16
  38. package/scripts/init.mjs +442 -114
  39. package/scripts/lib/design-history-stale.mjs +83 -0
  40. package/scripts/lib/extensions.mjs +749 -0
  41. package/scripts/lib/frontmatter.mjs +5 -1
  42. package/scripts/lib/hypo-ignore.mjs +12 -10
  43. package/scripts/lib/pkg-json.mjs +23 -5
  44. package/scripts/lib/project-create.mjs +225 -0
  45. package/scripts/lib/schema-vocab.mjs +96 -0
  46. package/scripts/lint.mjs +238 -31
  47. package/scripts/query.mjs +26 -10
  48. package/scripts/resume.mjs +11 -5
  49. package/scripts/session-audit.mjs +277 -0
  50. package/scripts/smoke-pack.mjs +224 -0
  51. package/scripts/stats.mjs +24 -10
  52. package/scripts/uninstall.mjs +369 -48
  53. package/scripts/upgrade.mjs +766 -195
  54. package/scripts/verify.mjs +24 -14
  55. package/scripts/weekly-report.mjs +211 -0
  56. package/skills/crystallize/SKILL.md +24 -7
  57. package/skills/graph/SKILL.md +4 -0
  58. package/skills/ingest/SKILL.md +29 -5
  59. package/skills/lint/SKILL.md +4 -0
  60. package/skills/query/SKILL.md +4 -0
  61. package/skills/verify/SKILL.md +4 -0
  62. package/templates/.hypoignore +19 -2
  63. package/templates/Home.md +2 -0
  64. package/templates/SCHEMA.md +61 -6
  65. package/templates/extensions/agents/.gitkeep +0 -0
  66. package/templates/extensions/commands/.gitkeep +0 -0
  67. package/templates/extensions/hooks/.gitkeep +0 -0
  68. package/templates/extensions/skills/.gitkeep +0 -0
  69. package/templates/gitignore +5 -0
  70. package/templates/hot.md +2 -0
  71. package/templates/hypo-config.md +1 -1
  72. package/templates/hypo-guide.md +63 -1
  73. package/templates/hypo-help.md +1 -1
  74. package/templates/pages/observability/_index.md +77 -0
  75. package/templates/projects/_template/index.md +2 -2
  76. package/templates/projects/_template/prd.md +1 -1
@@ -26,8 +26,8 @@ function parseArgs(argv) {
26
26
  const args = { hypoDir: null, file: null, json: false };
27
27
  for (const arg of argv.slice(2)) {
28
28
  if (arg.startsWith('--hypo-dir=')) args.hypoDir = expandHome(arg.slice(11));
29
- else if (arg.startsWith('--file=')) args.file = expandHome(arg.slice(7));
30
- else if (arg === '--json') args.json = true;
29
+ else if (arg.startsWith('--file=')) args.file = expandHome(arg.slice(7));
30
+ else if (arg === '--json') args.json = true;
31
31
  }
32
32
  if (!args.hypoDir) args.hypoDir = resolveHypoRoot();
33
33
  return args;
@@ -55,7 +55,11 @@ function parseFrontmatter(content) {
55
55
  for (const line of m[1].split('\n')) {
56
56
  const idx = line.indexOf(':');
57
57
  if (idx < 0) continue;
58
- fm[line.slice(0, idx).trim()] = line.slice(idx + 1).trim().replace(/\s*#.*$/, '').replace(/^["']|["']$/g, '');
58
+ fm[line.slice(0, idx).trim()] = line
59
+ .slice(idx + 1)
60
+ .trim()
61
+ .replace(/\s*#.*$/, '')
62
+ .replace(/^["']|["']$/g, '');
59
63
  }
60
64
  return fm;
61
65
  }
@@ -72,18 +76,22 @@ if (args.file) {
72
76
  files = [args.file];
73
77
  } else {
74
78
  const ignorePatterns = loadHypoIgnore(args.hypoDir);
75
- const scanDirs = ['pages', 'projects'].map(d => join(args.hypoDir, d));
76
- files = scanDirs.flatMap(d => collectMdFiles(d, [], args.hypoDir, ignorePatterns));
79
+ const scanDirs = ['pages', 'projects'].map((d) => join(args.hypoDir, d));
80
+ files = scanDirs.flatMap((d) => collectMdFiles(d, [], args.hypoDir, ignorePatterns));
77
81
  }
78
82
 
79
- const overdue = [];
83
+ const overdue = [];
80
84
  const upcoming = [];
81
- const missing = [];
82
- const ok = [];
85
+ const missing = [];
86
+ const ok = [];
83
87
 
84
88
  for (const file of files) {
85
89
  let content;
86
- try { content = readFileSync(file, 'utf-8'); } catch { continue; }
90
+ try {
91
+ content = readFileSync(file, 'utf-8');
92
+ } catch {
93
+ continue;
94
+ }
87
95
  const fm = parseFrontmatter(content);
88
96
  if (!fm) continue;
89
97
 
@@ -152,16 +160,18 @@ if (ok.length > 0 && overdue.length === 0 && upcoming.length === 0) {
152
160
  }
153
161
 
154
162
  const summary = [
155
- overdue.length ? `${overdue.length} overdue` : '',
163
+ overdue.length ? `${overdue.length} overdue` : '',
156
164
  upcoming.length ? `${upcoming.length} due soon` : '',
157
- missing.length ? `${missing.length} missing verify_by` : '',
158
- ok.length ? `${ok.length} ok` : '',
159
- ].filter(Boolean).join(', ');
165
+ missing.length ? `${missing.length} missing verify_by` : '',
166
+ ok.length ? `${ok.length} ok` : '',
167
+ ]
168
+ .filter(Boolean)
169
+ .join(', ');
160
170
 
161
171
  console.log(`Result: ${summary || 'nothing to verify'}`);
162
172
 
163
173
  // Emit signal lines for overdue/upcoming pages so /hypo:verify skill can drive review
164
- const needsReview = [...overdue, ...upcoming].filter(p => p.verify_by);
174
+ const needsReview = [...overdue, ...upcoming].filter((p) => p.verify_by);
165
175
  if (needsReview.length > 0) {
166
176
  console.log('');
167
177
  for (const p of needsReview) {
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * weekly-report.mjs — Weekly observability report (Lane E)
4
+ *
5
+ * Aggregates session-audit results into a Markdown report under
6
+ * `<hypo-dir>/journal/weekly/<YYYY-Www>.md` (spec §6.4 SoT).
7
+ *
8
+ * The autonomy score is heuristic on purpose — see
9
+ * pages/observability/_index.md for the definition and the 4-week
10
+ * baseline plan before we revisit LLM-judge classification.
11
+ *
12
+ * Usage:
13
+ * node scripts/weekly-report.mjs [--hypo-dir=<path>] [--week=YYYY-Www]
14
+ * [--limit N] [--write] [--json]
15
+ */
16
+
17
+ import { existsSync, mkdirSync, writeFileSync } from 'fs';
18
+ import { join } from 'path';
19
+ import { resolveHypoRoot, expandHome } from './lib/hypo-root.mjs';
20
+ import { loadSessionEntries, auditEntries } from './session-audit.mjs';
21
+
22
+ function parseArgs(argv) {
23
+ const args = {
24
+ hypoDir: null,
25
+ week: null,
26
+ limit: 200,
27
+ write: false,
28
+ json: false,
29
+ fallbackAll: false,
30
+ };
31
+ for (const arg of argv.slice(2)) {
32
+ if (arg.startsWith('--hypo-dir=')) args.hypoDir = expandHome(arg.slice(11));
33
+ else if (arg.startsWith('--week=')) args.week = arg.slice(7);
34
+ else if (arg.startsWith('--limit=')) args.limit = parseInt(arg.slice(8), 10) || 200;
35
+ else if (arg === '--write') args.write = true;
36
+ else if (arg === '--json') args.json = true;
37
+ else if (arg === '--fallback-all-projects') args.fallbackAll = true;
38
+ }
39
+ if (!args.hypoDir) args.hypoDir = resolveHypoRoot();
40
+ return args;
41
+ }
42
+
43
+ function isoWeek(date) {
44
+ // ISO-8601 week: Thursday in current week decides the year.
45
+ const d = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
46
+ const dayNum = d.getUTCDay() || 7;
47
+ d.setUTCDate(d.getUTCDate() + 4 - dayNum);
48
+ const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
49
+ const week = Math.ceil(((d - yearStart) / 86400000 + 1) / 7);
50
+ return `${d.getUTCFullYear()}-W${String(week).padStart(2, '0')}`;
51
+ }
52
+
53
+ function parseWeekArg(spec) {
54
+ // Accepts YYYY-Www (ISO 8601). Returns the Monday of that ISO week, or
55
+ // null if the week number does not exist in that year (round-trips via
56
+ // isoWeek to reject W00, W54, and W53 in non-53-week years).
57
+ const m = String(spec).match(/^(\d{4})-W(\d{2})$/);
58
+ if (!m) return null;
59
+ const year = parseInt(m[1], 10);
60
+ const week = parseInt(m[2], 10);
61
+ if (week < 1 || week > 53) return null;
62
+ const jan4 = new Date(Date.UTC(year, 0, 4));
63
+ const jan4Dow = jan4.getUTCDay() || 7;
64
+ const week1Mon = new Date(jan4);
65
+ week1Mon.setUTCDate(jan4.getUTCDate() - (jan4Dow - 1));
66
+ const weekStart = new Date(week1Mon);
67
+ weekStart.setUTCDate(week1Mon.getUTCDate() + (week - 1) * 7);
68
+ if (isoWeek(weekStart) !== spec) return null;
69
+ return weekStart;
70
+ }
71
+
72
+ function filterToWeek(results, weekLabel) {
73
+ return results.filter((r) => {
74
+ if (!r.recorded_at) return false;
75
+ return isoWeek(new Date(r.recorded_at)) === weekLabel;
76
+ });
77
+ }
78
+
79
+ function tally(results) {
80
+ const out = {
81
+ normal: 0,
82
+ 'search-0': 0,
83
+ 'search-many': 0,
84
+ 'ingest-missed': 0,
85
+ 'staleness-skip': 0,
86
+ };
87
+ for (const r of results) out[r.classification] = (out[r.classification] || 0) + 1;
88
+ return out;
89
+ }
90
+
91
+ function autonomyScore(results) {
92
+ // Heuristic v0 (see pages/observability/_index.md §3 for the formal sketch).
93
+ // Numerator = weighted "wiki was actually used" signals.
94
+ // Denominator = same signals expected (1 per non-stale session, plus URL→ingest opportunities).
95
+ let num = 0;
96
+ let den = 0;
97
+ for (const r of results) {
98
+ if (r.classification === 'staleness-skip') continue;
99
+ const m = r.metrics;
100
+ den += 1; // at minimum, expect *some* wiki engagement per real session
101
+ num += Math.min(m.search_count, 3) + m.ingest_count * 3 + m.feedback_count * 2;
102
+ if (m.urls > 0) {
103
+ // Each URL is a missed-ingest opportunity (weight 2). Numerator is *not*
104
+ // boosted here — `ingest_count` is already credited above; what URLs do
105
+ // is raise the bar for what "fully autonomous" looks like.
106
+ den += Math.min(m.urls, 5) * 2;
107
+ }
108
+ }
109
+ if (den === 0) return 0;
110
+ return Math.min(100, Math.max(0, Math.round((num / den) * 100)));
111
+ }
112
+
113
+ export function buildReport(
114
+ hypoDir,
115
+ { week, limit = 200, now = Date.now(), fallbackAll = false } = {},
116
+ ) {
117
+ const entries = loadSessionEntries(hypoDir, { fallbackAll });
118
+ const audited = auditEntries(entries, { limit, maxAgeDays: 365, now });
119
+ const weekLabel = week || isoWeek(new Date(now));
120
+ const weekResults = filterToWeek(audited, weekLabel);
121
+ const counts = tally(weekResults);
122
+ const score = autonomyScore(weekResults);
123
+ return { week: weekLabel, count: weekResults.length, counts, score, results: weekResults };
124
+ }
125
+
126
+ function renderMarkdown(report) {
127
+ const { week, count, counts, score, results } = report;
128
+ const today = new Date().toISOString().slice(0, 10);
129
+ const lines = [];
130
+ lines.push('---');
131
+ lines.push(`title: Observability — Week ${week}`);
132
+ lines.push('tags: [observability, autonomy, weekly]');
133
+ lines.push('type: weekly-journal');
134
+ lines.push(`week: ${week}`);
135
+ lines.push(`generated_by: manual`);
136
+ lines.push(`updated: ${today}`);
137
+ lines.push('---');
138
+ lines.push('');
139
+ lines.push(`# Observability — Week ${week}`);
140
+ lines.push('');
141
+ lines.push(
142
+ `> Generated by \`scripts/weekly-report.mjs\`. Heuristic definition: [[pages/observability/_index]].`,
143
+ );
144
+ lines.push('');
145
+ lines.push(`## Autonomy score (heuristic v0): **${score}%**`);
146
+ lines.push('');
147
+ lines.push(`Sessions audited: ${count}`);
148
+ lines.push('');
149
+ lines.push('| Classification | Count |');
150
+ lines.push('|---|---|');
151
+ for (const k of ['normal', 'search-0', 'search-many', 'ingest-missed', 'staleness-skip']) {
152
+ lines.push(`| ${k} | ${counts[k] || 0} |`);
153
+ }
154
+ lines.push('');
155
+ if (results.length === 0) {
156
+ lines.push(
157
+ '_No sessions recorded in this week. If this is unexpected, check `hooks/hypo-session-record.mjs` and `.cache/sessions/index.jsonl`._',
158
+ );
159
+ } else {
160
+ lines.push('## Sessions');
161
+ lines.push('');
162
+ lines.push('| Session | Class | search | ingest | urls | recorded |');
163
+ lines.push('|---|---|---|---|---|---|');
164
+ for (const r of results) {
165
+ const m = r.metrics;
166
+ lines.push(
167
+ `| ${r.session_id} | ${r.classification} | ${m.search_count} | ${m.ingest_count} | ${m.urls} | ${r.recorded_at || '—'} |`,
168
+ );
169
+ }
170
+ }
171
+ return lines.join('\n') + '\n';
172
+ }
173
+
174
+ function isMain() {
175
+ try {
176
+ return import.meta.url === `file://${process.argv[1]}`;
177
+ } catch {
178
+ return false;
179
+ }
180
+ }
181
+
182
+ if (isMain()) {
183
+ const args = parseArgs(process.argv);
184
+ if (args.week && !parseWeekArg(args.week)) {
185
+ console.error(`Error: invalid --week value "${args.week}" (expected YYYY-Www)`);
186
+ process.exit(2);
187
+ }
188
+ const report = buildReport(args.hypoDir, {
189
+ week: args.week,
190
+ limit: args.limit,
191
+ fallbackAll: args.fallbackAll,
192
+ });
193
+
194
+ if (args.json) {
195
+ console.log(JSON.stringify(report, null, 2));
196
+ process.exit(0);
197
+ }
198
+
199
+ const md = renderMarkdown(report);
200
+
201
+ if (args.write) {
202
+ const dir = join(args.hypoDir, 'journal', 'weekly');
203
+ mkdirSync(dir, { recursive: true });
204
+ const path = join(dir, `${report.week}.md`);
205
+ writeFileSync(path, md);
206
+ console.log(`✓ Wrote ${path}`);
207
+ console.log(` Autonomy score: ${report.score}% (sessions: ${report.count})`);
208
+ } else {
209
+ process.stdout.write(md);
210
+ }
211
+ }
@@ -1,15 +1,18 @@
1
1
  ---
2
- description: Surface synthesis candidates and consolidate scattered wiki knowledge into stable pages
2
+ description: Close a session (steps 1~6) and, on request, consolidate scattered wiki knowledge into stable pages (steps 7~11)
3
3
  ---
4
4
 
5
- You are running `/hypo:crystallize`. Find pages that are ready to be consolidated into stable, cross-linked knowledge — then guide the synthesis.
5
+ You are running `/hypo:crystallize`. The command serves two modes (spec §5.2.7 / §8.3):
6
+
7
+ 1. **Session close (steps 1~6)** — gate the 5 mandatory memory files plus open-questions (conditional) so `/compact` can pass.
8
+ 2. **Synthesis (steps 7~11)** — surface tag clusters, orphan pages, and drafts that are ready to consolidate.
9
+
10
+ When invoked at the end of a session (or with phrases like "세션 종료", "wrap up"), run the session-close checklist first. The synthesis scan only runs after close is confirmed and the user agrees.
6
11
 
7
12
  ## What this does
8
13
 
9
- - Finds tag groups with N pages sharing the same tag (synthesis candidates)
10
- - Lists orphan pages (no outbound `[[wikilinks]]`)
11
- - Lists draft / stub pages that could be fleshed out
12
- - After the script runs, you help the user pick what to crystallize and do it
14
+ - **Close mode**: walks the 6-step checklist (session-state, project hot.md, root hot.md, session-log, open-questions(변경 ), log.md) and verifies via `crystallize.mjs --check-session-close` — same gate the PreCompact hook runs.
15
+ - **Synthesis mode**: finds tag clusters (≥ N pages), orphan pages (no outbound `[[wikilinks]]`), and draft / stub pages, then guides consolidation into `pages/syntheses/<topic>.md` with back-links and `index.md` updates.
13
16
 
14
17
  ---
15
18
 
@@ -49,7 +52,17 @@ If `/hypo:crystallize` was invoked as a session-close action, run through this c
49
52
  5. **open-questions** — only if `pages/open-questions.md` exists and questions were raised or resolved this session: move resolved ones out; add newly raised ones. Skip if unchanged.
50
53
  6. **log.md** — append a `session` entry to `<wiki-root>/log.md`.
51
54
 
52
- After completing the checklist, report each item with ✓ and ask: "Session closed. Would you like to also run knowledge synthesis now, or stop here?"
55
+ After completing the checklist, verify it before reporting:
56
+
57
+ ```bash
58
+ node <package-root>/scripts/crystallize.mjs --check-session-close [--hypo-dir="<path>"]
59
+ ```
60
+
61
+ This runs the same strict check as the PreCompact hard gate (fix #17). Fix any
62
+ file reported `missing` or `stale` and re-run until it passes — otherwise
63
+ `/compact` will be blocked.
64
+
65
+ Once it passes, report each item with ✓ and ask: "Session closed. Would you like to also run knowledge synthesis now, or stop here?"
53
66
 
54
67
  ---
55
68
 
@@ -83,3 +96,7 @@ evidence_strength: inferred
83
96
 
84
97
  3. Cross-link all source pages back to the synthesis with `[[wikilink]]`.
85
98
  4. Add the synthesis to `index.md` under `## Pages — Syntheses`.
99
+
100
+ ---
101
+
102
+ > **Citation convention.** When you reference a wiki page in your response, link it as `[[page-slug]]`. The observability audit counts citations toward the autonomy score — see [[pages/observability/_index]] (run `/hypo:audit` to inspect).
@@ -52,3 +52,7 @@ node <package-root>/scripts/graph.mjs \
52
52
  - **dot**: wrap the output in a fenced code block tagged `dot` and suggest the user pipe it to `dot -Tsvg -o graph.svg`.
53
53
 
54
54
  If the graph has 0 edges, note that no `[[wikilinks]]` were found between pages.
55
+
56
+ ---
57
+
58
+ > **Citation convention.** When you reference a wiki page in your response, link it as `[[page-slug]]`. The observability audit counts citations toward the autonomy score — see [[pages/observability/_index]] (run `/hypo:audit` to inspect).
@@ -16,14 +16,14 @@ You are running `/hypo:ingest`. Add a new source document to `sources/` and crea
16
16
 
17
17
  Locate the Hypomnema package root (the directory two levels above this file (`skills/<name>/SKILL.md` → package root)).
18
18
 
19
- If the user specified a wiki directory, pass it as `--wiki-dir="<path>"`. Otherwise omit the flag and the script resolves the wiki root automatically via `HYPO_DIR` → `hypo-config.md` scan → `~/hypomnema`.
19
+ If the user specified a wiki directory, pass it as `--hypo-dir="<path>"`. Otherwise omit the flag and the script resolves the wiki root automatically via `HYPO_DIR` → `hypo-config.md` scan → `~/hypomnema`.
20
20
 
21
21
  ---
22
22
 
23
23
  ## Step 2 — Run ingest status check
24
24
 
25
25
  ```bash
26
- node <package-root>/scripts/ingest.mjs [--wiki-dir="<path>"] [--json]
26
+ node <package-root>/scripts/ingest.mjs [--hypo-dir="<path>"] [--json]
27
27
  ```
28
28
 
29
29
  Options:
@@ -33,7 +33,27 @@ Show the output verbatim.
33
33
 
34
34
  ---
35
35
 
36
- ## Step 3 — Handle the source file
36
+ ## Step 3 — Privacy guard (`.hypoignore`)
37
+
38
+ Before touching any source content, refuse to ingest secrets (`.env`, SSH keys, credentials). Run the guard for **both** the input path and the destination path:
39
+
40
+ 1. **If the user provided a file path**, check it (use an absolute path):
41
+
42
+ ```bash
43
+ node <package-root>/scripts/ingest.mjs [--hypo-dir="<path>"] --check="<absolute-input-path>"
44
+ ```
45
+
46
+ 2. **Always** check the destination `sources/<slug>.<ext>`:
47
+
48
+ ```bash
49
+ node <package-root>/scripts/ingest.mjs [--hypo-dir="<path>"] --check="sources/<slug>.<ext>"
50
+ ```
51
+
52
+ If either command exits non-zero, **stop**: surface the `Refused: ...` message to the user and do not download, read, or save the source. The slug check matters because a user could rename a `.env` to an innocuous slug — the destination must still be blocked.
53
+
54
+ ---
55
+
56
+ ## Step 4 — Handle the source file
37
57
 
38
58
  **If the user provided a file or URL to ingest:**
39
59
 
@@ -46,7 +66,7 @@ List un-ingested sources from the script output and ask which one to process now
46
66
 
47
67
  ---
48
68
 
49
- ## Step 4 — Synthesize a source-summary page
69
+ ## Step 5 — Synthesize a source-summary page
50
70
 
51
71
  For the chosen source, read its content and create `pages/<slug>.md` with the following frontmatter:
52
72
 
@@ -70,7 +90,7 @@ Cross-reference existing pages with `[[wikilink]]` syntax where relevant.
70
90
 
71
91
  ---
72
92
 
73
- ## Step 5 — Update log.md
93
+ ## Step 6 — Update log.md
74
94
 
75
95
  Append an ingest entry to `<wiki-root>/log.md`:
76
96
 
@@ -81,3 +101,7 @@ Append an ingest entry to `<wiki-root>/log.md`:
81
101
  - summary: pages/<slug>.md
82
102
  - tags: <tags>
83
103
  ```
104
+
105
+ ---
106
+
107
+ > **Citation convention.** When you reference a wiki page in your response, link it as `[[page-slug]]`. The observability audit counts citations toward the autonomy score — see [[pages/observability/_index]] (run `/hypo:audit` to inspect).
@@ -53,3 +53,7 @@ For **broken wikilinks**: list the affected files and ask if the user wants help
53
53
  For **missing `updated`**: suggest running with `--fix` to auto-add `updated: <today>` to each affected page's frontmatter. Note: `--fix` only repairs files that already have a valid, closed frontmatter block — files with no frontmatter or malformed frontmatter are skipped.
54
54
 
55
55
  For **missing required fields** (`title`, `type`): open the affected files and help the user fill them in.
56
+
57
+ ---
58
+
59
+ > **Citation convention.** When you reference a wiki page in your response, link it as `[[page-slug]]`. The observability audit counts citations toward the autonomy score — see [[pages/observability/_index]] (run `/hypo:audit` to inspect).
@@ -56,3 +56,7 @@ Read the top matching pages (up to 5) and produce a synthesized response:
56
56
  3. **Gaps** — if the wiki lacks coverage on the topic, note what is missing and suggest an ingest target.
57
57
 
58
58
  If zero results are returned, say so and offer to broaden the search or suggest using `/hypo:ingest` to add relevant sources.
59
+
60
+ ---
61
+
62
+ > **Citation convention.** When you reference a wiki page in your response, link it as `[[page-slug]]`. The observability audit counts citations toward the autonomy score — see [[pages/observability/_index]] (run `/hypo:audit` to inspect).
@@ -90,3 +90,7 @@ updated: <today YYYY-MM-DD>
90
90
  ```
91
91
 
92
92
  3. Save the file. Do not remove or reorder existing entries.
93
+
94
+ ---
95
+
96
+ > **Citation convention.** When you reference a wiki page in your response, link it as `[[page-slug]]`. The observability audit counts citations toward the autonomy score — see [[pages/observability/_index]] (run `/hypo:audit` to inspect).
@@ -8,10 +8,27 @@
8
8
  *.zip
9
9
  *.tar.gz
10
10
 
11
- # Credentials and secrets
11
+ # Credentials and secrets (§6.8 baseline)
12
12
  *.pem
13
13
  *.key
14
- *.env
14
+ .env*
15
+ *.p12
16
+ *.pfx
17
+ id_rsa*
18
+ id_ed25519*
19
+ *credentials*
20
+ *secret*
21
+ *token*
22
+ *password*
23
+ *passwd*
24
+
25
+ # Build artifacts
26
+ node_modules/
27
+ dist/
28
+ build/
29
+
30
+ # Hypomnema runtime cache (session growth metrics, future index.jsonl, etc.)
31
+ .cache/
15
32
 
16
33
  # Draft / scratch (uncomment to hide from hooks)
17
34
  # drafts/
package/templates/Home.md CHANGED
@@ -19,6 +19,8 @@ tags: [wiki, home]
19
19
  - [[SCHEMA]] — type system reference
20
20
  - [[hypo-guide]] — operations guide
21
21
 
22
+ > **Auto-ingest rule**: after any `WebFetch` / `WebSearch` that yields relevant knowledge, run `/hypo:ingest` immediately without waiting for an explicit request.
23
+
22
24
  ---
23
25
 
24
26
  ## Active Projects
@@ -2,7 +2,7 @@
2
2
  title: Wiki Schema
3
3
  type: schema
4
4
  updated: YYYY-MM-DD
5
- version: 1.0
5
+ version: 2.0
6
6
  ---
7
7
 
8
8
  # Wiki Schema
@@ -61,6 +61,17 @@ Files responsible for session continuity — separate from the type taxonomy abo
61
61
  - Session start: root `hot.md` → project `hot.md` → `session-state.md`
62
62
  - Session close: update project `hot.md` (what was done) + `session-state.md` (what to do next) + root `hot.md`
63
63
 
64
+ **Pointer table row format** (`hypo-hot-rebuild.mjs` parses this with a fixed regex — wrong col3 format causes the row to be silently skipped):
65
+
66
+ ```
67
+ | <Project Name> | YYYY-MM-DD | [[projects/<slug>/hot]] |
68
+ ```
69
+
70
+ Column semantics:
71
+ - col 1 (name): preserved as-is from the existing row
72
+ - col 2 (date): **ignored on read** — rebuilt from `projects/<slug>/hot.md` frontmatter `updated:` (falls back to today if the file is absent)
73
+ - col 3 (wikilink): must be `[[projects/<slug>/hot]]` — no trailing path, no markdown link `[text](url)`. Extra trailing columns are discarded during canonical rebuild (not dropped).
74
+
64
75
  ---
65
76
 
66
77
  ## 3. Required Frontmatter Fields
@@ -85,15 +96,59 @@ verify_by: <question to re-check at next review>
85
96
  verify_by_date: YYYY-MM-DD
86
97
  ```
87
98
 
99
+ ### 3.1. `feedback` type — projection fields (ADR 0031)
100
+
101
+ Feedback pages are the single source of truth for behavior corrections;
102
+ `hypomnema feedback-sync` projects them one-way into Claude Code's `MEMORY.md`
103
+ and `~/.claude/CLAUDE.md` `<learned_behaviors>`. They require:
104
+
105
+ ```yaml
106
+ status: active | superseded | archived
107
+ scope: global | project:<project-id> # global → eligible for CLAUDE.md projection;
108
+ # <project-id> must exact-match the resolved
109
+ # project-id (default: cwd → `/`,`.` replaced
110
+ # with `-`; or pass `--project-id=<id>`).
111
+ # Mismatched scope = page is NOT projected
112
+ # into that project's MEMORY.md.
113
+ tier: L1 | L2 # L1 required for CLAUDE.md <learned_behaviors>
114
+ targets: [project-memory, claude-learned] # which projection surfaces to derive
115
+ sensitivity: public | sanitized # `private` forbidden (wiki is git-pushed)
116
+ priority: 1-5 # over-cap sort key (higher first)
117
+ memory_summary: <one line for the MEMORY.md index>
118
+ reason: <why this rule is needed>
119
+ source: session:YYYY-MM-DD | commit:<hash> | pr:<n> | https://...
120
+
121
+ # conditional — required when `targets` includes `claude-learned`:
122
+ global_summary: <one line for the <learned_behaviors> entry>
123
+ promote_to_global: true # explicit opt-in to global projection
124
+ ```
125
+
126
+ Edit the feedback page only — never hand-edit the generated
127
+ `<!-- HYPO:FEEDBACK-SYNC:START … -->` managed blocks (sync detects tampering as a conflict).
128
+
88
129
  ---
89
130
 
90
131
  ## 4. Tag Vocabulary
91
132
 
92
- Use lowercase, hyphenated tags. Keep the vocabulary small and consistent.
93
-
94
- **Domain tags**: `ai`, `dev`, `ops`, `security`, `data`, `design`, `management`
95
- **Status tags**: `draft`, `stable`, `deprecated`, `needs-review`
96
- **Meta tags**: `wiki`, `index`, `operations`, `guide`, `schema`
133
+ Use lowercase, hyphenated tags. Vocabulary is locked `lint` blocks unknown tags
134
+ and forbidden patterns (PascalCase, plurals, whitespace, generic words).
135
+ Extend this list (and `~/hypomnema/SCHEMA.md`) before introducing a new tag.
136
+
137
+ **Meta**: `wiki`, `index`, `pages`, `home`, `overview`, `guide`, `operations`, `schema`, `reference`, `hypo`, `commands`, `hot-cache`, `migration`
138
+ **Workflow**: `automation`, `hooks`, `observability`, `autonomy`, `wiki-health`, `weekly`
139
+ **Project**: `project`, `prd`, `adr`, `session-state`
140
+ **Domain**: `ai`, `dev`, `ops`, `security`, `data`, `design`, `management`
141
+ **Status**: `active`, `completed`, `archived`, `draft`, `stable`, `deprecated`, `needs-review`, `proposed`, `superseded`
142
+ **Content classification**: `learning`, `tip`, `feedback`, `gotcha`, `concept`, `pattern`
143
+
144
+ ### Forbidden patterns
145
+
146
+ | Pattern | Reason | Use instead |
147
+ |---------|--------|-------------|
148
+ | PascalCase (`Jenkins`, `Claude`) | Inconsistent casing | `jenkins`, `claude-code` |
149
+ | Plurals (`learnings`, `tips`) | Singular form is canonical | `learning`, `tip` |
150
+ | Generic (`general`, `misc`, `other`, `todo`) | No search value | Specific domain tag |
151
+ | Whitespace (`llm wiki`) | Parse breakage | `llm-wiki` |
97
152
 
98
153
  ---
99
154
 
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,5 @@
1
+ # .gitignore — generated by hypo init
2
+ # Excludes Hypomnema runtime files that contain machine-specific or session PII.
3
+
4
+ # Runtime cache: sync-state.json contains hostname() and timestamps
5
+ .cache/
package/templates/hot.md CHANGED
@@ -14,6 +14,8 @@ tags: [wiki, operations]
14
14
 
15
15
  | Project | Last Session | Hot Cache |
16
16
  |---|---|---|
17
+ <!-- Row format: | Project Name | YYYY-MM-DD | [[projects/slug/hot]] | -->
18
+ <!-- col2 date is rebuilt from projects/<slug>/hot.md frontmatter on each session close -->
17
19
 
18
20
  ## Session Start Checklist
19
21
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  title: Hypomnema Config
3
3
  type: config
4
- version: "1.0.1"
4
+ version: "1.2.0"
5
5
  created: YYYY-MM-DD
6
6
  ---
7
7