hypomnema 1.8.0 → 1.8.2
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 +46 -0
- package/README.ko.md +8 -6
- package/README.md +8 -6
- package/commands/crystallize.md +32 -6
- package/commands/graph.md +7 -4
- package/commands/lint.md +8 -1
- package/commands/query.md +6 -4
- package/commands/resume.md +1 -0
- package/commands/verify.md +12 -1
- package/docs/ARCHITECTURE.md +26 -12
- package/docs/CONTRIBUTING.md +15 -6
- package/hooks/hypo-compact-guard.mjs +126 -23
- package/hooks/hypo-cwd-change.mjs +20 -19
- package/hooks/hypo-first-prompt.mjs +31 -16
- package/hooks/hypo-lookup.mjs +10 -5
- package/hooks/hypo-session-start.mjs +180 -2
- package/hooks/hypo-shared.mjs +410 -83
- package/hooks/hypo-web-fetch-ingest.mjs +9 -13
- package/package.json +2 -1
- package/scripts/doctor.mjs +32 -3
- package/scripts/graph.mjs +22 -2
- package/scripts/init.mjs +5 -1
- package/scripts/lib/crystallize-args.mjs +38 -2
- package/scripts/lib/crystallize-close-apply.mjs +745 -450
- package/scripts/lint.mjs +242 -39
- package/scripts/query.mjs +22 -2
- package/scripts/resume.mjs +177 -2
- package/scripts/upgrade.mjs +2 -2
- package/scripts/verify.mjs +22 -2
- package/templates/SCHEMA.md +23 -1
- package/templates/hypo-automation.md +4 -2
- package/templates/hypo-config.md +1 -1
- package/templates/hypo-guide.md +1 -1
- package/skills/crystallize/SKILL.md +0 -189
- package/skills/graph/SKILL.md +0 -58
- package/skills/ingest/SKILL.md +0 -107
- package/skills/lint/SKILL.md +0 -59
- package/skills/query/SKILL.md +0 -62
- package/skills/verify/SKILL.md +0 -96
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
* graph before adding a new source.
|
|
16
16
|
*
|
|
17
17
|
* Output contract — Claude Code docs, "Add context for Claude":
|
|
18
|
-
* PostToolUse uses **nested** hookSpecificOutput.additionalContext,
|
|
19
|
-
*
|
|
20
|
-
* buildOutput() in hypo-shared.mjs
|
|
21
|
-
*
|
|
22
|
-
*
|
|
18
|
+
* PostToolUse uses **nested** hookSpecificOutput.additionalContext, the
|
|
19
|
+
* same shape every other injecting event in this codebase now uses.
|
|
20
|
+
* buildOutput() in hypo-shared.mjs emits that nested shape given the
|
|
21
|
+
* hookEventName, so this hook calls it too instead of building the
|
|
22
|
+
* object by hand.
|
|
23
23
|
*
|
|
24
24
|
* URL redaction:
|
|
25
25
|
* additionalContext lands in the transcript. Query strings and
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
* in-hook failure branch is needed.
|
|
42
42
|
*/
|
|
43
43
|
|
|
44
|
-
import { isGateSkipped } from './hypo-shared.mjs';
|
|
44
|
+
import { isGateSkipped, buildOutput } from './hypo-shared.mjs';
|
|
45
45
|
|
|
46
46
|
let input = {};
|
|
47
47
|
try {
|
|
@@ -58,13 +58,9 @@ try {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
const context = buildContext(input);
|
|
61
|
-
const output =
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
hookEventName: 'PostToolUse',
|
|
65
|
-
additionalContext: context,
|
|
66
|
-
};
|
|
67
|
-
}
|
|
61
|
+
const output = context
|
|
62
|
+
? buildOutput('PostToolUse', context, { continue: true, suppressOutput: true })
|
|
63
|
+
: { continue: true, suppressOutput: true };
|
|
68
64
|
console.log(JSON.stringify(output));
|
|
69
65
|
|
|
70
66
|
function buildContext(data) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hypomnema",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.2",
|
|
4
4
|
"description": "LLM-native personal wiki system for Claude Code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -87,6 +87,7 @@
|
|
|
87
87
|
"fix:verify": "node scripts/fix-status-verify.mjs",
|
|
88
88
|
"graph": "node scripts/graph.mjs",
|
|
89
89
|
"smoke-pack": "node scripts/smoke-pack.mjs",
|
|
90
|
+
"check:release-vehicle": "node scripts/check-release-vehicle.mjs",
|
|
90
91
|
"check:versions": "node scripts/check-versions.mjs",
|
|
91
92
|
"smoke:plugin": "node scripts/smoke-plugin.mjs",
|
|
92
93
|
"check:bilingual": "node scripts/check-bilingual.mjs --changelog",
|
package/scripts/doctor.mjs
CHANGED
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
collectProjectWorkingDirs,
|
|
35
35
|
detectSessionCloseArtifact,
|
|
36
36
|
localAndUtcDates,
|
|
37
|
+
normalizeVerifiedScope,
|
|
37
38
|
SESSION_CLOSED_MARKER_STALE_MS,
|
|
38
39
|
isUsablePkgRootLocal,
|
|
39
40
|
selfLocationPkgRootFrom,
|
|
@@ -916,14 +917,32 @@ function deriveCommitProjects(hypoDir, hash) {
|
|
|
916
917
|
// an artifact with no verifiable scope, so this never matches, ever.
|
|
917
918
|
// • 'projects' — a project file, or a commit that touched one or more
|
|
918
919
|
// identifiable projects/<slug>/ paths. EVERY named project must appear
|
|
919
|
-
// in the marker's own `projects` list
|
|
920
|
+
// in the marker's own `projects` list, AND, when the marker carries a
|
|
921
|
+
// `verified_scope` (session-close-scope-boundary spec §3), in that
|
|
922
|
+
// scope's own `projects` too — an AND, never an OR. `projects` is
|
|
923
|
+
// evidence-based attribution; `verified_scope` is what the gate that
|
|
924
|
+
// wrote this marker actually checked, and the two can diverge (an
|
|
925
|
+
// unnarrowed gate run can attribute to more projects than it verified).
|
|
926
|
+
// A `verified_scope` of kind 'log-only' verified no project at all, so it
|
|
927
|
+
// can never cover a 'projects'-scoped artifact. A marker with NO
|
|
928
|
+
// `verified_scope` (every marker before this field existed) skips this
|
|
929
|
+
// extra check entirely — it neither tightens nor loosens the membership
|
|
930
|
+
// check above, so old markers read back byte-for-byte as before.
|
|
920
931
|
function markerCoversArtifact(marker, artifact) {
|
|
921
932
|
if (!marker.dates.includes(artifact.date)) return false;
|
|
922
933
|
switch (artifact.scope.kind) {
|
|
923
934
|
case 'root-universal':
|
|
924
935
|
return true;
|
|
925
936
|
case 'projects':
|
|
926
|
-
|
|
937
|
+
if (!artifact.scope.projects.every((p) => marker.projects.includes(p))) return false;
|
|
938
|
+
if (!marker.verifiedScope) return true;
|
|
939
|
+
// Unreachable for a 'projects' artifact today: a log-only close writes
|
|
940
|
+
// `projects: []`, so the membership check above already returned false.
|
|
941
|
+
// Kept deliberately, not by oversight — it states the kind's meaning
|
|
942
|
+
// (log-only verified no project at all) so this stays correct if
|
|
943
|
+
// `projects` ever carries something for a log-only close.
|
|
944
|
+
if (marker.verifiedScope.kind === 'log-only') return false;
|
|
945
|
+
return artifact.scope.projects.every((p) => marker.verifiedScope.projects.includes(p));
|
|
927
946
|
default:
|
|
928
947
|
return false; // 'unscoped'
|
|
929
948
|
}
|
|
@@ -1035,7 +1054,17 @@ function checkSessionCloseArtifacts(hypoDir) {
|
|
|
1035
1054
|
// and doctor has no corroborating signal of its own to add, so it
|
|
1036
1055
|
// must refuse it too rather than re-opening the same hole standalone.
|
|
1037
1056
|
const projects = Array.isArray(data?.projects) ? data.projects.filter(Boolean) : [];
|
|
1038
|
-
|
|
1057
|
+
// verified_scope (session-close-scope-boundary spec §3): the SAME
|
|
1058
|
+
// collapse the writer runs (hooks/hypo-shared.mjs), not a hand-rolled
|
|
1059
|
+
// copy — a shape this reader doesn't recognize, or a 'project'/'global'
|
|
1060
|
+
// scope with an empty `projects`, reads back as "field absent" (`null`),
|
|
1061
|
+
// never as a false claim.
|
|
1062
|
+
const verifiedScope = normalizeVerifiedScope(data?.verified_scope);
|
|
1063
|
+
markers.push({
|
|
1064
|
+
projects: [...new Set(projects)],
|
|
1065
|
+
dates: localAndUtcDates(new Date(ts)),
|
|
1066
|
+
verifiedScope,
|
|
1067
|
+
});
|
|
1039
1068
|
} catch {
|
|
1040
1069
|
// corrupt marker — not this check's job to clean up
|
|
1041
1070
|
}
|
package/scripts/graph.mjs
CHANGED
|
@@ -22,12 +22,32 @@ import { collectPagesGraph, extractWikilinks } from './lib/wikilink.mjs';
|
|
|
22
22
|
|
|
23
23
|
// ── arg parsing ───────────────────────────────────────────────────────────────
|
|
24
24
|
|
|
25
|
+
const ALLOWED_FLAGS = ['--hypo-dir=<path>', '--format=<fmt>', '--min-edges=<n>'];
|
|
26
|
+
|
|
25
27
|
function parseArgs(argv) {
|
|
26
28
|
const args = { hypoDir: null, format: 'json', minEdges: 0 };
|
|
27
29
|
for (const arg of argv.slice(2)) {
|
|
28
|
-
if (arg.startsWith('--hypo-dir='))
|
|
29
|
-
|
|
30
|
+
if (arg.startsWith('--hypo-dir=')) {
|
|
31
|
+
// See lint.mjs's parseArgs for why an empty raw value (--hypo-dir=,
|
|
32
|
+
// or --hypo-dir="$VAULT" with VAULT unset) is rejected instead of
|
|
33
|
+
// falling through to the default-resolution branch below.
|
|
34
|
+
const raw = arg.slice(11);
|
|
35
|
+
if (!raw) {
|
|
36
|
+
console.error(`graph.mjs: --hypo-dir requires a non-empty value (got '${arg}')`);
|
|
37
|
+
console.error(`graph.mjs accepts: ${ALLOWED_FLAGS.join(', ')}`);
|
|
38
|
+
process.exit(2);
|
|
39
|
+
}
|
|
40
|
+
args.hypoDir = expandHome(raw);
|
|
41
|
+
} else if (arg.startsWith('--format=')) args.format = arg.slice(9);
|
|
30
42
|
else if (arg.startsWith('--min-edges=')) args.minEdges = parseInt(arg.slice(12), 10) || 0;
|
|
43
|
+
else {
|
|
44
|
+
// No positional arguments here either, so an unmatched arg is rejected
|
|
45
|
+
// rather than dropped. See lint.mjs's parseArgs for the background
|
|
46
|
+
// this closes.
|
|
47
|
+
console.error(`graph.mjs: unrecognized argument '${arg}'`);
|
|
48
|
+
console.error(`graph.mjs accepts: ${ALLOWED_FLAGS.join(', ')}`);
|
|
49
|
+
process.exit(2);
|
|
50
|
+
}
|
|
31
51
|
}
|
|
32
52
|
if (!args.hypoDir) {
|
|
33
53
|
const info = resolveHypoRootInfo();
|
package/scripts/init.mjs
CHANGED
|
@@ -173,7 +173,11 @@ Init options:
|
|
|
173
173
|
--lint-strict Opt-in: also gate the wiki pre-commit hook on
|
|
174
174
|
\`lint --strict\` (promotes STRICT_PROMOTE_IDS warnings
|
|
175
175
|
to a blocking error), sequenced after the .hypoignore
|
|
176
|
-
guard.
|
|
176
|
+
guard. Exception: root hot.md/log.md's own
|
|
177
|
+
no-frontmatter warning stays a warn even under this
|
|
178
|
+
flag, so a legacy vault predating the frontmatter
|
|
179
|
+
convention still commits. Off by default; re-run init
|
|
180
|
+
to add or drop it.
|
|
177
181
|
--dry-run Show what would be done without making changes
|
|
178
182
|
--version Print the installed package version and exit
|
|
179
183
|
--help, -h Show this help message
|
|
@@ -3,6 +3,22 @@ import { isValidProjectName } from './project-create.mjs';
|
|
|
3
3
|
|
|
4
4
|
// ── arg parsing ──────────────────────────────────────────────────────────────
|
|
5
5
|
|
|
6
|
+
const ALLOWED_FLAGS = [
|
|
7
|
+
'--hypo-dir=<path>',
|
|
8
|
+
'--min-group=<n>',
|
|
9
|
+
'--check-session-close',
|
|
10
|
+
'--apply-session-close',
|
|
11
|
+
'--mark-session-closed',
|
|
12
|
+
'--log-only',
|
|
13
|
+
'--session-id=<id>',
|
|
14
|
+
'--payload=<path|->',
|
|
15
|
+
'--transcript-path=<path>',
|
|
16
|
+
'--session-cwd=<path>',
|
|
17
|
+
'--project=<slug>',
|
|
18
|
+
'--force',
|
|
19
|
+
'--json',
|
|
20
|
+
];
|
|
21
|
+
|
|
6
22
|
export function parseArgs(argv) {
|
|
7
23
|
const args = {
|
|
8
24
|
hypoDir: null,
|
|
@@ -19,8 +35,20 @@ export function parseArgs(argv) {
|
|
|
19
35
|
project: null,
|
|
20
36
|
};
|
|
21
37
|
for (const arg of argv.slice(2)) {
|
|
22
|
-
if (arg.startsWith('--hypo-dir='))
|
|
23
|
-
|
|
38
|
+
if (arg.startsWith('--hypo-dir=')) {
|
|
39
|
+
// See scripts/lint.mjs's parseArgs for why an empty raw value
|
|
40
|
+
// (--hypo-dir=, or --hypo-dir="$VAULT" with VAULT unset) is rejected
|
|
41
|
+
// instead of falling through to resolveHypoRoot() below. For this
|
|
42
|
+
// script specifically, a silent fallback here means a session-close
|
|
43
|
+
// apply writes to the wrong vault.
|
|
44
|
+
const raw = arg.slice(11);
|
|
45
|
+
if (!raw) {
|
|
46
|
+
console.error(`crystallize.mjs: --hypo-dir requires a non-empty value (got '${arg}')`);
|
|
47
|
+
console.error(`crystallize.mjs accepts: ${ALLOWED_FLAGS.join(', ')}`);
|
|
48
|
+
process.exit(2);
|
|
49
|
+
}
|
|
50
|
+
args.hypoDir = expandHome(raw);
|
|
51
|
+
} else if (arg.startsWith('--min-group=')) args.minGroup = parseInt(arg.slice(12), 10) || 2;
|
|
24
52
|
else if (arg === '--check-session-close') args.checkSessionClose = true;
|
|
25
53
|
else if (arg === '--apply-session-close') args.applySessionClose = true;
|
|
26
54
|
else if (arg === '--mark-session-closed') args.markSessionClosed = true;
|
|
@@ -32,6 +60,14 @@ export function parseArgs(argv) {
|
|
|
32
60
|
else if (arg.startsWith('--project=')) args.project = arg.slice(10);
|
|
33
61
|
else if (arg === '--force') args.force = true;
|
|
34
62
|
else if (arg === '--json') args.json = true;
|
|
63
|
+
else {
|
|
64
|
+
// No positional arguments here either, so an unmatched arg is rejected
|
|
65
|
+
// rather than dropped. See scripts/lint.mjs's parseArgs for the
|
|
66
|
+
// background this closes.
|
|
67
|
+
console.error(`crystallize.mjs: unrecognized argument '${arg}'`);
|
|
68
|
+
console.error(`crystallize.mjs accepts: ${ALLOWED_FLAGS.join(', ')}`);
|
|
69
|
+
process.exit(2);
|
|
70
|
+
}
|
|
35
71
|
}
|
|
36
72
|
if (!args.hypoDir) args.hypoDir = resolveHypoRoot();
|
|
37
73
|
// --project=<slug> override (check/mark only). Validate the SYNTAX here so a
|