@tienne/gestalt 0.120.1 → 0.121.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.
Files changed (132) hide show
  1. package/README.ko.md +22 -3
  2. package/README.md +33 -3
  3. package/dist/package.json +4 -3
  4. package/dist/plugin/.codex-plugin/plugin.json +40 -0
  5. package/dist/plugin/review-agents/writing-reviewer/AGENT.md +4 -2
  6. package/dist/plugin/role-agents/_shared/references/ai-tell-quick-rules.md +2 -1
  7. package/dist/plugin/role-agents/humanize-monolith/AGENT.md +5 -2
  8. package/dist/plugin/scripts/acquisition.mjs +96 -0
  9. package/dist/plugin/scripts/gestalt-cli.mjs +144 -0
  10. package/dist/plugin/scripts/npm-environment.mjs +64 -0
  11. package/dist/plugin/scripts/package-owner.mjs +20 -0
  12. package/dist/plugin/scripts/resolve-cli.mjs +22 -0
  13. package/dist/plugin/skills/_shared/agent-delegation.md +2 -0
  14. package/dist/plugin/skills/_shared/agent-model.md +2 -0
  15. package/dist/plugin/skills/_shared/cli-launcher.md +47 -36
  16. package/dist/plugin/skills/_shared/host-runtime.md +47 -0
  17. package/dist/plugin/skills/agent/SKILL.md +2 -0
  18. package/dist/plugin/skills/architecture/SKILL.md +2 -0
  19. package/dist/plugin/skills/blast-radius/SKILL.md +2 -0
  20. package/dist/plugin/skills/brief/SKILL.md +2 -0
  21. package/dist/plugin/skills/build-graph/SKILL.md +2 -0
  22. package/dist/plugin/skills/diff-radius/SKILL.md +2 -0
  23. package/dist/plugin/skills/dispatch/SKILL.md +2 -0
  24. package/dist/plugin/skills/doubt/SKILL.md +2 -0
  25. package/dist/plugin/skills/execute/SKILL.md +3 -1
  26. package/dist/plugin/skills/explain/SKILL.md +2 -0
  27. package/dist/plugin/skills/handoff/SKILL.md +2 -0
  28. package/dist/plugin/skills/interview/SKILL.md +2 -0
  29. package/dist/plugin/skills/jira-create/SKILL.md +2 -0
  30. package/dist/plugin/skills/lens/SKILL.md +2 -0
  31. package/dist/plugin/skills/local-pr/SKILL.md +4 -2
  32. package/dist/plugin/skills/market-scan/SKILL.md +2 -0
  33. package/dist/plugin/skills/pr/SKILL.md +6 -14
  34. package/dist/plugin/skills/presentation/SKILL.md +2 -0
  35. package/dist/plugin/skills/research/SKILL.md +2 -0
  36. package/dist/plugin/skills/reverse-plan/SKILL.md +2 -0
  37. package/dist/plugin/skills/review/SKILL.md +15 -18
  38. package/dist/plugin/skills/review-loop/SKILL.md +13 -10
  39. package/dist/plugin/skills/review-reply/SKILL.md +7 -15
  40. package/dist/plugin/skills/screen-ref/SKILL.md +2 -0
  41. package/dist/plugin/skills/setup/SKILL.md +9 -6
  42. package/dist/plugin/skills/share/SKILL.md +4 -2
  43. package/dist/plugin/skills/ship/SKILL.md +3 -1
  44. package/dist/plugin/skills/slack-send/SKILL.md +2 -0
  45. package/dist/plugin/skills/solve/SKILL.md +2 -0
  46. package/dist/plugin/skills/spec/SKILL.md +3 -1
  47. package/dist/plugin/skills/ux-research/SKILL.md +2 -0
  48. package/dist/plugin/skills/video-digest/SKILL.md +2 -0
  49. package/dist/schemas/gestalt.schema.json +25 -4
  50. package/dist/src/cli/commands/init.d.ts +2 -0
  51. package/dist/src/cli/commands/init.d.ts.map +1 -1
  52. package/dist/src/cli/commands/init.js +6 -2
  53. package/dist/src/cli/commands/init.js.map +1 -1
  54. package/dist/src/cli/commands/setup.d.ts +4 -1
  55. package/dist/src/cli/commands/setup.d.ts.map +1 -1
  56. package/dist/src/cli/commands/setup.js +2 -1
  57. package/dist/src/cli/commands/setup.js.map +1 -1
  58. package/dist/src/cli/index.d.ts.map +1 -1
  59. package/dist/src/cli/index.js +15 -3
  60. package/dist/src/cli/index.js.map +1 -1
  61. package/dist/src/core/config.d.ts +169 -150
  62. package/dist/src/core/config.d.ts.map +1 -1
  63. package/dist/src/core/config.js +41 -11
  64. package/dist/src/core/config.js.map +1 -1
  65. package/dist/src/core/model-diagnostics.d.ts +9 -0
  66. package/dist/src/core/model-diagnostics.d.ts.map +1 -0
  67. package/dist/src/core/model-diagnostics.js +47 -0
  68. package/dist/src/core/model-diagnostics.js.map +1 -0
  69. package/dist/src/core/types.d.ts +3 -3
  70. package/dist/src/execute/prompts.d.ts.map +1 -1
  71. package/dist/src/execute/prompts.js +4 -1
  72. package/dist/src/execute/prompts.js.map +1 -1
  73. package/dist/src/mcp/client-host.d.ts +12 -0
  74. package/dist/src/mcp/client-host.d.ts.map +1 -0
  75. package/dist/src/mcp/client-host.js +41 -0
  76. package/dist/src/mcp/client-host.js.map +1 -0
  77. package/dist/src/mcp/server.d.ts.map +1 -1
  78. package/dist/src/mcp/server.js +20 -6
  79. package/dist/src/mcp/server.js.map +1 -1
  80. package/dist/src/mcp/tools/status.d.ts +22 -7
  81. package/dist/src/mcp/tools/status.d.ts.map +1 -1
  82. package/dist/src/mcp/tools/status.js +8 -3
  83. package/dist/src/mcp/tools/status.js.map +1 -1
  84. package/package.json +4 -3
  85. package/plugin/.codex-plugin/plugin.json +2 -2
  86. package/plugin/.mcp.json +1 -1
  87. package/plugin/mcp.json +1 -1
  88. package/plugin/review-agents/writing-reviewer/AGENT.md +4 -2
  89. package/plugin/role-agents/_shared/references/ai-tell-quick-rules.md +2 -1
  90. package/plugin/role-agents/humanize-monolith/AGENT.md +5 -2
  91. package/plugin/scripts/acquisition.mjs +96 -0
  92. package/plugin/scripts/gestalt-cli.mjs +144 -0
  93. package/plugin/scripts/npm-environment.mjs +64 -0
  94. package/plugin/scripts/package-owner.mjs +20 -0
  95. package/plugin/scripts/resolve-cli.mjs +22 -0
  96. package/plugin/skills/_shared/agent-delegation.md +2 -0
  97. package/plugin/skills/_shared/agent-model.md +2 -0
  98. package/plugin/skills/_shared/cli-launcher.md +47 -36
  99. package/plugin/skills/_shared/host-runtime.md +47 -0
  100. package/plugin/skills/agent/SKILL.md +2 -0
  101. package/plugin/skills/architecture/SKILL.md +2 -0
  102. package/plugin/skills/blast-radius/SKILL.md +2 -0
  103. package/plugin/skills/brief/SKILL.md +2 -0
  104. package/plugin/skills/build-graph/SKILL.md +2 -0
  105. package/plugin/skills/diff-radius/SKILL.md +2 -0
  106. package/plugin/skills/dispatch/SKILL.md +2 -0
  107. package/plugin/skills/doubt/SKILL.md +2 -0
  108. package/plugin/skills/execute/SKILL.md +3 -1
  109. package/plugin/skills/explain/SKILL.md +2 -0
  110. package/plugin/skills/handoff/SKILL.md +2 -0
  111. package/plugin/skills/interview/SKILL.md +2 -0
  112. package/plugin/skills/jira-create/SKILL.md +2 -0
  113. package/plugin/skills/lens/SKILL.md +2 -0
  114. package/plugin/skills/local-pr/SKILL.md +4 -2
  115. package/plugin/skills/market-scan/SKILL.md +2 -0
  116. package/plugin/skills/pr/SKILL.md +6 -14
  117. package/plugin/skills/presentation/SKILL.md +2 -0
  118. package/plugin/skills/research/SKILL.md +2 -0
  119. package/plugin/skills/reverse-plan/SKILL.md +2 -0
  120. package/plugin/skills/review/SKILL.md +15 -18
  121. package/plugin/skills/review-loop/SKILL.md +13 -10
  122. package/plugin/skills/review-reply/SKILL.md +7 -15
  123. package/plugin/skills/screen-ref/SKILL.md +2 -0
  124. package/plugin/skills/setup/SKILL.md +9 -6
  125. package/plugin/skills/share/SKILL.md +4 -2
  126. package/plugin/skills/ship/SKILL.md +3 -1
  127. package/plugin/skills/slack-send/SKILL.md +2 -0
  128. package/plugin/skills/solve/SKILL.md +2 -0
  129. package/plugin/skills/spec/SKILL.md +3 -1
  130. package/plugin/skills/ux-research/SKILL.md +2 -0
  131. package/plugin/skills/video-digest/SKILL.md +2 -0
  132. package/schemas/gestalt.schema.json +25 -4
