hypomnema 1.3.1 → 1.3.3

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 (65) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +2 -2
  3. package/README.ko.md +11 -10
  4. package/README.md +11 -10
  5. package/commands/audit.md +1 -1
  6. package/commands/crystallize.md +11 -11
  7. package/commands/doctor.md +1 -1
  8. package/commands/feedback.md +2 -2
  9. package/commands/graph.md +1 -1
  10. package/commands/ingest.md +1 -1
  11. package/commands/init.md +1 -1
  12. package/commands/lint.md +1 -1
  13. package/commands/query.md +1 -1
  14. package/commands/rename.md +79 -0
  15. package/commands/resume.md +2 -2
  16. package/commands/stats.md +1 -1
  17. package/commands/uninstall.md +1 -1
  18. package/commands/upgrade.md +1 -1
  19. package/commands/verify.md +1 -1
  20. package/docs/ARCHITECTURE.md +3 -3
  21. package/docs/CONTRIBUTING.md +2 -2
  22. package/hooks/hypo-auto-commit.mjs +8 -24
  23. package/hooks/hypo-auto-minimal-crystallize.mjs +57 -11
  24. package/hooks/hypo-compact-guard.mjs +6 -3
  25. package/hooks/hypo-cwd-change.mjs +1 -1
  26. package/hooks/hypo-first-prompt.mjs +2 -2
  27. package/hooks/hypo-hot-rebuild.mjs +14 -1
  28. package/hooks/hypo-personal-check.mjs +91 -179
  29. package/hooks/hypo-pre-commit.mjs +1 -1
  30. package/hooks/hypo-session-end.mjs +1 -1
  31. package/hooks/hypo-session-start.mjs +7 -7
  32. package/hooks/hypo-shared.mjs +1082 -89
  33. package/hooks/hypo-web-fetch-ingest.mjs +2 -2
  34. package/hooks/version-check-fetch.mjs +2 -8
  35. package/hooks/version-check.mjs +18 -0
  36. package/package.json +3 -2
  37. package/scripts/check-tracker-ids.mjs +329 -0
  38. package/scripts/crystallize.mjs +322 -110
  39. package/scripts/doctor.mjs +6 -9
  40. package/scripts/feedback-sync.mjs +1 -1
  41. package/scripts/init.mjs +1 -1
  42. package/scripts/install-git-hooks.mjs +75 -40
  43. package/scripts/lib/check-tracker-ids.mjs +140 -0
  44. package/scripts/lib/design-history-stale.mjs +59 -14
  45. package/scripts/lib/extensions.mjs +4 -4
  46. package/scripts/lib/fix-manifest.mjs +2 -2
  47. package/scripts/lib/fix-status-verify.mjs +5 -4
  48. package/scripts/lib/plugin-detect.mjs +15 -6
  49. package/scripts/lib/project-create.mjs +1 -1
  50. package/scripts/lint.mjs +63 -8
  51. package/scripts/rename.mjs +836 -0
  52. package/scripts/resume.mjs +75 -19
  53. package/scripts/uninstall.mjs +1 -1
  54. package/scripts/upgrade.mjs +26 -25
  55. package/skills/crystallize/SKILL.md +12 -9
  56. package/skills/graph/SKILL.md +1 -1
  57. package/skills/ingest/SKILL.md +1 -1
  58. package/skills/lint/SKILL.md +1 -1
  59. package/skills/query/SKILL.md +1 -1
  60. package/skills/verify/SKILL.md +1 -1
  61. package/templates/SCHEMA.md +2 -2
  62. package/templates/hypo-config.md +2 -2
  63. package/templates/hypo-guide.md +22 -1
  64. package/templates/hypo-help.md +1 -0
  65. package/templates/projects/_template/index.md +1 -1
