@lcvbeek/patina 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 (58) hide show
  1. package/README.md +195 -0
  2. package/dist/commands/apply.d.ts +2 -0
  3. package/dist/commands/apply.d.ts.map +1 -0
  4. package/dist/commands/apply.js +186 -0
  5. package/dist/commands/apply.js.map +1 -0
  6. package/dist/commands/capture.d.ts +5 -0
  7. package/dist/commands/capture.d.ts.map +1 -0
  8. package/dist/commands/capture.js +88 -0
  9. package/dist/commands/capture.js.map +1 -0
  10. package/dist/commands/diff.d.ts +2 -0
  11. package/dist/commands/diff.d.ts.map +1 -0
  12. package/dist/commands/diff.js +43 -0
  13. package/dist/commands/diff.js.map +1 -0
  14. package/dist/commands/ingest.d.ts +14 -0
  15. package/dist/commands/ingest.d.ts.map +1 -0
  16. package/dist/commands/ingest.js +111 -0
  17. package/dist/commands/ingest.js.map +1 -0
  18. package/dist/commands/init.d.ts +2 -0
  19. package/dist/commands/init.d.ts.map +1 -0
  20. package/dist/commands/init.js +165 -0
  21. package/dist/commands/init.js.map +1 -0
  22. package/dist/commands/layers.d.ts +2 -0
  23. package/dist/commands/layers.d.ts.map +1 -0
  24. package/dist/commands/layers.js +141 -0
  25. package/dist/commands/layers.js.map +1 -0
  26. package/dist/commands/onboard.d.ts +2 -0
  27. package/dist/commands/onboard.d.ts.map +1 -0
  28. package/dist/commands/onboard.js +275 -0
  29. package/dist/commands/onboard.js.map +1 -0
  30. package/dist/commands/run.d.ts +4 -0
  31. package/dist/commands/run.d.ts.map +1 -0
  32. package/dist/commands/run.js +526 -0
  33. package/dist/commands/run.js.map +1 -0
  34. package/dist/commands/status.d.ts +2 -0
  35. package/dist/commands/status.d.ts.map +1 -0
  36. package/dist/commands/status.js +121 -0
  37. package/dist/commands/status.js.map +1 -0
  38. package/dist/index.d.ts +3 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +108 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/lib/claude.d.ts +12 -0
  43. package/dist/lib/claude.d.ts.map +1 -0
  44. package/dist/lib/claude.js +70 -0
  45. package/dist/lib/claude.js.map +1 -0
  46. package/dist/lib/metrics.d.ts +29 -0
  47. package/dist/lib/metrics.d.ts.map +1 -0
  48. package/dist/lib/metrics.js +96 -0
  49. package/dist/lib/metrics.js.map +1 -0
  50. package/dist/lib/parser.d.ts +28 -0
  51. package/dist/lib/parser.d.ts.map +1 -0
  52. package/dist/lib/parser.js +226 -0
  53. package/dist/lib/parser.js.map +1 -0
  54. package/dist/lib/storage.d.ts +126 -0
  55. package/dist/lib/storage.d.ts.map +1 -0
  56. package/dist/lib/storage.js +201 -0
  57. package/dist/lib/storage.js.map +1 -0
  58. package/package.json +45 -0