package/README.ko.md CHANGED
@@ -246,6 +246,20 @@ codex plugin add gestalt@gestalt
246
246
 
247
247
  스킬은 다음 Codex 세션부터 잡혀요. 슬래시 커맨드랑 Claude Code Task 패널은 Claude Code 전용이라, Codex에서는 하려는 일을 말로 설명하면 Codex가 해당 `SKILL.md`를 읽어 진행해요.
248
248
 
249
+ 설치 후 새 세션에서 “Gestalt 상태 확인해줘” 또는 “이 프로젝트를 Codex용 Gestalt로 초기화해줘”라고 요청하면 돼요.
250
+ MCP 초기화 때 Codex를 인식해서 실행 컨텍스트는 기본으로 `AGENTS.md`에 기록하고 에이전트 모델은 현재
251
+ Codex 세션을 상속해요. 명시한 `client`와 모델 설정은 유지하며 `client: "both"`면 두 호스트 파일에 계속 기록해요.
252
+ 프로젝트에 Codex 설정을 남기려면 초기화 스킬에 요청하면 돼요. 스킬이 아래의 버전 고정 런처로
253
+ `setup --client codex`를 실행해요. 코드 그래프와 Git 훅까지 설정하려면 `init --client codex`를
254
+ 실행하고 고정된 CLI가 아직 이 옵션을 지원하지 않으면 호환 절차를 적용해요.
255
+
256
+ 스킬은 [호스트 어댑터](./plugin/skills/_shared/host-runtime.md)로 Claude 도구 예시를 현재 Codex 도구에 맞춰 실행해요.
257
+ CLI 검사는 [버전 고정 런처](./plugin/skills/_shared/cli-launcher.md)를 거쳐서 전역 `gestalt` 설치가 필요 없어요.
258
+ 첫 CLI 실행은 플러그인과 같은 npm 버전을 내려받을 수 있으니 Node.js >=22와 npm이 PATH에 있어야 해요.
259
+
260
+ 연결이 실패하면 MCP 상태와 Node/npm부터 확인해요. 호스트나 모델이 예상과 다르면 `ges_status`의 `hostClient`,
261
+ `client`, `tierModels`를 먼저 확인해요. Claude Task 패널과 Claude 전용 훅은 Codex 실행을 막지 않아요.
262
+
249
263
  ---
250
264
 
251
265
  ### 옵션 5: OpenAI Codex CLI (MCP만)
@@ -593,7 +607,7 @@ ges_create_agent → action: "submit", sessionId: "<id>", agentContent: "..."
593
607
 
594
608
  ### 스킬
595
609
 
596
- Claude Code에서는 스킬마다 슬래시 커맨드로 불러요. Codex에서는 같은 스킬이 `gestalt:<이름>`이고 하려는 일을 말로 설명하면 Codex가 골라 읽어요.
610
+ Claude Code에서는 스킬마다 슬래시 커맨드로 불러요. Codex에서는 같은 스킬이 `gestalt:<이름>`이고 하려는 일을 말로 설명하면 Codex가 골라 읽어요. 비슷한 스킬 중 뭘 써야 할지 헷갈리면 [스킬 고르기](./docs/choosing-skills.md)를 보세요.
597
611
 
