@journeykit/canon 0.7.0 → 0.8.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.
@@ -0,0 +1,126 @@
1
+ #!/bin/sh
2
+ # canon-retrieve-pre-dispatch.sh — S8's generic pre-dispatch hook
3
+ # (`s8-retrieve-before-task`, design.md decision 4, tasks.md task 3.1):
4
+ # fires at a PreToolUse-equivalent point, generalized to any
5
+ # `task`-shaped dispatch (Claude Code's `Task` tool / Codex's
6
+ # equivalent subagent-dispatch tool call), mirroring a donor
7
+ # harness's `pre-edit-pattern-lookup.ts` fail-soft, advisory-only
8
+ # PreToolUse contract (design.md Context) — applied here to
9
+ # role-scoped strategy retrieval instead of edit-pattern lookup.
10
+ #
11
+ # WIRING (design decision 4 — reuses S5's install-hooks mechanism
12
+ # verbatim, invents no second convention): install the standard
13
+ # `{matcher, hooks: [{type: "command", command, timeout}]}` entry via
14
+ # the ALREADY-GENERIC `canon gate install-hooks` (S5, `s5-trust-spine-
15
+ # gate`) — this script adds NO new installer code:
16
+ #
17
+ # canon gate install-hooks --repo . --event PreToolUse \
18
+ # --matcher Task --command "sh /path/to/pre-dispatch.sh" \
19
+ # --timeout 15
20
+ #
21
+ # Place this file wherever your repo keeps materialized hook scripts
22
+ # (e.g. `.claude/hooks/canon-retrieve-pre-dispatch.sh`, following the
23
+ # manual-placement convention for materialized hook scripts)
24
+ # and point `--command` at that path — same manual-
25
+ # placement discipline canon-gate's own `PRE_COMMIT_SCRIPT` documents
26
+ # ("install it directly as `.git/hooks/pre-commit`, or invoke it from
27
+ # a lefthook/husky `run:` line").
28
+ #
29
+ # FAIL-SOFT, ADVISORY-ONLY — this script NEVER blocks the dispatch and
30
+ # NEVER emits a `permissionDecision`; it only ever adds
31
+ # `additionalContext` (Claude Code's `PreToolUse` hook output
32
+ # contract) or stays silent. Every one of the following degrades to a
33
+ # SILENT no-op, never a nonzero exit:
34
+ # - `canon` (or `jq`) not on PATH.
35
+ # - the dispatched tool is not task-shaped (`tool_name != "Task"`) —
36
+ # re-checked here even though the install recipe above already
37
+ # scopes the hook via `--matcher Task`, so this script stays a
38
+ # safe no-op if installed with a broader/no matcher.
39
+ # - no role derivable from the tool input, or empty/malformed stdin.
40
+ # - `canon retrieve` returns empty guidance — it is ALREADY
41
+ # fail-soft (S8Core's `retrieve_guidance` never errors; canon-
42
+ # cli's own CLI boundary never exits nonzero for a store outage,
43
+ # `crates/canon-cli/src/retrieve.rs`'s own module doc) — this
44
+ # script never adds a SECOND failure mode on top of that.
45
+ #
46
+ # ROLE/REGIME DERIVATION (an explicit design.md Open Question this
47
+ # script resolves, pinned here): Claude Code's `Task` tool call
48
+ # carries a `subagent_type` field naming which specialist role is
49
+ # about to run — read directly as `--role` (kebab-cased). `--regime`'s
50
+ # `<role>/<repo>/<area>/<hash>` is NOT fully recoverable from a bare
51
+ # tool-call payload (S6's `area`/`hash` taxonomy is repo-specific, not
52
+ # something a generic script can infer) — this ships a CONSERVATIVE
53
+ # default (`area=general`, a stable hash of that area) a repo is
54
+ # expected to override once it adopts a real regime taxonomy:
55
+ # - `CANON_RETRIEVE_ROLE` overrides the auto-derived role.
56
+ # - `CANON_RETRIEVE_AREA` overrides the default `general` area.
57
+ # - `CANON_RETRIEVE_HASH` overrides the derived hash segment.
58
+ # - `CANON_RETRIEVE_REGIME` overrides the whole assembled
59
+ # `regime_key`, bypassing all of the above.
60
+ set -eu
61
+
62
+ command -v canon >/dev/null 2>&1 || exit 0
63
+ command -v jq >/dev/null 2>&1 || exit 0
64
+
65
+ INPUT="$(cat 2>/dev/null || true)"
66
+ [ -n "$INPUT" ] || exit 0
67
+
68
+ TOOL_NAME="$(printf '%s' "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null || true)"
69
+ [ "$TOOL_NAME" = "Task" ] || exit 0
70
+
71
+ # `slugify` is used ONLY for the ROLE segment: `RoleId`'s grammar is
72
+ # strict kebab (`[a-z0-9]+(-[a-z0-9]+)*`), and a kebab string is a
73
+ # fixed point of `regime_key`'s own segment canonicalizer, so the
74
+ # derived `--role` still equals the assembled regime_key's leading
75
+ # segment. The repo/area segments are NOT slugified here — they are
76
+ # canonicalized by `canon regime-key` below (the write-path serializer).
77
+ slugify() {
78
+ printf '%s' "$1" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9' '-' | sed -E 's/-+/-/g; s/^-//; s/-$//'
79
+ }
80
+
81
+ ROLE="${CANON_RETRIEVE_ROLE:-}"
82
+ if [ -z "$ROLE" ]; then
83
+ ROLE="$(printf '%s' "$INPUT" | jq -r '.tool_input.subagent_type // empty' 2>/dev/null || true)"
84
+ fi
85
+ ROLE="$(slugify "$ROLE")"
86
+ [ -n "$ROLE" ] || exit 0
87
+
88
+ REGIME="${CANON_RETRIEVE_REGIME:-}"
89
+ if [ -z "$REGIME" ]; then
90
+ REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
91
+ REPO_RAW="$(basename "$REPO_ROOT")"
92
+ AREA_RAW="${CANON_RETRIEVE_AREA:-general}"
93
+ HASH="${CANON_RETRIEVE_HASH:-}"
94
+ if [ -z "$HASH" ]; then
95
+ HASH="$(printf '%s' "$AREA_RAW" | sha256sum 2>/dev/null | cut -c1-12)"
96
+ [ -n "$HASH" ] || HASH="$(printf '%s' "$AREA_RAW" | shasum -a 256 2>/dev/null | cut -c1-12)"
97
+ fi
98
+ [ -n "$HASH" ] || exit 0
99
+ # Assemble the regime_key through canon's OWN serializer (`canon
100
+ # regime-key` -> `canon_model::ids::regime_key`), the identical
101
+ # normalizer the S4/S6/S14 WRITE path uses — never a second shell
102
+ # derivation. A prior local `slugify` of the repo segment mapped a
103
+ # written `my_repo` to a queried `my-repo`, so `retrieve_guidance`
104
+ # fail-softed to empty and silently missed the namespace. `--role`
105
+ # is the already-kebab ROLE (a fixed point of that canonicalizer, so
106
+ # it still equals the assembled key's leading segment). Malformed
107
+ # segments make `canon regime-key` exit nonzero -> `|| true` -> empty
108
+ # REGIME -> the silent no-op just below.
109
+ REGIME="$(canon regime-key --role "$ROLE" --repo "$REPO_RAW" --area "$AREA_RAW" --hash "$HASH" 2>/dev/null || true)"
110
+ [ -n "$REGIME" ] || exit 0
111
+ fi
112
+
113
+ GUIDANCE_JSON="$(canon retrieve --role "$ROLE" --regime "$REGIME" --json 2>/dev/null || true)"
114
+ [ -n "$GUIDANCE_JSON" ] || exit 0
115
+
116
+ COUNT="$(printf '%s' "$GUIDANCE_JSON" | jq 'length' 2>/dev/null || echo 0)"
117
+ [ "$COUNT" -gt 0 ] 2>/dev/null || exit 0
118
+
119
+ CONTEXT="$(printf '%s' "$GUIDANCE_JSON" | jq -r --arg role "$ROLE" '
120
+ (["[canon-retrieve] guidance for role " + $role + ":"]
121
+ + (map("- " + .title + ": " + .content))) | join("\n")
122
+ ' 2>/dev/null || true)"
123
+ [ -n "$CONTEXT" ] || exit 0
124
+
125
+ jq -n --arg ctx "$CONTEXT" '{hookSpecificOutput: {hookEventName: "PreToolUse", additionalContext: $ctx}}'
126
+ exit 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@journeykit/canon",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "canon — harness knowledge substrate. JS launcher: platform detection + native binary resolution.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -22,15 +22,16 @@
22
22
  "directory": "packages/cli"
23
23
  },
24
24
  "scripts": {
25
- "build": "bun build src/index.ts --outdir dist --target node --format esm",
25
+ "bundle-skills": "bun run scripts/bundle-skills.ts",
26
+ "build": "bun run bundle-skills && bun build src/index.ts --outdir dist --target node --format esm",
26
27
  "dev": "bun src/index.ts",
27
28
  "start": "node dist/index.js"
28
29
  },
29
30
  "dependencies": {},
30
31
  "optionalDependencies": {
31
- "@journeykit/canon-core-darwin-arm64": "0.7.0",
32
- "@journeykit/canon-core-linux-x64": "0.7.0",
33
- "@journeykit/canon-core-win32-x64": "0.7.0"
32
+ "@journeykit/canon-core-darwin-arm64": "0.8.0",
33
+ "@journeykit/canon-core-linux-x64": "0.8.0",
34
+ "@journeykit/canon-core-win32-x64": "0.8.0"
34
35
  },
35
36
  "devDependencies": {
36
37
  "@types/node": "^20.0.0",