tink-harness 1.9.21 → 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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tink",
3
3
  "description": "A small harness layer for Claude Code and Codex.",
4
- "version": "1.9.21",
4
+ "version": "1.10.0",
5
5
  "author": {
6
6
  "name": "dotori"
7
7
  }
package/CHANGELOG.md CHANGED
@@ -2,9 +2,20 @@
2
2
 
3
3
  All notable changes to Tink are tracked here.
4
4
 
5
- ## [Unreleased]
5
+ ## [1.10.0] - 2026-06-12
6
6
 
7
- No unreleased changes yet.
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.
12
+
13
+ ## [1.9.22] - 2026-06-11
14
+
15
+ ### Added
16
+
17
+ - Install/update completion output now points to the GitHub repo with a one-line star note (ko/zh/en).
18
+ - Progress display gained a full progress map (one bar per phase with the active row marked, an overall bar, and the active phase's step list) shown at plan creation/restructure, phase completion, resume, and on request; the compact 3-line block remains the every-response footer.
8
19
 
9
20
  ## [1.9.21] - 2026-06-11
10
21
 
package/README.ko.md CHANGED
@@ -10,7 +10,7 @@ Tink는 사소하지 않은 모든 에이전트 작업을 눈에 보이는 파
10
10
 
11
11
  <sub>Claude Code와 Codex를 위한 작은 하네스 레이어</sub>
12
12
 
13
- **최신 패키지:** v1.9.21로컬 건강 리포트가 탭형 대시보드로 바뀌었습니다. 3D 하네스 지도, 쉬운 건강 요약, Claude Code와 Codex 양쪽 복사-붙여넣기 명령이 포함된 다음 행동 제안을 제공합니다. 전체 변경 이력은 [CHANGELOG](CHANGELOG.md)를 확인하세요.
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
- - `.tink/harnesses/`: 재사용 가능한 작업 하네스
205
- - `.tink/rules/`: 계약 내용에 맞춰 필요한 하네스, 체크, guard 후보만 고르는 작은 rule graph
206
- - `.tink/schemas/`: `contract.json` 같은 구조화 파일의 스키마
207
- - `.tink/current/`: 현재 실행 상태
208
- - `.tink/runs/`: 완료, 중단, 취소, 교체된 실행 기록
209
- - `.tink/maintenance/`: 검증, friction, weave 신호 기록
210
- - `.tink/memory/`: 승인된 실수, 선호, 교훈
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
- Tink는 기록을 읽어 하네스 건강 요약도 만들 수 있습니다. 요약은 어떤 하네스가 쓰였는지, 어디서 check가 실패하거나 막혔는지, 어떤 하네스가 자주 함께 쓰였는지, 어떤 하네스가 weave 개선 후보인지, frog 정리 검토 후보인지, 병합 검토 후보인지, 오래 쉬어서 보관 검토 후보인지, 더 지켜봐야 하는지를 보여줍니다. 후보 점수, 생애주기 상태, graph 관계, 최근 run timeline도 함께 제공합니다. 이것은 여전히 제안일 뿐입니다. 하네스 수정, 병합, 보관, 삭제, memory 저장, rule 업데이트는 Tink의 기존 명시적 승인 절차를 거쳐야 합니다.
213
+ 전부를 움직이는 원칙은 가지입니다.
213
214
 
214
- 읽기 전용 helper 개가 기록을 [빠른 시작](#하네스-건강을-눈으로-확인)에서 로컬 대시보드로 바꿔 줍니다. 리포트는 정적 로컬 페이지입니다 서버, 파일 감시, hidden cache, public `tink index` 명령이 없습니다. 제안만 준비하며, 재사용 상태 변경은 기존 승인 절차를 그대로 따릅니다.
215
+ 1. **일반 작업에는 하네스가 필요 없습니다.** 평범한 코드 변경·리뷰·문서 작업은 기본 절차(계획 단계 검증 증거)만으로 진행합니다. 하네스는 특화된 절차가 실제로 결과를 바꿀 때만 로드됩니다 출시 안전판, 목표 체크포인트, 계획 비평, 요구사항 인터뷰, 도메인 워크플로.
216
+ 2. **제안만 합니다.** 대시보드·`frog`·`weave`는 실제 사용 신호로 제안을 준비할 뿐입니다. 재사용되는 것(하네스, 메모리, 삭제)은 반드시 별도 명시 승인을 거칩니다. 오늘 실행의 승인이 미래 실행이 물려받을 변경을 허가하지 않습니다.
217
+ 3. **느낌이 아니라 증거.** 실행 기록, 실패한 체크, friction 이벤트가 무엇을 개선하고(`weave`), 초안에서 하네스로 승격하고, 정리할지(`frog`)를 결정합니다. 증거가 약하면 삭제가 아니라 유지·관찰이 기본입니다.
215
218
 
216
- 선택된 하네스에 따라 `.tink/current/goals.json`에는 현재 실행의 목표 체크포인트가, `.tink/current/delegation.md`에는 인수인계 패킷이 추가될 수 있습니다. Tink는 이런 브리프를 보이는 상태로 준비하지만, 별도 승인된 워크플로가 아니면 worker, tmux pane, worktree를 시작하지 않습니다.
219
+ 대시보드는 파일들로 만든 정적 로컬 페이지입니다 서버, 파일 감시, hidden cache, public `tink index` 명령이 없습니다.
217
220
 
218
- Rule graph는 작게 유지합니다. Tink는 먼저 필수 규칙을 고르고, 작업 사실이나 keyword에 맞는 선택 규칙만 가져오며, phase별로 이미 읽은 rule id를 기록해 같은 안내를 반복하지 않습니다.
221
+ <details>
222
+ <summary><strong>설계 문서 색인</strong> — 기여자용 세부 내용</summary>
219
223
 
220
- 설계 메모는 `docs/`에 둡니다. 기본 호환성 기준은 `docs/compatibility-policy.md`에 있으며, 새 작업은 Claude Code Codex, macOS Windows 함께 고려해야 합니다. Repo Signal 동작은 `docs/repo-signals.ko.md` 또는 `docs/repo-signals.md`에 정리되어 있고, 가벼운 graph 규칙 적용 계획은 `docs/graph-rule-adoption-plan.ko.md`에 정리되어 있습니다. 하네스 건강 요약은 `docs/harness-lifecycle-signals.ko.md` 또는 `docs/harness-lifecycle-signals.md`에 정리되어 있습니다. 외부 context 안전 기준은 `docs/mcp-safe-profile.md`와 `docs/external-context-policy.md`에 정리되어 있습니다. `.tink/current/` 상태를 읽거나 검토할 때는 `docs/work-state.ko.md` 또는 `docs/work-state.md`부터 보면 됩니다. 다음 업데이트 안정화 계획은 `docs/phase-5-update-confidence.ko.md`와 `docs/phase-5-update-confidence.md`에 정리되어 있습니다. 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`에서 확인할 수 있습니다. 남은 작업 단위는 `docs/planned-work-units.ko.md` 또는 `docs/planned-work-units.md`에 정리되어 있습니다. 더 큰 아이디어 구현 점검과 로드맵은 `docs/tink-idea-implementation-plan.ko.md`에 정리되어 있습니다.
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
- 중요한 원칙은 승인입니다. 현재 작업을 진행하는 승인과, 미래에도 재사용될 상태를 저장하는 승인은 별개입니다. 새 하네스, 메모리, rule graph, hook guard 저장은 항상 별도 승인이 필요합니다.
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.9.21"><img src="https://img.shields.io/github/v/release/dotoricode/tink-harness?label=release&color=2ea44f" alt="GitHub release"></a>
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.9.21 - The local health report is now a tabbed dashboard with a 3D harness map, plain-language health summaries, and next-action suggestions with copy-paste commands for both Claude Code and Codex. See <a href="CHANGELOG.md">CHANGELOG</a> for release history.</p>
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 uses files you can inspect:
256
+ Everything Tink knows lives in plain files you can read, diff, and delete:
257
257
 
258
- - `.tink/harnesses/`: reusable task harnesses
259
- - `.tink/rules/`: a small rule graph that chooses only relevant harnesses, checks, and opt-in guard candidates
260
- - `.tink/schemas/`: structured file schemas, including the current run contract
261
- - `.tink/current/`: the current run state
262
- - `.tink/runs/`: compact records from finished, blocked, canceled, or replaced runs
263
- - `.tink/maintenance/`: verification, friction, and weave signals that help repeated failures become approved improvements
264
- - `.tink/memory/`: approved mistakes, preferences, and lessons
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
- Tink can also read those records into a harness health summary. The summary shows which harnesses were used, where checks failed or got blocked, which harnesses often appear together, and which ones may deserve a weave improvement, frog cleanup review, merge review, dormant archive review, or more observation. It also includes an explainable candidate score, lifecycle state, graph relationships, and recent run timeline. It only prepares suggestions. Tink does not edit, merge, archive, delete, save memory, or update rules without the same explicit approval gates as the rest of Tink.
267
+ Three rules drive all of it:
267
268
 
268
- Two read-only helpers turn those records into the local dashboard shown in [Install & quick start](#install--quick-start). The report is a static local page - no server, no file watching, no hidden cache, no public `tink index` command. It only prepares suggestions; reusable-state changes keep their approval gates.
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
- When selected, current-run artifacts may also include `.tink/current/goals.json` for goal checkpoints or `.tink/current/delegation.md` for handoff packets. Tink prepares those briefs as visible state; it does not start workers, tmux panes, or worktrees unless a separate approved workflow does so.
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
- The rule graph stays small on purpose. Tink loads matching mandatory rules first, retrieves only relevant optional rules by task facts or keywords, and records loaded rule ids by phase so the same guidance is not repeated in one run.
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
- The important rule is approval.
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
- Tink may suggest a harness, a memory entry, a cleanup, or an improvement. Before each run is committed, Tink runs one quick sanity check and surfaces a proposal only when something important is at stake. Low-risk steps let you continue with recorded assumptions; irreversible or externally visible actions (publish, deploy, deletions, broad changes) require explicit approval. Saving anything reusable — a new harness, a memory entry, a `.claude/` workflow file — always needs its own separate approval; approving the current run does not authorize saves that future installs would inherit.
287
+ </details>
279
288
 
280
289
  ## What Tink is not
281
290
 
package/VERSIONING.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Versioning
2
2
 
3
- Current version: `1.9.21`
3
+ Current version: `1.10.0`
4
4
 
5
5
  Tink follows semver from `1.0.0` onward.
6
6
 
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 detectInstalledLanguage() {
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
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
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 = policy === 'none'
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
 
@@ -740,6 +856,16 @@ function nextStepFor(agent) {
740
856
  return 'Next: open Claude Code and run /tink:cast <task> to start. Run /tink:setup only to review or change settings.';
741
857
  }
742
858
 
859
+ function githubPointer(language) {
860
+ if (language === 'ko') {
861
+ return '문서·데모·로드맵: https://github.com/dotoricode/tink-harness' + String.fromCharCode(10) + 'Tink가 Claude Code·Codex 워크플로에 도움이 됐다면, star 하나가 다른 개발자들이 찾는 데 힘이 됩니다.';
862
+ }
863
+ if (language === 'zh') {
864
+ return '文档、演示与路线图: https://github.com/dotoricode/tink-harness' + String.fromCharCode(10) + '如果 Tink 对你的 Claude Code/Codex 工作流有帮助,一个 star 能帮助更多人发现它。';
865
+ }
866
+ return 'Docs, demo, and roadmap: https://github.com/dotoricode/tink-harness' + String.fromCharCode(10) + 'If Tink helps your Claude Code or Codex workflow, a star helps others find it.';
867
+ }
868
+
743
869
  function doneLineFor(agent) {
744
870
  if (agent === 'codex') return '\nDone. Open Codex and use $tink:cast <task> to start.';
745
871
  if (agent === 'all') return '\nDone. Use /tink:cast <task> in Claude Code or $tink:cast <task> in Codex to start.';
@@ -807,6 +933,14 @@ async function resolveChoices() {
807
933
  components = defaultComponentValues(agent, language);
808
934
  if (includesClaude(agent) && args.includes('--with-hook')) components.push('hook');
809
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
+ }
810
944
 
811
945
  if (!interactive) {
812
946
  scope = scope || 'repo';
@@ -991,7 +1125,7 @@ async function main() {
991
1125
  } else if (scope === 'global') {
992
1126
  log.message('skip .gitignore for global install');
993
1127
  }
994
- patchConfig(targets.installTarget, scope, hookScope, language);
1128
+ patchConfig(targets.installTarget, scope, hookScope, language, gitPolicy);
995
1129
 
996
1130
  const summary = [
997
1131
  `Language: ${language}`,
@@ -1007,10 +1141,12 @@ async function main() {
1007
1141
  if (interactive) {
1008
1142
  note(summary, COPY[language].installed);
1009
1143
  if (isUpdate) note(updateResultSummary(agent, targets), 'Update Result Summary');
1144
+ note(githubPointer(language), 'GitHub');
1010
1145
  outro(COPY[language].done);
1011
1146
  } else {
1012
1147
  console.log(`\n${summary}`);
1013
1148
  if (isUpdate) console.log(`\n${updateResultSummary(agent, targets)}`);
1149
+ console.log(`\n${githubPointer(language)}`);
1014
1150
  console.log(doneLineFor(agent));
1015
1151
  }
1016
1152
  }