598
612
  | 스킬 | 언제 쓰나요 |
599
613
  |------|-------------|
@@ -617,8 +631,13 @@ Claude Code에서는 스킬마다 슬래시 커맨드로 불러요. Codex에서
617
631
  | `/ship` | 로컬 PR로 리뷰를 수렴시킨 뒤 GitHub에 draft PR로 올리고 Copilot 리뷰까지 받아요 |
618
632
  | `/brief` | 성과 보고, 제안서, RFC, 의사결정 메모를 이해관계자가 읽을 산문으로 써요 |
619
633
  | `/presentation` | 발표 자료를 만들어요. `presentation-writer`가 콘텐츠를 쓰고 `presentation-designer`가 Reveal.js HTML로 조립해요 |
620
- | `/video-digest` | 유튜브 영상으로 요약 뷰어를 만들어요. 불릿마다 근거가 된 스크립트 문장이 달리고 목차와 플레이어, 재생 위치를 따라가는 스크립트가 붙어요. 마크다운으로도 내요 |
621
- | `/lens` | 논문 PDF나 웹 글, 팟캐스트를 같이 읽어 주는 뷰어를 만들어요. 문장마다 원문 쪽과 문단 출처가 달리고 원문 표 숫자와 어긋나는 주장에는 의심 표시가, 끝에는 검증 안 된 부분이 붙어요. 선행 연구 카드와 내 노트 연결, 원문 표로 그린, 눌러 보면 바뀌는 그림도 붙여요 |
634
+ | `/video-digest` | 유튜브 영상이나 영상 파일로 요약 뷰어를 만들어요. 불릿마다 근거가 된 스크립트 문장이 달리고 목차와 플레이어, 재생 위치를 따라가는 스크립트가 붙어요. 마크다운으로도 내요. [영상 요약 뷰어](./docs/video-digest.md) 참고 |
635
+ | `/lens` | 논문 PDF나 웹 글, 팟캐스트를 같이 읽어 주는 뷰어를 만들어요. 문장마다 원문 쪽과 문단 출처가 달리고 원문 표 숫자와 어긋나는 주장에는 의심 표시가, 끝에는 검증 안 된 부분이 붙어요. 선행 연구 카드와 내 노트 연결, 원문 표로 그린, 눌러 보면 바뀌는 그림도 붙여요. [읽기 렌즈](./docs/lens.md) 참고 |
636
+ | `/research` | 경쟁사 조사부터 화면 캡처, 비교표, 판단, 열린 질문까지 한 문서로 내요. 근거 없는 칸은 채우지 않고 확인 못함으로 적어요. [경쟁사 리서치](./docs/research-pipeline.md) 참고 |
637
+ | `/market-scan` | 같은 기능을 하는 서비스 지형을 훑어 레퍼런스 후보를 골라요. 화면 캡처와 분석은 하지 않아요 |
638
+ | `/screen-ref` | 경쟁 서비스의 화면만 수집해요. 화면 지도를 만들고 개인정보를 가짜 값으로 바꿔 캡처하며 결제 직전에서 멈춰요. 해석은 쓰지 않아요 |
639
+ | `/ux-research` | 모아 둔 화면을 사용자와 비즈니스 관점으로 읽고 와우 포인트와 페인 포인트를 써요. 관찰은 캡처에 연결하고 효과 예측에는 해석 표시를 붙여요 |
640
+ | `/reverse-plan` | 경쟁 서비스의 화면이 왜 그렇게 만들어졌는지 본업에서 거꾸로 짚고 가져올 것과 안 가져올 것을 갈라요. 전부 해석으로 써요 |
622
641
  | `/explain` | 개념이나 에러, 코드를 정한 대상에게 맞춰 설명하고 `explain-check`로 판정해요 |
623
642
  | `/jira-create` | `jira-writer`로 지라 티켓을 쓰고 미리보기를 승인하면 생성해요 |
624
643
  | `/slack-send` | 슬랙 메시지를 작성자 어투로 다듬고 승인하면 보내거나 예약해요 |
package/README.md CHANGED
@@ -221,6 +221,27 @@ Skills load on the next Codex session. Slash commands and the Claude Code Task
221
221
  panel are still Claude Code only — in Codex you invoke a skill by describing the
222
222
  task, and Codex reads the matching `SKILL.md`.
223
223
 
224
+ Start a new session after installing, then ask “Check Gestalt status” or
225
+ “Set up Gestalt for this Codex project.” The server recognizes Codex during MCP
226
+ initialization: new projects record execution context in `AGENTS.md`, and agent
227
+ models inherit the active Codex session by default. Explicit `client` and model
228
+ settings still take precedence; `client: "both"` continues writing both host files.
229
+ For a persistent Codex-only project, ask the setup skill to save `client: "codex"`.
230
+ The skill runs `setup --client codex` through the versioned launcher below
231
+ (or `init --client codex` for the graph and Git hook), with compatibility handling
232
+ when the pinned CLI predates that option.
233
+
234
+ Skills use the bundled [host adapter](./plugin/skills/_shared/host-runtime.md) to
235
+ translate Claude tool examples into the available Codex tools. They run CLI
236
+ checks through the [versioned launcher](./plugin/skills/_shared/cli-launcher.md),
237
+ so no global `gestalt` install is needed. The first CLI call may download the
238
+ plugin's exact npm version and needs Node.js >=22 and npm on PATH.
239
+
240
+ If startup fails, check MCP status and Node/npm availability. If the recorded
241
+ host or model is unexpected, inspect `ges_status` fields `hostClient`, `client`,
242
+ and `tierModels` before changing project settings. Claude Task panels and hooks
243
+ are optional host-specific features; they do not gate Codex execution.
244
+
224
245
  ---
225
246
 
226
247
  ### Option 5: OpenAI Codex CLI (MCP only)
@@ -629,7 +650,7 @@ The review `context-collector` uses `blastRadius()` from the Code Knowledge Grap
629
650
 
630
651
  ## Skills
631
652
 
