tink-harness 1.9.22 → 1.11.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.22",
4
+ "version": "1.11.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.11.0] - 2026-06-12
6
6
 
7
- No unreleased changes yet.
7
+ - **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.
8
+ - 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).
9
+ - 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.
10
+ - 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.
11
+
12
+ ## [1.10.0] - 2026-06-12
13
+
14
+ - 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.
15
+ - **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`.
16
+ - 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.
17
+ - 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).
18
+ - 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
19
 
9
20
  ## [1.9.22] - 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.22로컬 건강 리포트가 탭형 대시보드로 바뀌었습니다. 3D 하네스 지도, 쉬운 건강 요약, Claude Code와 Codex 양쪽 복사-붙여넣기 명령이 포함된 다음 행동 제안을 제공합니다. 전체 변경 이력은 [CHANGELOG](CHANGELOG.md)를 확인하세요.
13
+ **최신 패키지:** v1.11.0 대시보드: `npx tink-harness dashboard`로 하네스 건강 리포트를 만들고 브라우저로 바로 엽니다. update가 승인 이력·신호 기록을 지우던 심각한 버그와 frog로 삭제한 하네스가 부활하던 문제도 고쳤습니다. 전체 변경 이력은 [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이 쌓이면, 읽기 전용 helper 두 개가 기록을 로컬 대시보드로 바꿔 줍니다:
104
+ 몇 번의 run이 쌓이면, 명령 하나로 기록을 로컬 대시보드로 만들어 브라우저까지 열어 줍니다:
105
105
 
106
106
  ```bash
107
- node .tink/tools/generate-harness-lifecycle-summary.mjs
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
  ![Tink 대시보드 데모 — 건강 그룹 클릭, 하네스 카드 탐색, 3D 지도 조작](.github/assets/demo.gif)
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를 갱신할지 — 만 묻고 나머지는 자동으로 처리합니다. Tink가 관리하는 파일(commands, skills, maintenance, 런타임 tools)은 항상 최신으로 덮어쓰고, 사용자가 수정한 하네스·메모리·설정은 보존합니다.
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
 
@@ -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.22"><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.11.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.22 - 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.11.0 - 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 stops update from resurrecting harnesses you removed via frog. 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, two read-only helpers turn your records into a local dashboard:
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
- node .tink/tools/generate-harness-lifecycle-summary.mjs
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
  ![Tink dashboard demo - clicking a health group, browsing harness cards, and inspecting the 3D map](.github/assets/demo.gif)
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. 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, 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
 
