tink-harness 1.9.22 → 1.10.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/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +6 -2
- package/README.ko.md +27 -16
- package/README.md +28 -19
- package/VERSIONING.md +1 -1
- package/bin/install.js +132 -8
- package/commands/cast.md +53 -31
- package/commands/frog.md +9 -1
- package/commands/list.md +104 -104
- package/commands/setup.md +1 -1
- package/commands/update.md +1 -0
- package/commands/weave.md +32 -4
- package/package.json +1 -1
- package/templates/claude/commands/tink/cast.md +53 -31
- package/templates/claude/commands/tink/frog.md +9 -1
- package/templates/claude/commands/tink/list.md +104 -104
- package/templates/claude/commands/tink/setup.md +1 -1
- package/templates/claude/commands/tink/update.md +1 -0
- package/templates/claude/commands/tink/weave.md +32 -4
- package/templates/codex/skills/tink-core/RULES.md +7 -7
- package/templates/tink/harnesses/HARNESS.md +4 -5
- package/templates/tink/harnesses/index.json +0 -75
- package/templates/tink/rules/index.json +0 -28
- package/templates/tink/harnesses/bug-fix.md +0 -31
- package/templates/tink/harnesses/code-change.md +0 -30
- package/templates/tink/harnesses/docs.md +0 -30
- package/templates/tink/harnesses/research.md +0 -31
- package/templates/tink/harnesses/review.md +0 -31
package/CHANGELOG.md
CHANGED
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Tink are tracked here.
|
|
4
4
|
|
|
5
|
-
## [
|
|
5
|
+
## [1.10.0] - 2026-06-12
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- 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.
|
|
8
|
+
- **Default harness set is specialized-only.** The generic task-type harnesses `code-change`, `bug-fix`, `research`, `review`, and `docs` are retired: ordinary code/bug/research/review/docs work now runs as a **base run** (기본 절차) on the run state contract alone, and a harness is selected only when a specialized procedure genuinely fits. `npx tink-harness@latest update` removes unmodified retired harnesses automatically (user-woven copies are preserved), prunes their index entries and rule-graph nodes, and now also appends newly shipped default harnesses to an existing `index.json`.
|
|
9
|
+
- frog: when invoked without a target, the harness health summary's judgment (weave/frog/merge candidates, overlap groups) becomes the default cleanup agenda; retired-generic leftovers and stale `.tink/memory/candidate/` entries are also reviewed. weave: run-only drafts repeated across 2+ runs and supported memory candidates can now be promoted (임시 초안 → 하네스, candidate → approved) through the Save Gate.
|
|
10
|
+
- README: "How it works" rewritten as a compact file map plus three driving rules; the long design-docs paragraph moved into a collapsible contributors index (EN/KO).
|
|
11
|
+
- cast: overlay selection is now rule-bound - `goal-checkpoint` is required for runs with 2+ goals, sequential harnesses, 4+ expected steps, or multi-component scope, and `plan-consensus` must be explicitly considered (with a reason when skipped) for from-scratch/reimplementation/migration work. The approval payload gained a mandatory `오버레이 점검` line, and the synthesis-probe verdict wording no longer reads as "default harnesses are sufficient" for the whole set.
|
|
8
12
|
|
|
9
13
|
## [1.9.22] - 2026-06-11
|
|
10
14
|
|
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.10.0 — 기본 하네스가 기능 특화 세트로 바뀌었습니다. 일반 작업은 하네스 없이 기본 절차로 진행하고, update가 퇴역한 범용 하네스를 자동 정리하며 설치 때 선택(언어·범위·git 정책)을 재사용합니다. weave/frog에는 건강 요약 기반 정리와 임시초안 승격이 추가됐습니다. 전체 변경 이력은 [CHANGELOG](CHANGELOG.md)를 확인하세요.
|
|
14
14
|
|
|
15
15
|
[English](README.md) · **한국어** · [변경 이력](CHANGELOG.md)
|
|
16
16
|
|
|
@@ -147,7 +147,7 @@ Standalone / Codex:
|
|
|
147
147
|
npx tink-harness@latest update
|
|
148
148
|
```
|
|
149
149
|
|
|
150
|
-
업데이트는 질문 하나 — 어떤 agent surface를 갱신할지 — 만 묻고 나머지는 자동으로 처리합니다. Tink가 관리하는 파일(commands, skills, maintenance, 런타임 tools)은 항상 최신으로 덮어쓰고, 사용자가 수정한 하네스·메모리·설정은 보존합니다.
|
|
150
|
+
업데이트는 질문 하나 — 어떤 agent surface를 갱신할지 — 만 묻고 나머지는 자동으로 처리합니다. 언어·설치 범위·git 정책은 설치 때 선택한 값을 그대로 재사용하며, ".tink 커밋 안 함"을 선택했다면 업데이트가 `.gitignore`를 절대 건드리지 않습니다. Tink가 관리하는 파일(commands, skills, maintenance, 런타임 tools)은 항상 최신으로 덮어쓰고, 사용자가 수정한 하네스·메모리·설정은 보존합니다.
|
|
151
151
|
|
|
152
152
|
`CODEX_HOME`을 지정하지 않으면 Windows에서는 `%USERPROFILE%\.codex`, macOS/Linux에서는 `~/.codex`에 Codex skill이 설치됩니다.
|
|
153
153
|
|
|
@@ -199,27 +199,38 @@ Tink는 이제 비단순 작업에 대해 `.tink/current/contract.json`도 만
|
|
|
199
199
|
|
|
200
200
|
## 작동 방식
|
|
201
201
|
|
|
202
|
-
Tink
|
|
202
|
+
Tink가 아는 모든 것은 직접 읽고, diff 보고, 지울 수 있는 평범한 파일입니다.
|
|
203
203
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
204
|
+
| 경로 | 내용 |
|
|
205
|
+
| --- | --- |
|
|
206
|
+
| `.tink/harnesses/` | 하네스 세트 — 기능 특화 절차만 |
|
|
207
|
+
| `.tink/current/` | 현재 실행: 계획, 단계, 계약, 검증 체크 |
|
|
208
|
+
| `.tink/runs/` | 끝난 실행의 간결한 기록 |
|
|
209
|
+
| `.tink/memory/` | 승인된 교훈·선호. 초안은 `memory/candidate/`에서 대기 |
|
|
210
|
+
| `.tink/rules/` + `.tink/schemas/` | 하네스 선택용 작은 rule graph와 파일 스키마 |
|
|
211
|
+
| `.tink/maintenance/` + `.tink/tools/` | 사용 신호와 로컬 대시보드를 만드는 읽기 전용 helper |
|
|
211
212
|
|
|
212
|
-
|
|
213
|
+
이 전부를 움직이는 원칙은 세 가지입니다.
|
|
213
214
|
|
|
214
|
-
|
|
215
|
+
1. **일반 작업에는 하네스가 필요 없습니다.** 평범한 코드 변경·리뷰·문서 작업은 기본 절차(계획 → 단계 → 검증 증거)만으로 진행합니다. 하네스는 특화된 절차가 실제로 결과를 바꿀 때만 로드됩니다 — 출시 안전판, 목표 체크포인트, 계획 비평, 요구사항 인터뷰, 도메인 워크플로.
|
|
216
|
+
2. **제안만 합니다.** 대시보드·`frog`·`weave`는 실제 사용 신호로 제안을 준비할 뿐입니다. 재사용되는 것(하네스, 메모리, 삭제)은 반드시 별도 명시 승인을 거칩니다. 오늘 실행의 승인이 미래 실행이 물려받을 변경을 허가하지 않습니다.
|
|
217
|
+
3. **느낌이 아니라 증거.** 실행 기록, 실패한 체크, friction 이벤트가 무엇을 개선하고(`weave`), 초안에서 하네스로 승격하고, 정리할지(`frog`)를 결정합니다. 증거가 약하면 삭제가 아니라 유지·관찰이 기본입니다.
|
|
215
218
|
|
|
216
|
-
|
|
219
|
+
대시보드는 이 파일들로 만든 정적 로컬 페이지입니다 — 서버, 파일 감시, hidden cache, public `tink index` 명령이 없습니다.
|
|
217
220
|
|
|
218
|
-
|
|
221
|
+
<details>
|
|
222
|
+
<summary><strong>설계 문서 색인</strong> — 기여자용 세부 내용</summary>
|
|
219
223
|
|
|
220
|
-
|
|
224
|
+
- 호환성 기준 (Claude Code + Codex, macOS + Windows): `docs/compatibility-policy.md`
|
|
225
|
+
- Repo Signal: `docs/repo-signals.ko.md`, `docs/repo-signals.md` · graph 규칙 적용 계획: `docs/graph-rule-adoption-plan.ko.md`
|
|
226
|
+
- 하네스 건강 요약: `docs/harness-lifecycle-signals.ko.md`, `docs/harness-lifecycle-signals.md`
|
|
227
|
+
- 외부 context 안전: `docs/mcp-safe-profile.md`, `docs/external-context-policy.md`
|
|
228
|
+
- `.tink/current/` 상태 읽기: `docs/work-state.ko.md`, `docs/work-state.md`
|
|
229
|
+
- 업데이트 안정화: `docs/phase-5-update-confidence.ko.md`, `docs/phase-5-update-confidence.md`
|
|
230
|
+
- Context 효율: `docs/context-budget-ledger.ko.md`, `docs/context-budget-ledger.md`, `docs/context-metrics-evaluator.ko.md`, `docs/context-metrics-evaluator.md`, `docs/context-run-history-rollup.ko.md`, `docs/context-run-history-rollup.md`, `docs/context-threshold-status.ko.md`, `docs/context-threshold-status.md`, `docs/context-run-record-policy.ko.md`, `docs/context-run-record-policy.md`
|
|
231
|
+
- 남은 작업 단위: `docs/planned-work-units.ko.md`, `docs/planned-work-units.md` · 로드맵·아이디어 점검: `docs/tink-idea-implementation-plan.ko.md`
|
|
221
232
|
|
|
222
|
-
|
|
233
|
+
</details>
|
|
223
234
|
|
|
224
235
|
## 계획된 작업 단위
|
|
225
236
|
|
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.10.0"><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.10.0 - The default harness set is now specialized-only: generic work runs as a base run (no harness), update cleans up retired generic harnesses automatically and reuses your install-time choices, and weave/frog gained health-summary-driven cleanup and draft promotion. See <a href="CHANGELOG.md">CHANGELOG</a> for release history.</p>
|
|
28
28
|
|
|
29
29
|
**English** · [한국어](README.ko.md) · [Changelog](CHANGELOG.md)
|
|
30
30
|
|
|
@@ -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. Tink-owned files (commands, skills, maintenance, runtime tools) are always brought to the latest version; your customized harnesses, memory, and config are preserved.
|
|
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, maintenance, runtime tools) are always brought to the latest version; your customized harnesses, memory, and config 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
|
|
|
@@ -253,29 +253,38 @@ Use it when a harness is useful but slightly wrong.
|
|
|
253
253
|
|
|
254
254
|
## How it works
|
|
255
255
|
|
|
256
|
-
Tink
|
|
256
|
+
Everything Tink knows lives in plain files you can read, diff, and delete:
|
|
257
257
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
258
|
+
| Path | What it holds |
|
|
259
|
+
| --- | --- |
|
|
260
|
+
| `.tink/harnesses/` | the harness set — specialized procedures only |
|
|
261
|
+
| `.tink/current/` | the active run: plan, steps, contract, verification checks |
|
|
262
|
+
| `.tink/runs/` | compact records of finished runs |
|
|
263
|
+
| `.tink/memory/` | approved lessons and preferences; drafts wait in `memory/candidate/` |
|
|
264
|
+
| `.tink/rules/` + `.tink/schemas/` | a small rule graph for harness selection, plus file schemas |
|
|
265
|
+
| `.tink/maintenance/` + `.tink/tools/` | usage signals and the read-only helpers that render the local dashboard |
|
|
265
266
|
|
|
266
|
-
|
|
267
|
+
Three rules drive all of it:
|
|
267
268
|
|
|
268
|
-
|
|
269
|
+
1. **Generic work runs without a harness.** An ordinary code change, review, or doc edit runs on the base run contract alone — plan, steps, verification evidence. A harness is loaded only when a specialized procedure actually changes what happens: release gates, goal checkpoints, plan critique, requirements interviews, domain workflows.
|
|
270
|
+
2. **Suggestions only.** The dashboard, `frog`, and `weave` prepare proposals from real usage signals. Nothing reusable — a harness, a memory entry, a deletion — is saved without its own explicit approval. Approving today's run never authorizes changes that future runs would inherit.
|
|
271
|
+
3. **Evidence over vibes.** Run records, failed checks, and friction events decide what gets improved (`weave`), promoted from draft to harness, or cleaned up (`frog`). Weak evidence defaults to keep-and-observe, never to delete.
|
|
269
272
|
|
|
270
|
-
|
|
273
|
+
The dashboard is a static local page rendered from those files — the harness health summary behind it shows usage, failed checks, and weave/frog candidates. No server, no file watching, no hidden cache, no public `tink index` command. It only prepares suggestions; acting on them keeps the approval gates above.
|
|
271
274
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
Design notes live in `docs/`. The compatibility baseline is `docs/compatibility-policy.md`: every new slice should consider Claude Code and Codex, plus macOS and Windows. Repo signal behavior is described in `docs/repo-signals.md` or `docs/repo-signals.ko.md`. The lightweight graph-rule adoption plan is `docs/graph-rule-adoption-plan.ko.md`. Harness health summaries are described in `docs/harness-lifecycle-signals.md` or `docs/harness-lifecycle-signals.ko.md`. External context safety is described in `docs/mcp-safe-profile.md` and `docs/external-context-policy.md`. To read or review `.tink/current/` state, start with `docs/work-state.md` or `docs/work-state.ko.md`. Update confidence is still documented in `docs/phase-5-update-confidence.md` or `docs/phase-5-update-confidence.ko.md`. Context efficiency docs live in `docs/context-budget-ledger.md`, `docs/context-budget-ledger.ko.md`, `docs/context-metrics-evaluator.md`, `docs/context-metrics-evaluator.ko.md`, `docs/context-run-history-rollup.md`, `docs/context-run-history-rollup.ko.md`, `docs/context-threshold-status.md`, `docs/context-threshold-status.ko.md`, `docs/context-run-record-policy.md`, and `docs/context-run-record-policy.ko.md`. The planned work-unit list is `docs/planned-work-units.md` or `docs/planned-work-units.ko.md`, with details in the verification evidence, memory decision, context change, and update diagnosis docs. The broader Korean idea audit and roadmap is `docs/tink-idea-implementation-plan.ko.md`.
|
|
275
|
+
<details>
|
|
276
|
+
<summary><strong>Design docs index</strong> — details for contributors</summary>
|
|
275
277
|
|
|
276
|
-
|
|
278
|
+
- Compatibility baseline (Claude Code + Codex, macOS + Windows): `docs/compatibility-policy.md`
|
|
279
|
+
- Repo signals: `docs/repo-signals.md`, `docs/repo-signals.ko.md` · graph-rule adoption plan: `docs/graph-rule-adoption-plan.ko.md`
|
|
280
|
+
- Harness health summary: `docs/harness-lifecycle-signals.md`, `docs/harness-lifecycle-signals.ko.md`
|
|
281
|
+
- External context safety: `docs/mcp-safe-profile.md`, `docs/external-context-policy.md`
|
|
282
|
+
- Reading `.tink/current/` state: `docs/work-state.md`, `docs/work-state.ko.md`
|
|
283
|
+
- Update confidence: `docs/phase-5-update-confidence.md`, `docs/phase-5-update-confidence.ko.md`
|
|
284
|
+
- Context efficiency: `docs/context-budget-ledger.md`, `docs/context-budget-ledger.ko.md`, `docs/context-metrics-evaluator.md`, `docs/context-metrics-evaluator.ko.md`, `docs/context-run-history-rollup.md`, `docs/context-run-history-rollup.ko.md`, `docs/context-threshold-status.md`, `docs/context-threshold-status.ko.md`, `docs/context-run-record-policy.md`, `docs/context-run-record-policy.ko.md`
|
|
285
|
+
- Planned work units: `docs/planned-work-units.md`, `docs/planned-work-units.ko.md` · roadmap and idea audit: `docs/tink-idea-implementation-plan.ko.md`
|
|
277
286
|
|
|
278
|
-
|
|
287
|
+
</details>
|
|
279
288
|
|
|
280
289
|
## What Tink is not
|
|
281
290
|
|
package/VERSIONING.md
CHANGED
package/bin/install.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import crypto from 'node:crypto';
|
|
2
3
|
import fs from 'node:fs';
|
|
3
4
|
import os from 'node:os';
|
|
4
5
|
import path from 'node:path';
|
|
@@ -383,15 +384,14 @@ function shortList(items, emptyText = '- none') {
|
|
|
383
384
|
return shown.join('\n');
|
|
384
385
|
}
|
|
385
386
|
|
|
386
|
-
function
|
|
387
|
+
function readInstalledConfig() {
|
|
387
388
|
const candidates = [
|
|
388
389
|
path.join(process.cwd(), '.tink/config.json'),
|
|
389
390
|
path.join(os.homedir(), '.tink/config.json')
|
|
390
391
|
];
|
|
391
392
|
for (const configPath of candidates) {
|
|
392
393
|
try {
|
|
393
|
-
|
|
394
|
-
if (['en', 'ko', 'zh'].includes(config.language)) return config.language;
|
|
394
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
395
395
|
} catch {
|
|
396
396
|
// keep looking
|
|
397
397
|
}
|
|
@@ -399,6 +399,12 @@ function detectInstalledLanguage() {
|
|
|
399
399
|
return null;
|
|
400
400
|
}
|
|
401
401
|
|
|
402
|
+
function detectInstalledLanguage() {
|
|
403
|
+
const config = readInstalledConfig();
|
|
404
|
+
if (config && ['en', 'ko', 'zh'].includes(config.language)) return config.language;
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
|
|
402
408
|
function isAlwaysUpdatePath(src) {
|
|
403
409
|
const rel = path.relative(root, src).replace(/\\/g, '/');
|
|
404
410
|
return rel.startsWith('templates/claude/commands/') ||
|
|
@@ -408,6 +414,106 @@ function isAlwaysUpdatePath(src) {
|
|
|
408
414
|
rel.startsWith('templates/tink/tools/');
|
|
409
415
|
}
|
|
410
416
|
|
|
417
|
+
// Generic task-type harnesses retired from the default set: generic work now
|
|
418
|
+
// runs on the base run contract alone. Values are normalized (CR-stripped)
|
|
419
|
+
// sha256 hashes of every version ever shipped, so update can tell shipped
|
|
420
|
+
// content (safe to remove) from user-woven content (preserved).
|
|
421
|
+
const RETIRED_HARNESSES = {
|
|
422
|
+
'code-change': [
|
|
423
|
+
'883396f8a7c69f097476ffd23288597814c5eabfae4ada2ef8a643afb3a80345',
|
|
424
|
+
'b9320a0fc7f89a7e8898107f0e289758b7265c1fddce3497fc9297703a3edb44'
|
|
425
|
+
],
|
|
426
|
+
'bug-fix': [
|
|
427
|
+
'94f747e2cd299ae84fa82b8c54954e518fcca1e95a30edfe571fbf504dd70906',
|
|
428
|
+
'e2fe201b3de7d7ec748ebd6b7f8f11bb1f702d0deda21c87d21d7ce82867ce1f'
|
|
429
|
+
],
|
|
430
|
+
research: [
|
|
431
|
+
'57fc4446a0e7d831a1fbd8047f45ad1b3bb595606cb4e643a7fce97308d38197',
|
|
432
|
+
'8e36fa4e3f5f2cb87b5357b33c46a9f9428038f344a12a5b46c79dee0c474aa1'
|
|
433
|
+
],
|
|
434
|
+
review: [
|
|
435
|
+
'9f0c3f093885b9cf2d796bf60b8f1e5e08805273343a42b1bc87c06d36b7a2a0',
|
|
436
|
+
'3b46487689af1a19e436ea672ccfa78cbf82ffcce4540b826ee95239c968c889'
|
|
437
|
+
],
|
|
438
|
+
docs: [
|
|
439
|
+
'25e6a8fc0c7ba3b4c50519c91157172f8b551ff847f30cc96a5b3ef64cb2c530',
|
|
440
|
+
'3e42f06aad78f2b18cb5c0c1a0efca17f86cbba9d189f5dbf64efe1c8c75d9b0'
|
|
441
|
+
]
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
function normalizedSha256(content) {
|
|
445
|
+
return crypto.createHash('sha256').update(content.replace(/\r/g, '')).digest('hex');
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function removeRetiredHarnesses(templateRoot, target) {
|
|
449
|
+
const harnessDir = path.join(target, '.tink/harnesses');
|
|
450
|
+
if (!fs.existsSync(harnessDir)) return;
|
|
451
|
+
const cleared = [];
|
|
452
|
+
for (const [name, hashes] of Object.entries(RETIRED_HARNESSES)) {
|
|
453
|
+
const file = path.join(harnessDir, `${name}.md`);
|
|
454
|
+
if (!fs.existsSync(file)) {
|
|
455
|
+
cleared.push(name);
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
if (hashes.includes(normalizedSha256(fs.readFileSync(file, 'utf8')))) {
|
|
459
|
+
log.message(`${dryRun ? 'would remove retired' : 'remove retired'} ${displayPath(target, file)}`);
|
|
460
|
+
recordOperation('removedLegacy', target, file);
|
|
461
|
+
if (!dryRun) fs.rmSync(file, { force: true });
|
|
462
|
+
cleared.push(name);
|
|
463
|
+
} else {
|
|
464
|
+
log.message(`keep user-modified retired harness ${displayPath(target, file)}`);
|
|
465
|
+
recordOperation('preserved', target, file);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
syncHarnessIndex(templateRoot, target, cleared);
|
|
469
|
+
pruneRetiredRuleNodes(target, cleared);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function syncHarnessIndex(templateRoot, target, cleared) {
|
|
473
|
+
const indexPath = path.join(target, '.tink/harnesses/index.json');
|
|
474
|
+
const templateIndexPath = path.join(templateRoot, 'tink/harnesses/index.json');
|
|
475
|
+
if (!fs.existsSync(indexPath) || !fs.existsSync(templateIndexPath)) return;
|
|
476
|
+
try {
|
|
477
|
+
const installed = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
|
|
478
|
+
const template = JSON.parse(fs.readFileSync(templateIndexPath, 'utf8'));
|
|
479
|
+
if (!Array.isArray(installed) || !Array.isArray(template)) return;
|
|
480
|
+
// keep user entries and metadata; drop entries whose retired file is gone;
|
|
481
|
+
// append default entries the installed index does not know yet
|
|
482
|
+
const kept = installed.filter((entry) => !(entry && cleared.includes(entry.name)));
|
|
483
|
+
const knownNames = new Set(kept.map((entry) => entry && entry.name));
|
|
484
|
+
const added = template.filter((entry) => entry && !knownNames.has(entry.name));
|
|
485
|
+
const next = [...kept, ...added];
|
|
486
|
+
if (next.length === installed.length && added.length === 0) return;
|
|
487
|
+
log.message(`${dryRun ? 'would sync' : 'sync'} ${displayPath(target, indexPath)} (${installed.length - kept.length} retired removed, ${added.length} default added)`);
|
|
488
|
+
recordOperation('updated', target, indexPath);
|
|
489
|
+
if (!dryRun) fs.writeFileSync(indexPath, `${JSON.stringify(next, null, 2)}\n`);
|
|
490
|
+
} catch {
|
|
491
|
+
// unreadable index: leave it untouched
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function pruneRetiredRuleNodes(target, cleared) {
|
|
496
|
+
const rulesPath = path.join(target, '.tink/rules/index.json');
|
|
497
|
+
if (!fs.existsSync(rulesPath)) return;
|
|
498
|
+
try {
|
|
499
|
+
const rules = JSON.parse(fs.readFileSync(rulesPath, 'utf8'));
|
|
500
|
+
if (!rules || !Array.isArray(rules.nodes)) return;
|
|
501
|
+
const nodes = rules.nodes.filter((node) =>
|
|
502
|
+
!(node && node.type === 'harness' && cleared.includes(node.target)));
|
|
503
|
+
if (nodes.length === rules.nodes.length) return;
|
|
504
|
+
const removedIds = new Set(rules.nodes.filter((node) => !nodes.includes(node)).map((node) => node.id));
|
|
505
|
+
rules.nodes = nodes;
|
|
506
|
+
if (Array.isArray(rules.edges)) {
|
|
507
|
+
rules.edges = rules.edges.filter((edge) => !removedIds.has(edge.from) && !removedIds.has(edge.to));
|
|
508
|
+
}
|
|
509
|
+
log.message(`${dryRun ? 'would prune' : 'prune'} retired rule nodes from ${displayPath(target, rulesPath)}`);
|
|
510
|
+
recordOperation('updated', target, rulesPath);
|
|
511
|
+
if (!dryRun) fs.writeFileSync(rulesPath, `${JSON.stringify(rules, null, 2)}\n`);
|
|
512
|
+
} catch {
|
|
513
|
+
// unreadable rule graph: leave it untouched
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
411
517
|
function isGeneratedLegacyRuleGraph(src, dest) {
|
|
412
518
|
const rel = path.relative(root, src).replace(/\\/g, '/');
|
|
413
519
|
if (rel !== 'templates/tink/rules/index.json') return false;
|
|
@@ -683,6 +789,7 @@ function copySelected(scope, components, agent) {
|
|
|
683
789
|
copyDir(path.join(templateRoot, 'tink/maintenance'), path.join(target, '.tink/maintenance'), target);
|
|
684
790
|
copyDir(path.join(templateRoot, 'tink/tools'), path.join(target, '.tink/tools'), target);
|
|
685
791
|
writeFileFromTemplate(path.join(templateRoot, 'tink/config.json'), path.join(target, '.tink/config.json'), target);
|
|
792
|
+
if (isUpdate) removeRetiredHarnesses(templateRoot, target);
|
|
686
793
|
}
|
|
687
794
|
if (components.includes('memory')) {
|
|
688
795
|
copyDir(path.join(templateRoot, 'tink/memory'), path.join(target, '.tink/memory'), target);
|
|
@@ -700,13 +807,21 @@ function updateGitignore(target, policy) {
|
|
|
700
807
|
log.message('skip .gitignore update: tracking all .tink files');
|
|
701
808
|
return;
|
|
702
809
|
}
|
|
810
|
+
if (policy === 'none') {
|
|
811
|
+
// the user chose to keep .tink out of git; do not touch their .gitignore
|
|
812
|
+
log.message('skip .gitignore update: .tink stays untracked by your choice');
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
703
815
|
const gitignorePath = path.join(target, '.gitignore');
|
|
704
|
-
const ignoreBlock =
|
|
705
|
-
? ['.tink/']
|
|
706
|
-
: ['.tink/current/', '.tink/runs/', '.tink/cache/'];
|
|
816
|
+
const ignoreBlock = ['.tink/current/', '.tink/runs/', '.tink/cache/'];
|
|
707
817
|
|
|
708
818
|
if (fs.existsSync(gitignorePath)) {
|
|
709
819
|
const existing = fs.readFileSync(gitignorePath, 'utf8');
|
|
820
|
+
if (/^\.tink\/\s*$/m.test(existing)) {
|
|
821
|
+
// a legacy install already ignores the whole directory; nothing to add
|
|
822
|
+
log.message('skip .gitignore update: .tink/ already ignored');
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
710
825
|
const missing = ignoreBlock.filter((line) => !existing.includes(line));
|
|
711
826
|
if (missing.length) {
|
|
712
827
|
log.message(`${dryRun ? 'would update' : 'update'} .gitignore`);
|
|
@@ -718,7 +833,7 @@ function updateGitignore(target, policy) {
|
|
|
718
833
|
}
|
|
719
834
|
}
|
|
720
835
|
|
|
721
|
-
function patchConfig(target, scope, hookScope, language) {
|
|
836
|
+
function patchConfig(target, scope, hookScope, language, gitPolicy) {
|
|
722
837
|
const configPath = path.join(target, '.tink/config.json');
|
|
723
838
|
if (!fs.existsSync(configPath) || dryRun) return;
|
|
724
839
|
const rel = displayPath(target, configPath).replace(/\\/g, '/');
|
|
@@ -727,6 +842,7 @@ function patchConfig(target, scope, hookScope, language) {
|
|
|
727
842
|
config.install_scope = scope;
|
|
728
843
|
config.hook_scope = hookScope;
|
|
729
844
|
config.language = language;
|
|
845
|
+
if (gitPolicy) config.git_policy = gitPolicy;
|
|
730
846
|
fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
|
|
731
847
|
}
|
|
732
848
|
|
|
@@ -817,6 +933,14 @@ async function resolveChoices() {
|
|
|
817
933
|
components = defaultComponentValues(agent, language);
|
|
818
934
|
if (includesClaude(agent) && args.includes('--with-hook')) components.push('hook');
|
|
819
935
|
}
|
|
936
|
+
if (isUpdate) {
|
|
937
|
+
// reuse the choices stored at install time; only the agent surface is asked
|
|
938
|
+
const stored = readInstalledConfig();
|
|
939
|
+
if (stored) {
|
|
940
|
+
if (!scope && ['repo', 'global'].includes(stored.install_scope)) scope = stored.install_scope;
|
|
941
|
+
if (['harnesses', 'all', 'none'].includes(stored.git_policy)) gitPolicy = stored.git_policy;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
820
944
|
|
|
821
945
|
if (!interactive) {
|
|
822
946
|
scope = scope || 'repo';
|
|
@@ -1001,7 +1125,7 @@ async function main() {
|
|
|
1001
1125
|
} else if (scope === 'global') {
|
|
1002
1126
|
log.message('skip .gitignore for global install');
|
|
1003
1127
|
}
|
|
1004
|
-
patchConfig(targets.installTarget, scope, hookScope, language);
|
|
1128
|
+
patchConfig(targets.installTarget, scope, hookScope, language, gitPolicy);
|
|
1005
1129
|
|
|
1006
1130
|
const summary = [
|
|
1007
1131
|
`Language: ${language}`,
|