632
- Each skill is a slash command in Claude Code. In Codex the same skill is `gestalt:<name>`, and Codex picks it from your description of the task.
653
+ Each skill is a slash command in Claude Code. In Codex the same skill is `gestalt:<name>`, and Codex picks it from your description of the task. Not sure which of two similar skills to use? See [Choosing a skill](./docs/choosing-skills.md).
633
654
 
634
655
  | Skill | Use it to |
635
656
  |-------|-----------|
@@ -653,8 +674,13 @@ Each skill is a slash command in Claude Code. In Codex the same skill is `gestal
653
674
  | `/ship` | Converge review on a local PR, then open a draft GitHub PR and collect Copilot review |
654
675
  | `/brief` | Performance reports, proposals, RFCs, and decision memos written as prose for stakeholders |
655
676
  | `/presentation` | Slide decks — `presentation-writer` writes the content and `presentation-designer` builds Reveal.js HTML |
656
- | `/video-digest` | Turn a YouTube video into a summary viewer where every bullet cites transcript sentences, with a table of contents, player, and a script that follows playback. Also exports Markdown |
657
- | `/lens` | Read a paper PDF, web article, or podcast with a companion viewer. Every sentence cites the source page and paragraph, claims that disagree with the source's own tables get a doubt mark, and unverified parts are collected at the end. Adds prior-work cards, links to your own notes, and interactive figures drawn from the source tables |
677
+ | `/video-digest` | Turn a YouTube video or a video file into a summary viewer where every bullet cites transcript sentences, with a table of contents, player, and a script that follows playback. Also exports Markdown. See [Video digest](./docs/video-digest.md) |
678
+ | `/lens` | Read a paper PDF, web article, or podcast with a companion viewer. Every sentence cites the source page and paragraph, claims that disagree with the source's own tables get a doubt mark, and unverified parts are collected at the end. Adds prior-work cards, links to your own notes, and interactive figures drawn from the source tables. See [Reading lens](./docs/lens.md) |
679
+ | `/research` | Competitor research in one document: sources, products, screen captures, comparison, judgment, and open questions. Cells without evidence say "unconfirmed" instead of being filled in. See [Competitor research](./docs/research-pipeline.md) |
680
+ | `/market-scan` | Survey the landscape of services that do the same job and pick reference candidates. Stops before screen capture and analysis |
681
+ | `/screen-ref` | Collect only the competitor's screens. Builds a screen map, captures with personal data swapped for fake values, and stops before payment. No interpretation |
682
+ | `/ux-research` | Read collected screens from the user's and the business's point of view and write wow points and pain points. Observations link to captures and predictions carry an interpretation mark |
683
+ | `/reverse-plan` | Work backward from the competitor's core business to why the screen was built that way, then split what to bring over from what not to. Everything is marked as interpretation |
658
684
  | `/explain` | Explain a concept, error, or code for a chosen audience, then check it with `explain-check` |
659
685
  | `/jira-create` | Draft a Jira ticket with `jira-writer` and create it after you approve the preview |
660
686
  | `/slack-send` | Draft a Slack message in the author's voice and send or schedule it after you approve |
@@ -975,6 +1001,10 @@ Claude Code (you)
975
1001
  - [Getting Started](./docs/getting-started.md) — 5-minute walkthrough
976
1002
  - [Configuration Reference](./docs/configuration.md) — full config options
977
1003
  - [Code Knowledge Graph](./docs/code-graph.md) — static analysis and blast-radius
1004
+ - [Choosing a skill](./docs/choosing-skills.md) — which skill to use when several look alike
1005
+ - [Competitor research](./docs/research-pipeline.md) — the five research skills and the order to run them
1006
+ - [Reading lens](./docs/lens.md) — paper, article, and podcast viewer with source citations
1007
+ - [Video digest](./docs/video-digest.md) — YouTube or local video summary viewer with sourced bullets
978
1008
  - [Local PR](./docs/local-pr.md) — in-repo pull requests for agent-to-agent review
979
1009
  - [Architecture View](./docs/architecture-view.md) — evidence-backed architecture diagrams
980
1010
  - [Design Proposals](./docs/proposals/README.md) — planned work to tighten verification from interview to evaluate
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tienne/gestalt",
3
- "version": "0.120.1",
3
+ "version": "0.121.0",
4
4
  "description": "TypeScript AI Development Harness - Gestalt psychology-driven requirement clarification",
5
5
  "repository": {
6
6
  "type": "git",
@@ -29,11 +29,12 @@
29
29
  "scripts": {
30
30
  "dev": "tsx bin/gestalt.ts",
31
31
  "build": "tsc -p tsconfig.build.json",
32
- "postbuild": "rm -rf dist/plugin && mkdir -p dist/plugin && cp -r plugin/agents plugin/role-agents plugin/review-agents plugin/personas plugin/research-sources plugin/skills dist/plugin/ && cp -r schemas dist/ && cp package.json dist/ && chmod +x dist/bin/gestalt.js dist/bin/gestalt-hook.js && pnpm run verify:plugin && pnpm run verify:rules && pnpm run verify:rule-overlap && pnpm run verify:output-style && pnpm run verify:routing && pnpm run verify:agents",
32
+ "postbuild": "rm -rf dist/plugin && mkdir -p dist/plugin && cp -r plugin/agents plugin/role-agents plugin/review-agents plugin/personas plugin/research-sources plugin/skills plugin/scripts plugin/.codex-plugin dist/plugin/ && cp -r schemas dist/ && cp package.json dist/ && chmod +x dist/bin/gestalt.js dist/bin/gestalt-hook.js && pnpm run verify:plugin && pnpm run verify:rules && pnpm run verify:rule-overlap && pnpm run verify:output-style && pnpm run verify:routing && pnpm run verify:agents",
33
33
  "verify:plugin": "tsx scripts/verify-plugin-assets.ts",
34
34
  "prepublishOnly": "pnpm build",
35
35
  "test": "vitest run",
36
36
  "test:watch": "vitest",
37
+ "test:plugin-install": "vitest run --config vitest.plugin-install.config.ts",
37
38
  "serve": "tsx bin/gestalt.ts serve",
38
39
  "bench": "tsx benchmarks/run.ts",
39
40
  "resolution-bench": "tsx benchmarks/run-resolution-benchmark.ts",
@@ -47,7 +48,7 @@
47
48
  "postversion": "pnpm run version:sync && pnpm run build:output-style",
48
49
  "verify:rules": "tsx scripts/verify-rule-refs.ts",
49
50
  "verify:rule-overlap": "tsx scripts/verify-rule-overlap.ts",
50
- "gate": "pnpm typecheck && pnpm verify:rules && pnpm verify:rule-overlap && pnpm verify:routing && pnpm verify:agents && pnpm verify:skill-confusion && pnpm lint && pnpm format:check && pnpm build && pnpm test",
51
+ "gate": "pnpm typecheck && pnpm verify:rules && pnpm verify:rule-overlap && pnpm verify:routing && pnpm verify:agents && pnpm verify:skill-confusion && pnpm lint && pnpm format:check && pnpm build && pnpm test && pnpm test:plugin-install",
51
52
  "humanize:baseline": "tsx scripts/humanize-baseline.ts",
52
53
  "build:output-style": "tsx scripts/build-output-style.ts",
53
54
  "verify:output-style": "tsx scripts/build-output-style.ts --dry-run",
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "gestalt",
3
+ "version": "0.121.0",
4
+ "description": "Gestalt psychology-driven AI development harness. Transforms scattered requirements into structured, validated specifications through interactive interviews.",
5
+ "author": {
6
+ "name": "tienne"
7
+ },
8
+ "homepage": "https://github.com/tienne/gestalt",
9
+ "repository": "https://github.com/tienne/gestalt",
10
+ "license": "MIT",
11
+ "keywords": [
12
+ "gestalt",
13
+ "requirements",
14
+ "specification",
15
+ "interview",
16
+ "mcp",
17
+ "ai-development",
18
+ "code-review"
19
+ ],
20
+ "skills": "./skills/",
21
+ "mcpServers": "./mcp.json",
22
+ "interface": {
23
+ "displayName": "Gestalt",
24
+ "shortDescription": "Requirements-to-spec pipeline with review and PR workflows",
25
+ "longDescription": "Gestalt turns vague ideas into validated specifications through Gestalt-principle interviews, then drives execution, code review, and PR authoring. Bundles workflow skills, specialist agents, and MCP tools.",
26
+ "developerName": "tienne",
27
+ "category": "Developer Tools",
28
+ "capabilities": [
29
+ "Interactive",
30
+ "Read",
31
+ "Write"
32
+ ],
33
+ "websiteURL": "https://github.com/tienne/gestalt",
34
+ "defaultPrompt": [
35
+ "Review this PR with the Gestalt review pipeline.",
36
+ "Clarify these requirements into a structured spec.",
37
+ "Write a PR description for the current branch."
38
+ ]
39
+ }
40
+ }
@@ -53,17 +53,19 @@ git diff --word-diff=porcelain -- <파일>
53
53
 
54
54
  한 조각이 룰에 걸리는지 보려면 그 조각이 들어간 **문장 전체**를 읽어야 합니다. 조각만 떼어 보면 문장 구조로 걸리는 룰(A-18 좌향 수식, C-11 연결어미 뒤 쉼표, E-2 종결어미 반복)을 판정할 수 없습니다. 판정은 문장으로 하되 이슈로 올릴지는 그 룰에 걸린 부분이 바뀐 조각 안에 있는지로 정합니다.
55
55
 
56
+ 아래 런처 경로는 이 문서 디렉토리 기준입니다. 실행 전에 절대 경로로 풉니다. 작업 레포의 동명 파일은 사용하지 않습니다.
57
+
56
58
  **정적 탐지를 먼저 돌립니다.** 정규식으로 잡히는 룰은 사람이 눈으로 세는 것보다 정확합니다.
57
59
 
58
60
  ```bash
