djournal 0.1.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.
Files changed (38) hide show
  1. package/.agents/adapters/contract.md +48 -0
  2. package/.agents/adapters/shared/journal-hook.js +120 -0
  3. package/.agents/rules/AUTOMATION.md +84 -0
  4. package/.agents/rules/FEAT.md +83 -0
  5. package/.agents/rules/JOURNAL.md +127 -0
  6. package/.agents/rules/LINKS.md +96 -0
  7. package/.agents/rules/METADATA.md +179 -0
  8. package/.agents/rules/SAFETY.md +92 -0
  9. package/.agents/rules/STATE.md +31 -0
  10. package/.agents/skills/audit/SKILL.md +89 -0
  11. package/.agents/skills/audit/agents/openai.yaml +4 -0
  12. package/.agents/skills/decision/SKILL.md +74 -0
  13. package/.agents/skills/decision/agents/openai.yaml +4 -0
  14. package/.agents/skills/document/SKILL.md +68 -0
  15. package/.agents/skills/init-work/SKILL.md +62 -0
  16. package/.agents/skills/journal/SKILL.md +97 -0
  17. package/.agents/skills/journal-workflow/SKILL.md +45 -0
  18. package/.agents/skills/journal-workflow/agents/openai.yaml +4 -0
  19. package/.agents/skills/plan/SKILL.md +98 -0
  20. package/.agents/skills/recall/SKILL.md +67 -0
  21. package/.agents/skills/reconcile/SKILL.md +93 -0
  22. package/.agents/skills/reconcile/agents/openai.yaml +4 -0
  23. package/.agents/skills/research-codebase/SKILL.md +72 -0
  24. package/.agents/skills/research-web/SKILL.md +72 -0
  25. package/.agents/skills/resume/SKILL.md +68 -0
  26. package/.agents/skills/switch/SKILL.md +31 -0
  27. package/.claude/settings.json +41 -0
  28. package/.codex/hooks.json +40 -0
  29. package/AGENTS.md +23 -0
  30. package/CLAUDE.md +1 -0
  31. package/LICENSE +202 -0
  32. package/README.md +158 -0
  33. package/bin/journal.js +118 -0
  34. package/install.sh +35 -0
  35. package/lib/installer/index.js +538 -0
  36. package/lib/installer/merge.js +125 -0
  37. package/package.json +55 -0
  38. package/spec.md +297 -0
