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.
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +13 -2
- package/README.ko.md +31 -20
- package/README.md +32 -23
- package/VERSIONING.md +1 -1
- package/bin/install.js +257 -11
- 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 +6 -3
- 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 +6 -3
- 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,20 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Tink are tracked here.
|
|
4
4
|
|
|
5
|
-
## [
|
|
5
|
+
## [1.11.0] - 2026-06-12
|
|
6
6
|
|
|
7
|
-
|
|
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.
|
|
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이 쌓이면,
|
|
104
|
+
몇 번의 run이 쌓이면, 명령 하나로 기록을 로컬 대시보드로 만들어 브라우저까지 열어 줍니다:
|
|
105
105
|
|
|
106
106
|
```bash
|
|
107
|
-
|
|
108
|
-
node .tink/tools/render-harness-health-report.mjs
|
|
109
|
-
# .tink/maintenance/harness-health-report.html 열기
|
|
107
|
+
npx tink-harness dashboard # 파일만 만들려면 --no-open 추가
|
|
110
108
|
```
|
|
111
109
|
|
|
110
|
+
내부적으로는 읽기 전용 helper 두 개(`node .tink/tools/generate-harness-lifecycle-summary.mjs` → `node .tink/tools/render-harness-health-report.mjs`)를 실행한 뒤 `.tink/maintenance/harness-health-report.html`을 엽니다.
|
|
111
|
+
|
|
112
112
|

|
|
113
113
|
|
|
114
114
|
<sub>당신의 워크플로와 맞는다면, ⭐ 하나가 다른 개발자들이 찾는 데 도움이 됩니다.</sub>
|
|
@@ -147,7 +147,7 @@ Standalone / Codex:
|
|
|
147
147
|
npx tink-harness@latest update
|
|
148
148
|
```
|
|
149
149
|
|
|
150
|
-
업데이트는 질문 하나 — 어떤 agent surface를 갱신할지 — 만 묻고 나머지는 자동으로 처리합니다. Tink가 관리하는 파일(commands, skills,
|
|
150
|
+
업데이트는 질문 하나 — 어떤 agent surface를 갱신할지 — 만 묻고 나머지는 자동으로 처리합니다. 언어·설치 범위·git 정책은 설치 때 선택한 값을 그대로 재사용하며, ".tink 커밋 안 함"을 선택했다면 업데이트가 `.gitignore`를 절대 건드리지 않습니다. Tink가 관리하는 파일(commands, skills, 런타임 tools)은 항상 최신으로 덮어쓰고, 사용자가 수정한 하네스·메모리·설정과 `.tink/maintenance/`의 모든 기록(ledger 등)은 보존합니다.
|
|
151
151
|
|
|
152
152
|
`CODEX_HOME`을 지정하지 않으면 Windows에서는 `%USERPROFILE%\.codex`, macOS/Linux에서는 `~/.codex`에 Codex skill이 설치됩니다.
|
|
153
153
|
|
|
@@ -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.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.
|
|
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,
|
|
118
|
+
After a few runs, one command turns your records into a local dashboard and opens it in your browser:
|
|
119
119
|
|
|
120
120
|
```bash
|
|
121
|
-
|
|
122
|
-
node .tink/tools/render-harness-health-report.mjs
|
|
123
|
-
# then open .tink/maintenance/harness-health-report.html
|
|
121
|
+
npx tink-harness dashboard # add --no-open to just generate the file
|
|
124
122
|
```
|
|
125
123
|
|
|
124
|
+
Under the hood it runs the two read-only helpers (`node .tink/tools/generate-harness-lifecycle-summary.mjs`, then `node .tink/tools/render-harness-health-report.mjs`) and opens `.tink/maintenance/harness-health-report.html`.
|
|
125
|
+
|
|
126
126
|

|
|
127
127
|
|
|
128
128
|
<sub>If this matches your workflow, a ⭐ helps others find it.</sub>
|
|
@@ -187,7 +187,7 @@ To update an existing standalone install (Claude Code or Codex):
|
|
|
187
187
|
npx tink-harness@latest update
|
|
188
188
|
```
|
|
189
189
|
|
|
190
|
-
Update asks one question - which agent surface to refresh - and handles the rest automatically. Tink-owned files (commands, skills,
|
|
190
|
+
Update asks one question - which agent surface to refresh - and handles the rest automatically. Language, install scope, and git policy are reused from the choices you made at install time; if you chose not to commit `.tink`, update never touches your `.gitignore`. Tink-owned files (commands, skills, runtime tools) are always brought to the latest version; your customized harnesses, memory, config, and all `.tink/maintenance/` history records are preserved.
|
|
191
191
|
|
|
192
192
|
If `CODEX_HOME` is not set, Codex skills default to `%USERPROFILE%\.codex` on Windows and `~/.codex` on macOS/Linux.
|
|
193
193
|
|
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
267
|
+
Three rules drive all of it:
|
|
269
268
|
|
|
270
|
-
|
|
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
|
|
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
|
-
|
|
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,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
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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}`,
|