@fenglimg/fabric-cli 2.0.0-rc.35 → 2.0.0-rc.37

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 (36) hide show
  1. package/LICENSE +21 -0
  2. package/dist/{chunk-XVS4F3P6.js → chunk-D25XJ4BC.js} +49 -5
  3. package/dist/{chunk-G2CIOLD4.js → chunk-WWNXR34K.js} +1 -16
  4. package/dist/{doctor-2FCRAWDZ.js → doctor-764NFF3X.js} +112 -16
  5. package/dist/index.js +7 -6
  6. package/dist/{install-HOTE5BPA.js → install-U7MGIJ2L.js} +50 -22
  7. package/dist/metrics-ACEQFPDU.js +122 -0
  8. package/dist/{uninstall-BIJ5GLEU.js → uninstall-MH7ZIB6M.js} +6 -18
  9. package/package.json +30 -4
  10. package/templates/hooks/cite-policy-evict.cjs +80 -91
  11. package/templates/hooks/configs/README.md +19 -0
  12. package/templates/hooks/configs/codex-hooks.json +3 -0
  13. package/templates/hooks/configs/cursor-hooks.json +2 -1
  14. package/templates/hooks/fabric-hint.cjs +146 -8
  15. package/templates/hooks/knowledge-hint-broad.cjs +65 -104
  16. package/templates/hooks/knowledge-hint-narrow.cjs +122 -5
  17. package/templates/hooks/lib/cite-line-parser.cjs +7 -1
  18. package/templates/hooks/lib/client-adapter.cjs +106 -0
  19. package/templates/hooks/lib/config-cache.cjs +107 -0
  20. package/templates/hooks/lib/state-store.cjs +84 -0
  21. package/templates/skills/fabric-archive/SKILL.md +29 -7
  22. package/templates/skills/fabric-archive/ref/dry-run-scope.md +1 -1
  23. package/templates/skills/fabric-archive/ref/i18n-policy.md +6 -0
  24. package/templates/skills/fabric-archive/ref/phase-1-5-onboard.md +1 -1
  25. package/templates/skills/fabric-archive/ref/phase-1-cross-session.md +2 -0
  26. package/templates/skills/fabric-archive/ref/phase-2-5-viability.md +25 -11
  27. package/templates/skills/fabric-archive/ref/phase-3-5-scope.md +43 -15
  28. package/templates/skills/fabric-import/SKILL.md +75 -163
  29. package/templates/skills/fabric-import/ref/i18n-policy.md +6 -0
  30. package/templates/skills/fabric-import/ref/phase-2-mining.md +2 -2
  31. package/templates/skills/fabric-review/SKILL.md +31 -25
  32. package/templates/skills/fabric-review/ref/i18n-policy.md +6 -0
  33. package/templates/skills/fabric-review/ref/modify-flow.md +9 -1
  34. package/templates/skills/fabric-review/ref/per-mode-flows.md +1 -1
  35. package/templates/skills/lib/shared-policy.md +69 -0
  36. package/dist/serve-43JTEM3U.js +0 -142