@@ -0,0 +1,48 @@
1
+ # Journal Harness Adapter Contract
2
+
3
+ Harness adapters improve compliance with the journal workflow. They are not a
4
+ second workflow engine and do not own durable state.
5
+
6
+ ## Semantic events
7
+
8
+ | Event | Adapter behavior |
9
+ | --- | --- |
10
+ | `session_start` | Inject the active work name and tell the agent to follow `AGENTS.md` |
11
+ | `prompt_submit` | Remind the agent to classify the request; preserve explicit opt-out |
12
+ | `mutation_observed` | Optional ephemeral reminder only; never write state |
13
+ | `before_compact` | Optional reminder to preserve material state before context loss |
14
+ | `stop` | Validate the final journal status marker and request one closure pass |
15
+
16
+ An adapter may omit events its harness cannot support reliably. Instruction-only
17
+ behavior remains the compatibility baseline.
18
+
19
+ ## Input
20
+
21
+ The shared checker accepts one JSON object on stdin. It uses only stable fields:
22
+
23
+ - `cwd`
24
+ - `hook_event_name`
25
+ - `prompt`
26
+ - `last_assistant_message`
27
+ - `stop_hook_active`
28
+
29
+ It does not parse conversation transcripts. Harness wrappers may normalize
30
+ native payloads to these names.
31
+
32
+ ## Output
33
+
34
+ - Start and prompt events return `hookSpecificOutput.additionalContext`.
35
+ - Stop returns `{}` when closure is valid or no journal is installed.
36
+ - Stop returns `{ "decision": "block", "reason": "..." }` for one retry when
37
+ the final status marker is missing or invalid.
38
+ - Malformed input and absent journal roots fail open with a bounded stderr
39
+ diagnostic and exit code zero.
40
+
41
+ ## Invariants
42
+
43
+ - Hooks are read-only.
44
+ - `stop_hook_active: true` always permits stopping.
45
+ - `closed` markers must resolve inside `.journal/work/` to an existing Markdown
46
+ spine entry.
47
+ - Adapters do not infer task meaning from file changes or unstable transcripts.
48
+ - Adding a harness must not change `AUTOMATION.md` semantics.
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env node
2
+
3
+ "use strict";
4
+
5
+ const fs = require("node:fs");
6
+ const path = require("node:path");
7
+
8
+ const MARKER = /<!--\s*journal-status:\s*(closed|not-needed|off)(?:\s+([^>]*?))?\s*-->\s*$/i;
9
+
10
+ function readStdin() {
11
+ return new Promise((resolve) => {
12
+ let input = "";
13
+ process.stdin.setEncoding("utf8");
14
+ process.stdin.on("data", (chunk) => { input += chunk; });
15
+ process.stdin.on("end", () => resolve(input));
16
+ });
17
+ }
18
+
19
+ function findRoot(start) {
20
+ let current = path.resolve(start || process.cwd());
21
+ while (true) {
22
+ if (fs.existsSync(path.join(current, "AGENTS.md")) &&
23
+ fs.existsSync(path.join(current, ".agents", "rules", "AUTOMATION.md"))) {
24
+ return current;
25
+ }
26
+ const parent = path.dirname(current);
27
+ if (parent === current) return null;
28
+ current = parent;
29
+ }
30
+ }
31
+
32
+ function activeWork(root) {
33
+ try {
34
+ const state = JSON.parse(fs.readFileSync(path.join(root, ".journal", "state.json"), "utf8"));
35
+ if (typeof state.active_work_name !== "string" || !state.active_work_name) return null;
36
+ const work = path.join(root, ".journal", "work", state.active_work_name, "work.md");
37
+ return fs.existsSync(work) ? state.active_work_name : null;
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+
43
+ function context(event, message) {
44
+ return {
45
+ hookSpecificOutput: {
46
+ hookEventName: event,
47
+ additionalContext: message,
48
+ },
49
+ };
50
+ }
51
+
52
+ function validateClosedPath(root, value) {
53
+ if (!value) return false;
54
+ const relative = value.trim().replaceAll("\\", "/");
55
+ if (!relative.endsWith(".md") || path.isAbsolute(relative)) return false;
56
+ const resolved = path.resolve(root, relative);
57
+ const journalRoot = path.resolve(root, ".journal", "work") + path.sep;
58
+ if (!resolved.startsWith(journalRoot)) return false;
59
+ if (!fs.existsSync(resolved) || !fs.statSync(resolved).isFile()) return false;
60
+ return resolved.split(path.sep).join("/").includes("/journal/");
61
+ }
62
+
63
+ function handle(payload) {
64
+ const event = payload.hook_event_name;
65
+ const root = findRoot(payload.cwd);
66
+ if (!root) return {};
67
+
68
+ if (event === "SessionStart") {
69
+ const work = activeWork(root);
70
+ const suffix = work ? ` Active work: ${work}.` : " No valid active work is selected.";
71
+ return context(event, `Follow AGENTS.md and .agents/rules/AUTOMATION.md.${suffix}`);
72
+ }
73
+
74
+ if (event === "UserPromptSubmit") {
75
+ const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
76
+ if (/\bjournal\s*:\s*off\b/i.test(prompt)) {
77
+ return context(event, "Journal opt-out applies to this request. End with the journal-status: off marker.");
78
+ }
79
+ return context(event, "Classify this request using the ambient journal workflow. Close meaningful changed state before the final response, without adding ceremony to read-only or trivial work.");
80
+ }
81
+
82
+ if (event !== "Stop") return {};
83
+ if (payload.stop_hook_active === true) return {};
84
+
85
+ const message = typeof payload.last_assistant_message === "string"
86
+ ? payload.last_assistant_message
87
+ : "";
88
+ const match = message.match(MARKER);
89
+ if (!match) {
90
+ return {
91
+ decision: "block",
92
+ reason: "Perform the journal closure checkpoint from AGENTS.md. Journal meaningful changed state if needed, then end with one valid hidden journal-status marker.",
93
+ };
94
+ }
95
+
96
+ if (match[1].toLowerCase() === "closed" && !validateClosedPath(root, match[2])) {
97
+ return {
98
+ decision: "block",
99
+ reason: "The journal-status closed marker must reference an existing repository-relative Markdown spine entry under .journal/work/<work>/journal/.",
100
+ };
101
+ }
102
+ return {};
103
+ }
104
+
105
+ async function main() {
106
+ try {
107
+ const raw = await readStdin();
108
+ const payload = raw.trim() ? JSON.parse(raw) : {};
109
+ process.stdout.write(`${JSON.stringify(handle(payload))}\n`);
110
+ } catch (error) {
111
+ process.stderr.write(`journal hook skipped: ${error.message}\n`);
112
+ process.stdout.write("{}\n");
113
+ }
114
+ }
115
+
116
+ if (require.main === module) {
117
+ main();
118
+ }
119
+
120
+ module.exports = { handle };
@@ -0,0 +1,84 @@
1
+ # Ambient Journal Automation Rules
2
+
3
+ These rules coordinate existing journal skills. They do not replace the file,
4
+ metadata, link, state, or safety rules those skills own.
5
+
6
+ ## Task classification
7
+
8
+ Classify each request before acting:
9
+
10
+ | Class | Examples | Default behavior |
11
+ | --- | --- | --- |
12
+ | Read-only | explain, inspect, review, answer status | Recall or resume only when history matters; do not write a closing entry |
13
+ | Trivial mutation | typo, tiny formatting or config correction | Resume only when context matters; validate; journal only if durable project state changed |
14
+ | Meaningful mutation | feature, fix, migration, behavioral change | Resume; plan when warranted; implement; validate; journal |
15
+ | Durable knowledge | research, decision, canonical documentation | Resume; use the matching supporting skill; journal when work state changed |
16
+
17
+ Classification requires semantic judgment. File changes alone do not establish
18
+ that a task was meaningful.
19
+
20
+ ## Start checkpoint
21
+
22
+ - If the user says `journal: off`, skip ambient journal reads and writes for the
23
+ request unless another explicit instruction requires them.
24
+ - If `.journal/state.json` selects active work, use `resume` when historical
25
+ context can affect the result.
26
+ - If no active work exists and the request clearly starts a new durable work
27
+ stream, use `init-work`. Ask only when its identity or scope is materially
28
+ ambiguous.
29
+ - Do not create a work item for a read-only question or incidental edit.
30
+
31
+ ## Plan threshold
32
+
33
+ Use `plan` before implementation when the work has multiple dependent phases,
34
+ meaningful design choices, risky migration behavior, cross-component effects,
35
+ or a validation strategy that should survive the current conversation.
36
+
37
+ Skip a durable plan for changes that are local, obvious, reversible, and easy to
38
+ validate. Existing user-approved plans do not need another plan.
39
+
40
+ ## Work checkpoint
41
+
42
+ - Use `research-codebase` or `research-web` only when the findings should remain
43
+ durable evidence.
44
+ - Use `decision` when an accepted choice and its rationale constrain later work.
45
+ - Use `document` for a durable synthesized reference.
46
+ - Never copy semantic write logic into an orchestration hook or adapter.
47
+
48
+ ## Closure checkpoint
49
+
50
+ Before the final response:
51
+
52
+ 1. Decide whether meaningful journal state changed.
53
+ 2. If it did, use `journal` once after validation and reference supporting
54
+ entries actually used.
55
+ 3. Check the latest spine entry before writing. Update or skip when a new entry
56
+ would describe the same completed outcome.
57
+ 4. If validation failed, record the accurate partial or blocked state rather
58
+ than claiming completion.
59
+ 5. End the response with one status marker defined in root `AGENTS.md`.
60
+
61
+ `closed` requires a repository-relative path to an existing spine entry.
62
+ `not-needed` means the task was read-only or too small to change durable state.
63
+ `off` means the user explicitly opted out for the request.
64
+
65
+ ## Hook boundary
66
+
67
+ Harness hooks may:
68
+
69
+ - inject these workflow reminders;
70
+ - report active-work metadata;
71
+ - validate the final status marker and referenced entry;
72
+ - request at most one additional closure pass.
73
+
74
+ Hooks must not create work items, plans, research, decisions, documents, or
75
+ journal entries. They must safely no-op when the journal is absent and must not
76
+ read unstable transcript formats to infer work.
77
+
78
+ ## Failure behavior
79
+
80
+ - A hook failure warns but does not replace journal safety or state rules.
81
+ - A missing or invalid active state uses the bounded error from `STATE.md` when
82
+ a semantic write is required.
83
+ - An unsupported, disabled, or untrusted hook falls back to these instructions.
84
+ - The hook-provided `stop_hook_active` guard permits only one continuation pass.
@@ -0,0 +1,83 @@
1
+ # Work Item Rules
2
+
3
+ Agent configuration lives under `.agents/`. Durable journal data lives under
4
+ `.journal/`. Skills MUST read rules from `.agents/rules/` and MUST NOT write
5
+ runtime work data into `.agents/`.
6
+
7
+ Read these rules together with:
8
+
9
+ - `.agents/rules/METADATA.md`
10
+ - `.agents/rules/LINKS.md`
11
+ - `.agents/rules/SAFETY.md`
12
+ - `.agents/rules/STATE.md`
13
+
14
+ ## Work item layout
15
+
16
+ When creating a work item, create this structure:
17
+
18
+ ```text
19
+ .journal/work/YYYY-MM-DD-NN-work-name/
20
+ ├── work.md
21
+ ├── journal/
22
+ ├── _research/
23
+ ├── docs/
24
+ └── decisions/
25
+ ```
26
+
27
+ - `work.md`: canonical work-item metadata and a short human-readable overview
28
+ - `journal/`: chronological spine entries
29
+ - `_research/`: supporting codebase and web research
30
+ - `docs/`: supporting synthesized references
31
+ - `decisions/`: supporting standalone decisions
32
+
33
+ ## Naming
34
+
35
+ - Folder format: `YYYY-MM-DD-NN-work-name`.
36
+ - Date is the UTC creation date.
37
+ - `NN` is the next two-digit sequence for work items created that day.
38
+ - `work-name` is lowercase kebab-case, descriptive, and unique.
39
+ - Avoid spaces and special characters other than hyphens.
40
+ - The folder name MUST equal `slug` in `work.md`.
41
+
42
+ Example: `2026-06-29-01-portable-journal-skill-improvements`.
43
+
44
+ ## Initialization
45
+
46
+ `init-work` MUST:
47
+
48
+ 1. Inspect existing work folders to select a non-conflicting daily sequence.
49
+ 2. Create the complete directory structure above.
50
+ 3. Resolve identity according to `METADATA.md`.
51
+ 4. Generate one stable `wi_` UUIDv7.
52
+ 5. Create `work.md` with all required work-item metadata.
53
+ 6. Default to `status: active` and `visibility: local_only`.
54
+ 7. Set `createdAt` and `updatedAt` to the same current UTC timestamp.
55
+ 8. Set `.journal/state.json` to the new slug according to `STATE.md`.
56
+
57
+ Work items are projects, not repositories. A project MAY span many repositories,
58
+ branches, and working directories. Do not create a new work item merely because
59
+ the repository changed.
60
+
61
+ ## Lifecycle
62
+
63
+ Allowed status values:
64
+
65
+ - `draft`: created but not yet meaningful active work
66
+ - `active`: current ongoing work
67
+ - `paused`: intentionally inactive but expected to resume
68
+ - `completed`: intended outcome achieved
69
+ - `archived`: retained history with no expected continuation
70
+
71
+ Status updates preserve `createdAt`, update `updatedAt`, and never alter `id` or
72
+ `slug`. Visibility changes require an explicit user request.
73
+
74
+ Only one work item is selected as active in `.journal/state.json`, but other work
75
+ items may remain in `active` status. Active selection and lifecycle status are
76
+ different concepts.
77
+
78
+ ## Legacy work items
79
+
80
+ A historical folder without `work.md` remains readable. Resume and recall may
81
+ infer its slug and title from the folder name. Audit flags the missing file;
82
+ reconcile may create it using deterministic legacy timestamps and
83
+ `source`/identity rules without modifying existing entry bodies.
@@ -0,0 +1,127 @@
1
+ # Journal Entry Rules
2
+
3
+ Read these rules together with:
4
+
5
+ - `.agents/rules/METADATA.md`
6
+ - `.agents/rules/LINKS.md`
7
+ - `.agents/rules/SAFETY.md`
8
+
9
+ Markdown remains the source of truth. Every new entry MUST contain valid
10
+ canonical frontmatter followed by a concise, human-readable body.
11
+
12
+ ## Entry locations and roles
13
+
14
+ Role is derived from `entryType`; never store a separate `role` field.
15
+
16
+ | Directory | Entry types | Derived role |
17
+ | --- | --- | --- |
18
+ | `journal/` | `plan`, `implementation`, `status`, `manual` | spine |
19
+ | `_research/` | `research` | supporting |
20
+ | `docs/` | `doc` | supporting |
21
+ | `decisions/` | `decision` | supporting |
22
+
23
+ Supporting entries stand alone and are linked from spine or other supporting
24
+ entries. They do not add independent steps to the project timeline.
25
+
26
+ ## Filenames
27
+
28
+ - Spine: `YYYY-MM-DD-NN-brief-description.md`
29
+ - Codebase research: `YYYY-MM-DD-NN-codebase-topic.md`
30
+ - Web research: `YYYY-MM-DD-NN-web-topic.md`
31
+ - Documents: `YYYY-MM-DD-NN-topic.md`
32
+ - Decisions: `YYYY-MM-DD-NN-decision-topic.md`
33
+
34
+ `NN` is the next sequence within the destination directory for that UTC date.
35
+ Filenames are descriptive, lowercase kebab-case. Entry identity comes from the
36
+ stable `ent_` UUIDv7, not the filename or entry number.
37
+
38
+ ## Required spine body
39
+
40
+ Every spine entry MUST include these sections, adapted to its entry type:
41
+
42
+ ### Project Timeline
43
+
44
+ A chronological index of the work item's spine. Supporting entries appear only
45
+ as links from the relevant spine entry.
46
+
47
+ ### Current State
48
+
49
+ - components and their working/partial/broken status
50
+ - validation status with real counts when available
51
+ - key files and their current purpose
52
+
53
+ For a plan entry, this may be named `Current State Analysis`.
54
+
55
+ ### Work Done This Session
56
+
57
+ - specific work performed
58
+ - problems resolved
59
+ - decisions made and why
60
+ - deterministic evidence such as paths, commits, and validation results
61
+
62
+ For a plan entry, replace this with the feature overview, proposed design, and
63
+ phased implementation plan. Do not claim planned work was implemented.
64
+
65
+ ### Next Steps
66
+
67
+ - concrete actions in priority order
68
+ - known blockers and unresolved questions
69
+
70
+ Include architecture notes only when architecture changed or the plan proposes
71
+ an architectural change. Keep diagrams small and useful.
72
+
73
+ ## Type-specific content
74
+
75
+ - `plan`: problem, goals, non-goals, current state, design, phases, tests, edge
76
+ cases, open questions, and next steps
77
+ - `implementation`: changes, rationale, files, validation, commits, current state,
78
+ and next steps
79
+ - `status`: current state, evidence, blockers, and next steps; no invented work
80
+ - `manual`: user-supplied or exceptional spine note following the core sections
81
+ - `research`: question, summary, sources/key files, findings, and open questions
82
+ - `doc`: standalone synthesized reference with source links
83
+ - `decision`: context, options, decision, rationale, consequences, and supersession
84
+
85
+ ## Frontmatter and links
86
+
87
+ - Follow `METADATA.md` exactly.
88
+ - New output MUST have a useful one- or two-sentence `summary`.
89
+ - Skill-created files use `source: manual`.
90
+ - Entry `workItemId` MUST match the active work item's `work.md`.
91
+ - Store outgoing typed links according to `LINKS.md`.
92
+ - A human-readable body citation may accompany a structured link but does not
93
+ replace it.
94
+ - On edits, preserve `id` and `createdAt`; update `updatedAt`.
95
+
96
+ ## Project timeline and compaction
97
+
98
+ The latest spine entry is the primary retrieval index.
99
+
100
+ - Recent entries may be listed individually.
101
+ - Older entries may be summarized into chronological milestone ranges when the
102
+ timeline threatens the 100-200 line target.
103
+ - Keep decisions, regressions, releases, and major architecture changes visible
104
+ as individual milestones regardless of age.
105
+ - Preserve entry numbers as recorded, including historical forks or gaps.
106
+ - Never delete or rewrite original entries as part of compaction.
107
+ - The timeline must still let a reader locate detailed source entries.
108
+
109
+ ## Evidence and safety
110
+
111
+ - Include real file paths, commit hashes, PRs, commands, and test counts when known.
112
+ - Do not fabricate line numbers, test results, commits, or links.
113
+ - Prefer summaries and diff statistics over full diffs.
114
+ - Truncate failure output to the smallest useful redacted excerpt.
115
+ - Follow `SAFETY.md` before every write.
116
+
117
+ ## Legacy entries
118
+
119
+ Markdown without frontmatter remains valid historical input. Infer metadata
120
+ best-effort from its directory, filename, and first heading. Do not block resume
121
+ or recall. Audit reports missing metadata; reconcile may add it conservatively
122
+ without rewriting the body or renumbering the entry.
123
+
124
+ ## Compactness
125
+
126
+ Target 100-200 lines for a spine entry. Supporting entries may be longer when
127
+ the topic requires it, but should synthesize rather than dump source material.
@@ -0,0 +1,96 @@
1
+ # Entry Link Rules
2
+
3
+ Links form a directed graph between any two journal entries. The source entry owns
4
+ the outgoing link in its frontmatter; incoming links are derived by scanning all
5
+ entries.
6
+
7
+ Read `.agents/rules/METADATA.md` before creating or modifying links.
8
+
9
+ ## Link shape
10
+
11
+ Store outgoing links in the source entry's frontmatter:
12
+
13
+ ```yaml
14
+ links:
15
+ - id: lnk_0197c4d2-7b20-7abc-8d2e-0123456789ab
16
+ fromEntryId: ent_0197c4d2-7b20-7abc-8d2e-0123456789ab
17
+ toEntryId: ent_0197c4d3-1010-7def-8abc-0123456789ab
18
+ relation: references
19
+ createdAt: "2026-06-29T12:05:00.000Z"
20
+ targetPath: ../_research/codebase-portable-journal-skill-improvements.md
21
+ ```
22
+
23
+ Canonical fields:
24
+
25
+ - `id`: stable `lnk_` UUIDv7
26
+ - `fromEntryId`: MUST equal the containing entry's `id`
27
+ - `toEntryId`: MUST equal the target entry's `id`
28
+ - `relation`: `references`, `supersedes`, or `relates_to`
29
+ - `createdAt`: canonical ISO-8601 UTC timestamp
30
+
31
+ Filesystem addition:
32
+
33
+ - `targetPath`: required relative path from the source file to the target file
34
+
35
+ Do not add `referencedBy`; it is derived from other entries' outgoing links.
36
+
37
+ ## Relation semantics
38
+
39
+ - `references`: source uses or cites target for context. This is the default.
40
+ - `supersedes`: source replaces or rolls back target. Direction is newer to older.
41
+ - `relates_to`: loose association or evolution when neither other relation fits.
42
+
43
+ The entry endpoints carry what they are through `entryType`; the relation says
44
+ why they are connected. Any entry type may link to any other entry type.
45
+
46
+ ## Invariants
47
+
48
+ - The tuple `(fromEntryId, toEntryId, relation)` MUST be unique.
49
+ - `fromEntryId` and `toEntryId` MUST resolve to existing entries.
50
+ - `targetPath` MUST resolve to the same entry as `toEntryId`.
51
+ - Paths MUST be relative, use `/`, and include the `.md` extension.
52
+ - IDs remain authoritative across renames; paths are the human/filesystem locator.
53
+ - A path-only Markdown citation does not replace the structured link.
54
+ - Do not create a reciprocal link unless it has independent directional meaning.
55
+ - Do not create self-links.
56
+
57
+ Cross-work-item links are allowed.
58
+ Use the correct relative path and target ID.
59
+
60
+ ## Spine and supporting entries
61
+
62
+ The role is derived from `entryType`:
63
+
64
+ - Spine: `plan`, `implementation`, `status`, `manual`
65
+ - Supporting: `research`, `doc`, `decision`
66
+
67
+ A supporting entry is orphaned when no entry links to it. Audit should flag an
68
+ orphan. Reconcile may add a link only when the source entry is unambiguous.
69
+
70
+ Typical relationships:
71
+
72
+ - plan `references` research
73
+ - implementation `references` a doc
74
+ - implementation `references` a decision
75
+ - decision `references` the research supporting its rationale
76
+ - newer decision `supersedes` an older decision
77
+ - entries in an evolution chain `relates_to` one another
78
+
79
+ ## Link maintenance
80
+
81
+ - Creating or removing a link updates the source entry's `updatedAt`.
82
+ - Renaming a target updates every matching `targetPath`, but preserves link and
83
+ entry IDs and `createdAt`.
84
+ - Removing an incorrect link removes only the link object. Never delete either
85
+ entry as a side effect.
86
+ - When a path is broken but `toEntryId` resolves uniquely, reconcile may repair
87
+ `targetPath`.
88
+ - When an ID or target is ambiguous, report it for human review.
89
+ - Duplicate entry numbers do not affect link identity and are never auto-fixed.
90
+
91
+ ## Legacy references
92
+
93
+ For legacy files, body references to `_research/*.md`, `docs/*.md`,
94
+ `decisions/*.md`, or another journal entry are evidence of a potentially missing structured link. Audit
95
+ reports the discrepancy. Reconcile may create the link only after resolving one
96
+ unique target and confirming the relation from context.