package/README.md ADDED
@@ -0,0 +1,195 @@
1
+ <img width="250" height="72" alt="Patina (2)" src="https://github.com/user-attachments/assets/77650975-b20b-4a65-b582-bb86ff6c2ae3" />
2
+
3
+ # Patina
4
+
5
+ Patina is what forms naturally when you keep working with AI. Each retro cycle deposits a thin layer — captured moments, reflection answers, Claude's synthesis, a proposed instruction change. Over time, `patina.md` builds up into something with real depth: a working record of how your team uses AI, versioned in git, shared by everyone including new hires.
6
+
7
+ ---
8
+
9
+ ## How is this different from Claude Code's built-in `/insights`?
10
+
11
+ `/insights` produces a personal HTML report in `~/.claude/` — useful analysis, but it belongs to one person and doesn't persist between sessions. Patina produces `patina.md`, a structured document that lives in your repo, is versioned with git, and accumulates layers across cycles. The goal isn't better analysis — it's a shared artifact your team actually owns and maintains together.
12
+
13
+ ---
14
+
15
+ ## The loop
16
+
17
+ ```
18
+ patina capture # anyone on the team, anytime
19
+ → records a notable moment to .patina/captures/
20
+ → committed to the repo, visible to everyone
21
+
22
+ patina run # when you're ready for a retro
23
+ → auto-ingests Claude Code JSONL logs
24
+ → loads all captures since the last cycle
25
+ → 6 reflection questions (~10 min)
26
+ → Claude synthesises session data + captures + your answers
27
+ → coaching insight + proposed instruction diff
28
+
29
+ patina diff # review the proposed change
30
+ patina buff # apply it to .patina/patina.md
31
+ ```
32
+
33
+ Next session, the whole team works from an updated set of shared instructions.
34
+
35
+ ---
36
+
37
+ ## Commands
38
+
39
+ | Command | What it does |
40
+ |---|---|
41
+ | `patina init` | Scaffold `.patina/` in the current directory, create `patina.md` |
42
+ | `patina capture [text]` | Capture a notable moment while it's fresh — feeds into the next retro |
43
+ | `patina run` | Full retro session — auto-ingests logs, loads captures, asks reflection questions, calls Claude for synthesis |
44
+ | `patina diff` | Review the proposed instruction change from the last `patina run` |
45
+ | `patina buff` | Apply the pending diff to `patina.md` (`patina apply` also works) |
46
+ | `patina status` | Show metrics: token spend, rework rate, tool usage, trends across cycles |
47
+ | `patina ingest` | Manually parse Claude Code logs (optional — `patina run` does this automatically) |
48
+
49
+ ### patina capture
50
+
51
+ ```bash
52
+ patina capture "agent almost pushed directly to main — need an approval gate rule"
53
+ patina capture --tag near-miss "agent almost pushed directly to main"
54
+ patina capture # interactive mode
55
+ ```
56
+
57
+ Tags: `near-miss` / `went-well` / `frustration` / `pattern` / `other`
58
+
59
+ Captures are written to `.patina/captures/` as individual JSON files (one per capture, to avoid merge conflicts) and committed to the repo. Author is read from `git config user.name`.
60
+
61
+ ---
62
+
63
+ ## What gets committed
64
+
65
+ `.patina/` is partially tracked:
66
+
67
+ | Path | Committed | Why |
68
+ |---|---|---|
69
+ | `.patina/patina.md` | ✓ | The shared AI operating document |
70
+ | `.patina/cycles/` | ✓ | Each layer — full cycle reports, the team's accumulated record |
71
+ | `.patina/captures/` | ✓ | In-the-moment observations from anyone on the team |
72
+ | `.patina/sessions/` | ✗ | Personal session data, machine-specific |
73
+ | `.patina/metrics.json` | ✗ | Derived from sessions, not a source of truth |
74
+ | `.patina/pending-diff.json` | ✗ | Ephemeral — consumed by `patina buff` |
75
+
76
+ ---
77
+
78
+ ## What `patina run` produces
79
+
80
+ - `.patina/cycles/YYYY-MM-DD.md` — full cycle report: metrics snapshot, identified patterns, coaching insight, proposed instruction diff, reflection answers
81
+ - `.patina/pending-diff.json` — the diff staged for `patina buff`
82
+ - An updated `.patina/patina.md` once you run `patina buff`
83
+
84
+ ---
85
+
86
+ ## What `patina.md` is
87
+
88
+ Your team's AI operating constitution. It has sections for working agreements, agent profiles, delegation patterns, an incident log, eval criteria, and an opportunity backlog. `patina buff` appends a new entry to the cycle history and inserts the proposed instruction into the right section.
89
+
90
+ The file is yours — edit it directly whenever you want. Patina treats it as the source of truth for how your team works with AI and passes it to Claude during synthesis.
91
+
92
+ ### How agents read it
93
+
94
+ `patina init` adds the following line to your project's `CLAUDE.md` (creating it if it doesn't exist):
95
+
96
+ ```
97
+ @.patina/patina.md
98
+ ```
99
+
100
+ Claude Code's `@filename` import syntax means every Claude Code session in the project automatically gets the contents of `patina.md` — no manual copying needed. When `patina buff` updates `patina.md`, Claude picks up the change in the next session.
101
+
102
+ If a `CLAUDE.md` already exists, `init` appends the import line without touching anything else.
103
+
104
+ ---
105
+
106
+ ## What gets tracked
107
+
108
+ Everything stays local. No data leaves your machine except what you choose to send to Claude via the `claude` CLI during `patina run`.
109
+
110
+ What gets ingested from your Claude Code logs:
111
+ - Session timestamps and project names
112
+ - Estimated token counts
113
+ - Tool call names and frequencies
114
+ - Whether a session contained rework (detected heuristically from the JSONL)
115
+
116
+ What is never sent to Claude:
117
+ - Raw session content or conversation transcripts
118
+ - Anything outside `.patina/`
119
+
120
+ ---
121
+
122
+ ## Requirements
123
+
124
+ - Node.js 18+
125
+ - Access to Claude via one of:
126
+ - **Claude Code CLI** (recommended) — install at [claude.ai/code](https://claude.ai/code), authenticate once, and Patina uses it automatically. Respects your existing plan including Claude Max.
127
+ - **Anthropic API key** — set `ANTHROPIC_API_KEY` in your environment. Patina falls back to this if the CLI isn't found. Billed separately per token.
128
+
129
+ ```bash
130
+ export ANTHROPIC_API_KEY=sk-ant-...
131
+ ```
132
+
133
+ ---
134
+
135
+ ## Install
136
+
137
+ ```bash
138
+ git clone https://github.com/lcvbeek/patina.git
139
+ cd patina
140
+ npm install
141
+ ```
142
+
143
+ Run via:
144
+
145
+ ```bash
146
+ npx tsx src/index.ts <command>
147
+ ```
148
+
149
+ Or add an alias:
150
+
151
+ ```bash
152
+ alias patina="npx tsx /path/to/patina/src/index.ts"
153
+ ```
154
+
155
+ Global install via `npm install -g` is coming once the CLI is stable.
156
+
157
+ ---
158
+
159
+ ## First run
160
+
161
+ ```bash
162
+ cd your-project
163
+ patina init # creates .patina/ and patina.md
164
+ patina run # first cycle — answer the reflection questions
165
+ patina diff # review what Claude proposed
166
+ patina buff # apply the change
167
+ git add .patina/patina.md .patina/cycles/ .patina/captures/
168
+ git commit -m "First patina cycle"
169
+ ```
170
+
171
+ If you have no prior Claude Code session data, `patina run` will still work — your reflection answers are the primary input for the first cycle.
172
+
173
+ ---
174
+
175
+ ## Early software
176
+
177
+ This is v0.1.0. It works, but expect rough edges:
178
+
179
+ - The `claude` CLI call in `patina run` has a 120-second timeout; if Claude is slow the command will fail (your reflection answers are saved to `.patina/pending-reflection.json` so you can retry without re-answering)
180
+ - Session ingestion parses Claude Code's JSONL format — if Anthropic changes that format, ingestion will break
181
+ - Token estimates are heuristic, not exact
182
+
183
+ If something breaks or the instruction diff Claude produces is bad, that's useful signal. Open an issue or message me directly.
184
+
185
+ ---
186
+
187
+ ## Design decisions worth knowing
188
+
189
+ **Why a living doc instead of CLAUDE.md?** `patina.md` is a structured format Patina can reliably read and append to. You can copy entries into your `CLAUDE.md` or `AGENTS.md` manually — that handoff is intentional for now.
190
+
191
+ **Why does it use the `claude` CLI instead of the API directly?** No separate API key, and it respects your existing Claude Code authentication and model access.
192
+
193
+ **Why six questions?** The reflection questions supplement sparse log data and give Claude qualitative signal the JSONL doesn't contain — what felt frustrating, what nearly went wrong. Both matter for the synthesis.
194
+
195
+ **Why individual capture files instead of appending to one file?** Multiple teammates capturing on the same day would produce merge conflicts in a single file. One file per capture means clean parallel commits.
@@ -0,0 +1,2 @@
1
+ export declare function applyCommand(): Promise<void>;
2
+ //# sourceMappingURL=apply.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../src/commands/apply.ts"],"names":[],"mappings":"AAkJA,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CA8ElD"}
@@ -0,0 +1,186 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import readline from 'readline';
4
+ import { assertInitialised, readPendingDiff, LIVING_DOC_FILE, PENDING_DIFF_FILE, METRICS_FILE, } from '../lib/storage.js';
5
+ const isTTY = process.stdout.isTTY;
6
+ function bold(s) { return isTTY ? `\x1b[1m${s}\x1b[0m` : s; }
7
+ function dim(s) { return isTTY ? `\x1b[2m${s}\x1b[0m` : s; }
8
+ function green(s) { return isTTY ? `\x1b[32m${s}\x1b[0m` : s; }
9
+ function cyan(s) { return isTTY ? `\x1b[36m${s}\x1b[0m` : s; }
10
+ function yellow(s) { return isTTY ? `\x1b[33m${s}\x1b[0m` : s; }
11
+ function red(s) { return isTTY ? `\x1b[31m${s}\x1b[0m` : s; }
12
+ function hr(len = 60) { return dim('─'.repeat(len)); }
13
+ function confirm(question) {
14
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
15
+ return new Promise(resolve => {
16
+ rl.question(question, answer => {
17
+ rl.close();
18
+ resolve(answer.trim().toLowerCase().startsWith('y'));
19
+ });
20
+ });
21
+ }
22
+ /**
23
+ * Find the section header in patina.md and insert the diff text after it.
24
+ * Falls back to appending at the end of the section if the header isn't found exactly.
25
+ */
26
+ function applyDiffToDoc(content, section, diffText) {
27
+ const lines = content.split('\n');
28
+ // Try to find the matching section header (## 1. Working Agreements, etc.)
29
+ // Match by number prefix or by full title (case-insensitive)
30
+ const sectionLower = section.toLowerCase().trim();
31
+ let sectionIdx = -1;
32
+ for (let i = 0; i < lines.length; i++) {
33
+ const line = lines[i];
34
+ if (line.startsWith('## ') && line.toLowerCase().includes(sectionLower.replace(/^\d+\.\s*/, ''))) {
35
+ sectionIdx = i;
36
+ break;
37
+ }
38
+ // Also match by number alone: "## 1." prefix
39
+ const numMatch = sectionLower.match(/^(\d+)\./);
40
+ if (numMatch && line.startsWith(`## ${numMatch[1]}.`)) {
41
+ sectionIdx = i;
42
+ break;
43
+ }
44
+ }
45
+ if (sectionIdx === -1) {
46
+ // Section not found — append before the last --- or at end
47
+ const lastHrIdx = lines.lastIndexOf('---');
48
+ const insertAt = lastHrIdx !== -1 ? lastHrIdx : lines.length;
49
+ const newLines = [
50
+ ...lines.slice(0, insertAt),
51
+ '',
52
+ `<!-- Added by patina apply ${new Date().toISOString().slice(0, 10)} -->`,
53
+ ...diffText.split('\n'),
54
+ '',
55
+ ...lines.slice(insertAt),
56
+ ];
57
+ return newLines.join('\n');
58
+ }
59
+ // Find the end of this section (next ## heading or end of file)
60
+ let sectionEnd = lines.length;
61
+ for (let i = sectionIdx + 1; i < lines.length; i++) {
62
+ if (lines[i].startsWith('## ')) {
63
+ sectionEnd = i;
64
+ break;
65
+ }
66
+ }
67
+ // Find the last non-empty, non-comment line in the section to insert after
68
+ let insertAfter = sectionEnd - 1;
69
+ for (let i = sectionEnd - 1; i > sectionIdx; i--) {
70
+ if (lines[i].trim() !== '' && !lines[i].trim().startsWith('<!--')) {
71
+ insertAfter = i;
72
+ break;
73
+ }
74
+ }
75
+ const newLines = [
76
+ ...lines.slice(0, insertAfter + 1),
77
+ ...diffText.split('\n'),
78
+ '',
79
+ ...lines.slice(insertAfter + 1),
80
+ ];
81
+ return newLines.join('\n');
82
+ }
83
+ const CYCLE_HISTORY_CAP = 5;
84
+ /**
85
+ * Update the Retro Cycle History table in patina.md.
86
+ * Keeps at most CYCLE_HISTORY_CAP rows — oldest rows are dropped when the cap
87
+ * is exceeded. Full cycle detail is preserved in .patina/cycles/.
88
+ */
89
+ function updateCycleHistory(content, insight, changeDesc) {
90
+ const today = new Date().toISOString().slice(0, 10);
91
+ const cycleCount = (content.match(/^\| \d+/gm) || []).length + 1;
92
+ const newRow = `| ${cycleCount} | ${today} | ${insight.slice(0, 60)}… | ${changeDesc.slice(0, 50)}… |`;
93
+ const placeholder = '| — | — | — | — |';
94
+ if (content.includes(placeholder)) {
95
+ return content.replace(placeholder, newRow);
96
+ }
97
+ // Append a new row before the end of the history table
98
+ const historyHeader = '## 7. Retro Cycle History';
99
+ const historyIdx = content.indexOf(historyHeader);
100
+ if (historyIdx === -1)
101
+ return content;
102
+ const after = content.slice(historyIdx);
103
+ const lastRowMatch = after.match(/(\| .+ \|\n?)(?!.*\| .+ \|)/s);
104
+ if (!lastRowMatch)
105
+ return content;
106
+ const insertPos = historyIdx + after.indexOf(lastRowMatch[0]) + lastRowMatch[0].length;
107
+ let updated = content.slice(0, insertPos) + newRow + '\n' + content.slice(insertPos);
108
+ // Trim oldest rows if over cap — find all data rows in Section 7 and drop from the top
109
+ const headerMatch = updated.match(/## 7\. Retro Cycle History[\s\S]*?\| Cycle \| Date \| Key Insight \| Change Made \|\n\|[-| ]+\|\n/);
110
+ if (headerMatch) {
111
+ const tableStart = updated.indexOf(headerMatch[0]) + headerMatch[0].length;
112
+ const afterTable = updated.slice(tableStart);
113
+ const rowRegex = /^\| \d+ \|.+\|\n?/gm;
114
+ const rows = [...afterTable.matchAll(rowRegex)];
115
+ if (rows.length > CYCLE_HISTORY_CAP) {
116
+ const excess = rows.length - CYCLE_HISTORY_CAP;
117
+ const firstKeep = rows[excess].index;
118
+ updated = updated.slice(0, tableStart) + afterTable.slice(firstKeep);
119
+ }
120
+ }
121
+ return updated;
122
+ }
123
+ export async function applyCommand() {
124
+ assertInitialised();
125
+ const cwd = process.cwd();
126
+ const pending = readPendingDiff(cwd);
127
+ if (!pending) {
128
+ console.log(yellow('No pending diff found.'));
129
+ console.log(dim('Run `patina run` first, then `patina diff` to review.'));
130
+ return;
131
+ }
132
+ const livingDocPath = path.join(cwd, LIVING_DOC_FILE);
133
+ console.log(`\n${bold('patina apply')} — applying instruction change`);
134
+ console.log(hr());
135
+ console.log(` Section : ${cyan(pending.section)}`);
136
+ console.log(` Adding :\n`);
137
+ pending.diff.split('\n').forEach(line => {
138
+ console.log(` ${green('+ ' + line)}`);
139
+ });
140
+ console.log();
141
+ const ok = await confirm(`Apply this change to ${LIVING_DOC_FILE}? [y/N] `);
142
+ if (!ok) {
143
+ console.log(dim('Aborted. Pending diff unchanged.'));
144
+ return;
145
+ }
146
+ // Read or create living doc
147
+ let content = fs.existsSync(livingDocPath)
148
+ ? fs.readFileSync(livingDocPath, 'utf-8')
149
+ : '# AI Operating Constitution\n\n## 1. Working Agreements\n\n';
150
+ // Apply the diff
151
+ content = applyDiffToDoc(content, pending.section, pending.diff);
152
+ // Update cycle history
153
+ content = updateCycleHistory(content, pending.rationale, pending.diff.slice(0, 50));
154
+ // Update the "last updated" timestamp
155
+ content = content.replace(/> Maintained by `patina`\. Last updated: .+/, `> Maintained by \`patina\`. Last updated: ${new Date().toISOString().slice(0, 10)}`);
156
+ fs.writeFileSync(livingDocPath, content, 'utf-8');
157
+ // Warn if patina.md is getting large
158
+ const SIZE_WARN_BYTES = 5 * 1024; // 5KB
159
+ if (Buffer.byteLength(content, 'utf-8') > SIZE_WARN_BYTES) {
160
+ console.log(yellow('⚠') + ` patina.md is over 5KB — consider reviewing sections for outdated entries.`);
161
+ }
162
+ // Clear pending diff
163
+ const pendingDiffPath = path.join(cwd, PENDING_DIFF_FILE);
164
+ if (fs.existsSync(pendingDiffPath))
165
+ fs.unlinkSync(pendingDiffPath);
166
+ // Update metrics.json with cycle count
167
+ const metricsPath = path.join(cwd, METRICS_FILE);
168
+ if (fs.existsSync(metricsPath)) {
169
+ try {
170
+ const metrics = JSON.parse(fs.readFileSync(metricsPath, 'utf-8'));
171
+ metrics.cycles_completed = (metrics.cycles_completed || 0) + 1;
172
+ metrics.last_apply = new Date().toISOString();
173
+ fs.writeFileSync(metricsPath, JSON.stringify(metrics, null, 2) + '\n', 'utf-8');
174
+ }
175
+ catch { /* non-fatal */ }
176
+ }
177
+ console.log();
178
+ console.log(green('✓') + ` Applied to ${bold(LIVING_DOC_FILE)}`);
179
+ console.log(green('✓') + ' Cycle history updated');
180
+ console.log(green('✓') + ' Pending diff cleared');
181
+ console.log();
182
+ console.log(dim(`Next: review ${LIVING_DOC_FILE} to confirm the change looks right.`));
183
+ console.log(dim('Run `patina ingest` at the start of your next cycle to continue tracking.'));
184
+ console.log();
185
+ }
186
+ //# sourceMappingURL=apply.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply.js","sourceRoot":"","sources":["../../src/commands/apply.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,QAAQ,MAAM,UAAU,CAAC;AAChC,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,YAAY,GACb,MAAM,mBAAmB,CAAC;AAE3B,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC;AACnC,SAAS,IAAI,CAAC,CAAS,IAAK,OAAO,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACtE,SAAS,GAAG,CAAC,CAAS,IAAM,OAAO,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACtE,SAAS,KAAK,CAAC,CAAS,IAAI,OAAO,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACvE,SAAS,IAAI,CAAC,CAAS,IAAK,OAAO,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACvE,SAAS,MAAM,CAAC,CAAS,IAAG,OAAO,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACvE,SAAS,GAAG,CAAC,CAAS,IAAM,OAAO,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACvE,SAAS,EAAE,CAAC,GAAG,GAAG,EAAE,IAAQ,OAAO,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AAE1D,SAAS,OAAO,CAAC,QAAgB;IAC/B,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACtF,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;QAC3B,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE;YAC7B,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;QACvD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,SAAS,cAAc,CAAC,OAAe,EAAE,OAAe,EAAE,QAAgB;IACxE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAElC,2EAA2E;IAC3E,6DAA6D;IAC7D,MAAM,YAAY,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;IAClD,IAAI,UAAU,GAAG,CAAC,CAAC,CAAC;IAEpB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC;YACjG,UAAU,GAAG,CAAC,CAAC;YACf,MAAM;QACR,CAAC;QACD,6CAA6C;QAC7C,MAAM,QAAQ,GAAG,YAAY,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAChD,IAAI,QAAQ,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;YACtD,UAAU,GAAG,CAAC,CAAC;YACf,MAAM;QACR,CAAC;IACH,CAAC;IAED,IAAI,UAAU,KAAK,CAAC,CAAC,EAAE,CAAC;QACtB,2DAA2D;QAC3D,MAAM,SAAS,GAAG,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAC3C,MAAM,QAAQ,GAAG,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC;QAC7D,MAAM,QAAQ,GAAG;YACf,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC;YAC3B,EAAE;YACF,8BAA8B,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM;YACzE,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC;YACvB,EAAE;YACF,GAAG,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC;SACzB,CAAC;QACF,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;IAED,gEAAgE;IAChE,IAAI,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC;IAC9B,KAAK,IAAI,CAAC,GAAG,UAAU,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACnD,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;YAC/B,UAAU,GAAG,CAAC,CAAC;YACf,MAAM;QACR,CAAC;IACH,CAAC;IAED,2EAA2E;IAC3E,IAAI,WAAW,GAAG,UAAU,GAAG,CAAC,CAAC;IACjC,KAAK,IAAI,CAAC,GAAG,UAAU,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC,EAAE,EAAE,CAAC;QACjD,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YAClE,WAAW,GAAG,CAAC,CAAC;YAChB,MAAM;QACR,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAAG;QACf,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,GAAG,CAAC,CAAC;QAClC,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC;QACvB,EAAE;QACF,GAAG,KAAK,CAAC,KAAK,CAAC,WAAW,GAAG,CAAC,CAAC;KAChC,CAAC;IAEF,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,iBAAiB,GAAG,CAAC,CAAC;AAE5B;;;;GAIG;AACH,SAAS,kBAAkB,CAAC,OAAe,EAAE,OAAe,EAAE,UAAkB;IAC9E,MAAM,KAAK,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACpD,MAAM,UAAU,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;IACjE,MAAM,MAAM,GAAG,KAAK,UAAU,MAAM,KAAK,MAAM,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC;IACvG,MAAM,WAAW,GAAG,mBAAmB,CAAC;IAExC,IAAI,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QAClC,OAAO,OAAO,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAC9C,CAAC;IAED,uDAAuD;IACvD,MAAM,aAAa,GAAG,2BAA2B,CAAC;IAClD,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IAClD,IAAI,UAAU,KAAK,CAAC,CAAC;QAAE,OAAO,OAAO,CAAC;IAEtC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IACxC,MAAM,YAAY,GAAG,KAAK,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;IACjE,IAAI,CAAC,YAAY;QAAE,OAAO,OAAO,CAAC;IAElC,MAAM,SAAS,GAAG,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IACvF,IAAI,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,GAAG,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAErF,uFAAuF;IACvF,MAAM,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC,mGAAmG,CAAC,CAAC;IACvI,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;QAC3E,MAAM,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAC7C,MAAM,QAAQ,GAAG,qBAAqB,CAAC;QACvC,MAAM,IAAI,GAAG,CAAC,GAAG,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;QAEhD,IAAI,IAAI,CAAC,MAAM,GAAG,iBAAiB,EAAE,CAAC;YACpC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,GAAG,iBAAiB,CAAC;YAC/C,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,KAAM,CAAC;YACtC,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QACvE,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,iBAAiB,EAAE,CAAC;IACpB,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAE1B,MAAM,OAAO,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IAErC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,wBAAwB,CAAC,CAAC,CAAC;QAC9C,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,uDAAuD,CAAC,CAAC,CAAC;QAC1E,OAAO;IACT,CAAC;IAED,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;IAEtD,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,cAAc,CAAC,gCAAgC,CAAC,CAAC;IACvE,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;IAClB,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACpD,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IAC7B,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;QACtC,OAAO,CAAC,GAAG,CAAC,OAAO,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,GAAG,EAAE,CAAC;IAEd,MAAM,EAAE,GAAG,MAAM,OAAO,CAAC,wBAAwB,eAAe,UAAU,CAAC,CAAC;IAE5E,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC,CAAC;QACrD,OAAO;IACT,CAAC;IAED,4BAA4B;IAC5B,IAAI,OAAO,GAAG,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC;QACxC,CAAC,CAAC,EAAE,CAAC,YAAY,CAAC,aAAa,EAAE,OAAO,CAAC;QACzC,CAAC,CAAC,6DAA6D,CAAC;IAElE,iBAAiB;IACjB,OAAO,GAAG,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjE,uBAAuB;IACvB,OAAO,GAAG,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IAEpF,sCAAsC;IACtC,OAAO,GAAG,OAAO,CAAC,OAAO,CACvB,6CAA6C,EAC7C,6CAA6C,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CACrF,CAAC;IAEF,EAAE,CAAC,aAAa,CAAC,aAAa,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAElD,qCAAqC;IACrC,MAAM,eAAe,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,MAAM;IACxC,IAAI,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,eAAe,EAAE,CAAC;QAC1D,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,6EAA6E,CAAC,CAAC;IAC3G,CAAC;IAED,qBAAqB;IACrB,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAC;IAC1D,IAAI,EAAE,CAAC,UAAU,CAAC,eAAe,CAAC;QAAE,EAAE,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;IAEnE,uCAAuC;IACvC,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;IACjD,IAAI,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAC/B,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAA4B,CAAC;YAC7F,OAAO,CAAC,gBAAgB,GAAG,CAAE,OAAO,CAAC,gBAA2B,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;YAC3E,OAAO,CAAC,UAAU,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YAC9C,EAAE,CAAC,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;QAClF,CAAC;QAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC;IAC7B,CAAC;IAED,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,eAAe,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC;IACjE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,wBAAwB,CAAC,CAAC;IACnD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,uBAAuB,CAAC,CAAC;IAClD,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,gBAAgB,eAAe,qCAAqC,CAAC,CAAC,CAAC;IACvF,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,2EAA2E,CAAC,CAAC,CAAC;IAC9F,OAAO,CAAC,GAAG,EAAE,CAAC;AAChB,CAAC"}
@@ -0,0 +1,5 @@
1
+ export interface CaptureOptions {
2
+ tag?: string;
3
+ }
4
+ export declare function captureCommand(text: string | undefined, options: CaptureOptions): Promise<void>;
5
+ //# sourceMappingURL=capture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../../src/commands/capture.ts"],"names":[],"mappings":"AA0CA,MAAM,WAAW,cAAc;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,IAAI,CAAC,CA6Ef"}
@@ -0,0 +1,88 @@
1
+ import { spawnSync } from 'child_process';
2
+ import readline from 'readline';
3
+ import { assertInitialised, writeCapture, CAPTURE_TAGS, } from '../lib/storage.js';
4
+ // ---------------------------------------------------------------------------
5
+ // ANSI helpers
6
+ // ---------------------------------------------------------------------------
7
+ const isTTY = process.stdout.isTTY;
8
+ function bold(s) { return isTTY ? `\x1b[1m${s}\x1b[0m` : s; }
9
+ function dim(s) { return isTTY ? `\x1b[2m${s}\x1b[0m` : s; }
10
+ function green(s) { return isTTY ? `\x1b[32m${s}\x1b[0m` : s; }
11
+ function cyan(s) { return isTTY ? `\x1b[36m${s}\x1b[0m` : s; }
12
+ // ---------------------------------------------------------------------------
13
+ // Helpers
14
+ // ---------------------------------------------------------------------------
15
+ function getGitAuthor() {
16
+ const result = spawnSync('git', ['config', 'user.name'], { encoding: 'utf8' });
17
+ if (result.status === 0 && result.stdout.trim()) {
18
+ return result.stdout.trim();
19
+ }
20
+ return process.env.USER ?? 'unknown';
21
+ }
22
+ function generateId(now) {
23
+ const ts = now.toISOString().replace(/[:.]/g, '-');
24
+ const rand = Math.random().toString(36).slice(2, 6);
25
+ return `${ts}-${rand}`;
26
+ }
27
+ export async function captureCommand(text, options) {
28
+ assertInitialised();
29
+ let captureText = text?.trim();
30
+ let captureTag;
31
+ // Validate tag option if provided
32
+ if (options.tag) {
33
+ if (!CAPTURE_TAGS.includes(options.tag)) {
34
+ console.error(`Invalid tag "${options.tag}". Valid tags: ${CAPTURE_TAGS.join(', ')}`);
35
+ process.exit(1);
36
+ }
37
+ captureTag = options.tag;
38
+ }
39
+ // Interactive mode when no inline text given
40
+ if (!captureText) {
41
+ const rl = readline.createInterface({
42
+ input: process.stdin,
43
+ output: process.stdout,
44
+ terminal: isTTY,
45
+ });
46
+ captureText = await new Promise((resolve) => {
47
+ rl.question(`${bold('What happened?')}\n> `, (answer) => {
48
+ resolve(answer.trim());
49
+ });
50
+ });
51
+ if (!captureText) {
52
+ rl.close();
53
+ console.log(dim('Nothing captured.'));
54
+ return;
55
+ }
56
+ if (!captureTag) {
57
+ const tagAnswer = await new Promise((resolve) => {
58
+ rl.question(`\n${bold('Tag')} ${dim(`(${CAPTURE_TAGS.join(' / ')} — Enter to skip)`)}\n> `, (answer) => resolve(answer.trim().toLowerCase()));
59
+ });
60
+ if (CAPTURE_TAGS.includes(tagAnswer)) {
61
+ captureTag = tagAnswer;
62
+ }
63
+ }
64
+ rl.close();
65
+ }
66
+ const now = new Date();
67
+ const capture = {
68
+ id: generateId(now),
69
+ text: captureText,
70
+ tag: captureTag,
71
+ author: getGitAuthor(),
72
+ timestamp: now.toISOString(),
73
+ };
74
+ writeCapture(capture);
75
+ const tagLabel = captureTag ? ` ${cyan(`[${captureTag}]`)}` : '';
76
+ const when = now.toLocaleDateString('en-US', {
77
+ month: 'short',
78
+ day: 'numeric',
79
+ hour: '2-digit',
80
+ minute: '2-digit',
81
+ });
82
+ console.log();
83
+ console.log(`${green('✓')} Captured${tagLabel}: ${captureText}`);
84
+ console.log(dim(` by ${capture.author} · ${when}`));
85
+ console.log(dim(' Feeds into your next `patina run`.'));
86
+ console.log();
87
+ }
88
+ //# sourceMappingURL=capture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture.js","sourceRoot":"","sources":["../../src/commands/capture.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function diffCommand(): Promise<void>;
2
+ //# sourceMappingURL=diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/commands/diff.ts"],"names":[],"mappings":"AAgBA,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAsCjD"}
@@ -0,0 +1,43 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { assertInitialised, readPendingDiff, LIVING_DOC_FILE, } from '../lib/storage.js';
4
+ const isTTY = process.stdout.isTTY;
5
+ function bold(s) { return isTTY ? `\x1b[1m${s}\x1b[0m` : s; }
6
+ function dim(s) { return isTTY ? `\x1b[2m${s}\x1b[0m` : s; }
7
+ function green(s) { return isTTY ? `\x1b[32m${s}\x1b[0m` : s; }
8
+ function cyan(s) { return isTTY ? `\x1b[36m${s}\x1b[0m` : s; }
9
+ function yellow(s) { return isTTY ? `\x1b[33m${s}\x1b[0m` : s; }
10
+ function hr(len = 60) { return dim('─'.repeat(len)); }
11
+ export async function diffCommand() {
12
+ assertInitialised();
13
+ const cwd = process.cwd();
14
+ const pending = readPendingDiff(cwd);
15
+ if (!pending) {
16
+ console.log(yellow('No pending diff found.'));
17
+ console.log(dim('Run `patina run` first to generate a retrospective.'));
18
+ return;
19
+ }
20
+ const livingDocPath = path.join(cwd, LIVING_DOC_FILE);
21
+ const livingDocExists = fs.existsSync(livingDocPath);
22
+ console.log(`\n${bold('patina diff')} — proposed instruction change`);
23
+ console.log(hr());
24
+ console.log(` Generated : ${dim(new Date(pending.timestamp).toLocaleString())}`);
25
+ console.log(` Target : ${cyan(livingDocExists ? LIVING_DOC_FILE : LIVING_DOC_FILE + ' (will be created)')}`);
26
+ console.log(` Section : ${cyan(pending.section)}`);
27
+ console.log();
28
+ console.log(bold('Rationale'));
29
+ console.log(hr());
30
+ console.log(` ${pending.rationale}`);
31
+ console.log();
32
+ console.log(bold('Proposed addition'));
33
+ console.log(hr());
34
+ pending.diff.split('\n').forEach(line => {
35
+ console.log(` ${green('+ ' + line)}`);
36
+ });
37
+ console.log();
38
+ console.log(hr());
39
+ console.log(`Run ${cyan('`patina buff`')} to apply this change to ${LIVING_DOC_FILE}.`);
40
+ console.log(`Delete ${dim('.patina/pending-diff.json')} to discard it.`);
41
+ console.log();
42
+ }
43
+ //# sourceMappingURL=diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/commands/diff.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ export interface IngestOptions {
2
+ claudeDir?: string;
3
+ verbose?: boolean;
4
+ }
5
+ /**
6
+ * Core ingest logic — returns counts. Used by both ingestCommand and auto-ingest in patina run.
7
+ */
8
+ export declare function runIngest(options?: IngestOptions): {
9
+ ingested: number;
10
+ skipped: number;
11
+ errors: number;
12
+ };
13
+ export declare function ingestCommand(options?: IngestOptions): Promise<void>;
14
+ //# sourceMappingURL=ingest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingest.d.ts","sourceRoot":"","sources":["../../src/commands/ingest.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,OAAO,GAAE,aAAkB,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CA0C5G;AAED,wBAAsB,aAAa,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6E9E"}