@chief-clancy/terminal 0.1.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/LICENSE +21 -0
- package/README.md +62 -0
- package/dist/hooks/clancy-branch-guard.js +1 -0
- package/dist/hooks/clancy-check-update.js +2 -0
- package/dist/hooks/clancy-context-monitor.js +9 -0
- package/dist/hooks/clancy-credential-guard.js +2 -0
- package/dist/hooks/clancy-drift-detector.js +1 -0
- package/dist/hooks/clancy-notification.js +1 -0
- package/dist/hooks/clancy-post-compact.js +2 -0
- package/dist/hooks/clancy-statusline.js +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/installer/file-ops/file-ops.d.ts +35 -0
- package/dist/installer/file-ops/file-ops.d.ts.map +1 -0
- package/dist/installer/file-ops/file-ops.js +95 -0
- package/dist/installer/file-ops/file-ops.js.map +1 -0
- package/dist/installer/file-ops/index.d.ts +2 -0
- package/dist/installer/file-ops/index.d.ts.map +1 -0
- package/dist/installer/file-ops/index.js +2 -0
- package/dist/installer/file-ops/index.js.map +1 -0
- package/dist/installer/hook-installer/hook-installer.d.ts +22 -0
- package/dist/installer/hook-installer/hook-installer.d.ts.map +1 -0
- package/dist/installer/hook-installer/hook-installer.js +213 -0
- package/dist/installer/hook-installer/hook-installer.js.map +1 -0
- package/dist/installer/hook-installer/index.d.ts +2 -0
- package/dist/installer/hook-installer/index.d.ts.map +1 -0
- package/dist/installer/hook-installer/index.js +2 -0
- package/dist/installer/hook-installer/index.js.map +1 -0
- package/dist/installer/install/index.d.ts +3 -0
- package/dist/installer/install/index.d.ts.map +1 -0
- package/dist/installer/install/index.js +2 -0
- package/dist/installer/install/index.js.map +1 -0
- package/dist/installer/install/install.d.ts +124 -0
- package/dist/installer/install/install.d.ts.map +1 -0
- package/dist/installer/install/install.js +255 -0
- package/dist/installer/install/install.js.map +1 -0
- package/dist/installer/manifest/index.d.ts +2 -0
- package/dist/installer/manifest/index.d.ts.map +1 -0
- package/dist/installer/manifest/index.js +2 -0
- package/dist/installer/manifest/index.js.map +1 -0
- package/dist/installer/manifest/manifest.d.ts +46 -0
- package/dist/installer/manifest/manifest.d.ts.map +1 -0
- package/dist/installer/manifest/manifest.js +180 -0
- package/dist/installer/manifest/manifest.js.map +1 -0
- package/dist/installer/prompts/index.d.ts +2 -0
- package/dist/installer/prompts/index.d.ts.map +1 -0
- package/dist/installer/prompts/index.js +2 -0
- package/dist/installer/prompts/index.js.map +1 -0
- package/dist/installer/prompts/prompts.d.ts +34 -0
- package/dist/installer/prompts/prompts.d.ts.map +1 -0
- package/dist/installer/prompts/prompts.js +28 -0
- package/dist/installer/prompts/prompts.js.map +1 -0
- package/dist/installer/role-filter/index.d.ts +2 -0
- package/dist/installer/role-filter/index.d.ts.map +1 -0
- package/dist/installer/role-filter/index.js +2 -0
- package/dist/installer/role-filter/index.js.map +1 -0
- package/dist/installer/role-filter/role-filter.d.ts +33 -0
- package/dist/installer/role-filter/role-filter.d.ts.map +1 -0
- package/dist/installer/role-filter/role-filter.js +91 -0
- package/dist/installer/role-filter/role-filter.js.map +1 -0
- package/dist/installer/shared/fs-errors/fs-errors.d.ts +3 -0
- package/dist/installer/shared/fs-errors/fs-errors.d.ts.map +1 -0
- package/dist/installer/shared/fs-errors/fs-errors.js +7 -0
- package/dist/installer/shared/fs-errors/fs-errors.js.map +1 -0
- package/dist/installer/shared/fs-errors/index.d.ts +2 -0
- package/dist/installer/shared/fs-errors/index.d.ts.map +1 -0
- package/dist/installer/shared/fs-errors/index.js +2 -0
- package/dist/installer/shared/fs-errors/index.js.map +1 -0
- package/dist/installer/shared/fs-guards/fs-guards.d.ts +3 -0
- package/dist/installer/shared/fs-guards/fs-guards.d.ts.map +1 -0
- package/dist/installer/shared/fs-guards/fs-guards.js +18 -0
- package/dist/installer/shared/fs-guards/fs-guards.js.map +1 -0
- package/dist/installer/shared/fs-guards/index.d.ts +2 -0
- package/dist/installer/shared/fs-guards/index.d.ts.map +1 -0
- package/dist/installer/shared/fs-guards/index.js +2 -0
- package/dist/installer/shared/fs-guards/index.js.map +1 -0
- package/dist/installer/shared/type-guards/index.d.ts +2 -0
- package/dist/installer/shared/type-guards/index.d.ts.map +1 -0
- package/dist/installer/shared/type-guards/index.js +2 -0
- package/dist/installer/shared/type-guards/index.js.map +1 -0
- package/dist/installer/shared/type-guards/type-guards.d.ts +8 -0
- package/dist/installer/shared/type-guards/type-guards.d.ts.map +1 -0
- package/dist/installer/shared/type-guards/type-guards.js +10 -0
- package/dist/installer/shared/type-guards/type-guards.js.map +1 -0
- package/dist/installer/ui/index.d.ts +2 -0
- package/dist/installer/ui/index.d.ts.map +1 -0
- package/dist/installer/ui/index.js +2 -0
- package/dist/installer/ui/index.js.map +1 -0
- package/dist/installer/ui/ui.d.ts +23 -0
- package/dist/installer/ui/ui.d.ts.map +1 -0
- package/dist/installer/ui/ui.js +121 -0
- package/dist/installer/ui/ui.js.map +1 -0
- package/dist/runner/autopilot/autopilot.d.ts +71 -0
- package/dist/runner/autopilot/autopilot.d.ts.map +1 -0
- package/dist/runner/autopilot/autopilot.js +206 -0
- package/dist/runner/autopilot/autopilot.js.map +1 -0
- package/dist/runner/autopilot/index.d.ts +2 -0
- package/dist/runner/autopilot/index.d.ts.map +1 -0
- package/dist/runner/autopilot/index.js +2 -0
- package/dist/runner/autopilot/index.js.map +1 -0
- package/dist/runner/cli-bridge/cli-bridge.d.ts +34 -0
- package/dist/runner/cli-bridge/cli-bridge.d.ts.map +1 -0
- package/dist/runner/cli-bridge/cli-bridge.js +53 -0
- package/dist/runner/cli-bridge/cli-bridge.js.map +1 -0
- package/dist/runner/cli-bridge/index.d.ts +2 -0
- package/dist/runner/cli-bridge/index.d.ts.map +1 -0
- package/dist/runner/cli-bridge/index.js +2 -0
- package/dist/runner/cli-bridge/index.js.map +1 -0
- package/dist/runner/dep-factory/deliver-phase.d.ts +24 -0
- package/dist/runner/dep-factory/deliver-phase.d.ts.map +1 -0
- package/dist/runner/dep-factory/deliver-phase.js +57 -0
- package/dist/runner/dep-factory/deliver-phase.js.map +1 -0
- package/dist/runner/dep-factory/dep-factory.d.ts +38 -0
- package/dist/runner/dep-factory/dep-factory.d.ts.map +1 -0
- package/dist/runner/dep-factory/dep-factory.js +193 -0
- package/dist/runner/dep-factory/dep-factory.js.map +1 -0
- package/dist/runner/dep-factory/index.d.ts +2 -0
- package/dist/runner/dep-factory/index.d.ts.map +1 -0
- package/dist/runner/dep-factory/index.js +2 -0
- package/dist/runner/dep-factory/index.js.map +1 -0
- package/dist/runner/dep-factory/invoke-phase.d.ts +20 -0
- package/dist/runner/dep-factory/invoke-phase.d.ts.map +1 -0
- package/dist/runner/dep-factory/invoke-phase.js +45 -0
- package/dist/runner/dep-factory/invoke-phase.js.map +1 -0
- package/dist/runner/implement/implement.d.ts +38 -0
- package/dist/runner/implement/implement.d.ts.map +1 -0
- package/dist/runner/implement/implement.js +61 -0
- package/dist/runner/implement/implement.js.map +1 -0
- package/dist/runner/implement/index.d.ts +2 -0
- package/dist/runner/implement/index.d.ts.map +1 -0
- package/dist/runner/implement/index.js +2 -0
- package/dist/runner/implement/index.js.map +1 -0
- package/dist/runner/notify/index.d.ts +2 -0
- package/dist/runner/notify/index.d.ts.map +1 -0
- package/dist/runner/notify/index.js +2 -0
- package/dist/runner/notify/index.js.map +1 -0
- package/dist/runner/notify/notify.d.ts +49 -0
- package/dist/runner/notify/notify.d.ts.map +1 -0
- package/dist/runner/notify/notify.js +90 -0
- package/dist/runner/notify/notify.js.map +1 -0
- package/dist/runner/prompt-builder/index.d.ts +2 -0
- package/dist/runner/prompt-builder/index.d.ts.map +1 -0
- package/dist/runner/prompt-builder/index.js +2 -0
- package/dist/runner/prompt-builder/index.js.map +1 -0
- package/dist/runner/prompt-builder/prompt-builder.d.ts +53 -0
- package/dist/runner/prompt-builder/prompt-builder.d.ts.map +1 -0
- package/dist/runner/prompt-builder/prompt-builder.js +122 -0
- package/dist/runner/prompt-builder/prompt-builder.js.map +1 -0
- package/dist/runner/session-report/index.d.ts +2 -0
- package/dist/runner/session-report/index.d.ts.map +1 -0
- package/dist/runner/session-report/index.js +2 -0
- package/dist/runner/session-report/index.js.map +1 -0
- package/dist/runner/session-report/session-report.d.ts +81 -0
- package/dist/runner/session-report/session-report.d.ts.map +1 -0
- package/dist/runner/session-report/session-report.js +227 -0
- package/dist/runner/session-report/session-report.js.map +1 -0
- package/dist/runner/shared/types.d.ts +30 -0
- package/dist/runner/shared/types.d.ts.map +1 -0
- package/dist/runner/shared/types.js +2 -0
- package/dist/runner/shared/types.js.map +1 -0
- package/dist/shared/ansi/ansi.d.ts +59 -0
- package/dist/shared/ansi/ansi.d.ts.map +1 -0
- package/dist/shared/ansi/ansi.js +59 -0
- package/dist/shared/ansi/ansi.js.map +1 -0
- package/dist/shared/ansi/index.d.ts +2 -0
- package/dist/shared/ansi/index.d.ts.map +1 -0
- package/dist/shared/ansi/index.js +2 -0
- package/dist/shared/ansi/index.js.map +1 -0
- package/package.json +52 -0
- package/src/agents/agents.test.ts +57 -0
- package/src/agents/arch-agent.md +80 -0
- package/src/agents/concerns-agent.md +96 -0
- package/src/agents/design-agent.md +146 -0
- package/src/agents/devils-advocate.md +54 -0
- package/src/agents/quality-agent.md +178 -0
- package/src/agents/tech-agent.md +101 -0
- package/src/agents/verification-gate.md +128 -0
- package/src/roles/implementer/commands/autopilot.md +11 -0
- package/src/roles/implementer/commands/dry-run.md +15 -0
- package/src/roles/implementer/commands/implement.md +19 -0
- package/src/roles/implementer/workflows/autopilot.md +136 -0
- package/src/roles/implementer/workflows/implement.md +161 -0
- package/src/roles/planner/commands/approve-plan.md +11 -0
- package/src/roles/planner/commands/plan.md +22 -0
- package/src/roles/planner/workflows/approve-plan.md +970 -0
- package/src/roles/planner/workflows/plan.md +868 -0
- package/src/roles/reviewer/commands/logs.md +7 -0
- package/src/roles/reviewer/commands/review.md +9 -0
- package/src/roles/reviewer/commands/status.md +9 -0
- package/src/roles/reviewer/workflows/logs.md +109 -0
- package/src/roles/reviewer/workflows/review.md +197 -0
- package/src/roles/reviewer/workflows/status.md +142 -0
- package/src/roles/roles.test.ts +87 -0
- package/src/roles/setup/commands/doctor.md +7 -0
- package/src/roles/setup/commands/help.md +80 -0
- package/src/roles/setup/commands/init.md +7 -0
- package/src/roles/setup/commands/map-codebase.md +17 -0
- package/src/roles/setup/commands/settings.md +7 -0
- package/src/roles/setup/commands/uninstall.md +5 -0
- package/src/roles/setup/commands/update-docs.md +9 -0
- package/src/roles/setup/commands/update.md +13 -0
- package/src/roles/setup/workflows/doctor.md +131 -0
- package/src/roles/setup/workflows/init.md +1096 -0
- package/src/roles/setup/workflows/map-codebase.md +130 -0
- package/src/roles/setup/workflows/scaffold.md +872 -0
- package/src/roles/setup/workflows/settings.md +958 -0
- package/src/roles/setup/workflows/uninstall.md +170 -0
- package/src/roles/setup/workflows/update-docs.md +95 -0
- package/src/roles/setup/workflows/update.md +287 -0
- package/src/roles/strategist/commands/approve-brief.md +23 -0
- package/src/roles/strategist/commands/brief.md +29 -0
- package/src/roles/strategist/workflows/approve-brief.md +1540 -0
- package/src/roles/strategist/workflows/brief.md +1330 -0
- package/src/templates/CLAUDE.md +101 -0
- package/src/templates/templates.test.ts +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alex Clapperton
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# @chief-clancy/terminal
|
|
2
|
+
|
|
3
|
+
Installer, slash commands, hooks, runners, agents, and Claude CLI bridge for [Clancy](https://github.com/Pushedskydiver/chief-clancy).
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@chief-clancy/terminal) [](../../LICENSE)
|
|
6
|
+
|
|
7
|
+
> This package is part of the [Clancy monorepo](https://github.com/Pushedskydiver/chief-clancy). You don't install it directly — run `npx chief-clancy` to install Clancy into your project.
|
|
8
|
+
|
|
9
|
+
## What's in it
|
|
10
|
+
|
|
11
|
+
### Installer
|
|
12
|
+
|
|
13
|
+
The install orchestrator that powers `npx chief-clancy`. Copies slash commands, workflows, hooks, and runtime bundles into the user's `.claude/` and `.clancy/` directories. Supports global (`~/.claude`) and local (`./.claude`) installs, SHA-256 manifest tracking for patch preservation, and role-based filtering via `CLANCY_ROLES`.
|
|
14
|
+
|
|
15
|
+
### Slash commands & workflows
|
|
16
|
+
|
|
17
|
+
18 slash commands across 5 roles, each backed by a detailed workflow file:
|
|
18
|
+
|
|
19
|
+
| Role | Commands |
|
|
20
|
+
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
21
|
+
| **Implementer** | `/clancy:implement`, `/clancy:autopilot`, `/clancy:dry-run` |
|
|
22
|
+
| **Reviewer** | `/clancy:review`, `/clancy:status`, `/clancy:logs` |
|
|
23
|
+
| **Setup** | `/clancy:init`, `/clancy:settings`, `/clancy:doctor`, `/clancy:map-codebase`, `/clancy:update-docs`, `/clancy:update`, `/clancy:uninstall`, `/clancy:help` |
|
|
24
|
+
| **Planner** | `/clancy:plan`, `/clancy:approve-plan` |
|
|
25
|
+
| **Strategist** | `/clancy:brief`, `/clancy:approve-brief` |
|
|
26
|
+
|
|
27
|
+
### Hooks
|
|
28
|
+
|
|
29
|
+
CJS bundles built by esbuild — best-effort, fail-open, never block the user:
|
|
30
|
+
|
|
31
|
+
| Hook | Event | Purpose |
|
|
32
|
+
| ------------------------- | ------------ | ------------------------------------------------------------ |
|
|
33
|
+
| `clancy-credential-guard` | PreToolUse | Blocks credential writes to source files |
|
|
34
|
+
| `clancy-branch-guard` | PreToolUse | Blocks force push, protected branch push, destructive resets |
|
|
35
|
+
| `clancy-context-monitor` | PostToolUse | Context % warnings and time guard |
|
|
36
|
+
| `clancy-drift-detector` | PostToolUse | Warns when runtime files are outdated |
|
|
37
|
+
| `clancy-statusline` | Statusline | Context usage bar and update notices |
|
|
38
|
+
| `clancy-check-update` | SessionStart | Background npm version check |
|
|
39
|
+
| `clancy-post-compact` | PostCompact | Re-injects ticket context after compaction |
|
|
40
|
+
| `clancy-notification` | Notification | Native OS desktop notifications |
|
|
41
|
+
|
|
42
|
+
### Runners
|
|
43
|
+
|
|
44
|
+
Two execution modes powered by the `@chief-clancy/core` pipeline:
|
|
45
|
+
|
|
46
|
+
- **`runImplement`** — single ticket: fetch, implement, deliver, exit
|
|
47
|
+
- **`runAutopilot`** — loop: repeat implement until queue is empty, generate session report
|
|
48
|
+
|
|
49
|
+
### Agents
|
|
50
|
+
|
|
51
|
+
Specialist agent prompts (`.md` files) for codebase scanning, devil's advocate grilling, verification gates, and more.
|
|
52
|
+
|
|
53
|
+
## Documentation
|
|
54
|
+
|
|
55
|
+
- [Architecture](../../docs/ARCHITECTURE.md) — full module map and package boundaries
|
|
56
|
+
- [Role documentation](../../docs/roles/) — detailed docs for each role
|
|
57
|
+
- [Configuration](../../docs/guides/CONFIGURATION.md) — all env vars and settings
|
|
58
|
+
- [Testing](../../docs/TESTING.md) — test patterns, E2E setup, CI schedule
|
|
59
|
+
|
|
60
|
+
## License
|
|
61
|
+
|
|
62
|
+
MIT — see [LICENSE](../../LICENSE).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var E=require("node:fs");function r(){return{decision:"approve"}}function u(t){return{decision:"block",reason:t}}function p(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}var l={};var k=0;function g(t){try{let e=JSON.parse(t);return p(e)?e:l}catch{return l}}function f(t){let e=t.argv[2];if(e)return g(e);try{let n=t.readFileSync(k,"utf8");return g(n)}catch{return l}}var i=["main","master","develop"],A=/\bgit\s+push\b/,R=/\s--force\b/,S=/\s-f\b/,b=/--force-with-lease/,T=/\bgit\s+reset\s+--hard\b/,O=/\bgit\s+clean\s+(.*)/,y=/(?:^|\s)-[a-zA-Z]*f/,P=/(?:^|\s)-[a-zA-Z]*n/,D=/\bgit\s+checkout\s+--\s+\./,F=/\bgit\s+restore\s+\.(?:\s*$|\s*[;&|])/,N=/\bgit\s+branch\s+.*-D\b/,x="Blocked: git push --force destroys remote history. Use --force-with-lease instead.",B=t=>`Blocked: direct push to protected branch '${t}'. Create a PR instead.`,L="Blocked: git reset --hard discards all uncommitted changes.",I="Blocked: git clean -f deletes untracked files. Use -n for a dry run first.",H="Blocked: git checkout -- . discards all unstaged changes.",U="Blocked: git restore . discards all unstaged changes.",M="Blocked: git branch -D force-deletes without merge check. Use -d for safe deletion.";function _(t){return t?i.includes(t)?[...i]:[...i,t]:[...i]}function G(t){let e=/\bgit\s+push\b[^&|;]*/.exec(t);return e?e[0]:null}function v(t){let e=G(t);if(!e)return null;let n=R.test(e)||S.test(e),o=b.test(e);return n&&!o?x:null}function J(t,e){if(!A.test(t))return null;let n=t.split(/\s+/),o=n.indexOf("push");if(o<0)return null;let c=(n.slice(o+1).filter(a=>!a.startsWith("-"))[1]??"").split(":")[0],h=e.find(a=>c===a);return h?B(h):null}function w(t){return T.test(t)?L:null}function Y(t){let e=O.exec(t);if(!e)return null;let n=e[1],o=y.test(n),s=P.test(n);return o&&!s?I:null}function j(t){return D.test(t)?H:null}function z(t){return F.test(t)?U:null}function K(t){return N.test(t)?M:null}function m(t,e){return t?[()=>v(t),()=>J(t,e),()=>w(t),()=>Y(t),()=>j(t),()=>z(t),()=>K(t)].map(s=>s()).find(Boolean)??null:null}try{process.env.CLANCY_BRANCH_GUARD==="false"&&(console.log(JSON.stringify(r())),process.exit(0));let e=f({argv:process.argv,readFileSync:E.readFileSync}),n=e.tool_name??"",o=e.tool_input??{},s=typeof o.command=="string"?o.command:"";n==="Bash"||(console.log(JSON.stringify(r())),process.exit(0));let d=_(process.env.CLANCY_BASE_BRANCH),c=m(s,d);console.log(JSON.stringify(c?u(c):r()))}catch{console.log(JSON.stringify(r()))}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var S=require("node:child_process"),r=require("node:fs"),o=require("node:os");var c=require("node:path"),u="VERSION",a=(0,c.join)(".claude","commands","clancy"),x=(0,c.join)(".clancy","briefs"),v=(0,c.join)(".clancy",".brief-stale-count"),D=7,F=864e5;function d(t,e){let n=(0,c.join)(t,a);if(l(n,e))return n;let i=(0,c.join)(e.homedir(),a);return l(i,e)?i:null}function l(t,e){try{return e.readFileSync((0,c.join)(t,u),"utf8"),!0}catch{return!1}}function f(t,e){try{return e.readFileSync((0,c.join)(t,u),"utf8").trim()}catch{return"0.0.0"}}var b=11;function g(t){let e=t.slice(0,b-1),n=new Date(`${e}T00:00:00Z`);return!isNaN(n.getTime())?n:null}function k(t,e){if(!(t.endsWith(".md")&&!t.endsWith(".feedback.md"))||e.deps.existsSync((0,c.join)(e.briefsPath,`${t}.approved`)))return!1;let s=g(t);return s!==null&&s.getTime()<e.staleThreshold}function h(t,e,n){let i=(0,c.join)(t,x);try{let s=n.readdirSync(i),m=e-D*F,w={briefsPath:i,staleThreshold:m,deps:n};return s.filter(_=>k(_,w)).length}catch{return null}}function p(t){let e=(0,c.join)(t,".claude","cache");return{dir:e,file:(0,c.join)(e,"clancy-update-check.json")}}function y(t){return(0,c.join)(t,v)}try{let t=process.cwd(),e=(0,o.homedir)();A(t);let n=d(t,{readFileSync:r.readFileSync,homedir:o.homedir});n||process.exit(0),E(n,e)}catch{}function A(t){let e=y(t),n=h(t,Date.now(),{readdirSync:r.readdirSync,existsSync:r.existsSync});if(n===null){try{(0,r.unlinkSync)(e)}catch{}return}try{(0,r.writeFileSync)(e,String(n))}catch{}}function E(t,e){let n=f(t,{readFileSync:r.readFileSync}),i=p(e);try{(0,r.mkdirSync)(i.dir,{recursive:!0})}catch{}(0,S.spawn)(process.execPath,["-e",I(n,i.file)],{detached:!0,stdio:"ignore",windowsHide:!0}).unref()}function I(t,e){let n=JSON.stringify(t),i=JSON.stringify(e);return["const { execFileSync } = require('child_process');","const fs = require('fs');","let latest = 'unknown';","try {"," latest = execFileSync('npm', ['view', 'chief-clancy', 'version'], { timeout: 10000, encoding: 'utf8' }).trim();","} catch {}",`const installed = ${n};`,"const hasLatest = latest !== 'unknown' && latest !== '';","const cache = {"," update_available: hasLatest && latest !== installed,"," installed,"," latest,"," checked: Math.floor(Date.now() / 1000),","};",`try { fs.writeFileSync(${i}, JSON.stringify(cache)); } catch {}`].join(`
|
|
2
|
+
`)}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";var g=require("node:fs"),k=require("node:os");function x(e,t){return{hookSpecificOutput:{hookEventName:e,additionalContext:t}}}var P=require("node:path");function l(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function y(e,t){let n=(0,P.join)(e,".clancy","lock.json");try{let r=JSON.parse(t.readFileSync(n,"utf8"));return l(r)?r:null}catch{return null}}var M=require("node:stream/consumers");var w={},G=3e3;function Y(e){try{let t=JSON.parse(e);return l(t)?t:w}catch{return w}}function H(e){return new Promise(t=>{setTimeout(()=>t(w),e)})}function S(e){let t=e.timeoutMs??G,n=(0,M.text)(e.stdin).then(Y);return Promise.race([n,H(t)])}var L=require("node:path");function b(e){return String(e).replace(/[^a-zA-Z0-9_-]/g,"")}function C(e,t){return(0,L.join)(t.tmpdir(),`clancy-ctx-${b(e)}.json`)}function I(e,t){return(0,L.join)(t.tmpdir(),`clancy-ctx-${b(e)}-warned.json`)}var J=35,B=25,q=60,K=5,V=80,X=100,N=30;function A(e,t,n){return e||t>=K||n}function D(e,t,n){let r={message:null,debounce:t};if(!e||e.timestamp!==void 0&&n-e.timestamp>q)return r;let o=e.remaining_percentage;if(!(o<=J))return r;let m=t.callsSinceWarn+1,a=o<=B,d=a?"critical":"warning",p=t.lastLevel===null,c=t.lastLevel==="warning";return A(p,m,a&&c)?{message:a?Z(e.used_pct,o):z(e.used_pct,o),debounce:{callsSinceWarn:0,lastLevel:d}}:{message:null,debounce:{callsSinceWarn:m,lastLevel:t.lastLevel}}}function O(e,t){let n={message:null,debounce:t},{startedAt:r,timeLimitMinutes:i,nowMs:o}=e;if(!r||i<=0)return n;let s=new Date(r).getTime();if(isNaN(s))return n;let a=o-s,d=Math.floor(a/6e4),p=i*6e4,c=Math.floor(a/p*100);if(c<V)return n;let f=t.callsSinceWarn+1,u=c>=X,E=u?"critical":"warning",$=t.lastLevel===null,j=t.lastLevel==="warning";return A($,f,u&&j)?{message:u?tt(d,i):Q(d,i,c),debounce:{callsSinceWarn:0,lastLevel:E}}:{message:null,debounce:{callsSinceWarn:f,lastLevel:t.lastLevel}}}var v={callsSinceWarn:0,lastLevel:null},h={context:v,time:v};function F(e){try{let t=JSON.parse(e);return l(t)?{context:_(t.context),time:_(t.time)}:h}catch{return h}}function W(e){try{let t=JSON.parse(e);if(!l(t))return null;let n=t.remaining_percentage,r=t.used_pct;if(!(typeof n=="number"&&typeof r=="number"&&Number.isFinite(n)&&Number.isFinite(r)))return null;let o={remaining_percentage:n,used_pct:r},s=t.timestamp;return typeof s=="number"&&Number.isFinite(s)?{...o,timestamp:s}:o}catch{return null}}function R(e){if(e===void 0)return N;let t=Number(e);return Number.isFinite(t)?t:N}function _(e){if(!l(e))return v;let t=e.callsSinceWarn,n=typeof t=="number"?t:0,r=e.lastLevel;return{callsSinceWarn:n,lastLevel:r==="warning"||r==="critical"?r:null}}function z(e,t){return`CONTEXT WARNING: Usage at ${e}%. Remaining: ${t}%. Context is getting limited. Stop exploring and move to implementation. Avoid reading additional files unless strictly necessary. Commit completed work as soon as it is ready.`}function Z(e,t){return`CONTEXT CRITICAL: Usage at ${e}%. Remaining: ${t}%. Context is nearly exhausted. Stop reading files and wrap up immediately:
|
|
2
|
+
1. Commit whatever work is staged on the current feature branch
|
|
3
|
+
2. Append a WIP entry to .clancy/progress.txt: YYYY-MM-DD HH:MM | TICKET-KEY | Summary | WIP \u2014 context exhausted
|
|
4
|
+
3. Inform the user what was completed and what remains.
|
|
5
|
+
Do NOT start any new work.`}function Q(e,t,n){return`TIME WARNING: Ticket implementation at ${e}min of ${t}min limit (${n}%).
|
|
6
|
+
Wrap up implementation and prepare for delivery. Avoid starting new approaches.`}function tt(e,t){return`TIME CRITICAL: Time limit reached (${e}min of ${t}min).
|
|
7
|
+
STOP implementation immediately. Commit current work, push the branch,
|
|
8
|
+
and create the PR with whatever is ready. Log a WIP entry if incomplete.`}S({stdin:process.stdin}).then(et).catch(()=>{});function et(e){let t=e.session_id??"";if(!t)return;let n=e.cwd??process.cwd(),r={readFileSync:g.readFileSync},i={tmpdir:k.tmpdir},o=I(t,i),s=nt(o,r),m=C(t,i),a=U(m,r),d=a?W(a):null,p=Math.floor(Date.now()/1e3),c=D(d,s.context,p),T=y(n,r),f=R(process.env.CLANCY_TIME_LIMIT),u=O({startedAt:T?.startedAt,timeLimitMinutes:f,nowMs:Date.now()},s.time);rt(o,s,{context:c,time:u}),it(c,u)}function U(e,t){try{return t.readFileSync(e,"utf8")}catch{return null}}function nt(e,t){let n=U(e,t);return n?F(n):h}function rt(e,t,n){if(!(n.context.debounce!==t.context||n.time.debounce!==t.time))return;let i={context:n.context.debounce,time:n.time.debounce};try{(0,g.writeFileSync)(e,JSON.stringify(i))}catch{}}function it(e,t){let n=[e.message,t.message].filter(i=>i!==null);if(n.length===0)return;let r=x("PostToolUse",n.join(`
|
|
9
|
+
`));process.stdout.write(JSON.stringify(r))}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var x=require("node:fs");function o(){return{decision:"approve"}}function i(e){return{decision:"block",reason:e}}function f(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}var a={};var P=0;function g(e){try{let t=JSON.parse(e);return f(t)?t:a}catch{return a}}function c(e){let t=e.argv[2];if(t)return g(t);try{let n=e.readFileSync(P,"utf8");return g(n)}catch{return a}}function r(e,t,n){return new RegExp(`(?:${e})\\s*[:=]\\s*["']?${t}{${n},}["']?`,"i")}var l="[A-Za-z0-9\\-_.]",h="[A-Za-z0-9+/=]",v="[A-Za-z0-9/+=]",b=[{name:"Generic API key",regex:r("api[_-]?key|apikey",l,20)},{name:"Generic secret",regex:r("secret|private[_-]?key",l,20)},{name:"Generic token",regex:r("auth[_-]?token|access[_-]?token|bearer",l,20)},{name:"Generic password",regex:r("password|passwd|pwd",`[^\\s"']`,8)},{name:"AWS Secret Key",regex:r("aws_secret_access_key|aws_secret",v,40)},{name:"Atlassian API token",regex:r("jira_api_token|atlassian[_-]?token",h,24)},{name:"AWS Access Key",regex:/AKIA[0-9A-Z]{16}/},{name:"GitHub PAT (classic)",regex:/ghp_[A-Za-z0-9]{36}/},{name:"GitHub PAT (fine-grained)",regex:/github_pat_[A-Za-z0-9_]{82}/},{name:"GitHub OAuth token",regex:/gho_[A-Za-z0-9]{36}/},{name:"Slack token",regex:/xox[bpors]-[0-9]{10,}-[A-Za-z0-9-]+/},{name:"Stripe key",regex:/(?:sk|pk)_(?:live|test)_[A-Za-z0-9]{24,}/},{name:"Linear API key",regex:/lin_api_[A-Za-z0-9]{40,}/},{name:"Private key",regex:/-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/},{name:"Database connection string",regex:/(?:mongodb|postgres|mysql|redis):\/\/[^\s"']+:[^\s"']+@/i}],w=[".clancy/.env",".env.local",".env.example",".env.development",".env.test"];function m(e){return w.some(t=>e.endsWith(t))}function A(e){return e?b.filter(({regex:t})=>t.test(e)).map(({name:t})=>t):[]}function T(e){if(!(typeof e=="object"&&e!==null))return;let n=e.new_string;return typeof n=="string"?n:void 0}function d(e,t){if(e==="Write"){let n=t.content;return typeof n=="string"?n:null}if(e==="Edit"){let n=t.new_string;return typeof n=="string"?n:null}if(e==="MultiEdit"){let n=t.edits;return Array.isArray(n)?n.map(T).filter(Boolean).join(`
|
|
2
|
+
`):null}return null}try{let e=c({argv:process.argv,readFileSync:x.readFileSync}),t=e.tool_name??"",n=e.tool_input??{},s=typeof n.file_path=="string"?n.file_path:"",u=d(t,n),_=u!==null,y=s!==""&&m(s);(!_||y)&&(console.log(JSON.stringify(o())),process.exit(0));let p=A(u);if(p.length>0){let k=p.join(", "),S=`Credential guard: blocked writing to ${s}. Detected: ${k}. Move credentials to .clancy/.env instead.`;console.log(JSON.stringify(i(S)))}else console.log(JSON.stringify(o()))}catch{console.log(JSON.stringify(o()))}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var u=require("node:fs"),f=require("node:os");function l(t,r){return{hookSpecificOutput:{hookEventName:t,additionalContext:r}}}var x=require("node:stream/consumers");function a(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}var p={},D=3e3;function F(t){try{let r=JSON.parse(t);return a(r)?r:p}catch{return p}}function T(t){return new Promise(r=>{setTimeout(()=>r(p),t)})}function d(t){let r=t.timeoutMs??D,n=(0,x.text)(t.stdin).then(F);return Promise.race([n,T(r)])}var P=require("node:path");function h(t){return String(t).replace(/[^a-zA-Z0-9_-]/g,"")}function m(t,r){return(0,P.join)(r.tmpdir(),`clancy-drift-${h(t)}`)}var s=require("node:path");var j=(0,s.join)(".clancy","version.json"),g=(0,s.join)(".claude","commands","clancy","VERSION");function S(t,r){return!t||!r?!1:t.trim()!==r.trim()}function y(t,r){try{return r.readFileSync(t,"utf8")}catch{return null}}function I(t,r){let n=y((0,s.join)(t,j),r);if(!n)return null;try{let o=JSON.parse(n);if(!a(o))return null;let e=o.version;return typeof e=="string"?e:null}catch{return null}}function O(t,r,n){let o=(0,s.join)(t,g),e=(0,s.join)(r,g),i=y(o,n);if(i)return i.trim();let c=y(e,n);return c?c.trim():null}function b(t,r){return`DRIFT WARNING: Clancy runtime files are outdated (runtime: ${t}, commands: ${r}). Run /clancy:update to sync your installation.`}d({stdin:process.stdin}).then(t=>{let r=t.session_id??"";if(!r)return;let n=m(r,{tmpdir:f.tmpdir});try{(0,u.writeFileSync)(n,"1",{flag:"wx"})}catch{return}let o=t.cwd??process.cwd(),e={readFileSync:u.readFileSync},i=I(o,e),c=O(o,(0,f.homedir)(),e);if(!i||!c||!S(i,c))return;let N=b(i,c),w=l("PostToolUse",N);process.stdout.write(JSON.stringify(w))}).catch(()=>{});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var l=require("node:child_process");var a=require("node:stream/consumers");function s(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}var i={},p=3e3;function m(t){try{let e=JSON.parse(t);return s(e)?e:i}catch{return i}}function d(t){return new Promise(e=>{setTimeout(()=>e(i),t)})}function c(t){let e=t.timeoutMs??p,n=(0,a.text)(t.stdin).then(m);return Promise.race([n,d(e)])}var y="Clancy notification";function f(t){return typeof t.message=="string"&&t.message!==""?t.message:typeof t.notification=="string"&&t.notification!==""?t.notification:x(t)??y}function x(t){let e=t.text;if(typeof e=="string"&&e!=="")return e;let n=t.hookSpecificOutput,r=typeof n=="object"&&n!==null?n.message:void 0;if(typeof r=="string"&&r!=="")return r}function E(t){return t.replaceAll("\\","\\\\").replaceAll('"','\\"')}function T(t){return t.replaceAll("`","``").replaceAll("$","`$").replaceAll('"','`"')}function S(t,e){let o=`display notification "${E(t)}" with title "Clancy"`;e("osascript",["-e",o],{timeout:5e3,windowsHide:!0})}function M(t,e){e("notify-send",["Clancy",t],{timeout:5e3,windowsHide:!0})}function g(t,e){let o=`Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.MessageBox]::Show("${T(t)}", "Clancy")`;e("powershell",["-NoProfile","-Command",o],{timeout:5e3,windowsHide:!0})}var w={darwin:S,linux:M,win32:g};function u(t,e){let n=w[e.platform];if(!n){e.log(`[Clancy] ${t}`);return}try{n(t,e.exec)}catch{e.log(`[Clancy] ${t}`)}}var A=process.env.CLANCY_DESKTOP_NOTIFY==="false";A&&process.exit(0);c({stdin:process.stdin}).then(t=>{let e=f(t);u(e,{platform:process.platform,exec:l.execFileSync,log:console.log})}).catch(()=>{});
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var d=require("node:fs");function c(t,n){return{hookSpecificOutput:{hookEventName:t,additionalContext:n}}}var p=require("node:path");function o(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}function s(t,n){let e=(0,p.join)(t,".clancy","lock.json");try{let r=JSON.parse(n.readFileSync(e,"utf8"));return o(r)?r:null}catch{return null}}var m=require("node:stream/consumers");var a={},x=3e3;function P(t){try{let n=JSON.parse(t);return o(n)?n:a}catch{return a}}function T(t){return new Promise(n=>{setTimeout(()=>n(a),t)})}function u(t){let n=t.timeoutMs??x,e=(0,m.text)(t.stdin).then(P);return Promise.race([e,T(n)])}function f(t){let{ticketKey:n,ticketBranch:e}=t;if(!n||!e)return null;let r=t.ticketTitle||"Unknown",i=t.targetBranch??"main",l=t.parentKey!==void 0&&t.parentKey!=="none"&&t.parentKey!==""?`Parent: ${t.parentKey}.`:void 0,y=t.description?`Requirements: ${t.description.slice(0,2e3)}`:void 0;return[`CONTEXT RESTORED: You are implementing ticket [${n}] ${r}.`,`Branch: ${e} targeting ${i}.`,l,y,"Continue your implementation. Do not start over."].filter(Boolean).join(`
|
|
2
|
+
`)}u({stdin:process.stdin}).then(t=>{let n=t.cwd??process.cwd(),e=s(n,{readFileSync:d.readFileSync});if(!e)return;let r=f(e);if(!r)return;let i=c("PostCompact",r);process.stdout.write(JSON.stringify(i))}).catch(()=>{});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var c=require("node:fs"),s=require("node:os");var h=require("node:stream/consumers");function i(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}var d={},C=3e3;function D(t){try{let n=JSON.parse(t);return i(n)?n:d}catch{return d}}function I(t){return new Promise(n=>{setTimeout(()=>n(d),t)})}function f(t){let n=t.timeoutMs??C,e=(0,h.text)(t.stdin).then(D);return Promise.race([e,I(n)])}var x=require("node:path");function b(t){return String(t).replace(/[^a-zA-Z0-9_-]/g,"")}function l(t,n){return(0,x.join)(n.tmpdir(),`clancy-ctx-${b(t)}.json`)}var p=require("node:path");var $=16.5,m=10,T="\u2588",F="\u2591",o="\x1B[0m",y="\x1B[2m",O="\x1B[32m",g="\x1B[33m",B="\x1B[38;5;208m",R="\x1B[5;31m",w=" \u2502 ";function _(t){let n=Math.max(0,(t-$)/(100-$)*100),e=Math.max(0,Math.min(100,Math.round(100-n)));return{usableRemaining:n,usedPct:e}}function A(t,n,e){let{usedPct:r}=_(n);return{session_id:t,remaining_percentage:n,used_pct:r,timestamp:e}}function j(t){let n=Math.max(0,Math.min(m,Math.floor(t/m))),e=T.repeat(n)+F.repeat(m-n),r=t<50,a=t>=50&&t<65,u=t>=65&&t<80;return r?`${O}${e} ${t}%${o}`:a?`${g}${e} ${t}%${o}`:u?`${B}${e} ${t}%${o}`:`${R}\u{1F480} ${e} ${t}%${o}`}function P(t,n){try{let e=JSON.parse(n.readFileSync(t,"utf8"));return i(e)?e.update_available===!0:!1}catch{return!1}}function M(t){let n=t.env??(0,p.join)(t.homedir(),".claude");return(0,p.join)(n,"cache","clancy-update-check.json")}function E(t,n){let e=t?`${g}\u2B06 /clancy:update${o}`:void 0,a=n!==void 0?`${y}Clancy${o} ${j(_(n).usedPct)}`:`${y}Clancy${o}`;return[e,a].filter(Boolean).join(w)}f({stdin:process.stdin}).then(U).catch(()=>{});function U(t){let n=t.session_id??"",e=t.context_window?.remaining_percentage;n!==""&&e!==void 0&&v(n,e);let a=M({env:process.env.CLAUDE_CONFIG_DIR,homedir:s.homedir}),u=P(a,{readFileSync:c.readFileSync}),S=E(u,e);process.stdout.write(S)}function v(t,n){try{let e=l(t,{tmpdir:s.tmpdir}),r=A(t,n,Math.floor(Date.now()/1e3));(0,c.writeFileSync)(e,JSON.stringify(r))}catch{}}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @chief-clancy/terminal
|
|
3
|
+
*
|
|
4
|
+
* Installer, slash commands, hooks, AFK runner, agents,
|
|
5
|
+
* and Claude CLI bridge.
|
|
6
|
+
*/
|
|
7
|
+
export declare const PACKAGE_NAME: "@chief-clancy/terminal";
|
|
8
|
+
export { copyDir, inlineWorkflows } from './installer/file-ops/index.js';
|
|
9
|
+
export { installHooks } from './installer/hook-installer/index.js';
|
|
10
|
+
export type { InstallMode, InstallPaths, RunInstallOptions, } from './installer/install/index.js';
|
|
11
|
+
export { parseEnabledRoles, parseInstallFlag, resolveInstallPaths, runInstall, validateSources, } from './installer/install/index.js';
|
|
12
|
+
export { backupModifiedFiles, buildManifest, detectModifiedFiles, } from './installer/manifest/index.js';
|
|
13
|
+
export { createPrompts } from './installer/prompts/index.js';
|
|
14
|
+
export { copyRoleFiles } from './installer/role-filter/index.js';
|
|
15
|
+
export { printBanner, printSuccess } from './installer/ui/index.js';
|
|
16
|
+
export { blue, bold, cyan, dim, green, red, yellow, } from './shared/ansi/index.js';
|
|
17
|
+
export { runAutopilot } from './runner/autopilot/index.js';
|
|
18
|
+
export { invokeClaudePrint, invokeClaudeSession, } from './runner/cli-bridge/index.js';
|
|
19
|
+
export { buildPipelineDeps } from './runner/dep-factory/index.js';
|
|
20
|
+
export { sendNotification } from './runner/notify/index.js';
|
|
21
|
+
export { runImplement } from './runner/implement/index.js';
|
|
22
|
+
export { buildPrompt, buildReworkPrompt, } from './runner/prompt-builder/index.js';
|
|
23
|
+
export { buildSessionReport, generateSessionReport, } from './runner/session-report/index.js';
|
|
24
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,YAAY,EAAG,wBAAiC,CAAC;AAE9D,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,qCAAqC,CAAC;AACnE,YAAY,EACV,WAAW,EACX,YAAY,EACZ,iBAAiB,GAClB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACnB,UAAU,EACV,eAAe,GAChB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,mBAAmB,EACnB,aAAa,EACb,mBAAmB,GACpB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAEpE,OAAO,EACL,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,GAAG,EACH,KAAK,EACL,GAAG,EACH,MAAM,GACP,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EACL,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EACL,WAAW,EACX,iBAAiB,GAClB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,kBAAkB,EAClB,qBAAqB,GACtB,MAAM,kCAAkC,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @chief-clancy/terminal
|
|
3
|
+
*
|
|
4
|
+
* Installer, slash commands, hooks, AFK runner, agents,
|
|
5
|
+
* and Claude CLI bridge.
|
|
6
|
+
*/
|
|
7
|
+
export const PACKAGE_NAME = '@chief-clancy/terminal';
|
|
8
|
+
export { copyDir, inlineWorkflows } from './installer/file-ops/index.js';
|
|
9
|
+
export { installHooks } from './installer/hook-installer/index.js';
|
|
10
|
+
export { parseEnabledRoles, parseInstallFlag, resolveInstallPaths, runInstall, validateSources, } from './installer/install/index.js';
|
|
11
|
+
export { backupModifiedFiles, buildManifest, detectModifiedFiles, } from './installer/manifest/index.js';
|
|
12
|
+
export { createPrompts } from './installer/prompts/index.js';
|
|
13
|
+
export { copyRoleFiles } from './installer/role-filter/index.js';
|
|
14
|
+
export { printBanner, printSuccess } from './installer/ui/index.js';
|
|
15
|
+
export { blue, bold, cyan, dim, green, red, yellow, } from './shared/ansi/index.js';
|
|
16
|
+
export { runAutopilot } from './runner/autopilot/index.js';
|
|
17
|
+
export { invokeClaudePrint, invokeClaudeSession, } from './runner/cli-bridge/index.js';
|
|
18
|
+
export { buildPipelineDeps } from './runner/dep-factory/index.js';
|
|
19
|
+
export { sendNotification } from './runner/notify/index.js';
|
|
20
|
+
export { runImplement } from './runner/implement/index.js';
|
|
21
|
+
export { buildPrompt, buildReworkPrompt, } from './runner/prompt-builder/index.js';
|
|
22
|
+
export { buildSessionReport, generateSessionReport, } from './runner/session-report/index.js';
|
|
23
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,wBAAiC,CAAC;AAE9D,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,qCAAqC,CAAC;AAMnE,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACnB,UAAU,EACV,eAAe,GAChB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,mBAAmB,EACnB,aAAa,EACb,mBAAmB,GACpB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAEpE,OAAO,EACL,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,GAAG,EACH,KAAK,EACL,GAAG,EACH,MAAM,GACP,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EACL,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EACL,WAAW,EACX,iBAAiB,GAClB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,kBAAkB,EAClB,qBAAqB,GACtB,MAAM,kCAAkC,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compute the SHA-256 hash of a file.
|
|
3
|
+
*
|
|
4
|
+
* @param filePath - Absolute path to the file.
|
|
5
|
+
* @returns The hex-encoded SHA-256 hash string.
|
|
6
|
+
*/
|
|
7
|
+
export declare const fileHash: (filePath: string) => string;
|
|
8
|
+
/**
|
|
9
|
+
* Recursively copy a directory, throwing if any destination path is a symlink.
|
|
10
|
+
*
|
|
11
|
+
* Checks both the destination root and individual file paths for symlinks.
|
|
12
|
+
* The source is trusted installer content and not checked.
|
|
13
|
+
* Reads the source before creating the destination to avoid leaving empty
|
|
14
|
+
* directories on failure.
|
|
15
|
+
*
|
|
16
|
+
* @param src - Source directory path.
|
|
17
|
+
* @param dest - Destination directory path.
|
|
18
|
+
* @returns Nothing — throws on symlink or missing source.
|
|
19
|
+
*/
|
|
20
|
+
export declare const copyDir: (src: string, dest: string) => void;
|
|
21
|
+
/**
|
|
22
|
+
* Inline workflow content into command markdown files.
|
|
23
|
+
*
|
|
24
|
+
* For global installs, @-file references resolve relative to the project root
|
|
25
|
+
* (not ~/.claude), so the workflow files won't be found at runtime. This
|
|
26
|
+
* replaces matching `@.claude/clancy/workflows/<name>.md` references with the
|
|
27
|
+
* actual workflow content. References to missing files are left unchanged.
|
|
28
|
+
* Handles multiple references per file.
|
|
29
|
+
*
|
|
30
|
+
* @param commandsDir - The installed commands directory.
|
|
31
|
+
* @param workflowsDir - The installed workflows directory.
|
|
32
|
+
* @returns Nothing — files are modified in place.
|
|
33
|
+
*/
|
|
34
|
+
export declare const inlineWorkflows: (commandsDir: string, workflowsDir: string) => void;
|
|
35
|
+
//# sourceMappingURL=file-ops.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-ops.d.ts","sourceRoot":"","sources":["../../../src/installer/file-ops/file-ops.ts"],"names":[],"mappings":"AAqBA;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,GAAI,UAAU,MAAM,KAAG,MAI3C,CAAC;AAoBF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,OAAO,GAAI,KAAK,MAAM,EAAE,MAAM,MAAM,KAAG,IAOnD,CAAC;AA6CF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,GAC1B,aAAa,MAAM,EACnB,cAAc,MAAM,KACnB,IAMF,CAAC"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { copyFileSync, mkdirSync, readdirSync, readFileSync, writeFileSync, } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { hasErrorCode } from '../../installer/shared/fs-errors/index.js';
|
|
5
|
+
import { rejectSymlink } from '../../installer/shared/fs-guards/index.js';
|
|
6
|
+
/**
|
|
7
|
+
* Compute the SHA-256 hash of a file.
|
|
8
|
+
*
|
|
9
|
+
* @param filePath - Absolute path to the file.
|
|
10
|
+
* @returns The hex-encoded SHA-256 hash string.
|
|
11
|
+
*/
|
|
12
|
+
export const fileHash = (filePath) => {
|
|
13
|
+
const content = readFileSync(filePath);
|
|
14
|
+
return createHash('sha256').update(content).digest('hex');
|
|
15
|
+
};
|
|
16
|
+
/** Copy a single directory entry from src to dest, recursing into subdirectories. */
|
|
17
|
+
const copyEntry = (src, dest) => (entry) => {
|
|
18
|
+
const srcPath = join(src, entry.name);
|
|
19
|
+
const destPath = join(dest, entry.name);
|
|
20
|
+
if (entry.isDirectory()) {
|
|
21
|
+
copyDir(srcPath, destPath);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (!entry.isFile())
|
|
25
|
+
return; // Skip symlinks, FIFOs, sockets, etc.
|
|
26
|
+
rejectSymlink(destPath);
|
|
27
|
+
copyFileSync(srcPath, destPath);
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Recursively copy a directory, throwing if any destination path is a symlink.
|
|
31
|
+
*
|
|
32
|
+
* Checks both the destination root and individual file paths for symlinks.
|
|
33
|
+
* The source is trusted installer content and not checked.
|
|
34
|
+
* Reads the source before creating the destination to avoid leaving empty
|
|
35
|
+
* directories on failure.
|
|
36
|
+
*
|
|
37
|
+
* @param src - Source directory path.
|
|
38
|
+
* @param dest - Destination directory path.
|
|
39
|
+
* @returns Nothing — throws on symlink or missing source.
|
|
40
|
+
*/
|
|
41
|
+
export const copyDir = (src, dest) => {
|
|
42
|
+
rejectSymlink(dest);
|
|
43
|
+
const entries = readdirSync(src, { withFileTypes: true });
|
|
44
|
+
mkdirSync(dest, { recursive: true });
|
|
45
|
+
entries.forEach(copyEntry(src, dest));
|
|
46
|
+
};
|
|
47
|
+
/** Matches `@.claude/clancy/workflows/<filename>.md` on its own line. Disallows path separators. */
|
|
48
|
+
const WORKFLOW_REF = /^@\.claude\/clancy\/workflows\/([^/\\]+\.md)\r?$/gm;
|
|
49
|
+
/** Resolve a workflow @-file reference to its content, or return the original if missing. */
|
|
50
|
+
const resolveWorkflowRef = (workflowsDir, fullMatch, fileName) => {
|
|
51
|
+
const workflowFile = join(workflowsDir, fileName);
|
|
52
|
+
try {
|
|
53
|
+
rejectSymlink(workflowFile);
|
|
54
|
+
return readFileSync(workflowFile, 'utf8');
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
if (hasErrorCode(error, 'ENOENT'))
|
|
58
|
+
return fullMatch;
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
/** Inline workflow references in a single command file. */
|
|
63
|
+
const inlineFileWorkflows = (commandsDir, workflowsDir) => (entry) => {
|
|
64
|
+
const isMarkdownFile = entry.isFile() && entry.name.endsWith('.md');
|
|
65
|
+
if (!isMarkdownFile)
|
|
66
|
+
return;
|
|
67
|
+
const cmdPath = join(commandsDir, entry.name);
|
|
68
|
+
const content = readFileSync(cmdPath, 'utf8');
|
|
69
|
+
const resolved = content.replace(WORKFLOW_REF, (match, fileName) => resolveWorkflowRef(workflowsDir, match, fileName));
|
|
70
|
+
const hasChanges = resolved !== content;
|
|
71
|
+
if (!hasChanges)
|
|
72
|
+
return;
|
|
73
|
+
rejectSymlink(cmdPath);
|
|
74
|
+
writeFileSync(cmdPath, resolved);
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Inline workflow content into command markdown files.
|
|
78
|
+
*
|
|
79
|
+
* For global installs, @-file references resolve relative to the project root
|
|
80
|
+
* (not ~/.claude), so the workflow files won't be found at runtime. This
|
|
81
|
+
* replaces matching `@.claude/clancy/workflows/<name>.md` references with the
|
|
82
|
+
* actual workflow content. References to missing files are left unchanged.
|
|
83
|
+
* Handles multiple references per file.
|
|
84
|
+
*
|
|
85
|
+
* @param commandsDir - The installed commands directory.
|
|
86
|
+
* @param workflowsDir - The installed workflows directory.
|
|
87
|
+
* @returns Nothing — files are modified in place.
|
|
88
|
+
*/
|
|
89
|
+
export const inlineWorkflows = (commandsDir, workflowsDir) => {
|
|
90
|
+
rejectSymlink(commandsDir);
|
|
91
|
+
rejectSymlink(workflowsDir);
|
|
92
|
+
const entries = readdirSync(commandsDir, { withFileTypes: true });
|
|
93
|
+
entries.forEach(inlineFileWorkflows(commandsDir, workflowsDir));
|
|
94
|
+
};
|
|
95
|
+
//# sourceMappingURL=file-ops.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-ops.js","sourceRoot":"","sources":["../../../src/installer/file-ops/file-ops.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/installer/file-ops/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/installer/file-ops/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Options for the hook installer. */
|
|
2
|
+
type HookInstallerOptions = {
|
|
3
|
+
readonly claudeConfigDir: string;
|
|
4
|
+
readonly hooksSourceDir: string;
|
|
5
|
+
readonly verificationGatePrompt?: string;
|
|
6
|
+
};
|
|
7
|
+
/** Compiled hook files Clancy ships. */
|
|
8
|
+
export declare const HOOK_FILES: readonly ["clancy-check-update.js", "clancy-statusline.js", "clancy-context-monitor.js", "clancy-credential-guard.js", "clancy-branch-guard.js", "clancy-post-compact.js", "clancy-notification.js", "clancy-drift-detector.js"];
|
|
9
|
+
/**
|
|
10
|
+
* Install Clancy hooks into the Claude config directory.
|
|
11
|
+
*
|
|
12
|
+
* Copies hook scripts, writes a CommonJS `package.json` to the hooks dir,
|
|
13
|
+
* and merges hook registrations into `settings.json`.
|
|
14
|
+
*
|
|
15
|
+
* Best-effort — never throws. Returns `false` if installation fails.
|
|
16
|
+
*
|
|
17
|
+
* @param options - The hook installer options.
|
|
18
|
+
* @returns `true` if hooks were installed successfully.
|
|
19
|
+
*/
|
|
20
|
+
export declare function installHooks(options: HookInstallerOptions): boolean;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=hook-installer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook-installer.d.ts","sourceRoot":"","sources":["../../../src/installer/hook-installer/hook-installer.ts"],"names":[],"mappings":"AA+BA,sCAAsC;AACtC,KAAK,oBAAoB,GAAG;IAC1B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAC;CAC1C,CAAC;AAEF,wCAAwC;AACxC,eAAO,MAAM,UAAU,kOASb,CAAC;AAmOX;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CA6BnE"}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook installer for Claude Code settings.
|
|
3
|
+
*
|
|
4
|
+
* Copies compiled hook scripts into the Claude config directory and
|
|
5
|
+
* registers them in `settings.json` without clobbering existing config.
|
|
6
|
+
* All internal logic is immutable — settings are built as new objects,
|
|
7
|
+
* never mutated in place.
|
|
8
|
+
*/
|
|
9
|
+
import { copyFileSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { hasErrorCode } from '../../installer/shared/fs-errors/index.js';
|
|
12
|
+
import { rejectSymlink } from '../../installer/shared/fs-guards/index.js';
|
|
13
|
+
import { isPlainObject } from '../../installer/shared/type-guards/index.js';
|
|
14
|
+
/** Compiled hook files Clancy ships. */
|
|
15
|
+
export const HOOK_FILES = [
|
|
16
|
+
'clancy-check-update.js',
|
|
17
|
+
'clancy-statusline.js',
|
|
18
|
+
'clancy-context-monitor.js',
|
|
19
|
+
'clancy-credential-guard.js',
|
|
20
|
+
'clancy-branch-guard.js',
|
|
21
|
+
'clancy-post-compact.js',
|
|
22
|
+
'clancy-notification.js',
|
|
23
|
+
'clancy-drift-detector.js',
|
|
24
|
+
];
|
|
25
|
+
/** Build a `node "path"` command string for a hook file. */
|
|
26
|
+
function buildNodeCommand(hooksDir, fileName) {
|
|
27
|
+
return `node ${JSON.stringify(join(hooksDir, fileName))}`;
|
|
28
|
+
}
|
|
29
|
+
/** Wrap a command string in a single-hook entry. */
|
|
30
|
+
function commandEntry(command) {
|
|
31
|
+
return { hooks: [{ type: 'command', command }] };
|
|
32
|
+
}
|
|
33
|
+
/** Wrap an agent prompt in a single-hook entry. */
|
|
34
|
+
function agentEntry(prompt, timeout) {
|
|
35
|
+
return { hooks: [{ type: 'agent', prompt, timeout }] };
|
|
36
|
+
}
|
|
37
|
+
/** Check whether a single hook matches a given command string. */
|
|
38
|
+
function isCommandMatch(hook, command) {
|
|
39
|
+
return hook.type === 'command' && hook.command === command;
|
|
40
|
+
}
|
|
41
|
+
/** Check whether a single hook matches an agent prompt fingerprint. */
|
|
42
|
+
function isAgentMatch(hook, fingerprint) {
|
|
43
|
+
const hasStringPrompt = hook.type === 'agent' && typeof hook.prompt === 'string';
|
|
44
|
+
return hasStringPrompt && hook.prompt.slice(0, 100) === fingerprint;
|
|
45
|
+
}
|
|
46
|
+
/** Flatten all hooks from a list of entries, filtering out non-object values. */
|
|
47
|
+
function allHooks(entries) {
|
|
48
|
+
return entries
|
|
49
|
+
.flatMap((e) => e.hooks)
|
|
50
|
+
.filter((h) => isPlainObject(h));
|
|
51
|
+
}
|
|
52
|
+
/** Check whether an event's entries already contain a given command. */
|
|
53
|
+
function hasCommand(entries, command) {
|
|
54
|
+
return allHooks(entries).some((h) => isCommandMatch(h, command));
|
|
55
|
+
}
|
|
56
|
+
/** Check whether an event's entries already contain a matching agent prompt. */
|
|
57
|
+
function hasAgentPrompt(entries, prompt) {
|
|
58
|
+
const fingerprint = prompt.slice(0, 100);
|
|
59
|
+
return allHooks(entries).some((h) => isAgentMatch(h, fingerprint));
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Build the desired hook registrations for a given hooks directory.
|
|
63
|
+
*
|
|
64
|
+
* Returns a pure data structure mapping event names to hook entries.
|
|
65
|
+
* No deduplication — that happens during merge.
|
|
66
|
+
*
|
|
67
|
+
* @param hooksDir - The installed hooks directory path.
|
|
68
|
+
* @param verificationGatePrompt - Optional agent prompt for the Stop hook.
|
|
69
|
+
* @returns A record of event names to their desired hook entries.
|
|
70
|
+
*/
|
|
71
|
+
function buildDesiredHooks(hooksDir, verificationGatePrompt) {
|
|
72
|
+
const cmd = (fileName) => buildNodeCommand(hooksDir, fileName);
|
|
73
|
+
const base = {
|
|
74
|
+
SessionStart: [commandEntry(cmd('clancy-check-update.js'))],
|
|
75
|
+
PostToolUse: [
|
|
76
|
+
commandEntry(cmd('clancy-context-monitor.js')),
|
|
77
|
+
commandEntry(cmd('clancy-drift-detector.js')),
|
|
78
|
+
],
|
|
79
|
+
PreToolUse: [
|
|
80
|
+
commandEntry(cmd('clancy-credential-guard.js')),
|
|
81
|
+
commandEntry(cmd('clancy-branch-guard.js')),
|
|
82
|
+
],
|
|
83
|
+
PostCompact: [commandEntry(cmd('clancy-post-compact.js'))],
|
|
84
|
+
Notification: [commandEntry(cmd('clancy-notification.js'))],
|
|
85
|
+
};
|
|
86
|
+
if (!verificationGatePrompt)
|
|
87
|
+
return base;
|
|
88
|
+
return { ...base, Stop: [agentEntry(verificationGatePrompt, 120)] };
|
|
89
|
+
}
|
|
90
|
+
/** Filter out desired entries that are already registered in existing. */
|
|
91
|
+
function filterNewEntries(existing, desired) {
|
|
92
|
+
return desired.filter((entry) => {
|
|
93
|
+
if (entry.hooks.length === 0)
|
|
94
|
+
return false;
|
|
95
|
+
const hook = entry.hooks[0];
|
|
96
|
+
if (hook.type === 'command')
|
|
97
|
+
return !hasCommand(existing, hook.command);
|
|
98
|
+
return !hasAgentPrompt(existing, hook.prompt);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/** Merge desired entries into existing for a single event, deduplicating. */
|
|
102
|
+
function mergeEventEntries(existing, desired) {
|
|
103
|
+
const newEntries = filterNewEntries(existing, desired);
|
|
104
|
+
return [...existing, ...newEntries];
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Merge desired hook registrations into existing ones, deduplicating.
|
|
108
|
+
*
|
|
109
|
+
* @param existing - The current hook registrations from settings.json.
|
|
110
|
+
* @param desired - The hook registrations to add.
|
|
111
|
+
* @returns A new merged registrations record.
|
|
112
|
+
*/
|
|
113
|
+
function mergeHookRegistrations(existing, desired) {
|
|
114
|
+
const allEvents = [
|
|
115
|
+
...new Set([...Object.keys(existing), ...Object.keys(desired)]),
|
|
116
|
+
];
|
|
117
|
+
const entries = allEvents.map((event) => {
|
|
118
|
+
const merged = mergeEventEntries(existing[event] ?? [], desired[event] ?? []);
|
|
119
|
+
return [event, merged];
|
|
120
|
+
});
|
|
121
|
+
return Object.fromEntries(entries);
|
|
122
|
+
}
|
|
123
|
+
/** Check whether a value looks like a HookEntry ({ hooks: [...] }). */
|
|
124
|
+
function isHookEntry(value) {
|
|
125
|
+
if (!isPlainObject(value))
|
|
126
|
+
return false;
|
|
127
|
+
const hooks = value.hooks;
|
|
128
|
+
return Array.isArray(hooks);
|
|
129
|
+
}
|
|
130
|
+
/** Safely extract a HookRegistrations record from raw settings.hooks. */
|
|
131
|
+
function normalizeHooks(raw) {
|
|
132
|
+
if (!isPlainObject(raw))
|
|
133
|
+
return {};
|
|
134
|
+
const safeEntries = Object.entries(raw)
|
|
135
|
+
.filter(([, value]) => Array.isArray(value))
|
|
136
|
+
.map(([event, value]) => [event, value.filter(isHookEntry)]);
|
|
137
|
+
return Object.fromEntries(safeEntries);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Merge hook registrations and statusLine into existing settings immutably.
|
|
141
|
+
*
|
|
142
|
+
* @param settings - The current settings.json contents.
|
|
143
|
+
* @param desired - The desired hook registrations.
|
|
144
|
+
* @param statusLineCommand - The statusLine command string.
|
|
145
|
+
* @returns A new settings object with hooks and statusLine merged.
|
|
146
|
+
*/
|
|
147
|
+
function mergeSettings(settings, desired, statusLineCommand) {
|
|
148
|
+
const existingHooks = normalizeHooks(settings.hooks);
|
|
149
|
+
const mergedHooks = mergeHookRegistrations(existingHooks, desired);
|
|
150
|
+
const statusLine = { type: 'command', command: statusLineCommand };
|
|
151
|
+
return { ...settings, hooks: mergedHooks, statusLine };
|
|
152
|
+
}
|
|
153
|
+
/** Copy all hook files from source to the install directory. */
|
|
154
|
+
function copyHookFiles(sourceDir, installDir) {
|
|
155
|
+
rejectSymlink(installDir);
|
|
156
|
+
mkdirSync(installDir, { recursive: true });
|
|
157
|
+
HOOK_FILES.forEach((f) => {
|
|
158
|
+
const dest = join(installDir, f);
|
|
159
|
+
rejectSymlink(dest);
|
|
160
|
+
copyFileSync(join(sourceDir, f), dest);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
/** Write a CommonJS package.json so hooks resolve as CJS in ESM projects. */
|
|
164
|
+
function writeCommonJsMarker(installDir) {
|
|
165
|
+
const packageJsonPath = join(installDir, 'package.json');
|
|
166
|
+
rejectSymlink(packageJsonPath);
|
|
167
|
+
writeFileSync(packageJsonPath, JSON.stringify({ type: 'commonjs' }, null, 2) + '\n');
|
|
168
|
+
}
|
|
169
|
+
/** Read and parse settings.json, returning empty on ENOENT or malformed JSON. */
|
|
170
|
+
function readSettingsFile(settingsFile) {
|
|
171
|
+
try {
|
|
172
|
+
const parsed = JSON.parse(readFileSync(settingsFile, 'utf8'));
|
|
173
|
+
return isPlainObject(parsed) ? parsed : {};
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
if (hasErrorCode(err, 'ENOENT') || err instanceof SyntaxError)
|
|
177
|
+
return {};
|
|
178
|
+
throw err;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Install Clancy hooks into the Claude config directory.
|
|
183
|
+
*
|
|
184
|
+
* Copies hook scripts, writes a CommonJS `package.json` to the hooks dir,
|
|
185
|
+
* and merges hook registrations into `settings.json`.
|
|
186
|
+
*
|
|
187
|
+
* Best-effort — never throws. Returns `false` if installation fails.
|
|
188
|
+
*
|
|
189
|
+
* @param options - The hook installer options.
|
|
190
|
+
* @returns `true` if hooks were installed successfully.
|
|
191
|
+
*/
|
|
192
|
+
export function installHooks(options) {
|
|
193
|
+
const { claudeConfigDir, hooksSourceDir } = options;
|
|
194
|
+
const hooksInstallDir = join(claudeConfigDir, 'hooks');
|
|
195
|
+
const settingsFile = join(claudeConfigDir, 'settings.json');
|
|
196
|
+
try {
|
|
197
|
+
copyHookFiles(hooksSourceDir, hooksInstallDir);
|
|
198
|
+
writeCommonJsMarker(hooksInstallDir);
|
|
199
|
+
rejectSymlink(settingsFile);
|
|
200
|
+
const existing = readSettingsFile(settingsFile);
|
|
201
|
+
const desired = buildDesiredHooks(hooksInstallDir, options.verificationGatePrompt);
|
|
202
|
+
const statusLineCmd = buildNodeCommand(hooksInstallDir, 'clancy-statusline.js');
|
|
203
|
+
const merged = mergeSettings(existing, desired, statusLineCmd);
|
|
204
|
+
writeFileSync(settingsFile, JSON.stringify(merged, null, 2) + '\n');
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
209
|
+
console.error(` Hook install failed: ${message}`);
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
//# sourceMappingURL=hook-installer.js.map
|