tink-harness 1.10.0 → 1.11.1
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/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +11 -0
- package/README.ko.md +6 -6
- package/README.md +7 -7
- package/VERSIONING.md +1 -1
- package/bin/install.js +145 -19
- package/commands/list.md +1 -1
- package/commands/update.md +5 -3
- package/package.json +1 -1
- package/templates/claude/commands/tink/list.md +1 -1
- package/templates/claude/commands/tink/update.md +5 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Tink are tracked here.
|
|
4
4
|
|
|
5
|
+
## [1.11.1] - 2026-06-12
|
|
6
|
+
|
|
7
|
+
- Installer component labels clarified after user confusion: "Claude Code commands (/tink:*)" and "Claude Code skill (operating rules)" now state their role and install path in the label/hint (`.claude/commands/tink/` vs `.claude/skills/tink/`), and Codex skills explicitly say `~/.codex/skills/ (CODEX_HOME)` - the two Claude items no longer look like duplicates, and the Codex item can't be mistaken for a Claude one.
|
|
8
|
+
|
|
9
|
+
## [1.11.0] - 2026-06-12
|
|
10
|
+
|
|
11
|
+
- **Fixed: update wiped run history.** `.tink/maintenance/` record files (`ledger.jsonl`, `friction.jsonl`, `weave-queue.json`) were in the always-overwrite set, so every npx `update` replaced the user's approval ledger and weave/friction signals with empty seeds - silently resetting dashboard usage history. They are now seed-only: created when missing, never overwritten.
|
|
12
|
+
- Fixed: update resurrected harnesses the user removed via an approved `/tink:frog` operation. The updater now reads `ledger.jsonl` frog entries and skips re-creating those files and index entries (other missing defaults are still restored; `--force` restores everything).
|
|
13
|
+
- Fixed: `dashboard` reported "opened in browser" even when the opener failed (e.g. no `xdg-open`); it now detects failure and tells you to open the file manually. Also fixed the installer's harness count including the human catalog file, and a `/tink:list` example that contradicted its own category rules.
|
|
14
|
+
- New `dashboard` subcommand: `npx tink-harness dashboard` generates the harness health report (lifecycle summary + HTML) from local `.tink` records and opens it in the default browser - no more memorizing the two `node .tink/tools/...` commands. `--no-open` generates the file only. Falls back to the packaged tools when `.tink/tools/` is missing, and finds `.tink` in the current or home directory.
|
|
15
|
+
|
|
5
16
|
## [1.10.0] - 2026-06-12
|
|
6
17
|
|
|
7
18
|
- update: previously the npx `update` reset install scope and git policy to defaults; it now reuses the choices stored at install time (`.tink/config.json` gains `git_policy`). Choosing "커밋 안 함" (commit no .tink files) now means `.gitignore` is never created or edited - by install or by update - and a legacy whole-directory `.tink/` ignore line is left untouched.
|
package/README.ko.md
CHANGED
|
@@ -10,7 +10,7 @@ Tink는 사소하지 않은 모든 에이전트 작업을 눈에 보이는 파
|
|
|
10
10
|
|
|
11
11
|
<sub>Claude Code와 Codex를 위한 작은 하네스 레이어</sub>
|
|
12
12
|
|
|
13
|
-
**최신 패키지:** v1.
|
|
13
|
+
**최신 패키지:** v1.11.1 — 한 줄 대시보드: `npx tink-harness dashboard`로 하네스 건강 리포트를 만들고 브라우저로 바로 엽니다. update가 승인 이력·신호 기록을 지우던 심각한 버그를 고쳤고, 설치 항목 라벨(명령 vs skill, 설치 경로 표기)도 명확해졌습니다. 전체 변경 이력은 [CHANGELOG](CHANGELOG.md)를 확인하세요.
|
|
14
14
|
|
|
15
15
|
[English](README.md) · **한국어** · [변경 이력](CHANGELOG.md)
|
|
16
16
|
|
|
@@ -101,14 +101,14 @@ $tink:cast 인증 모듈 리팩터링 # Codex
|
|
|
101
101
|
|
|
102
102
|
## 하네스 건강을 눈으로 확인
|
|
103
103
|
|
|
104
|
-
몇 번의 run이 쌓이면,
|
|
104
|
+
몇 번의 run이 쌓이면, 명령 하나로 기록을 로컬 대시보드로 만들어 브라우저까지 열어 줍니다:
|
|
105
105
|
|
|
106
106
|
```bash
|
|
107
|
-
|
|
108
|
-
node .tink/tools/render-harness-health-report.mjs
|
|
109
|
-
# .tink/maintenance/harness-health-report.html 열기
|
|
107
|
+
npx tink-harness dashboard # 파일만 만들려면 --no-open 추가
|
|
110
108
|
```
|
|
111
109
|
|
|
110
|
+
내부적으로는 읽기 전용 helper 두 개(`node .tink/tools/generate-harness-lifecycle-summary.mjs` → `node .tink/tools/render-harness-health-report.mjs`)를 실행한 뒤 `.tink/maintenance/harness-health-report.html`을 엽니다.
|
|
111
|
+
|
|
112
112
|