@@ -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:
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
256
+ Everything Tink knows lives in plain files you can read, diff, and delete:
265
257
 
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.
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 |
267
266
 
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.
267
+ Three rules drive all of it:
269
268
 
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.
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.
271
272
 
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
+ 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.
273
274
 
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.22`
3
+ Current version: `1.11.0`
4
4
 
5
5
  Tink follows semver from `1.0.0` onward.
6
6
 
package/bin/install.js CHANGED
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env node
2
+ import { spawnSync } from 'node:child_process';
3
+ import crypto from 'node:crypto';
2
4
  import fs from 'node:fs';
3
5
  import os from 'node:os';
4
6
  import path from 'node:path';
@@ -124,7 +126,70 @@ function argValue(name) {
124
126
  }
125
127
 
126
128
  function usage() {
127
- 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
+ }
128
193
  }
129
194
 
130
195
  function normalizeSurfaces(surfaces) {
@@ -365,7 +430,8 @@ function handleCancel(value) {
365
430
 
366
431
  function readHarnessCount() {
367
432
  const dir = path.join(root, 'templates/tink/harnesses');
368
- return fs.readdirSync(dir).filter((name) => name.endsWith('.md')).length;
433
+ // HARNESS.md is the human catalog, not a harness
434
+ return fs.readdirSync(dir).filter((name) => name.endsWith('.md') && name !== 'HARNESS.md').length;
369
435
  }
370
436
 
371
437
  function displayPath(base, filePath) {
@@ -383,15 +449,14 @@ function shortList(items, emptyText = '- none') {
383
449
  return shown.join('\n');
384
450
  }
385
451
 
386
- function detectInstalledLanguage() {
452
+ function readInstalledConfig() {
387
453
  const candidates = [
388
454
  path.join(process.cwd(), '.tink/config.json'),
389
455
  path.join(os.homedir(), '.tink/config.json')
390
456
  ];
391
457
  for (const configPath of candidates) {
392
458
  try {
393
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
394
- if (['en', 'ko', 'zh'].includes(config.language)) return config.language;
459
+ return JSON.parse(fs.readFileSync(configPath, 'utf8'));
395
460
  } catch {
396
461
  // keep looking
397
462
  }
@@ -399,15 +464,165 @@ function detectInstalledLanguage() {
399
464
  return null;
400
465
  }
401
466
 
467
+ function detectInstalledLanguage() {
468
+ const config = readInstalledConfig();
469
+ if (config && ['en', 'ko', 'zh'].includes(config.language)) return config.language;
470
+ return null;
471
+ }
472
+
402
473
  function isAlwaysUpdatePath(src) {
403
474
  const rel = path.relative(root, src).replace(/\\/g, '/');
404
475
  return rel.startsWith('templates/claude/commands/') ||
405
476
  rel.startsWith('templates/claude/skills/') ||
406
477
  rel.startsWith('templates/codex/skills/') ||
407
- rel.startsWith('templates/tink/maintenance/') ||
408
478
  rel.startsWith('templates/tink/tools/');
409
479
  }
410
480
 
481
+ function isSeedOnlyPath(src) {
482
+ // Runtime record files (ledger, friction, weave queue): the template only
483
+ // seeds them. Once they exist they hold user history and must never be
484
+ // overwritten by install or update.
485
+ const rel = path.relative(root, src).replace(/\\/g, '/');
486
+ return rel.startsWith('templates/tink/maintenance/');
487
+ }
488
+
489
+ // Generic task-type harnesses retired from the default set: generic work now
490
+ // runs on the base run contract alone. Values are normalized (CR-stripped)
491
+ // sha256 hashes of every version ever shipped, so update can tell shipped
492
+ // content (safe to remove) from user-woven content (preserved).
493
+ const RETIRED_HARNESSES = {
494
+ 'code-change': [
495
+ '883396f8a7c69f097476ffd23288597814c5eabfae4ada2ef8a643afb3a80345',
496
+ 'b9320a0fc7f89a7e8898107f0e289758b7265c1fddce3497fc9297703a3edb44'
497
+ ],
498
+ 'bug-fix': [
499
+ '94f747e2cd299ae84fa82b8c54954e518fcca1e95a30edfe571fbf504dd70906',
500
+ 'e2fe201b3de7d7ec748ebd6b7f8f11bb1f702d0deda21c87d21d7ce82867ce1f'
501
+ ],
502
+ research: [
503
+ '57fc4446a0e7d831a1fbd8047f45ad1b3bb595606cb4e643a7fce97308d38197',
504
+ '8e36fa4e3f5f2cb87b5357b33c46a9f9428038f344a12a5b46c79dee0c474aa1'
505
+ ],
506
+ review: [
507
+ '9f0c3f093885b9cf2d796bf60b8f1e5e08805273343a42b1bc87c06d36b7a2a0',
508
+ '3b46487689af1a19e436ea672ccfa78cbf82ffcce4540b826ee95239c968c889'
509
+ ],
510
+ docs: [
511
+ '25e6a8fc0c7ba3b4c50519c91157172f8b551ff847f30cc96a5b3ef64cb2c530',
512
+ '3e42f06aad78f2b18cb5c0c1a0efca17f86cbba9d189f5dbf64efe1c8c75d9b0'
513
+ ]
514
+ };
515
+
516
+ function normalizedSha256(content) {
517
+ return crypto.createHash('sha256').update(content.replace(/\r/g, '')).digest('hex');
518
+ }
519
+
520
+ // Default harnesses the user explicitly removed via an approved /tink:frog
521
+ // operation. Update must not resurrect them.
522
+ const retiredByFrog = new Set();
523
+
524
+ function harnessNameFromDest(dest) {
525
+ const match = String(dest).replace(/\\/g, '/').match(/\.tink\/harnesses\/([^/]+)\.md$/);
526
+ return match ? match[1] : '';
527
+ }
528
+
529
+ function loadFroggedHarnessNames(target) {
530
+ retiredByFrog.clear();
531
+ const ledgerPath = path.join(target, '.tink/maintenance/ledger.jsonl');
532
+ if (!fs.existsSync(ledgerPath)) return;
533
+ let lines;
534
+ try {
535
+ lines = fs.readFileSync(ledgerPath, 'utf8').split('\n');
536
+ } catch {
537
+ return;
538
+ }
539
+ for (const line of lines) {
540
+ const text = line.replace(/^/, '').trim();
541
+ if (!text) continue;
542
+ try {
543
+ const entry = JSON.parse(text);
544
+ if (entry && entry.type === 'frog' && entry.result === 'applied' && Array.isArray(entry.files)) {
545
+ for (const file of entry.files) {
546
+ const name = harnessNameFromDest(file);
547
+ if (name && name !== 'index') retiredByFrog.add(name);
548
+ }
549
+ }
550
+ } catch {
551
+ // skip malformed ledger lines
552
+ }
553
+ }
554
+ }
555
+
556
+ function removeRetiredHarnesses(templateRoot, target) {
557
+ const harnessDir = path.join(target, '.tink/harnesses');
558
+ if (!fs.existsSync(harnessDir)) return;
559
+ const cleared = [];
560
+ for (const [name, hashes] of Object.entries(RETIRED_HARNESSES)) {
561
+ const file = path.join(harnessDir, `${name}.md`);
562
+ if (!fs.existsSync(file)) {
563
+ cleared.push(name);
564
+ continue;
565
+ }
566
+ if (hashes.includes(normalizedSha256(fs.readFileSync(file, 'utf8')))) {
567
+ log.message(`${dryRun ? 'would remove retired' : 'remove retired'} ${displayPath(target, file)}`);
568
+ recordOperation('removedLegacy', target, file);
569
+ if (!dryRun) fs.rmSync(file, { force: true });
570
+ cleared.push(name);
571
+ } else {
572
+ log.message(`keep user-modified retired harness ${displayPath(target, file)}`);
573
+ recordOperation('preserved', target, file);
574
+ }
575
+ }
576
+ syncHarnessIndex(templateRoot, target, cleared);
577
+ pruneRetiredRuleNodes(target, cleared);
578
+ }
579
+
580
+ function syncHarnessIndex(templateRoot, target, cleared) {
581
+ const indexPath = path.join(target, '.tink/harnesses/index.json');
582
+ const templateIndexPath = path.join(templateRoot, 'tink/harnesses/index.json');
583
+ if (!fs.existsSync(indexPath) || !fs.existsSync(templateIndexPath)) return;
584
+ try {
585
+ const installed = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
586
+ const template = JSON.parse(fs.readFileSync(templateIndexPath, 'utf8'));
587
+ if (!Array.isArray(installed) || !Array.isArray(template)) return;
588
+ // keep user entries and metadata; drop entries whose retired file is gone;
589
+ // append default entries the installed index does not know yet
590
+ const kept = installed.filter((entry) => !(entry && cleared.includes(entry.name)));
591
+ const knownNames = new Set(kept.map((entry) => entry && entry.name));
592
+ const added = template.filter((entry) =>
593
+ entry && !knownNames.has(entry.name) && !retiredByFrog.has(entry.name));
594
+ const next = [...kept, ...added];
595
+ if (next.length === installed.length && added.length === 0) return;
596
+ log.message(`${dryRun ? 'would sync' : 'sync'} ${displayPath(target, indexPath)} (${installed.length - kept.length} retired removed, ${added.length} default added)`);
597
+ recordOperation('updated', target, indexPath);
598
+ if (!dryRun) fs.writeFileSync(indexPath, `${JSON.stringify(next, null, 2)}\n`);
599
+ } catch {
600
+ // unreadable index: leave it untouched
601
+ }
602
+ }
603
+
604
+ function pruneRetiredRuleNodes(target, cleared) {
605
+ const rulesPath = path.join(target, '.tink/rules/index.json');
606
+ if (!fs.existsSync(rulesPath)) return;
607
+ try {
608
+ const rules = JSON.parse(fs.readFileSync(rulesPath, 'utf8'));
609
+ if (!rules || !Array.isArray(rules.nodes)) return;
610
+ const nodes = rules.nodes.filter((node) =>
611
+ !(node && node.type === 'harness' && cleared.includes(node.target)));
612
+ if (nodes.length === rules.nodes.length) return;
613
+ const removedIds = new Set(rules.nodes.filter((node) => !nodes.includes(node)).map((node) => node.id));
614
+ rules.nodes = nodes;
615
+ if (Array.isArray(rules.edges)) {
616
+ rules.edges = rules.edges.filter((edge) => !removedIds.has(edge.from) && !removedIds.has(edge.to));
617
+ }
618
+ log.message(`${dryRun ? 'would prune' : 'prune'} retired rule nodes from ${displayPath(target, rulesPath)}`);
619
+ recordOperation('updated', target, rulesPath);
620
+ if (!dryRun) fs.writeFileSync(rulesPath, `${JSON.stringify(rules, null, 2)}\n`);
621
+ } catch {
622
+ // unreadable rule graph: leave it untouched
623
+ }
624
+ }
625
+
411
626
  function isGeneratedLegacyRuleGraph(src, dest) {
412
627
  const rel = path.relative(root, src).replace(/\\/g, '/');
413
628
  if (rel !== 'templates/tink/rules/index.json') return false;
@@ -443,6 +658,13 @@ function isGeneratedLegacyRuleGraph(src, dest) {
443
658
 
444
659
  function writeFileFromTemplate(src, dest, base) {
445
660
  const exists = fs.existsSync(dest);
661
+ if (exists && !force && isSeedOnlyPath(src)) {
662
+ return;
663
+ }
664
+ if (isUpdate && !exists && retiredByFrog.has(harnessNameFromDest(dest))) {
665
+ log.message(`skip frog-removed ${displayPath(base, dest)}`);
666
+ return;
667
+ }
446
668
  if (exists && !force) {
447
669
  if (isUpdate) {
448
670
  const srcContent = fs.readFileSync(src);
@@ -677,12 +899,14 @@ function copySelected(scope, components, agent) {
677
899
  }
678
900
  }
679
901
  if (components.includes('harnesses')) {
902
+ if (isUpdate) loadFroggedHarnessNames(target);
680
903
  copyDir(path.join(templateRoot, 'tink/harnesses'), path.join(target, '.tink/harnesses'), target);
681
904
  copyDir(path.join(templateRoot, 'tink/rules'), path.join(target, '.tink/rules'), target);
682
905
  copyDir(path.join(templateRoot, 'tink/schemas'), path.join(target, '.tink/schemas'), target);
683
906
  copyDir(path.join(templateRoot, 'tink/maintenance'), path.join(target, '.tink/maintenance'), target);
684
907
  copyDir(path.join(templateRoot, 'tink/tools'), path.join(target, '.tink/tools'), target);
685
908
  writeFileFromTemplate(path.join(templateRoot, 'tink/config.json'), path.join(target, '.tink/config.json'), target);
909
+ if (isUpdate) removeRetiredHarnesses(templateRoot, target);
686
910
  }
687
911
  if (components.includes('memory')) {
688
912
  copyDir(path.join(templateRoot, 'tink/memory'), path.join(target, '.tink/memory'), target);
@@ -700,13 +924,21 @@ function updateGitignore(target, policy) {
700
924
  log.message('skip .gitignore update: tracking all .tink files');
701
925
  return;
702
926
  }
927
+ if (policy === 'none') {
928
+ // the user chose to keep .tink out of git; do not touch their .gitignore
929
+ log.message('skip .gitignore update: .tink stays untracked by your choice');
930
+ return;
931
+ }
703
932
  const gitignorePath = path.join(target, '.gitignore');
704
- const ignoreBlock = policy === 'none'
705
- ? ['.tink/']
706
- : ['.tink/current/', '.tink/runs/', '.tink/cache/'];
933
+ const ignoreBlock = ['.tink/current/', '.tink/runs/', '.tink/cache/'];
707
934
 
708
935
  if (fs.existsSync(gitignorePath)) {
709
936
  const existing = fs.readFileSync(gitignorePath, 'utf8');
937
+ if (/^\.tink\/\s*$/m.test(existing)) {
938
+ // a legacy install already ignores the whole directory; nothing to add
939
+ log.message('skip .gitignore update: .tink/ already ignored');
940
+ return;
941
+ }
710
942
  const missing = ignoreBlock.filter((line) => !existing.includes(line));
711
943
  if (missing.length) {
712
944
  log.message(`${dryRun ? 'would update' : 'update'} .gitignore`);
@@ -718,7 +950,7 @@ function updateGitignore(target, policy) {
718
950
  }
719
951
  }
720
952
 
721
- function patchConfig(target, scope, hookScope, language) {
953
+ function patchConfig(target, scope, hookScope, language, gitPolicy) {
722
954
  const configPath = path.join(target, '.tink/config.json');
723
955
  if (!fs.existsSync(configPath) || dryRun) return;
724
956
  const rel = displayPath(target, configPath).replace(/\\/g, '/');
@@ -727,6 +959,7 @@ function patchConfig(target, scope, hookScope, language) {
727
959
  config.install_scope = scope;
728
960
  config.hook_scope = hookScope;
729
961
  config.language = language;
962
+ if (gitPolicy) config.git_policy = gitPolicy;
730
963
  fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
731
964
  }
732
965
 
@@ -817,6 +1050,14 @@ async function resolveChoices() {
817
1050
  components = defaultComponentValues(agent, language);
818
1051
  if (includesClaude(agent) && args.includes('--with-hook')) components.push('hook');
819
1052
  }
1053
+ if (isUpdate) {
1054
+ // reuse the choices stored at install time; only the agent surface is asked
1055
+ const stored = readInstalledConfig();
1056
+ if (stored) {
1057
+ if (!scope && ['repo', 'global'].includes(stored.install_scope)) scope = stored.install_scope;
1058
+ if (['harnesses', 'all', 'none'].includes(stored.git_policy)) gitPolicy = stored.git_policy;
1059
+ }
1060
+ }
820
1061
 
821
1062
  if (!interactive) {
822
1063
  scope = scope || 'repo';
@@ -975,6 +1216,11 @@ async function main() {
975
1216
  process.exit(0);
976
1217
  }
977
1218
 
1219
+ if (command === 'dashboard') {
1220
+ runDashboard();
1221
+ return;
1222
+ }
1223
+
978
1224
  if (command !== 'install' && command !== 'update') {
979
1225
  console.error(`Unknown command: ${command}`);
980
1226
  usage();
@@ -1001,7 +1247,7 @@ async function main() {
1001
1247
  } else if (scope === 'global') {
1002
1248
  log.message('skip .gitignore for global install');
1003
1249
  }
1004
- patchConfig(targets.installTarget, scope, hookScope, language);
1250
+ patchConfig(targets.installTarget, scope, hookScope, language, gitPolicy);
1005
1251
 
1006
1252
  const summary = [
1007
1253
  `Language: ${language}`,