59
- gestalt humanize-check --before <파일> --after <파일> --register doc --json
61
+ node "../../scripts/gestalt-cli.mjs" humanize-check --before <파일> --after <파일> --register doc --json
60
62
  ```
61
63
 
62
64
  같은 파일을 `--before`와 `--after`에 둘 다 넘기면 윤문 전후 비교가 아니라 그 파일에 남아 있는 S1 패턴을 세어 줍니다. 잡히는 룰은 21개뿐이니 나머지는 읽고 판정합니다.
63
65
 
64
66
  `--register`는 대상 문서의 성격에 맞춰 고릅니다. 문서는 `doc`, 리뷰 코멘트나 대화체는 `chat`, 보고서는 `report`입니다. `chat`에서는 몇몇 S2가 S1으로 올라갑니다. 사람은 대화에서 개념을 명사 덩어리로 뭉치지 않기 때문입니다.
65
67
 
66
- 레포에 이 명령이 없으면 그 단계를 건너뛰고 직접 읽어 판정합니다. 없다고 리뷰를 멈추지 않습니다.
68
+ 런처를 못 찾거나 버전 해석이 실패하면 그 단계를 건너뛰고 직접 읽어 판정합니다. 건너뛴 사실을 보고합니다.
67
69
 
68
70
  ## 어휘 선택을 볼 때
69
71
 
@@ -214,9 +214,10 @@
214
214
  ## 코드 검사 (자가 채점보다 위)
215
215
 
216
216
  아래 등급은 모델이 스스로 매기는 참고값이다. 실제 판단은 코드가 한다.
217
+ 런처 경로는 이 문서 디렉토리 기준이다. 실행 전에 절대 경로로 풀고 작업 레포의 동명 파일을 사용하지 않는다.
217
218
 
218
219
  ```bash
219
- gestalt humanize-check --before before.md --after after.md --register chat
220
+ node "../../../scripts/gestalt-cli.mjs" humanize-check --before before.md --after after.md --register chat
220
221
  ```
221
222
 
222
223
  변경률, S1 잔존, 보호 토큰 생존, 구조 보존 4가지 측면에서 각각 측정하고 exit code로 답한다
@@ -74,11 +74,14 @@ S2
74
74
 
75
75
  ### 0단계 — 스캔 (룰을 좁힌다)
76
76
 
77
+ 아래 런처 경로는 이 문서 디렉토리 기준이다. 실행 전에 절대 경로로 풀고 작업 레포의 동명 파일을 사용하지 않는다.
78
+ 런처를 못 찾거나 버전 해석이 실패하면 스캔과 코드 검사를 건너뛰고 직접 읽어 판정한다. 건너뛴 사실을 보고한다.
79
+
77
80
  원문이 파일로 있으면 먼저 스캔을 돌린다. 말투는 산문이 `doc`, 리뷰 코멘트와 슬랙이 `chat`,
78
81
  보고서가 `report`다.
79
82
 
80
83
  ```bash