package/scripts/lint.mjs CHANGED
@@ -81,7 +81,7 @@ const TYPE_CONDITIONAL_FIELDS = {
81
81
  'tool-eval': ['status'],
82
82
  postmortem: ['outcome'],
83
83
  learning: ['source'],
84
- // feedback: ADR 0031 / fix #37 — projection SoT requires full classification
84
+ // feedback: ADR 0031 — projection SoT requires full classification
85
85
  feedback: [
86
86
  'status',
87
87
  'scope',
@@ -102,7 +102,7 @@ const TYPE_ENUM_FIELDS = {
102
102
  prd: { status: ['draft', 'active', 'completed', 'cancelled', 'archived'] },
103
103
  adr: { status: ['proposed', 'accepted', 'deprecated', 'superseded'] },
104
104
  'tool-eval': { status: ['adopted', 'evaluating', 'rejected'] },
105
- // feedback: ADR 0031 / fix #37 — sensitivity:private is forbidden (wiki is a
105
+ // feedback: ADR 0031 — sensitivity:private is forbidden (wiki is a
106
106
  // git-pushed public surface)
107
107
  feedback: {
108
108
  status: ['active', 'superseded', 'archived'],
@@ -135,15 +135,56 @@ function collectPages(dir, root, pages = [], ignorePatterns = []) {
135
135
 
136
136
  // ── slug map ─────────────────────────────────────────────────────────────────
137
137
 
138
- function buildSlugMap(pages) {
138
+ // `extraTargets` are link-target-only slugs (root *.md, sources/*) that resolve
139
+ // wikilinks but are not themselves linted — added verbatim, with NO derived
140
+ // basename/dir-relative aliases, so they can't mask an unrelated broken link.
141
+ function buildSlugMap(pages, extraTargets = []) {
139
142
  const map = new Set();
140
143
  for (const { rel } of pages) {
141
- map.add(rel.replace(/\.md$/, '').replace(/\\/g, '/'));
144
+ const noExt = rel.replace(/\.md$/, '').replace(/\\/g, '/');
145
+ map.add(noExt);
142
146
  map.add(basename(rel, '.md'));
147
+ // Dir-relative alias: drop the leading scan-dir segment so the vault's
148
+ // convention link [[learnings/foo]] resolves to pages/learnings/foo.md.
149
+ // (A page directly under a scan dir has no extra segment to drop.)
150
+ const slash = noExt.indexOf('/');
151
+ if (slash !== -1) map.add(noExt.slice(slash + 1));
143
152
  }
153
+ for (const t of extraTargets) map.add(t);
144
154
  return map;
145
155
  }
146
156
 
157
+ // Link-target-only slugs: files that are valid wikilink destinations but are
158
+ // NOT linted themselves. Root-level *.md (hot.md / log.md / hypo-guide.md /
159
+ // SCHEMA.md — special operational files whose types sit outside VALID_TYPES)
160
+ // and sources/* (immutable captured sources). Returned as verbatim slugs so
161
+ // buildSlugMap adds no derived aliases for them.
162
+ function collectLinkTargets(hypoDir, ignorePatterns = []) {
163
+ const targets = [];
164
+ if (existsSync(hypoDir)) {
165
+ for (const entry of readdirSync(hypoDir)) {
166
+ const full = join(hypoDir, entry);
167
+ // root-level *.md FILES only (no recursion), honoring .hypoignore exactly
168
+ // like collectPages — otherwise an ignored root file (e.g. a secret) would
169
+ // resolve [[its-slug]] as valid, a false negative.
170
+ if (
171
+ extname(entry) === '.md' &&
172
+ !entry.startsWith('.') &&
173
+ !isIgnored(full, hypoDir, ignorePatterns) &&
174
+ statSync(full).isFile()
175
+ ) {
176
+ targets.push(entry.replace(/\.md$/, ''));
177
+ }
178
+ }
179
+ }
180
+ // sources/*: linkable as the full 'sources/<name>' slug only — deliberately no
181
+ // bare basename, so a stale [[name]] can't silently resolve to a source file.
182
+ for (const { rel } of collectPages(join(hypoDir, 'sources'), hypoDir, [], ignorePatterns)) {
183
+ targets.push(rel.replace(/\.md$/, '').replace(/\\/g, '/'));
184
+ }
185
+ return targets;
186
+ }
187
+
147
188
  // ── wikilink extractor ────────────────────────────────────────────────────────
148
189
 
149
190
  // Strip regions where wikilinks are not real references:
@@ -168,7 +209,12 @@ function stripNonWikilinkRegions(content) {
168
209
  function extractWikilinks(content) {
169
210
  const stripped = stripNonWikilinkRegions(content);
170
211
  const links = [];
171
- for (const m of stripped.matchAll(/\[\[([^\]|#]+?)(?:[|#][^\]]*?)?\]\]/g)) {
212
+ // Target capture excludes `\` and stops before an optional `\` preceding the
213
+ // alias/anchor delimiter, so a markdown-table-escaped alias
214
+ // `[[a/b\|label]]` (the `\|` is a literal pipe inside a table cell) yields the
215
+ // clean target `a/b`, not `a/b\`. A bare `[[a\]]` (no delimiter) simply fails
216
+ // to match rather than being mis-read as `[[a]]`.
217
+ for (const m of stripped.matchAll(/\[\[([^\]|#\\]+?)(?:\\?[|#][^\]]*?)?\]\]/g)) {
172
218
  links.push(m[1].trim());
173
219
  }
174
220
  return links;
@@ -367,7 +413,8 @@ const args = parseArgs(process.argv);
367
413
  const ignorePatterns = loadHypoIgnore(args.hypoDir);
368
414
  const scanDirs = ['pages', 'projects', 'journal'].map((d) => join(args.hypoDir, d));
369
415
  const pages = scanDirs.flatMap((d) => collectPages(d, args.hypoDir, [], ignorePatterns));
370
- const slugMap = buildSlugMap(pages);
416
+ const linkTargets = collectLinkTargets(args.hypoDir, ignorePatterns);
417
+ const slugMap = buildSlugMap(pages, linkTargets);
371
418
  const tagVocab = parseSchemaVocab(args.hypoDir);
372
419
  const pageDirs = parseSchemaPageDirs(args.hypoDir);
373
420
 
@@ -382,7 +429,7 @@ for (const s of findDesignHistoryStale(args.hypoDir)) {
382
429
  issue(
383
430
  'warn',
384
431
  `projects/${s.project}/design-history.md`,
385
- `design-history stale: session-log 최신=${s.lastSession} > design-history 최신=${s.lastDesignHistory}${gap} — projects/${s.project}/design-history.md에 설계 변경 사항을 append 하세요`,
432
+ `design-history stale: session-log 설계-관련 최신=${s.lastSession} > design-history 최신=${s.lastDesignHistory}${gap} — projects/${s.project}/design-history.md에 설계 변경 사항을 append 하거나, 무-설계 세션이면 session-log 엔트리에 "ADR 없음" 마커를 명시하세요`,
386
433
  null,
387
434
  'W8',
388
435
  );
@@ -474,4 +521,12 @@ if (args.json) {
474
521
  }
475
522
  }
476
523
 
477
- process.exit(errors.length > 0 ? 1 : 0);
524
+ // Set exitCode and let Node exit naturally rather than calling process.exit():
525
+ // when stdout is a pipe, the JSON write above is async, and a synchronous
526
+ // process.exit() tears the process down before the OS pipe buffer (64 KiB) is
527
+ // drained — truncating large `--json` output mid-string. Consumers that spawn
528
+ // this script and JSON.parse the stdout (crystallize's runLint, the PreCompact
529
+ // gate) then crash on the partial output. There are no pending async handles
530
+ // here (pure synchronous fs), so the event loop empties at end-of-script and
531
+ // Node flushes stdout fully before exiting with this code.
532
+ process.exitCode = errors.length > 0 ? 1 : 0;