|
|
113
113
|
|
|
114
114
|
<sub>당신의 워크플로와 맞는다면, ⭐ 하나가 다른 개발자들이 찾는 데 도움이 됩니다.</sub>
|
|
@@ -147,7 +147,7 @@ Standalone / Codex:
|
|
|
147
147
|
npx tink-harness@latest update
|
|
148
148
|
```
|
|
149
149
|
|
|
150
|
-
업데이트는 질문 하나 — 어떤 agent surface를 갱신할지 — 만 묻고 나머지는 자동으로 처리합니다. 언어·설치 범위·git 정책은 설치 때 선택한 값을 그대로 재사용하며, ".tink 커밋 안 함"을 선택했다면 업데이트가 `.gitignore`를 절대 건드리지 않습니다. Tink가 관리하는 파일(commands, skills,
|
|
150
|
+
업데이트는 질문 하나 — 어떤 agent surface를 갱신할지 — 만 묻고 나머지는 자동으로 처리합니다. 언어·설치 범위·git 정책은 설치 때 선택한 값을 그대로 재사용하며, ".tink 커밋 안 함"을 선택했다면 업데이트가 `.gitignore`를 절대 건드리지 않습니다. Tink가 관리하는 파일(commands, skills, 런타임 tools)은 항상 최신으로 덮어쓰고, 사용자가 수정한 하네스·메모리·설정과 `.tink/maintenance/`의 모든 기록(ledger 등)은 보존합니다.
|
|
151
151
|
|
|
152
152
|
`CODEX_HOME`을 지정하지 않으면 Windows에서는 `%USERPROFILE%\.codex`, macOS/Linux에서는 `~/.codex`에 Codex skill이 설치됩니다.
|
|
153
153
|
|
package/README.md
CHANGED
|
@@ -17,14 +17,14 @@
|
|
|
17
17
|
<p><sub>A small harness layer for Claude Code and Codex</sub></p>
|
|
18
18
|
|
|
19
19
|
<p>
|
|
20
|
-
<a href="https://github.com/dotoricode/tink-harness/releases/tag/v1.
|
|
20
|
+
<a href="https://github.com/dotoricode/tink-harness/releases/tag/v1.11.1"><img src="https://img.shields.io/github/v/release/dotoricode/tink-harness?label=release&color=2ea44f" alt="GitHub release"></a>
|
|
21
21
|
<a href="https://www.npmjs.com/package/tink-harness"><img src="https://img.shields.io/npm/v/tink-harness?label=npm&color=cb3837" alt="npm version"></a>
|
|
22
22
|
<a href="https://github.com/dotoricode/tink-harness/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/dotoricode/tink-harness/ci.yml?branch=main&label=ci" alt="CI"></a>
|
|
23
23
|
<a href="https://github.com/dotoricode/tink-harness/blob/main/LICENSE"><img src="https://img.shields.io/github/license/dotoricode/tink-harness" alt="License"></a>
|
|
24
24
|
<a href="https://github.com/dotoricode/tink-harness/stargazers"><img src="https://img.shields.io/github/stars/dotoricode/tink-harness?style=social" alt="GitHub stars"></a>
|
|
25
25
|
</p>
|
|
26
26
|
|
|
27
|
-
<p><strong>Latest package:</strong> v1.
|
|
27
|
+
<p><strong>Latest package:</strong> v1.11.1 - One-line dashboard: <code>npx tink-harness dashboard</code> generates the harness health report and opens it in your browser. Also fixes a serious update bug that wiped approval-ledger and signal history, and clarifies the installer component labels (commands vs skill, with install paths). See <a href="CHANGELOG.md">CHANGELOG</a> for release history.</p>
|
|
28
28
|
|
|
29
29
|
**English** · [한국어](README.ko.md) · [Changelog](CHANGELOG.md)
|
|
30
30
|
|
|
@@ -115,14 +115,14 @@ $tink:cast refactor the auth module # Codex
|
|
|
115
115
|
|
|
116
116
|
## See your harness health
|
|
117
117
|
|
|
118
|
-
After a few runs,
|
|
118
|
+
After a few runs, one command turns your records into a local dashboard and opens it in your browser:
|
|
119
119
|
|
|
120
120
|
```bash
|
|
121
|
-
|
|
122
|
-
node .tink/tools/render-harness-health-report.mjs
|
|
123
|
-
# then open .tink/maintenance/harness-health-report.html
|
|
121
|
+
npx tink-harness dashboard # add --no-open to just generate the file
|
|
124
122
|
```
|
|
125
123
|
|
|
124
|
+
Under the hood it runs the two read-only helpers (`node .tink/tools/generate-harness-lifecycle-summary.mjs`, then `node .tink/tools/render-harness-health-report.mjs`) and opens `.tink/maintenance/harness-health-report.html`.
|
|
125
|
+
|
|
126
126
|

|
|
127
127
|
|
|
128
128
|
<sub>If this matches your workflow, a ⭐ helps others find it.</sub>
|
|
@@ -187,7 +187,7 @@ To update an existing standalone install (Claude Code or Codex):
|
|
|
187
187
|
npx tink-harness@latest update
|
|
188
188
|
```
|
|
189
189
|
|
|
190
|
-
Update asks one question - which agent surface to refresh - and handles the rest automatically. Language, install scope, and git policy are reused from the choices you made at install time; if you chose not to commit `.tink`, update never touches your `.gitignore`. Tink-owned files (commands, skills,
|
|
190
|
+
Update asks one question - which agent surface to refresh - and handles the rest automatically. Language, install scope, and git policy are reused from the choices you made at install time; if you chose not to commit `.tink`, update never touches your `.gitignore`. Tink-owned files (commands, skills, runtime tools) are always brought to the latest version; your customized harnesses, memory, config, and all `.tink/maintenance/` history records are preserved.
|
|
191
191
|
|
|
192
192
|
If `CODEX_HOME` is not set, Codex skills default to `%USERPROFILE%\.codex` on Windows and `~/.codex` on macOS/Linux.
|
|
193
193
|
|
package/VERSIONING.md
CHANGED
package/bin/install.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
2
3
|
import crypto from 'node:crypto';
|
|
3
4
|
import fs from 'node:fs';
|
|
4
5
|
import os from 'node:os';
|
|
@@ -78,24 +79,24 @@ const COPY = {
|
|
|
78
79
|
|
|
79
80
|
const COMPONENTS = {
|
|
80
81
|
en: [
|
|
81
|
-
{ value: 'commands', label: 'Claude Code commands', hint: '
|
|
82
|
+
{ value: 'commands', label: 'Claude Code commands (/tink:*)', hint: '7 slash commands (cast, verify, frog, ...) → .claude/commands/tink/' },
|
|
82
83
|
{ value: 'skill', label: 'Tink skill', hint: 'Tink operating rules for Claude Code' },
|
|
83
|
-
{ value: 'harnesses', label: 'Built-in harnesses', hint: '
|
|
84
|
-
{ value: 'memory', label: 'Memory templates', hint: 'Approved mistakes/preferences/lessons files' },
|
|
84
|
+
{ value: 'harnesses', label: 'Built-in harnesses', hint: 'Specialized task procedures → .tink/harnesses/' },
|
|
85
|
+
{ value: 'memory', label: 'Memory templates', hint: 'Approved mistakes/preferences/lessons files → .tink/memory/' },
|
|
85
86
|
{ value: 'hook', label: 'Hook recommendation (optional)', hint: 'Registers a safe UserPromptSubmit hook when selected. Off by default.' }
|
|
86
87
|
],
|
|
87
88
|
ko: [
|
|
88
|
-
{ value: 'commands', label: 'Claude Code 명령', hint: '
|
|
89
|
+
{ value: 'commands', label: 'Claude Code 명령 (/tink:*)', hint: '슬래시 명령 7개 (cast, verify, frog 등) → .claude/commands/tink/' },
|
|
89
90
|
{ value: 'skill', label: 'Tink skill', hint: 'Claude Code가 읽는 Tink 작업 원칙' },
|
|
90
|
-
{ value: 'harnesses', label: '기본 harness', hint: '
|
|
91
|
-
{ value: 'memory', label: 'Memory 템플릿', hint: '승인된 실수/선호/교훈 파일' },
|
|
91
|
+
{ value: 'harnesses', label: '기본 harness', hint: '기능 특화 작업 절차 → .tink/harnesses/' },
|
|
92
|
+
{ value: 'memory', label: 'Memory 템플릿', hint: '승인된 실수/선호/교훈 파일 → .tink/memory/' },
|
|
92
93
|
{ value: 'hook', label: 'Hook 추천 (선택)', hint: '선택하면 안전한 UserPromptSubmit hook으로 등록합니다. 기본 off.' }
|
|
93
94
|
],
|
|
94
95
|
zh: [
|
|
95
|
-
{ value: 'commands', label: 'Claude Code 命令', hint: '
|
|
96
|
+
{ value: 'commands', label: 'Claude Code 命令 (/tink:*)', hint: '7 个斜杠命令 (cast, verify, frog 等) → .claude/commands/tink/' },
|
|
96
97
|
{ value: 'skill', label: 'Tink skill', hint: 'Claude Code 读取的 Tink 工作规则' },
|
|
97
|
-
{ value: 'harnesses', label: '内置 harness', hint: '
|
|
98
|
-
{ value: 'memory', label: 'Memory 模板', hint: '经批准的错误/偏好/经验文件' },
|
|
98
|
+
{ value: 'harnesses', label: '内置 harness', hint: '功能特化任务流程 → .tink/harnesses/' },
|
|
99
|
+
{ value: 'memory', label: 'Memory 模板', hint: '经批准的错误/偏好/经验文件 → .tink/memory/' },
|
|
99
100
|
{ value: 'hook', label: 'Hook 推荐(可选)', hint: '选择后注册安全的 UserPromptSubmit hook。默认关闭。' }
|
|
100
101
|
]
|
|
101
102
|
};
|
|
@@ -125,7 +126,70 @@ function argValue(name) {
|
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
function usage() {
|
|
128
|
-
console.log(`Tink installer for Claude Code and Codex\n\nUsage:\n tink-harness [install] [--scope=repo|global] [--global] [--lang=en|ko|zh] [--yes] [--with-hook] [--clean-codex-picker] [--dry-run] [--force]\n tink-harness update [--scope=repo|global] [--global] [--lang=en|ko|zh] [--yes] [--clean-codex-picker] [--dry-run] [--force]\n\nIf the command is not installed yet, use:\n npx tink-harness@latest [install]\n npx tink-harness@latest update\n\nCommands:\n install Install Tink.\n update Update Tink to the latest templates. Asks only the agent surface; Tink-owned files always refresh, user-modified harness/memory/config files are kept.\n\nDefault interactive flow:\n 1. Select language\n 2. Show TINK wizard\n 3. Select Claude Code, Codex, or both\n 4. Select components\n 5. Select repo/global installation scope\n 6. Select Advanced options\n 7. Select git tracking policy for project state\n\nAdvanced options:\n --dry-run Preview only. Show what would be written or removed, but do not change files.\n --force Overwrite user-modified files. Use only when you want official templates to replace local edits.\n --clean-codex-picker Codex-only cleanup. Remove repo-local Claude Tink surfaces that show as Source Command Tink entries.\n\nEnvironment:\n TINK_INSTALL_SURFACES=claude|codex|all\n TINK_CLEAN_CODEX_PICKER=1\n\nScopes:\n repo Install shared .tink files into the current project.\n global Install shared .tink files into your home directory.\n`);
|
|
129
|
+
console.log(`Tink installer for Claude Code and Codex\n\nUsage:\n tink-harness [install] [--scope=repo|global] [--global] [--lang=en|ko|zh] [--yes] [--with-hook] [--clean-codex-picker] [--dry-run] [--force]\n tink-harness update [--scope=repo|global] [--global] [--lang=en|ko|zh] [--yes] [--clean-codex-picker] [--dry-run] [--force]\n tink-harness dashboard [--no-open]\n\nIf the command is not installed yet, use:\n npx tink-harness@latest [install]\n npx tink-harness@latest update\n\nCommands:\n install Install Tink.\n update Update Tink to the latest templates. Asks only the agent surface; Tink-owned files always refresh, user-modified harness/memory/config files are kept.\n dashboard Generate the harness health report from local .tink records and open it in your browser. Use --no-open to skip opening.\n\nDefault interactive flow:\n 1. Select language\n 2. Show TINK wizard\n 3. Select Claude Code, Codex, or both\n 4. Select components\n 5. Select repo/global installation scope\n 6. Select Advanced options\n 7. Select git tracking policy for project state\n\nAdvanced options:\n --dry-run Preview only. Show what would be written or removed, but do not change files.\n --force Overwrite user-modified files. Use only when you want official templates to replace local edits.\n --clean-codex-picker Codex-only cleanup. Remove repo-local Claude Tink surfaces that show as Source Command Tink entries.\n\nEnvironment:\n TINK_INSTALL_SURFACES=claude|codex|all\n TINK_CLEAN_CODEX_PICKER=1\n\nScopes:\n repo Install shared .tink files into the current project.\n global Install shared .tink files into your home directory.\n`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function findTinkRoot() {
|
|
133
|
+
for (const dir of [process.cwd(), os.homedir()]) {
|
|
134
|
+
if (fs.existsSync(path.join(dir, '.tink'))) return dir;
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function openInBrowser(file) {
|
|
140
|
+
let result;
|
|
141
|
+
if (process.platform === 'win32') {
|
|
142
|
+
result = spawnSync('cmd', ['/c', 'start', '', file], { stdio: 'ignore' });
|
|
143
|
+
} else if (process.platform === 'darwin') {
|
|
144
|
+
result = spawnSync('open', [file], { stdio: 'ignore' });
|
|
145
|
+
} else {
|
|
146
|
+
result = spawnSync('xdg-open', [file], { stdio: 'ignore' });
|
|
147
|
+
}
|
|
148
|
+
return !result.error && result.status === 0;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function runDashboard() {
|
|
152
|
+
const target = findTinkRoot();
|
|
153
|
+
const language = detectInstalledLanguage() || detectLanguage();
|
|
154
|
+
if (!target) {
|
|
155
|
+
console.error(language === 'ko'
|
|
156
|
+
? '.tink 디렉토리를 찾을 수 없습니다(현재 디렉토리·홈 디렉토리 확인). 먼저 `npx tink-harness@latest install`을 실행하세요.'
|
|
157
|
+
: 'No .tink directory found in the current or home directory. Run `npx tink-harness@latest install` first.');
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
const toolFor = (name) => {
|
|
161
|
+
const installed = path.join(target, '.tink/tools', name);
|
|
162
|
+
return fs.existsSync(installed) ? installed : path.join(root, 'templates/tink/tools', name);
|
|
163
|
+
};
|
|
164
|
+
const steps = [
|
|
165
|
+
toolFor('generate-harness-lifecycle-summary.mjs'),
|
|
166
|
+
toolFor('render-harness-health-report.mjs')
|
|
167
|
+
];
|
|
168
|
+
for (const tool of steps) {
|
|
169
|
+
const result = spawnSync(process.execPath, [tool], { cwd: target, stdio: 'inherit' });
|
|
170
|
+
if (result.status !== 0) {
|
|
171
|
+
console.error(language === 'ko'
|
|
172
|
+
? `대시보드 생성 실패: ${path.basename(tool)}`
|
|
173
|
+
: `Dashboard step failed: ${path.basename(tool)}`);
|
|
174
|
+
process.exit(result.status || 1);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const reportPath = path.join(target, '.tink/maintenance/harness-health-report.html');
|
|
178
|
+
if (!fs.existsSync(reportPath)) {
|
|
179
|
+
console.error(language === 'ko'
|
|
180
|
+
? `리포트 파일이 없습니다: ${reportPath}`
|
|
181
|
+
: `Report not found: ${reportPath}`);
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
console.log(language === 'ko' ? `대시보드: ${reportPath}` : `Dashboard: ${reportPath}`);
|
|
185
|
+
if (args.includes('--no-open')) return;
|
|
186
|
+
if (openInBrowser(reportPath)) {
|
|
187
|
+
console.log(language === 'ko' ? '기본 브라우저에서 열었습니다.' : 'Opened in your default browser.');
|
|
188
|
+
} else {
|
|
189
|
+
console.log(language === 'ko'
|
|
190
|
+
? '브라우저 자동 열기에 실패했습니다. 위 경로의 파일을 직접 열어주세요.'
|
|
191
|
+
: 'Could not open a browser automatically. Open the file above manually.');
|
|
192
|
+
}
|
|
129
193
|
}
|
|
130
194
|
|
|
131
195
|
function normalizeSurfaces(surfaces) {
|
|
@@ -206,17 +270,21 @@ function componentOptionsFor(agent, language) {
|
|
|
206
270
|
|
|
207
271
|
const claudeSkill = {
|
|
208
272
|
value: 'claude-skill',
|
|
209
|
-
label: 'Claude Code
|
|
273
|
+
label: language === 'ko' ? 'Claude Code skill (작업 원칙)' : language === 'zh' ? 'Claude Code skill(工作规则)' : 'Claude Code skill (operating rules)',
|
|
210
274
|
hint: language === 'ko'
|
|
211
|
-
? 'Claude Code가 읽는
|
|
212
|
-
:
|
|
275
|
+
? '명령과 별개 항목 — Claude Code가 항상 읽는 동작 규칙 문서 → .claude/skills/tink/'
|
|
276
|
+
: language === 'zh'
|
|
277
|
+
? '与命令不同的项目 — Claude Code 始终读取的工作规则 → .claude/skills/tink/'
|
|
278
|
+
: 'Different from the commands - the rules document Claude Code always reads → .claude/skills/tink/'
|
|
213
279
|
};
|
|
214
280
|
const codexSkills = {
|
|
215
281
|
value: 'codex-skills',
|
|
216
|
-
label: 'Codex
|
|
282
|
+
label: language === 'ko' ? 'Codex skills ($tink:*)' : 'Codex skills ($tink:*)',
|
|
217
283
|
hint: language === 'ko'
|
|
218
|
-
? 'Codex
|
|
219
|
-
:
|
|
284
|
+
? 'Codex 전용 — $tink:* action skills → ~/.codex/skills/ (CODEX_HOME)'
|
|
285
|
+
: language === 'zh'
|
|
286
|
+
? '仅用于 Codex — $tink:* action skills → ~/.codex/skills/ (CODEX_HOME)'
|
|
287
|
+
: 'Codex only - $tink:* action skills → ~/.codex/skills/ (CODEX_HOME)'
|
|
220
288
|
};
|
|
221
289
|
|
|
222
290
|
if (agent === 'claude') return [claudeSkill];
|
|
@@ -366,7 +434,8 @@ function handleCancel(value) {
|
|
|
366
434
|
|
|
367
435
|
function readHarnessCount() {
|
|
368
436
|
const dir = path.join(root, 'templates/tink/harnesses');
|
|
369
|
-
|
|
437
|
+
// HARNESS.md is the human catalog, not a harness
|
|
438
|
+
return fs.readdirSync(dir).filter((name) => name.endsWith('.md') && name !== 'HARNESS.md').length;
|
|
370
439
|
}
|
|
371
440
|
|
|
372
441
|
function displayPath(base, filePath) {
|
|
@@ -410,10 +479,17 @@ function isAlwaysUpdatePath(src) {
|
|
|
410
479
|
return rel.startsWith('templates/claude/commands/') ||
|
|
411
480
|
rel.startsWith('templates/claude/skills/') ||
|
|
412
481
|
rel.startsWith('templates/codex/skills/') ||
|
|
413
|
-
rel.startsWith('templates/tink/maintenance/') ||
|
|
414
482
|
rel.startsWith('templates/tink/tools/');
|
|
415
483
|
}
|
|
416
484
|
|
|
485
|
+
function isSeedOnlyPath(src) {
|
|
486
|
+
// Runtime record files (ledger, friction, weave queue): the template only
|
|
487
|
+
// seeds them. Once they exist they hold user history and must never be
|
|
488
|
+
// overwritten by install or update.
|
|
489
|
+
const rel = path.relative(root, src).replace(/\\/g, '/');
|
|
490
|
+
return rel.startsWith('templates/tink/maintenance/');
|
|
491
|
+
}
|
|
492
|
+
|
|
417
493
|
// Generic task-type harnesses retired from the default set: generic work now
|
|
418
494
|
// runs on the base run contract alone. Values are normalized (CR-stripped)
|
|
419
495
|
// sha256 hashes of every version ever shipped, so update can tell shipped
|
|
@@ -445,6 +521,42 @@ function normalizedSha256(content) {
|
|
|
445
521
|
return crypto.createHash('sha256').update(content.replace(/\r/g, '')).digest('hex');
|
|
446
522
|
}
|
|
447
523
|
|
|
524
|
+
// Default harnesses the user explicitly removed via an approved /tink:frog
|
|
525
|
+
// operation. Update must not resurrect them.
|
|
526
|
+
const retiredByFrog = new Set();
|
|
527
|
+
|
|
528
|
+
function harnessNameFromDest(dest) {
|
|
529
|
+
const match = String(dest).replace(/\\/g, '/').match(/\.tink\/harnesses\/([^/]+)\.md$/);
|
|
530
|
+
return match ? match[1] : '';
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function loadFroggedHarnessNames(target) {
|
|
534
|
+
retiredByFrog.clear();
|
|
535
|
+
const ledgerPath = path.join(target, '.tink/maintenance/ledger.jsonl');
|
|
536
|
+
if (!fs.existsSync(ledgerPath)) return;
|
|
537
|
+
let lines;
|
|
538
|
+
try {
|
|
539
|
+
lines = fs.readFileSync(ledgerPath, 'utf8').split('\n');
|
|
540
|
+
} catch {
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
for (const line of lines) {
|
|
544
|
+
const text = line.replace(/^/, '').trim();
|
|
545
|
+
if (!text) continue;
|
|
546
|
+
try {
|
|
547
|
+
const entry = JSON.parse(text);
|
|
548
|
+
if (entry && entry.type === 'frog' && entry.result === 'applied' && Array.isArray(entry.files)) {
|
|
549
|
+
for (const file of entry.files) {
|
|
550
|
+
const name = harnessNameFromDest(file);
|
|
551
|
+
if (name && name !== 'index') retiredByFrog.add(name);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
} catch {
|
|
555
|
+
// skip malformed ledger lines
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
448
560
|
function removeRetiredHarnesses(templateRoot, target) {
|
|
449
561
|
const harnessDir = path.join(target, '.tink/harnesses');
|
|
450
562
|
if (!fs.existsSync(harnessDir)) return;
|
|
@@ -481,7 +593,8 @@ function syncHarnessIndex(templateRoot, target, cleared) {
|
|
|
481
593
|
// append default entries the installed index does not know yet
|
|
482
594
|
const kept = installed.filter((entry) => !(entry && cleared.includes(entry.name)));
|
|
483
595
|
const knownNames = new Set(kept.map((entry) => entry && entry.name));
|
|
484
|
-
const added = template.filter((entry) =>
|
|
596
|
+
const added = template.filter((entry) =>
|
|
597
|
+
entry && !knownNames.has(entry.name) && !retiredByFrog.has(entry.name));
|
|
485
598
|
const next = [...kept, ...added];
|
|
486
599
|
if (next.length === installed.length && added.length === 0) return;
|
|
487
600
|
log.message(`${dryRun ? 'would sync' : 'sync'} ${displayPath(target, indexPath)} (${installed.length - kept.length} retired removed, ${added.length} default added)`);
|
|
@@ -549,6 +662,13 @@ function isGeneratedLegacyRuleGraph(src, dest) {
|
|
|
549
662
|
|
|
550
663
|
function writeFileFromTemplate(src, dest, base) {
|
|
551
664
|
const exists = fs.existsSync(dest);
|
|
665
|
+
if (exists && !force && isSeedOnlyPath(src)) {
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
if (isUpdate && !exists && retiredByFrog.has(harnessNameFromDest(dest))) {
|
|
669
|
+
log.message(`skip frog-removed ${displayPath(base, dest)}`);
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
552
672
|
if (exists && !force) {
|
|
553
673
|
if (isUpdate) {
|
|
554
674
|
const srcContent = fs.readFileSync(src);
|
|
@@ -783,6 +903,7 @@ function copySelected(scope, components, agent) {
|
|
|
783
903
|
}
|
|
784
904
|
}
|
|
785
905
|
if (components.includes('harnesses')) {
|
|
906
|
+
if (isUpdate) loadFroggedHarnessNames(target);
|
|
786
907
|
copyDir(path.join(templateRoot, 'tink/harnesses'), path.join(target, '.tink/harnesses'), target);
|
|
787
908
|
copyDir(path.join(templateRoot, 'tink/rules'), path.join(target, '.tink/rules'), target);
|
|
788
909
|
copyDir(path.join(templateRoot, 'tink/schemas'), path.join(target, '.tink/schemas'), target);
|
|
@@ -1099,6 +1220,11 @@ async function main() {
|
|
|
1099
1220
|
process.exit(0);
|
|
1100
1221
|
}
|
|
1101
1222
|
|
|
1223
|
+
if (command === 'dashboard') {
|
|
1224
|
+
runDashboard();
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1102
1228
|
if (command !== 'install' && command !== 'update') {
|
|
1103
1229
|
console.error(`Unknown command: ${command}`);
|
|
1104
1230
|
usage();
|
package/commands/list.md
CHANGED
|
@@ -11,7 +11,7 @@ List available Tink harnesses without loading every harness body.
|
|
|
11
11
|
2. Read only compact usage metadata from `.tink/runs/` (frontmatter `selected_harnesses` / `actually_loaded_harnesses` + dates), `.tink/maintenance/ledger.jsonl`, and `.tink/maintenance/weave-queue.json`. Do not load raw logs.
|
|
12
12
|
3. Treat `.tink/current/` as weak evidence unless it is clearly from the same active conversation. If context is uncertain, label it `stale current candidate`, not proof of usage.
|
|
13
13
|
4. Classify every harness into exactly one of three categories:
|
|
14
|
-
- **working** — directly performs or gates tasks (e.g. `ship`, `
|
|
14
|
+
- **working** — directly performs or gates tasks (e.g. `ship`, `requirements-interview`, `plan-consensus`, `goal-checkpoint`, `delegation-brief`). Generic work (code change, research, review, docs) runs on the base run without a harness, so it does not appear here.
|
|
15
15
|
- **meta** — manages other harnesses or Tink itself. Treat these names as meta regardless of `kind`: `harness-synthesis`, `harness-curation`, `tink-feedback-apply`.
|
|
16
16
|
- **custom (this repo)** — `kind: synthesized` in `index.json` (created in this repo, not part of the default set). If a synthesized harness also matches a meta name, prefer meta.
|
|
17
17
|
5. Compute the signal per harness:
|
package/commands/update.md
CHANGED
|
@@ -15,7 +15,8 @@ This command does not run the update itself. It detects how Tink was installed i
|
|
|
15
15
|
- If the project root (or `cwd`) has `.claude-plugin/plugin.json` and a top-level `commands/` directory, Tink was installed via Claude Code plugin marketplace.
|
|
16
16
|
- Otherwise, treat it as an `npx tink-harness install` (standalone) installation.
|
|
17
17
|
3. Scan for files that have diverged from the latest installed templates (read-only inspection only):
|
|
18
|
-
- **Always updated**: `.claude/commands/tink/`, `.claude/skills/tink/`, `.tink/
|
|
18
|
+
- **Always updated**: `.claude/commands/tink/`, `.claude/skills/tink/`, `.tink/tools/` — template changes always propagate here.
|
|
19
|
+
- **Never overwritten once they exist**: `.tink/maintenance/` record files (`ledger.jsonl`, `friction.jsonl`, `weave-queue.json`) hold user history; the template only seeds them when missing.
|
|
19
20
|
- **Preserved if user-modified**: `.tink/harnesses/`, `.tink/hooks/`, `.tink/memory/`, `.tink/config.json` — respects `weave` customizations and local configuration.
|
|
20
21
|
4. Show the appropriate update path and a short list of files in the "preserved" category that have diverged.
|
|
21
22
|
|
|
@@ -38,7 +39,8 @@ npx tink-harness@latest update
|
|
|
38
39
|
```
|
|
39
40
|
|
|
40
41
|
The `update` subcommand asks only one question - which agent surface to refresh (Claude Code, Codex, or both). Everything else updates automatically:
|
|
41
|
-
- **Always overwrites**: commands, skills,
|
|
42
|
+
- **Always overwrites**: commands, skills, and runtime tools (`.claude/commands/tink/`, `.claude/skills/tink/`, `.tink/tools/`) — so you get the latest harness runner, report tools, and command behavior automatically.
|
|
43
|
+
- **Never overwrites records**: `.tink/maintenance/` ledger, friction, and weave-queue files are user history; they are only seeded when missing.
|
|
42
44
|
- **Preserves if modified**: harnesses, hooks, memory, and config (`.tink/harnesses/`, `.tink/hooks/`, `.tink/memory/`, `.tink/config.json`) — respects your `weave` customizations and local settings.
|
|
43
45
|
- **Reuses stored choices**: language, install scope, and git policy come from `.tink/config.json`. With `git_policy: "none"` (커밋 안 함) the updater never creates or edits `.gitignore`, and an existing whole-directory `.tink/` ignore line is left as-is.
|
|
44
46
|
|
|
@@ -62,7 +64,7 @@ If the source-repo guard triggers, print only this and stop — do not present p
|
|
|
62
64
|
|
|
63
65
|
**설치 경로**: <plugin marketplace | npx standalone>
|
|
64
66
|
|
|
65
|
-
**항상 업데이트됨**: commands, skills,
|
|
67
|
+
**항상 업데이트됨**: commands, skills, tools (최신 버전으로 자동 반영) · maintenance 기록 파일은 보존
|
|
66
68
|
|
|
67
69
|
**사용자 수정 파일** (업데이트 시 보존):
|
|
68
70
|
- <path1>
|
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@ List available Tink harnesses without loading every harness body.
|
|
|
11
11
|
2. Read only compact usage metadata from `.tink/runs/` (frontmatter `selected_harnesses` / `actually_loaded_harnesses` + dates), `.tink/maintenance/ledger.jsonl`, and `.tink/maintenance/weave-queue.json`. Do not load raw logs.
|
|
12
12
|
3. Treat `.tink/current/` as weak evidence unless it is clearly from the same active conversation. If context is uncertain, label it `stale current candidate`, not proof of usage.
|
|
13
13
|
4. Classify every harness into exactly one of three categories:
|
|
14
|
-
- **working** — directly performs or gates tasks (e.g. `ship`, `
|
|
14
|
+
- **working** — directly performs or gates tasks (e.g. `ship`, `requirements-interview`, `plan-consensus`, `goal-checkpoint`, `delegation-brief`). Generic work (code change, research, review, docs) runs on the base run without a harness, so it does not appear here.
|
|
15
15
|
- **meta** — manages other harnesses or Tink itself. Treat these names as meta regardless of `kind`: `harness-synthesis`, `harness-curation`, `tink-feedback-apply`.
|
|
16
16
|
- **custom (this repo)** — `kind: synthesized` in `index.json` (created in this repo, not part of the default set). If a synthesized harness also matches a meta name, prefer meta.
|
|
17
17
|
5. Compute the signal per harness:
|
|
@@ -15,7 +15,8 @@ This command does not run the update itself. It detects how Tink was installed i
|
|
|
15
15
|
- If the project root (or `cwd`) has `.claude-plugin/plugin.json` and a top-level `commands/` directory, Tink was installed via Claude Code plugin marketplace.
|
|
16
16
|
- Otherwise, treat it as an `npx tink-harness install` (standalone) installation.
|
|
17
17
|
3. Scan for files that have diverged from the latest installed templates (read-only inspection only):
|
|
18
|
-
- **Always updated**: `.claude/commands/tink/`, `.claude/skills/tink/`, `.tink/
|
|
18
|
+
- **Always updated**: `.claude/commands/tink/`, `.claude/skills/tink/`, `.tink/tools/` — template changes always propagate here.
|
|
19
|
+
- **Never overwritten once they exist**: `.tink/maintenance/` record files (`ledger.jsonl`, `friction.jsonl`, `weave-queue.json`) hold user history; the template only seeds them when missing.
|
|
19
20
|
- **Preserved if user-modified**: `.tink/harnesses/`, `.tink/hooks/`, `.tink/memory/`, `.tink/config.json` — respects `weave` customizations and local configuration.
|
|
20
21
|
4. Show the appropriate update path and a short list of files in the "preserved" category that have diverged.
|
|
21
22
|
|
|
@@ -38,7 +39,8 @@ npx tink-harness@latest update
|
|
|
38
39
|
```
|
|
39
40
|
|
|
40
41
|
The `update` subcommand asks only one question - which agent surface to refresh (Claude Code, Codex, or both). Everything else updates automatically:
|
|
41
|
-
- **Always overwrites**: commands, skills,
|
|
42
|
+
- **Always overwrites**: commands, skills, and runtime tools (`.claude/commands/tink/`, `.claude/skills/tink/`, `.tink/tools/`) — so you get the latest harness runner, report tools, and command behavior automatically.
|
|
43
|
+
- **Never overwrites records**: `.tink/maintenance/` ledger, friction, and weave-queue files are user history; they are only seeded when missing.
|
|
42
44
|
- **Preserves if modified**: harnesses, hooks, memory, and config (`.tink/harnesses/`, `.tink/hooks/`, `.tink/memory/`, `.tink/config.json`) — respects your `weave` customizations and local settings.
|
|
43
45
|
- **Reuses stored choices**: language, install scope, and git policy come from `.tink/config.json`. With `git_policy: "none"` (커밋 안 함) the updater never creates or edits `.gitignore`, and an existing whole-directory `.tink/` ignore line is left as-is.
|
|
44
46
|
|
|
@@ -62,7 +64,7 @@ If the source-repo guard triggers, print only this and stop — do not present p
|
|
|
62
64
|
|
|
63
65
|
**설치 경로**: <plugin marketplace | npx standalone>
|
|
64
66
|
|
|
65
|
-
**항상 업데이트됨**: commands, skills,
|
|
67
|
+
**항상 업데이트됨**: commands, skills, tools (최신 버전으로 자동 반영) · maintenance 기록 파일은 보존
|
|
66
68
|
|
|
67
69
|
**사용자 수정 파일** (업데이트 시 보존):
|
|
68
70
|
- <path1>
|