81
- gestalt humanize-scan --file <원문> --register <doc|chat|report>
84
+ node "../../scripts/gestalt-cli.mjs" humanize-scan --file <원문> --register <doc|chat|report>
82
85
  ```
83
86
 
84
87
  종료 코드로도 답한다. 걸린 게 있으면 0, 어투는 깨끗한데 맞춤법만 걸리면 11, 탐지기가 가리는 범위에서 0건이면 10이다. 11이면 어투는 그대로 두고 맞춤법 절만 고쳐서 낸다.
@@ -202,7 +205,7 @@ summary에 적는다. 댈 게 없으면 그건 안 고친 것이라 원문을
202
205
  위 3단계 자가검증은 스스로 매기는 참고값이다. 원문과 윤문본이 파일로 있으면 판단은 코드에 맡긴다.
203
206
 
204
207
  ```bash
205
- gestalt humanize-check --before <원문> --after <윤문본> --register chat --attempt 1
208
+ node "../../scripts/gestalt-cli.mjs" humanize-check --before <원문> --after <윤문본> --register chat --attempt 1
206
209
  ```
207
210
 
208
211
  변경률, S1 제거율, 보호 토큰 생존, 구조 보존, 신규 유입을 각각 측정하고 exit code로 답한다
@@ -0,0 +1,96 @@
1
+ import { spawn } from 'node:child_process';
2
+ import process from 'node:process';
3
+ import { Buffer } from 'node:buffer';
4
+ import { setTimeout, clearTimeout } from 'node:timers';
5
+
6
+ // One cancellation scope spans npm resolution, version verification and cwd cleanup.
7
+ // A child's own signal/timeout is a failed attempt, never a user's cancellation.
8
+ export function createAcquisition({ cwd, env }) {
9
+ let current;
10
+ let signal;
11
+ const cancel = (received) => {
12
+ signal = received;
13
+ current?.cancel(received);
14
+ };
15
+ const terminate = () => cancel('SIGTERM');
16
+ const hangup = () => cancel('SIGHUP');
17
+ const interrupt = () => cancel('SIGINT');
18
+ process.on('SIGTERM', terminate);
19
+ process.on('SIGHUP', hangup);
20
+ process.on('SIGINT', interrupt);
21
+
22
+ return {
23
+ get signal() {
24
+ return signal;
25
+ },
26
+ run(command, args, timeoutMs) {
27
+ return new Promise((resolveResult) => {
28
+ const child = spawn(command, args, { cwd, env, stdio: ['ignore', 'pipe', 'pipe'] });
29
+ let stdout = '';
30
+ let bytes = 0;
31
+ let failed = false;
32
+ let interruptTimer;
33
+ let killTimer;
34
+ let drainTimer;
35
+ const stop = (received) => {
36
+ clearTimeout(interruptTimer);
37
+ child.kill(received);
38
+ // Give npm a chance to forward cancellation first. KILL targets only this
39
+ // direct child; descendants can survive if npm cannot relay the signal.
40
+ killTimer ??= setTimeout(() => child.kill('SIGKILL'), 5000);
41
+ };
42
+ const active = {
43
+ cancel(received) {
44
+ if (received === 'SIGINT') {
45
+ // Do not duplicate group INT. Any surviving child gets TERM after grace.
46
+ interruptTimer ??= setTimeout(() => stop('SIGTERM'), 2000);
47
+ } else stop(received);
48
+ },
49
+ };
50
+ current = active;
51
+ const deadline = setTimeout(() => {
52
+ failed = true;
53
+ stop('SIGTERM');
54
+ }, timeoutMs);
55
+ child.stdout.setEncoding('utf8');
56
+ child.stdout.on('data', (chunk) => {
57
+ bytes += Buffer.byteLength(chunk);
58
+ if (bytes > 64 * 1024) {
59
+ failed = true;
60
+ stop('SIGTERM');
61
+ } else stdout += chunk;
62
+ });
63
+ child.stderr.resume();
64
+ child.once('error', () => {
65
+ failed = true;
66
+ });
67
+ child.once('exit', () => {
68
+ clearTimeout(deadline);
69
+ clearTimeout(interruptTimer);
70
+ clearTimeout(killTimer);
71
+ if (current === active) current = undefined;
72
+ // Descendants can inherit both pipes after npm exits. Drain briefly, then
73
+ // release both handles so they cannot keep resolution open indefinitely.
74
+ drainTimer = setTimeout(() => {
75
+ child.stdout.destroy();
76
+ child.stderr.destroy();
77
+ }, 1000);
78
+ });
79
+ child.once('close', (status) => {
80
+ clearTimeout(deadline);
81
+ clearTimeout(interruptTimer);
82
+ clearTimeout(killTimer);
83
+ clearTimeout(drainTimer);
84
+ if (current === active) current = undefined;
85
+ resolveResult({ status: failed ? null : status, stdout });
86
+ });
87
+ if (signal) active.cancel(signal);
88
+ });
89
+ },
90
+ dispose() {
91
+ process.off('SIGTERM', terminate);
92
+ process.off('SIGHUP', hangup);
93
+ process.off('SIGINT', interrupt);
94
+ },
95
+ };
96
+ }
@@ -0,0 +1,144 @@
1
+ import process from 'node:process';
2
+ import { spawn } from 'node:child_process';
3
+ import {
4
+ existsSync,
5
+ mkdtempSync,
6
+ readFileSync,
7
+ realpathSync,
8
+ rmSync,
9
+ writeFileSync,
10
+ } from 'node:fs';
11
+ import { tmpdir } from 'node:os';
12
+ import { dirname, join, resolve } from 'node:path';
13
+ import { fileURLToPath } from 'node:url';
14
+ import { ownsPackage } from './package-owner.mjs';
15
+ import { createAcquisition } from './acquisition.mjs';
16
+ import { npmEnvironment } from './npm-environment.mjs';
17
+
18
+ const scripts = dirname(fileURLToPath(import.meta.url));
19
+ const pluginRoot = resolve(scripts, '..');
20
+ const args = process.argv.slice(2);
21
+ const fail = (message) => {
22
+ process.stderr.write(`gestalt CLI: ${message}\n`);
23
+ process.exit(1);
24
+ };
25
+
26
+ let version;
27
+ try {
28
+ version = JSON.parse(readFileSync(join(pluginRoot, '.codex-plugin/plugin.json'), 'utf8')).version;
29
+ if (
30
+ typeof version !== 'string' ||
31
+ !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(version)
32
+ ) {
33
+ throw new Error('invalid version');
34
+ }
35
+ } catch {
36
+ fail('cannot read the bundled plugin version; reinstall the plugin.');
37
+ }
38
+ if (Number(process.versions.node.split('.')[0]) < 22) fail('Node.js >=22 is required.');
39
+
40
+ // A development checkout is identified by the wrapper's own location, never cwd.
41
+ // Without tsx, use a matching compiled entry if available, then the exact npm pin.
42
+ const checkout = resolve(pluginRoot, '..');
43
+ let localArgs;
44
+ let sourceTsconfig;
45
+ try {
46
+ const pkg = JSON.parse(readFileSync(join(checkout, 'package.json'), 'utf8'));
47
+ const entry = join(checkout, 'bin/gestalt.ts');
48
+ if (pkg.name === '@tienne/gestalt' && pkg.version === version && existsSync(entry)) {
49
+ const tsx = join(checkout, 'node_modules/tsx/dist/loader.mjs');
50
+ // Keep the target project's tsconfig paths out of the source loader.
51
+ if (existsSync(tsx)) {
52
+ localArgs = ['--import', tsx, entry];
53
+ sourceTsconfig = join(checkout, 'tsconfig.json');
54
+ }
55
+ }
56
+ if (!localArgs && pkg.name === '@tienne/gestalt' && pkg.version === version) {
57
+ const compiled = [join(checkout, 'bin/gestalt.js'), join(checkout, 'dist/bin/gestalt.js')].find(
58
+ existsSync,
59
+ );
60
+ if (compiled) localArgs = [compiled];
61
+ }
62
+ } catch {
63
+ // A copied plugin is deliberately independent of its containing project.
64
+ }
65
+
66
+ let bin;
67
+ if (!localArgs) {
68
+ const neutral = mkdtempSync(join(tmpdir(), 'gestalt-cli-'));
69
+ const acquisition = createAcquisition({ cwd: neutral, env: npmEnvironment(process.env) });
70
+ try {
71
+ writeFileSync(join(neutral, 'package.json'), '{"private":true}\n');
72
+ for (const offline of [true, false]) {
73
+ if (!offline)
74
+ process.stderr.write(`gestalt CLI: resolving @tienne/gestalt@${version} from npm.\n`);
75
+ const result = await acquisition.run(
76
+ 'npm',
77
+ [
78
+ 'exec',
79
+ '--yes',
80
+ `--prefix=${neutral}`,
81
+ '--no-workspaces',
82
+ ...(offline ? ['--offline'] : []),
83
+ `--package=@tienne/gestalt@${version}`,
84
+ '--',
85
+ process.execPath,
86
+ join(scripts, 'resolve-cli.mjs'),
87
+ version,
88
+ ],
89
+ 180_000,
90
+ );
91
+ if (acquisition.signal) break;
92
+ if (result.status !== 0) continue;
93
+ try {
94
+ const candidate = JSON.parse(result.stdout.trim());
95
+ if (typeof candidate !== 'string') continue;
96
+ bin = realpathSync(candidate);
97
+ if (!ownsPackage(bin, version)) {
98
+ bin = undefined;
99
+ continue;
100
+ }
101
+ // Do not trust the resolver process or a stale global executable.
102
+ const check = await acquisition.run(process.execPath, [bin, '--version'], 10_000);
103
+ if (acquisition.signal) break;
104
+ if (check.status === 0 && check.stdout.trim() === version) break;
105
+ bin = undefined;
106
+ } catch {
107
+ bin = undefined;
108
+ }
109
+ }
110
+ } finally {
111
+ try {
112
+ rmSync(neutral, { recursive: true, force: true });
113
+ } finally {
114
+ acquisition.dispose();
115
+ }
116
+ }
117
+ if (acquisition.signal) process.kill(process.pid, acquisition.signal);
118
+ if (!bin)
119
+ fail(
120
+ `could not resolve @tienne/gestalt@${version}; check npm, network access, and its native dependencies. No other version was executed.`,
121
+ );
122
+ }
123
+
124
+ const child = spawn(process.execPath, [...(localArgs ?? [bin]), ...args], {
125
+ cwd: process.cwd(),
126
+ stdio: 'inherit',
127
+ ...(sourceTsconfig ? { env: { ...process.env, TSX_TSCONFIG_PATH: sourceTsconfig } } : {}),
128
+ });
129
+ const terminate = () => child.kill('SIGTERM');
130
+ const hangup = () => child.kill('SIGHUP');
131
+ // Group Ctrl-C reaches the child: do not forward it twice. Programmatic callers
132
+ // must use SIGTERM to cancel only this pid. Signal tests cover both delivery modes.
133
+ const interrupt = () => {};
134
+ process.on('SIGTERM', terminate);
135
+ process.on('SIGHUP', hangup);
136
+ process.on('SIGINT', interrupt);
137
+ child.once('error', (error) => fail(error.message));
138
+ child.once('exit', (code, signal) => {
139
+ process.off('SIGTERM', terminate);
140
+ process.off('SIGHUP', hangup);
141
+ process.off('SIGINT', interrupt);
142
+ if (signal) process.kill(process.pid, signal);
143
+ else process.exit(code ?? 1);
144
+ });
@@ -0,0 +1,64 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import process from 'node:process';
4
+ import { dirname, join } from 'node:path';
5
+
6
+ // Registry settings may deliberately use a provider token. Preserve referenced
7
+ // names before filtering known credentials; native-build and network settings pass through.
8
+ export function npmEnvironment(source) {
9
+ const env = { ...source };
10
+ const find = (name) => Object.keys(env).find((key) => key.toLowerCase() === name);
11
+ const get = (name) => {
12
+ const key = find(name);
13
+ return key === undefined ? undefined : env[key];
14
+ };
15
+ const referenced = new Set();
16
+ // Match npm's optional references and escaped dollar signs in configuration.
17
+ const envExpr = /(?<!\\)(\\*)\$\{([^${}?]+)(\?)?\}/g;
18
+ const collect = (text) => {
19
+ for (const match of text.matchAll(envExpr)) {
20
+ if (match[1].length % 2 === 0) referenced.add(match[2].toLowerCase());
21
+ }
22
+ };
23
+ const expand = (path) =>
24
+ path.replace(envExpr, (whole, escapes, key, optional) => {
25
+ if (escapes.length % 2) return whole.slice((escapes.length + 1) / 2);
26
+ const value = env[key] ?? (optional ? '' : '${' + key + '}');
27
+ return escapes.slice(escapes.length / 2) + value;
28
+ });
29
+ const home = get('home') ?? get('userprofile') ?? homedir();
30
+ const files = [
31
+ get('npm_config_userconfig') ?? join(home, '.npmrc'),
32
+ get('npm_config_globalconfig') ??
33
+ join(get('npm_config_prefix') ?? dirname(dirname(process.execPath)), 'etc/npmrc'),
34
+ ];
35
+ for (const file of files) {
36
+ try {
37
+ const text = readFileSync(expand(file).replace(/^~(?=[/\\])/, home), 'utf8');
38
+ collect(
39
+ text
40
+ .split('\n')
41
+ .filter((line) => !/^\s*[#;]/.test(line))
42
+ .join('\n'),
43
+ );
44
+ } catch {
45
+ // Missing/unreadable npm settings are handled by npm itself.
46
+ }
47
+ }
48
+ for (const [key, value] of Object.entries(env)) {
49
+ if (/^npm_config_/i.test(key)) collect(value);
50
+ }
51
+ for (const key of Object.keys(env)) {
52
+ if (/^npm_|^node_auth_token$/i.test(key) || referenced.has(key.toLowerCase())) continue;
53
+ if (
54
+ /^(ANTHROPIC_|OPENAI_|CLAUDE_CODE_OAUTH_|GEMINI_|CODEX_|XAI_|GROK_|GOOGLE_API_KEY$|AWS_ACCESS_KEY_ID$|AWS_SECRET_ACCESS_KEY$|AWS_SESSION_TOKEN$|GH_TOKEN$|GITHUB_TOKEN$)/i.test(
55
+ key,
56
+ ) ||
57
+ /(_API_KEY|_SECRET|_SECRET_KEY|_CLIENT_SECRET|_PASSWORD|_ACCESS_TOKEN|_AUTH_TOKEN|_BOT_TOKEN)$|^HF_TOKEN$/i.test(
58
+ key,
59
+ )
60
+ )
61
+ delete env[key];
62
+ }
63
+ return env;
64
+ }
@@ -0,0 +1,20 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+
4
+ // The nearest manifest owns the bin; an unrelated parent must not vouch for it.
5
+ export function ownsPackage(bin, version) {
6
+ try {
7
+ let directory = dirname(bin);
8
+ while (directory !== dirname(directory)) {
9
+ const manifest = join(directory, 'package.json');
10
+ if (existsSync(manifest)) {
11
+ const pkg = JSON.parse(readFileSync(manifest, 'utf8'));
12
+ return pkg.name === '@tienne/gestalt' && pkg.version === version;
13
+ }
14
+ directory = dirname(directory);
15
+ }
16
+ } catch {
17
+ return false;
18
+ }
19
+ return false;
20
+ }
@@ -0,0 +1,22 @@
1
+ import process from 'node:process';
2
+ import { realpathSync } from 'node:fs';
3
+ import { delimiter, join } from 'node:path';
4
+ import { ownsPackage } from './package-owner.mjs';
5
+
6
+ // npm exec prepends its package bin directory. Verify the owning package instead
7
+ // of accepting a same-named executable inherited from the user's PATH.
8
+ const expected = process.argv[2];
9
+ for (const entry of (process.env.PATH ?? '').split(delimiter)) {
10
+ if (!entry) continue;
11
+ const candidate = join(entry, 'gestalt');
12
+ try {
13
+ const bin = realpathSync(candidate);
14
+ if (ownsPackage(bin, expected)) {
15
+ process.stdout.write(JSON.stringify(bin));
16
+ process.exit(0);
17
+ }
18
+ } catch {
19
+ // Broken PATH entries and unrelated packages do not resolve this pin.
20
+ }
21
+ }
22
+ process.exitCode = 1;
@@ -1,5 +1,7 @@
1
1
  # 에이전트를 서브에이전트로 위임하기 (공유 규칙)
2
2
 
3
+ > **Codex 등 다른 호스트에서는** [`host-runtime.md`](./host-runtime.md)를 먼저 적용한다. `inherit`는 모델 인자 생략이며 Claude 별칭 폴백을 강제하지 않는다.
4
+
3
5
  > **현재 적용: `review`, `pr`, `brief`, `presentation`, `review-reply`, `slack-send`, `jira-create`, `ship`, `explain`, `research`, `ux-research`, `reverse-plan`.** 에이전트를 불러 쓰는 스킬은 전부 위임한다. 새 스킬이 `ges_agent get`을 메인에서 하면 그건 예외가 아니라 빠뜨린 것이다.
4
6
 
5
7
  ## 왜 필요한가
@@ -1,5 +1,7 @@
1
1
  # 에이전트 tier로 모델 고르기 (공유 규칙)
2
2
 
3
+ > **Codex 등 다른 호스트에서는** [`host-runtime.md`](./host-runtime.md)를 먼저 적용한다. `inherit`는 모델 인자 생략이며 Claude 별칭 폴백을 강제하지 않는다.
4
+
3
5
  `ges_agent`의 `get`과 `list`는 둘 다 `tier`와 **해석된 `model`** 을 돌려준다. 에이전트 frontmatter의
4
6
  tier가 "이 역할이 어느 정도 모델을 필요로 하나"를 선언하고 서버가 그걸 호스트 Agent 도구가 받는
5
7
  별칭으로 옮겨준 값이다. 둘의 차이는 `systemPrompt`가 딸려오느냐뿐이다. `get`은 한 에이전트의