@@ -0,0 +1,84 @@
1
+ /**
2
+ * v2.0.0-rc.37 NEW-19: shared `.fabric/.cache/` sidecar I/O for hook scripts.
3
+ *
4
+ * Hooks persist tiny per-session state (turn counters, last-emit timestamps,
5
+ * shown-hint sets) under `.fabric/.cache/`. Each hook had its own copy of the
6
+ * read-JSON-or-null / write-JSON-best-effort / read-text / write-text helpers
7
+ * (cite-policy-evict's readEvictState/writeEvictState, broad's
8
+ * readBroadLastEmit/writeBroadLastEmit, fabric-hint's shown-cache + edit-counter
9
+ * + maintenance-last-emit). This module is the single canonical implementation.
10
+ *
11
+ * Provides (all keyed on a bare `fileName` resolved under .fabric/.cache/):
12
+ * - cachePath(projectRoot, fileName) → absolute path
13
+ * - readJsonState(root, fileName, validate?) → parsed | null
14
+ * null on missing / parse error / validate() === false. Never throws.
15
+ * - writeJsonState(root, fileName, value) → boolean
16
+ * mkdir -p + write; false on failure. Never throws.
17
+ * - readTextState(root, fileName) → trimmed string | null
18
+ * - writeTextState(root, fileName, text) → boolean
19
+ *
20
+ * Never-throw contract: write failures return false (counter loss is
21
+ * acceptable — the hook never blocks user flow on sidecar I/O, KT-DEC-0007).
22
+ */
23
+
24
+ const { existsSync, mkdirSync, readFileSync, writeFileSync } = require("node:fs");
25
+ const { dirname, join } = require("node:path");
26
+
27
+ const CACHE_DIR_REL = join(".fabric", ".cache");
28
+
29
+ function cachePath(projectRoot, fileName) {
30
+ return join(projectRoot, CACHE_DIR_REL, fileName);
31
+ }
32
+
33
+ function readJsonState(projectRoot, fileName, validate) {
34
+ const path = cachePath(projectRoot, fileName);
35
+ if (!existsSync(path)) return null;
36
+ try {
37
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
38
+ if (typeof validate === "function" && !validate(parsed)) return null;
39
+ return parsed;
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+
45
+ function writeJsonState(projectRoot, fileName, value) {
46
+ const path = cachePath(projectRoot, fileName);
47
+ try {
48
+ mkdirSync(dirname(path), { recursive: true });
49
+ writeFileSync(path, JSON.stringify(value));
50
+ return true;
51
+ } catch {
52
+ return false;
53
+ }
54
+ }
55
+
56
+ function readTextState(projectRoot, fileName) {
57
+ const path = cachePath(projectRoot, fileName);
58
+ if (!existsSync(path)) return null;
59
+ try {
60
+ return readFileSync(path, "utf8").trim();
61
+ } catch {
62
+ return null;
63
+ }
64
+ }
65
+
66
+ function writeTextState(projectRoot, fileName, text) {
67
+ const path = cachePath(projectRoot, fileName);
68
+ try {
69
+ mkdirSync(dirname(path), { recursive: true });
70
+ writeFileSync(path, String(text));
71
+ return true;
72
+ } catch {
73
+ return false;
74
+ }
75
+ }
76
+
77
+ module.exports = {
78
+ cachePath,
79
+ readJsonState,
80
+ writeJsonState,
81
+ readTextState,
82
+ writeTextState,
83
+ CACHE_DIR_REL,
84
+ };
@@ -21,11 +21,17 @@ If none hold, stop the skill and tell the user (UX i18n Policy class 2):
21
21
 
22
22
  Render per `fabric_language` resolved in Phase 0.5.
23
23
 
24
- This skill is `Check-not-Ask`, not a preference interview. Phase 2 proactively gathers evidence; Phase 2.5 viability gate aborts if no archive signal exists; Phase 3 classifies / layers / slugs + batch-review; Phase 4 persists via `fab_extract_knowledge` (one call per candidate).
24
+ This skill runs automatically it does not interview the user for preferences. It gathers evidence, aborts if no archive signal exists, then classifies + persists.
25
25
 
26
26
  ## 执行流程 (1 User Review Round)
27
27
 
28
- Phase chain: `00.5 1 [1.5] 2 → 2.5 → 3 3.5 4 4.5`. Each phase below is a navigator stub full procedure, decision tables, and worked examples live in `ref/`.
28
+ v2.0.0-rc.37 NEW-9 collapsed the flow to **3 macro-phases**; the legacy fine-grained phases survive as labelled sub-steps (back-compat for `ref/` navigation + existing traces):
29
+
30
+ - **GATHER** = Phase 0 (range) → 0.5 (config) → 1 (ledger scan via `fab_archive_scan`) → [1.5 onboard] → 2 (candidates). Resolve scope, load config, deterministically scan the ledger for in-scope sessions, collect candidate observations.
31
+ - **REVIEW** = Phase 2.5 (viability gate — abort guard) → 3 (classify / layer / slug + batch review) → 3.5 (scope + relevance_paths). The single user review round lives here.
32
+ - **PERSIST** = Phase 4 (`fab_extract_knowledge`, one call per candidate) → 4.5 (archive-attempt ledger).
33
+
34
+ Sub-step chain: `0 → 0.5 → 1 → [1.5] → 2 → 2.5 → 3 → 3.5 → 4 → 4.5`. Each below is a navigator stub — full procedure, decision tables, and worked examples live in `ref/`.
29
35
 
30
36
  ### Phase 0 — Range Resolution
31
37
 
@@ -43,11 +49,19 @@ Read `fabric_language` (`zh-CN` / `en` / `zh-CN-hybrid` / `match-existing`); emi
43
49
 
44
50
  `Read ref/i18n-policy.md` for the full 5-class taxonomy + edge cases.
45
51
 
46
- ### Phase 1 — Collect Cross-Session Digests
52
+ ### Phase 1 — Collect Cross-Session Digests (server-side ledger scan, rc.37 NEW-9)
53
+
54
+ The deterministic ledger scan now runs **server-side** — call `fab_archive_scan({ range, session_id })` (range = Phase 0's `session_id[]` or `"all"`/omitted). It returns:
55
+
56
+ - `anchor_ts` — ts of the last `knowledge_proposed` (the lower bound).
57
+ - `session_ids[]` — distinct in-scope sessions since the anchor, ALREADY filtered through the outcome-ledger state machine (drops `user_dismissed`, sessions inside the 12h anti-loop cooldown, and watermarked sessions with no new high-value signal). First-seen order.
58
+ - `dropped[]` — `{session_id, reason}` for transparency.
59
+ - `covered_through_ts` — max ts examined (becomes the next watermark).
60
+ - `already_proposed_keys[]` — idempotency keys already proposed but not yet reviewed; drop matching candidates in Phase 3 (cross-session pending dedupe).
47
61
 
48
- Stitch context from every session accumulated since the last `knowledge_proposed` event. Tail-scan `.fabric/events.jsonl` → find anchor → forward-collect distinct `session_id`s load per-session digests from `.fabric/.cache/session-digests/<session_id>.md` concatenate into `### Cross-session digest` block, populating `source_sessions[]` + `session_context` for Phase 4. Cap at `archive_digest_max_sessions`.
62
+ Then (LLM side, Boundary B): for each returned `session_id`, load `.fabric/.cache/session-digests/<session_id>.md`, concatenate into a `### Cross-session digest` block, and populate `source_sessions[]` + `session_context` for Phase 4. Cap at `archive_digest_max_sessions`. Missing digest files degrade silently.
49
63
 
50
- `Read ref/phase-1-cross-session.md` for the Step 4.5 ledger filter state machine (rules a-f), `ANTI_LOOP_HOURS` / `HIGH_VALUE_EVENT_TYPES` / `NORMATIVE_KEYWORDS` constants, and 3 worked examples (user_dismissed / cooldown / re-scan-with-signal).
64
+ `Read ref/phase-1-cross-session.md` for the (now server-side) filter state machine rules + the digest-stitch + graceful-degradation notes. The hand-rolled `tail -n 200` events.jsonl scan is retired — `fab_archive_scan` is the source of truth for which sessions to load.
51
65
 
52
66
  Graceful degradation: missing digest cache → single-session fallback. Missing `session_archive_attempted` events (pre-rc.25) → legacy "scan everything since anchor" behaviour.
53
67
 
@@ -63,7 +77,15 @@ Gather raw evidence: tail `.fabric/events.jsonl` since last `knowledge_proposed`
63
77
 
64
78
  ### Phase 2.5 — Viability Gate (Anti-Archive Guard)
65
79
 
66
- Coarse viability check. **PASS conditions**: user_explicit_invoke OR ≥1 archive signal hit (normative language, wrong-turn-and-revert, long diagnostic loop, new dependency, new pattern, decision confirmation, dismissal-with-reason, process formalization). **FAIL branch by entry_point**: E1/E3/E5 silent-skip (emit Phase 4.5 event `outcome='skipped_no_signal'`); E2/E4 render gate-FAIL message (emit `outcome='viability_failed'`).
80
+ Coarse viability check. **PASS conditions**: user_explicit_invoke OR ≥1 archive signal hit. v2.0.0-rc.37 NEW-4 simplifies the legacy 8-signal list into **3 major categories** (each maps to multiple legacy signals for back-compat scoring):
81
+
82
+ 1. **User-driven knowledge expression** — user message contains normative language (`always`/`never`/`from now on`/`下次注意`/`记一下`/`以后`/`永远不要`), OR user weighed ≥2 alternatives and gave rationale (decision confirmation), OR user explicitly dismissed an approach AND stated why (dismissal-with-reason). Legacy signals #1 + #6 + #7.
83
+ 2. **Reflective discovery** — AI tried path X, reflected, then took path Y (wrong-turn-and-revert); OR a long diagnostic loop (>15 min / >10 turns) surfaced a non-obvious cause; OR a reusable pattern was named in the session ("the X phase", "the Y pattern"). Legacy signals #2 + #3 + #5.
84
+ 3. **Concrete artifact change** — a new dependency was added (package.json/pyproject.toml/Cargo.toml diff), OR a load-bearing multi-step procedure was formalized in a specific order. Legacy signals #4 + #8.
85
+
86
+ Pre-PASS MUST step (rc.37 NEW-4): for each candidate, run a quick `Glob` over `.fabric/knowledge/**/*.md` keyed on slug-stem to check for duplicate canonical entry. If duplicate found → drop candidate (treat as anti-signal #4 'duplicate of existing canonical'). This is a HARD gate, not advisory — silently writing a near-duplicate is the highest-noise failure mode.
87
+
88
+ **FAIL → branch by entry_point**: E1/E3/E5 silent-skip (emit Phase 4.5 event `outcome='skipped_no_signal'`); E2/E4 render gate-FAIL message (emit `outcome='viability_failed'`). Gate-FAIL message for E2/E4 MUST include the "to force-archive, explicitly invoke fabric-archive" remediation pointer so the user has an unambiguous escape hatch when the gate misclassifies (zh-CN: `如需强制归档,请显式调用 fabric-archive` / en: `To force-archive, explicitly invoke fabric-archive`).
67
89
 
68
90
  `Read ref/phase-2-5-viability.md` for verbose signal definitions, zh-CN/en gate-FAIL message bodies, anti-archive signals (typo / refactor / rename / duplicate), and the events.jsonl 4KB POSIX atomicity constraint.
69
91
 
@@ -126,7 +148,7 @@ MANDATORY closing step on EVERY invocation (Phase 4 success path + every early-e
126
148
  - NEVER classify a candidate as `personal` when a 强 team signal applies. Default to team on ambiguity.
127
149
  - NEVER emit a non-empty `relevance_paths` when `relevance_scope=broad` — broad MUST always carry `relevance_paths=[]`.
128
150
  - NEVER emit a non-empty `relevance_paths` when `layer=personal` — personal forces `relevance_scope=broad` + `relevance_paths=[]`.
129
- - NEVER use multi-signal sources for relevance_paths in rc.5 `edit_paths` is the SOLE source. `read_paths`, body regex, and symbol extraction are reserved for rc.7+.
151
+ - v2.0.0-rc.37 NEW-7 widened Phase 3.5: `edit_paths` `user_mentioned_paths` drives `relevance_paths`; `read_paths` flows separately to `evidence_paths` (structured frontmatter, not body markdown). NEVER lift body regex / symbol extraction into `relevance_paths` — those remain reserved for v2.1+.
130
152
  - NEVER batch multiple candidates into a single fab_extract_knowledge call; one call per candidate.
131
153
  - NEVER paraphrase the verbatim layer heuristic block above — the Chinese text is contract-locked.
132
154
  - MUST preserve protected tokens exactly: `stable_id`, `knowledge_proposed`, `knowledge_archive_aborted`, `knowledge_scope_degraded`, `.fabric/knowledge/pending/`, `fab_extract_knowledge`, `relevance_paths`, `relevance_scope`, `narrow`, `broad`, `edit_paths`, `source_sessions`, `proposed_reason`, `session_context`, `intent_clues`, `tech_stack`, `impact`, `must_read_if`, `pending_path`, `layer`, `team`, `personal`, `MUST`, `NEVER`, `强 team`, `强 personal`, `默认 team`.
@@ -1,6 +1,6 @@
1
1
  # Dry-run Scope (unified)
2
2
 
3
- `dry_run = true` (per Phase 4.5 detection rule — substring match on `--dry-run` | `dry-run` | `dry_run` | `预览` token) suspends ALL side-effecting writes below; read-side machinery (Phase 1 digest collection, Phase 2.5 viability gate evaluation, Phase 3 candidate render) executes normally so the user can preview what WOULD happen.
3
+ `dry_run = true` (per Phase 4.5 detection rule — invocation MUST carry the verbatim token `--dry-run`; v2.0.0-rc.37 NEW-10 dropped the legacy substring fallback on bare `dry-run` / `dry_run` / `预览` to eliminate false positives on incidental mentions of those words mid-prompt) suspends ALL side-effecting writes below; read-side machinery (Phase 1 digest collection, Phase 2.5 viability gate evaluation, Phase 3 candidate render) executes normally so the user can preview what WOULD happen.
4
4
 
5
5
  | Write operation | Normal mode | Dry-run mode |
6
6
  |---|---|---|
@@ -1,5 +1,11 @@
1
1
  # UX i18n Policy — full reference
2
2
 
3
+ > **Shared core (rc.37 NEW-13):** the cross-skill invariants — protected-token
4
+ > NEVER-translate list, AskUserQuestion routing-key rule, layer heuristic, and
5
+ > events-emit convention — live once in `../../lib/shared-policy.md`. This file
6
+ > keeps only the fabric-archive-specific 5-class examples. Read the shared lib
7
+ > for the common rules; do not fork them here.
8
+
3
9
  > **Loaded on demand.** Only consult when rendering bilingual output AND you're unsure which class a string belongs to. SKILL.md gives the operative rule: read `.fabric/fabric-config.json` → `fabric_language`, emit prose in resolved variant, never translate protected tokens. The 5-class taxonomy below disambiguates edge cases.
4
10
 
5
11
  ## UX i18n Policy (5-class bilingualization)
@@ -23,7 +23,7 @@ is the canonical taxonomy for this gate.
23
23
  |-------|--------|-------------------------------------------------------|
24
24
  | **E1** | `hook_passive` | stdout JSON `{decision:'block', ...}` from `archive-hint.cjs` detected at skill entry (the Stop-hook reminder path). |
25
25
  | **E2** | `explicit_user_invoke` | User prompt is a direct invocation: `fabric archive` / `/fabric-archive` / `archive what we just did` / `归档一下` / similar imperative. |
26
- | **E3** | `ai_self_trigger` | AI internal marker `self-archive policy triggered by signal: <X>` present (substring match on the verbatim prefix `self-archive policy triggered by signal`; `<X>` is one of the 4 self-trigger signals from AGENTS.md E3 section: `Normative` / `Wrong-turn-and-revert` / `Decision confirmation` / `Explicit dismissal`). |
26
+ | **E3** | `ai_self_trigger` | AI internal marker `self-archive policy triggered by signal: <X>` present (substring match on the verbatim prefix `self-archive policy triggered by signal` per AGENTS.md self-archive policy section; `<X>` is the signal name. v2.0.0-rc.37 NEW-2 simplified the AGENTS.md taxonomy to 2 categories: `User-driven normative` / `Wrong-turn-and-revert`. Back-compat: legacy 4-state names (`Normative` / `Decision confirmation` / `Explicit dismissal`) still route correctly because the substring gate only matches the verbatim prefix and treats any text after `signal:` as the signal label.) |
27
27
  | **E4** | `user_range_rollback` | Prompt contains a **range hint** (parsed in Phase 0 — e.g. `今日` / `上周` / `rc.20`) AND the user is invoking. Sub-mode of E2. |
28
28
  | **E5** | `cron` | Prompt contains literal `今日复盘` / `daily recap` / `daily-archive` AND no human is present (running under `/loop`, OS cron, or scheduled trigger). |
29
29
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  > **Loaded on demand.** SKILL.md hot path retains Phase 1's purpose statement + 5-step summary + graceful-degradation note. This file holds Steps 1-5 detailed implementation (events.jsonl tail-scan, anchor-walk, digest load, rc.25 TASK-05 ledger filter algorithm + constants + worked examples, cross-session context build).
4
4
 
5
+ > **v2.0.0-rc.37 NEW-9 — Steps 1-4.5 moved server-side.** The deterministic part of this algorithm (events.jsonl tail-scan, anchor-find, session forward-collect, and the Step 4.5 outcome-ledger filter state machine) now runs in the server and is exposed as the `fab_archive_scan` MCP tool — call it instead of hand-running `tail`/grep. The tool returns the already-filtered `session_ids[]` + `anchor_ts` + `covered_through_ts` + `already_proposed_keys[]`. Steps 1-4.5 below remain as the AUTHORITATIVE SPEC of what the server computes (and the contract tests pin it); the Skill no longer executes them by hand. Step 5 (digest load + cross-session context stitch) stays LLM-side per Boundary B.
6
+
5
7
  ## Step 1 — Read events.jsonl tail
6
8
 
7
9
  Use `Bash` with `tail -n 200 .fabric/events.jsonl` (tolerate ENOENT — empty ledger is a normal first-run state).
@@ -2,18 +2,32 @@
2
2
 
3
3
  > **Loaded on demand.** SKILL.md hot path retains the signal lists, gate decision pseudocode, and entry-point branching summary. This file holds the verbose explanations for each signal, gate-FAIL message variants (zh-CN/en), the events.jsonl 4KB atomicity constraint note, and rationale.
4
4
 
5
- ## Archive signals — verbose explanation
5
+ ## Archive signals — verbose explanation (rc.37 NEW-4 simplified 8 → 3)
6
6
 
7
- Scan `user_messages_summary` + `recent_paths` + the events tail collected in Phase 2. Each signal listed in SKILL.md hot path is explained below:
7
+ Scan `user_messages_summary` + `recent_paths` + the events tail collected in Phase 2. The legacy 8-signal list was simplified in v2.0.0-rc.37 NEW-4 to **3 major categories**, mirroring the AGENTS.md Self-archive policy rc.37 NEW-2 simplification. Legacy signal names remain valid scoring inputs for back-compat (any path that fires a legacy signal also fires the corresponding category):
8
8
 
9
- 1. **Explicit normative language** user said `always` / `never` / `from now on` / `下次注意` / `记一下` / `以后` / `永远不要`. Strongest single signal — even one normative cue is sufficient.
10
- 2. **Wrong-turn-and-revert** — a path was edited, then reverted (or partially undone) after diagnosis. Indicates a pitfall worth recording (the why-not lives in the revert).
11
- 3. **Long diagnostic loop** an issue took > 15 minutes (or > ~10 tool turns) of debugging before resolution. Implies a non-obvious cause worth capturing.
12
- 4. **New dependency adoption** — a new package / library / external tool was introduced (e.g. `package.json` / `pyproject.toml` / `Cargo.toml` diff adds a dep).
13
- 5. **New pattern emergence** — a reusable abstraction or naming convention was named ("the X phase", "the Y pattern", "let's call this Z").
14
- 6. **Decision confirmation** — ≥ 2 alternatives were weighed AND a rationale was given before settling. The rationale is the archivable knowledge.
15
- 7. **Explicit dismissal-with-reason** — user rejected an approach AND stated why. The why is the archivable knowledge, not the dismissal itself.
16
- 8. **Process formalization** — a multi-step procedure was executed in a specific order AND the order was identified as load-bearing.
9
+ ### Category 1User-driven knowledge expression
10
+
11
+ Covers legacy signals #1 (normative language) + #6 (decision confirmation) + #7 (dismissal-with-reason). All three are "user message contains structured knowledge worth keeping":
12
+
13
+ - **Normative language** — user said `always` / `never` / `from now on` / `下次注意` / `记一下` / `以后` / `永远不要`. Strongest single cue.
14
+ - **Decision confirmation** — ≥ 2 alternatives were weighed AND a rationale was given before settling. The rationale is the archivable knowledge.
15
+ - **Dismissal-with-reason** — user rejected an approach AND stated why. The why is archivable, not the dismissal itself.
16
+
17
+ ### Category 2 — Reflective discovery
18
+
19
+ Covers legacy signals #2 (wrong-turn-and-revert) + #3 (long diagnostic loop) + #5 (new pattern emergence). All three are "AI execution surfaced a non-obvious insight":
20
+
21
+ - **Wrong-turn-and-revert** — a path was edited, then reverted (or partially undone) after diagnosis. The why-not lives in the revert.
22
+ - **Long diagnostic loop** — an issue took > 15 minutes (or > ~10 tool turns) of debugging before resolution. Non-obvious cause worth capturing.
23
+ - **New pattern emergence** — a reusable abstraction or naming convention was named in-session ("the X phase", "the Y pattern", "let's call this Z").
24
+
25
+ ### Category 3 — Concrete artifact change
26
+
27
+ Covers legacy signals #4 (new dependency) + #8 (process formalization). Both surface in tangible workspace artifacts:
28
+
29
+ - **New dependency adoption** — a new package / library / external tool was introduced (`package.json` / `pyproject.toml` / `Cargo.toml` diff adds a dep).
30
+ - **Process formalization** — a multi-step procedure was executed in a specific order AND the order was identified as load-bearing.
17
31
 
18
32
  ## Anti-archive signals — verbose explanation
19
33
 
@@ -22,7 +36,7 @@ These force the gate to FAIL **unless** an archive signal also fires (i.e. anti-
22
36
  1. **Typo-only edits** — the entire session is whitespace / spelling / formatting changes. No semantic content to archive.
23
37
  2. **Pure refactor** — rename / move / extract with no behavior change AND no naming convention being established.
24
38
  3. **Narrow rename request** — user asked to rename one symbol / file with no rationale. Zero generalization potential.
25
- 4. **Duplicate of existing canonical** — the observation is already covered by an existing entry under `.fabric/knowledge/<type>/`. Do a quick Glob before deciding.
39
+ 4. **Duplicate of existing canonical** — v2.0.0-rc.37 NEW-4: this check is now **mandatory** (was "do a quick Glob before deciding"). Pre-PASS MUST step: for each candidate, `Glob('.fabric/knowledge/**/*.md')` keyed on slug-stem. If duplicate found → drop candidate. Silently writing a near-duplicate is the highest-noise failure mode and dwarfs the cost of one extra glob per candidate.
26
40
 
27
41
  ## Gate-FAIL user messages (E2 / E4 only)
28
42
 
@@ -1,45 +1,73 @@
1
1
  # Phase 3.5 — Scope Decision + relevance_paths Derivation (ref)
2
2
 
3
- > **Loaded on demand.** SKILL.md hot path retains the scope decision pseudocode, personal-layer-forces-broad rule, and brief examples. This file holds the rc.5 single-signal `edit_paths` derivation algorithm (Steps 1-6) + worked generalization example + inline-edit re-derivation rules.
3
+ > **Loaded on demand.** SKILL.md hot path retains the scope decision pseudocode, personal-layer-forces-broad rule, and brief examples. This file holds the rc.37 multi-signal derivation algorithm (edit_paths + read_paths + user_mentioned_paths), Steps 1-6 + worked generalization example + inline-edit re-derivation rules + the frontmatter `evidence_paths` upgrade.
4
4
 
5
- ## relevance_paths derivation algorithm (rc.5 single-signal: edit_paths only)
5
+ ## relevance_paths derivation algorithm (rc.37 multi-signal NEW-7)
6
6
 
7
- rc.5 uses ONLY the `edit_paths` signal — list of paths modified by `Edit` / `Write` / `MultiEdit` tool calls in the current session. Multi-signal (read_paths + body regex + symbols) is explicitly deferred to rc.7 per design decision.
7
+ rc.37 NEW-7 widens Step 1 from the rc.5 single-signal (`edit_paths` only) to three sources:
8
+
9
+ 1. **`edit_paths`** — files modified by `Edit` / `Write` / `MultiEdit` tool calls. The primary activation signal: if the agent CHANGED a file, the knowledge derived in this session most likely applies there.
10
+ 2. **`read_paths`** — files inspected via `Read` / `Grep` / `Glob` without modification. Secondary signal: read-only inspection often anchors the applicability surface even when no write happened (e.g. discovering that a pitfall surfaces in a getter that the agent only READ).
11
+ 3. **`user_mentioned_paths`** — paths the user typed verbatim in messages (`packages/server/src/foo.ts`, `\`packages/cli/**/*.ts\`` etc.). Strongest signal of all: an explicit user-named path is ground-truth applicability surface, independent of what the agent did.
8
12
 
9
13
  ```
10
- Step 1: COLLECT
14
+ Step 1: COLLECT (rc.37 NEW-7 — three sources)
11
15
  edit_paths = []
16
+ read_paths = []
17
+ user_mentioned_paths = []
18
+
19
+ // 1a — edit signal (rc.5 primary)
12
20
  Scan session transcript for tool_use entries where
13
21
  tool_use.name ∈ {Edit, Write, MultiEdit}
14
22
  Extract the file_path argument from each, push into edit_paths.
15
23
 
16
- Step 2: DEDUPE
17
- edit_paths = unique(edit_paths)
18
-
19
- Step 3: BLACKLIST FILTER
24
+ // 1b — read signal (rc.37 NEW-7 secondary)
25
+ Scan session transcript for tool_use entries where
26
+ tool_use.name ∈ {Read, Grep, Glob}
27
+ Extract the file_path / path / glob argument from each, push into read_paths.
28
+
29
+ // 1c — user-mentioned signal (rc.37 NEW-7 ground truth)
30
+ Scan user messages for token sequences matching workspace-relative
31
+ path patterns: `<segment>/<segment>/...<ext>` or `<segment>/**` or
32
+ ``<path>`` (backtick-quoted). De-dupe and push into user_mentioned_paths.
33
+
34
+ Step 2: DEDUPE + CLASSIFY
35
+ // Union all three sources for the relevance_paths candidate set.
36
+ candidate_paths = unique(edit_paths ∪ user_mentioned_paths)
37
+ // read_paths stay separate — they become evidence_paths (Step 6) rather
38
+ // than activation triggers. A path that appears in BOTH edit_paths and
39
+ // read_paths goes to candidate_paths (writes dominate reads).
40
+ evidence_candidate_paths = unique(read_paths \ edit_paths)
41
+
42
+ Step 3: BLACKLIST FILTER (applies to BOTH candidate sets)
20
43
  Drop paths matching any of:
21
44
  - **/*.<ext> where <ext> is a single trivial extension on a single file
22
45
  (i.e. avoid emitting bare **/*.md as a relevance pattern)
23
46
  - Repo-root single files: README.md, package.json, package-lock.json,
24
47
  pnpm-lock.yaml, tsconfig.json, .gitignore, LICENSE, CHANGELOG.md
25
- - Read-only paths (never modified) — those go to ## Evidence, not relevance_paths
26
48
 
27
49
  Step 4: PUBLIC-PREFIX GENERALIZE (depth ≤ 2, minGroupSize = 2)
28
- Group remaining paths by common prefix.
50
+ Group remaining candidate_paths by common prefix.
29
51
  For each group of ≥ 2 sibling paths sharing a prefix:
30
52
  - Compute longest common directory prefix
31
53
  - Limit generalization depth: at most 2 levels below the common prefix
32
54
  - Emit glob: <common-prefix>/**/*.<ext> (or <common-prefix>/**/<filename>)
33
55
  Singleton paths (group size = 1) are kept as-is (literal path, no glob).
56
+ (Evidence paths are NOT generalized — they stay literal so plan-context
57
+ retrieval can do exact-match recall lookups.)
34
58
 
35
59
  Step 5: SCOPE GATE
36
- IF relevance_scope == broad → relevance_paths = [] (force empty regardless of edit_paths)
60
+ IF relevance_scope == broad → relevance_paths = [] (force empty regardless of candidate_paths)
37
61
  IF relevance_scope == narrow → relevance_paths = result of Step 4
38
62
 
39
- Step 6: ATTACH READ-ONLY EVIDENCE
40
- Read-only paths (filtered in Step 3) are emitted as a ## Evidence markdown
41
- block in the pending entry body — NOT in relevance_paths. They document
42
- what the agent consulted without making them part of the activation gate.
63
+ Step 6: ATTACH evidence_paths to FRONTMATTER (rc.37 NEW-7 upgrade)
64
+ Pass evidence_candidate_paths (from Step 2, post-blacklist Step 3) to
65
+ fab_extract_knowledge as the `evidence_paths` input field. Server writes
66
+ them to frontmatter `evidence_paths: [...]` (NOT to body `## Evidence`).
67
+ This makes evidence consumable by plan-context retrieval as structured
68
+ data instead of forcing markdown re-parsing every recall. The legacy
69
+ body `## Evidence` block stays for back-compat readers but is no longer
70
+ the source of truth.
43
71
  ```
44
72
 
45
73
  ## Worked generalization example