hypomnema 1.8.2 → 1.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +141 -51
- package/README.ko.md +18 -17
- package/README.md +16 -15
- package/commands/crystallize.md +89 -24
- package/commands/doctor.md +3 -1
- package/docs/ARCHITECTURE.md +45 -14
- package/docs/CONTRIBUTING.md +26 -8
- package/hooks/atomic-write.mjs +44 -0
- package/hooks/base-store.mjs +409 -90
- package/hooks/close-gate-store.mjs +131 -16
- package/hooks/close-journal.mjs +250 -0
- package/hooks/close-receipt.mjs +559 -0
- package/hooks/hooks.json +3 -38
- package/hooks/hypo-auto-commit.mjs +80 -10
- package/hooks/hypo-auto-minimal-crystallize.mjs +129 -25
- package/hooks/hypo-auto-stage.mjs +13 -1
- package/hooks/hypo-close-guard.mjs +2 -1
- package/hooks/hypo-cwd-change.mjs +45 -20
- package/hooks/hypo-file-watch.mjs +44 -6
- package/hooks/hypo-first-prompt.mjs +15 -9
- package/hooks/hypo-hot-rebuild.mjs +169 -112
- package/hooks/hypo-personal-check.mjs +9 -1
- package/hooks/hypo-session-record.mjs +1 -1
- package/hooks/hypo-session-start.mjs +566 -20
- package/hooks/hypo-shared.mjs +2250 -206
- package/hooks/hypo-stop.mjs +207 -0
- package/hooks/proposal-store.mjs +201 -35
- package/hooks/shared.json +15 -0
- package/hooks/version-check.mjs +9 -7
- package/package.json +3 -1
- package/scripts/crystallize.mjs +3 -4
- package/scripts/doctor.mjs +549 -123
- package/scripts/init.mjs +86 -109
- package/scripts/lib/core-hooks.mjs +48 -22
- package/scripts/lib/crystallize-close-apply.mjs +2397 -188
- package/scripts/lib/crystallize-close-check.mjs +43 -23
- package/scripts/lib/design-history-stale.mjs +36 -9
- package/scripts/lib/frontmatter.mjs +39 -0
- package/scripts/lib/git-hooks-dir.mjs +427 -52
- package/scripts/lib/hook-inventory.mjs +180 -0
- package/scripts/lib/page-usage.mjs +1 -1
- package/scripts/lib/pkg-provenance.mjs +18 -1
- package/scripts/lib/plugin-detect.mjs +43 -9
- package/scripts/lib/project-create.mjs +40 -44
- package/scripts/lib/schema-vocab.mjs +22 -11
- package/scripts/lib/template-schema-version.mjs +48 -0
- package/scripts/lib/time.mjs +46 -0
- package/scripts/lint.mjs +66 -18
- package/scripts/proposal.mjs +423 -18
- package/scripts/resume.mjs +3 -2
- package/scripts/session-audit.mjs +2 -1
- package/scripts/uninstall.mjs +144 -66
- package/scripts/upgrade.mjs +523 -234
- package/scripts/verify.mjs +2 -1
- package/scripts/weekly-report.mjs +2 -1
- package/templates/SCHEMA.md +27 -7
- package/templates/gitignore +11 -0
- package/templates/hypo-automation.md +10 -13
- package/templates/hypo-config.md +1 -1
- package/templates/hypo-guide.md +53 -25
- package/templates/hot.md +0 -24
package/scripts/lint.mjs
CHANGED
|
@@ -33,8 +33,13 @@ import { findDesignHistoryStale } from './lib/design-history-stale.mjs';
|
|
|
33
33
|
import { FEEDBACK_SCOPE_RE } from './lib/feedback-scope.mjs';
|
|
34
34
|
import { FAILURE_TYPE_ENUM } from './lib/failure-type.mjs';
|
|
35
35
|
import { collectPagesLint, collectPagesLinkable, slugForms } from './lib/wikilink.mjs';
|
|
36
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
parseFrontmatter,
|
|
38
|
+
SEQUENCE_ENTRY_RE,
|
|
39
|
+
hasBlockListSourcesConsulted,
|
|
40
|
+
} from './lib/frontmatter.mjs';
|
|
37
41
|
import { buildSlugMap } from './lib/slug-resolver.mjs';
|
|
42
|
+
import { parseStrictDate } from './lib/time.mjs';
|
|
38
43
|
|
|
39
44
|
// ── arg parsing ──────────────────────────────────────────────────────────────
|
|
40
45
|
|
|
@@ -322,8 +327,11 @@ const issues = [];
|
|
|
322
327
|
// Dates are compared as strings, which is only the same as comparing time
|
|
323
328
|
// when every value is zero-padded YYYY-MM-DD. `2026-3-1 > 2026-12-15` is true
|
|
324
329
|
// lexically and false in fact, so anything off this shape is excluded from the
|
|
325
|
-
// comparison and reported instead of silently deciding the verdict.
|
|
326
|
-
|
|
330
|
+
// comparison and reported instead of silently deciding the verdict. A format
|
|
331
|
+
// check alone is not enough either: `new Date('2026-02-30')` normalizes to
|
|
332
|
+
// March 2 instead of failing, so a calendar-overflow literal used to read as
|
|
333
|
+
// validated. `parseStrictDate` (lib/time.mjs) is used below wherever a date
|
|
334
|
+
// here is judged usable, and rejects that case too.
|
|
327
335
|
const bySlugForm = new Map();
|
|
328
336
|
const synthesisPages = [];
|
|
329
337
|
|
|
@@ -465,9 +473,41 @@ function lintPage({ path, rel }, slugMap, tagVocab, pageDirs, validTypes) {
|
|
|
465
473
|
else bySlugForm.set(form, { updated: fm.updated || null, count: 1 });
|
|
466
474
|
}
|
|
467
475
|
}
|
|
468
|
-
if (fm.type === 'synthesis'
|
|
469
|
-
|
|
470
|
-
|
|
476
|
+
if (fm.type === 'synthesis') {
|
|
477
|
+
// The synthesis's own `updated` is present but fails the strict parse
|
|
478
|
+
// (wrong shape, or calendar overflow like `2026-02-30`). Checked here,
|
|
479
|
+
// per page, rather than in the sources_consulted comparison loop below:
|
|
480
|
+
// that loop only sees pages whose sources parsed, so a synthesis with a
|
|
481
|
+
// block-list sources_consulted, or with none at all, would never be
|
|
482
|
+
// reported. Without this, such a date drops out of W15 with no warning
|
|
483
|
+
// naming why. A missing `updated` is W3's alone and is not repeated here.
|
|
484
|
+
if (fm.updated && parseStrictDate(fm.updated) == null) {
|
|
485
|
+
issue(
|
|
486
|
+
'warn',
|
|
487
|
+
rel,
|
|
488
|
+
`synthesis 자신의 updated: "${fm.updated}"가 유효한 YYYY-MM-DD 날짜가 아니어서 sources_consulted 비교를 건너뜁니다`,
|
|
489
|
+
null,
|
|
490
|
+
'W16',
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
// sources_consulted written as a YAML block list (`- item` on its own
|
|
494
|
+
// line) parses to an empty value under the line-scanner above, which only
|
|
495
|
+
// reads unindented `key: value` lines: indistinguishable from the field
|
|
496
|
+
// never being set. Detect that shape from the raw block first, so "wrote
|
|
497
|
+
// it, wrong syntax" gets its own W16 instead of silently comparing zero
|
|
498
|
+
// sources and saying nothing.
|
|
499
|
+
if (hasBlockListSourcesConsulted(fmBlock ? fmBlock[1] : '')) {
|
|
500
|
+
issue(
|
|
501
|
+
'warn',
|
|
502
|
+
rel,
|
|
503
|
+
'sources_consulted가 YAML 블록 목록으로 쓰여 있어 인식되지 않습니다. 한 줄 흐름 목록으로 쓰세요: sources_consulted: [a, b]',
|
|
504
|
+
null,
|
|
505
|
+
'W16',
|
|
506
|
+
);
|
|
507
|
+
} else if (fm.sources_consulted) {
|
|
508
|
+
const sources = parseTagsField(fm.sources_consulted) || [];
|
|
509
|
+
if (sources.length > 0) synthesisPages.push({ rel, updated: fm.updated, sources });
|
|
510
|
+
}
|
|
471
511
|
}
|
|
472
512
|
|
|
473
513
|
// type-conditional required fields
|
|
@@ -777,13 +817,14 @@ for (const { rel, updated, sources } of synthesisPages) {
|
|
|
777
817
|
unresolved.push(`${source} (${hit.count}개 페이지가 같은 이름)`);
|
|
778
818
|
continue;
|
|
779
819
|
}
|
|
780
|
-
if (
|
|
781
|
-
unresolved.push(`${source} (updated
|
|
820
|
+
if (parseStrictDate(hit.updated) == null) {
|
|
821
|
+
unresolved.push(`${source} (updated 없음, 또는 YYYY-MM-DD 형식의 실제 날짜가 아님)`);
|
|
782
822
|
continue;
|
|
783
823
|
}
|
|
784
824
|
if (!newest || hit.updated > newest) newest = hit.updated;
|
|
785
825
|
}
|
|
786
|
-
|
|
826
|
+
// An unusable own `updated` was already reported per page in lintPage.
|
|
827
|
+
const ownDateUsable = parseStrictDate(updated) != null;
|
|
787
828
|
if (newest && ownDateUsable && newest > updated) {
|
|
788
829
|
issue(
|
|
789
830
|
'warn',
|
|
@@ -793,15 +834,22 @@ for (const { rel, updated, sources } of synthesisPages) {
|
|
|
793
834
|
'W15',
|
|
794
835
|
);
|
|
795
836
|
}
|
|
796
|
-
// W16: sources W15 could not compare,
|
|
797
|
-
// for each.
|
|
798
|
-
//
|
|
799
|
-
//
|
|
800
|
-
//
|
|
801
|
-
//
|
|
802
|
-
//
|
|
803
|
-
//
|
|
804
|
-
//
|
|
837
|
+
// W16 (this emit site): sources W15 could not compare, joined into one warn
|
|
838
|
+
// per page naming the reason for each. This is one of three shapes W16 now
|
|
839
|
+
// covers, not the only one: a block-list sources_consulted
|
|
840
|
+
// (hasBlockListSourcesConsulted, above) and a synthesis's own `updated`
|
|
841
|
+
// that fails the strict date parse (checked per page in lintPage) are separate
|
|
842
|
+
// emit sites, so a single page can carry more than one W16 warning at once
|
|
843
|
+
// (an unparseable own `updated` alongside an unresolved source, for
|
|
844
|
+
// example). Without this particular emit, the rule fails open, and
|
|
845
|
+
// silently: a page whose sources mostly do not resolve still gets a W15
|
|
846
|
+
// (or no warning at all) with nothing saying how much of the evidence was
|
|
847
|
+
// actually read. The reasons are kept apart because the fix differs: a
|
|
848
|
+
// name that resolves to nothing is a typo or a value this field is not
|
|
849
|
+
// meant to hold, a name claimed by two pages needs the longer form, and a
|
|
850
|
+
// source with no usable `updated` is W3's problem on that page. Excluded
|
|
851
|
+
// from STRICT_PROMOTE_IDS and given its own id for the same reasons as
|
|
852
|
+
// W15.
|
|
805
853
|
if (unresolved.length > 0) {
|
|
806
854
|
issue(
|
|
807
855
|
'warn',
|