@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.
- package/README.md +195 -0
- package/dist/commands/apply.d.ts +2 -0
- package/dist/commands/apply.d.ts.map +1 -0
- package/dist/commands/apply.js +186 -0
- package/dist/commands/apply.js.map +1 -0
- package/dist/commands/capture.d.ts +5 -0
- package/dist/commands/capture.d.ts.map +1 -0
- package/dist/commands/capture.js +88 -0
- package/dist/commands/capture.js.map +1 -0
- package/dist/commands/diff.d.ts +2 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +43 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/ingest.d.ts +14 -0
- package/dist/commands/ingest.d.ts.map +1 -0
- package/dist/commands/ingest.js +111 -0
- package/dist/commands/ingest.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +165 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/layers.d.ts +2 -0
- package/dist/commands/layers.d.ts.map +1 -0
- package/dist/commands/layers.js +141 -0
- package/dist/commands/layers.js.map +1 -0
- package/dist/commands/onboard.d.ts +2 -0
- package/dist/commands/onboard.d.ts.map +1 -0
- package/dist/commands/onboard.js +275 -0
- package/dist/commands/onboard.js.map +1 -0
- package/dist/commands/run.d.ts +4 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +526 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +121 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +108 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/claude.d.ts +12 -0
- package/dist/lib/claude.d.ts.map +1 -0
- package/dist/lib/claude.js +70 -0
- package/dist/lib/claude.js.map +1 -0
- package/dist/lib/metrics.d.ts +29 -0
- package/dist/lib/metrics.d.ts.map +1 -0
- package/dist/lib/metrics.js +96 -0
- package/dist/lib/metrics.js.map +1 -0
- package/dist/lib/parser.d.ts +28 -0
- package/dist/lib/parser.d.ts.map +1 -0
- package/dist/lib/parser.js +226 -0
- package/dist/lib/parser.js.map +1 -0
- package/dist/lib/storage.d.ts +126 -0
- package/dist/lib/storage.d.ts.map +1 -0
- package/dist/lib/storage.js +201 -0
- package/dist/lib/storage.js.map +1 -0
- 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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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"}
|