chief-clancy 0.8.22 → 0.9.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 (94) hide show
  1. package/bin/clancy.js +153 -0
  2. package/package.json +8 -88
  3. package/README.md +0 -292
  4. package/dist/bundle/clancy-afk.js +0 -6
  5. package/dist/bundle/clancy-once.js +0 -239
  6. package/dist/installer/file-ops/file-ops.d.ts +0 -32
  7. package/dist/installer/file-ops/file-ops.d.ts.map +0 -1
  8. package/dist/installer/file-ops/file-ops.js +0 -58
  9. package/dist/installer/file-ops/file-ops.js.map +0 -1
  10. package/dist/installer/hook-installer/hook-installer.d.ts +0 -31
  11. package/dist/installer/hook-installer/hook-installer.d.ts.map +0 -1
  12. package/dist/installer/hook-installer/hook-installer.js +0 -137
  13. package/dist/installer/hook-installer/hook-installer.js.map +0 -1
  14. package/dist/installer/install.d.ts +0 -3
  15. package/dist/installer/install.d.ts.map +0 -1
  16. package/dist/installer/install.js +0 -270
  17. package/dist/installer/install.js.map +0 -1
  18. package/dist/installer/manifest/manifest.d.ts +0 -41
  19. package/dist/installer/manifest/manifest.d.ts.map +0 -1
  20. package/dist/installer/manifest/manifest.js +0 -97
  21. package/dist/installer/manifest/manifest.js.map +0 -1
  22. package/dist/installer/prompts/prompts.d.ts +0 -33
  23. package/dist/installer/prompts/prompts.d.ts.map +0 -1
  24. package/dist/installer/prompts/prompts.js +0 -55
  25. package/dist/installer/prompts/prompts.js.map +0 -1
  26. package/dist/installer/role-filter/role-filter.d.ts +0 -15
  27. package/dist/installer/role-filter/role-filter.d.ts.map +0 -1
  28. package/dist/installer/role-filter/role-filter.js +0 -48
  29. package/dist/installer/role-filter/role-filter.js.map +0 -1
  30. package/dist/installer/ui/ui.d.ts +0 -9
  31. package/dist/installer/ui/ui.d.ts.map +0 -1
  32. package/dist/installer/ui/ui.js +0 -94
  33. package/dist/installer/ui/ui.js.map +0 -1
  34. package/dist/scripts/shared/env-parser/env-parser.d.ts +0 -30
  35. package/dist/scripts/shared/env-parser/env-parser.d.ts.map +0 -1
  36. package/dist/scripts/shared/env-parser/env-parser.js +0 -64
  37. package/dist/scripts/shared/env-parser/env-parser.js.map +0 -1
  38. package/dist/utils/ansi/ansi.d.ts +0 -55
  39. package/dist/utils/ansi/ansi.d.ts.map +0 -1
  40. package/dist/utils/ansi/ansi.js +0 -55
  41. package/dist/utils/ansi/ansi.js.map +0 -1
  42. package/hooks/clancy-branch-guard.js +0 -128
  43. package/hooks/clancy-check-update.js +0 -114
  44. package/hooks/clancy-context-monitor.js +0 -189
  45. package/hooks/clancy-credential-guard.js +0 -120
  46. package/hooks/clancy-drift-detector.js +0 -96
  47. package/hooks/clancy-notification.js +0 -105
  48. package/hooks/clancy-post-compact.js +0 -53
  49. package/hooks/clancy-statusline.js +0 -82
  50. package/hooks/package.json +0 -3
  51. package/registry/boards.json +0 -44
  52. package/src/agents/arch-agent.md +0 -72
  53. package/src/agents/concerns-agent.md +0 -89
  54. package/src/agents/design-agent.md +0 -130
  55. package/src/agents/devils-advocate.md +0 -53
  56. package/src/agents/quality-agent.md +0 -161
  57. package/src/agents/tech-agent.md +0 -92
  58. package/src/agents/verification-gate.md +0 -128
  59. package/src/roles/implementer/commands/dry-run.md +0 -14
  60. package/src/roles/implementer/commands/once.md +0 -17
  61. package/src/roles/implementer/commands/run.md +0 -11
  62. package/src/roles/implementer/workflows/once.md +0 -146
  63. package/src/roles/implementer/workflows/run.md +0 -127
  64. package/src/roles/planner/commands/approve-plan.md +0 -10
  65. package/src/roles/planner/commands/plan.md +0 -20
  66. package/src/roles/planner/workflows/approve-plan.md +0 -535
  67. package/src/roles/planner/workflows/plan.md +0 -536
  68. package/src/roles/reviewer/commands/logs.md +0 -7
  69. package/src/roles/reviewer/commands/review.md +0 -9
  70. package/src/roles/reviewer/commands/status.md +0 -9
  71. package/src/roles/reviewer/workflows/logs.md +0 -104
  72. package/src/roles/reviewer/workflows/review.md +0 -186
  73. package/src/roles/reviewer/workflows/status.md +0 -134
  74. package/src/roles/setup/commands/doctor.md +0 -7
  75. package/src/roles/setup/commands/help.md +0 -80
  76. package/src/roles/setup/commands/init.md +0 -7
  77. package/src/roles/setup/commands/map-codebase.md +0 -16
  78. package/src/roles/setup/commands/settings.md +0 -7
  79. package/src/roles/setup/commands/uninstall.md +0 -5
  80. package/src/roles/setup/commands/update-docs.md +0 -9
  81. package/src/roles/setup/commands/update.md +0 -12
  82. package/src/roles/setup/workflows/doctor.md +0 -124
  83. package/src/roles/setup/workflows/init.md +0 -1073
  84. package/src/roles/setup/workflows/map-codebase.md +0 -125
  85. package/src/roles/setup/workflows/scaffold.md +0 -845
  86. package/src/roles/setup/workflows/settings.md +0 -944
  87. package/src/roles/setup/workflows/uninstall.md +0 -161
  88. package/src/roles/setup/workflows/update-docs.md +0 -92
  89. package/src/roles/setup/workflows/update.md +0 -277
  90. package/src/roles/strategist/commands/approve-brief.md +0 -21
  91. package/src/roles/strategist/commands/brief.md +0 -27
  92. package/src/roles/strategist/workflows/approve-brief.md +0 -834
  93. package/src/roles/strategist/workflows/brief.md +0 -890
  94. package/src/templates/CLAUDE.md +0 -87
package/bin/clancy.js ADDED
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Clancy installer — CLI entry point for `npx chief-clancy`.
5
+ *
6
+ * Thin wiring layer that delegates to the @chief-clancy/terminal
7
+ * installer orchestrator. All logic lives in the terminal package;
8
+ * this script only resolves paths, builds the dependency bag, and
9
+ * hands off to `runInstall`.
10
+ */
11
+ import {
12
+ copyFileSync,
13
+ existsSync,
14
+ lstatSync,
15
+ mkdirSync,
16
+ readFileSync,
17
+ writeFileSync,
18
+ } from 'node:fs';
19
+ import { createRequire } from 'node:module';
20
+ import { dirname, join } from 'node:path';
21
+ import { createInterface } from 'node:readline';
22
+ import { fileURLToPath } from 'node:url';
23
+
24
+ import {
25
+ createPrompts,
26
+ dim,
27
+ parseInstallFlag,
28
+ printBanner,
29
+ red,
30
+ resolveInstallPaths,
31
+ runInstall,
32
+ } from '@chief-clancy/terminal';
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Resolve package metadata and terminal source directories
36
+ // ---------------------------------------------------------------------------
37
+
38
+ const require = createRequire(import.meta.url);
39
+ const pkg = require('../package.json');
40
+
41
+ const terminalEntry = fileURLToPath(
42
+ import.meta.resolve('@chief-clancy/terminal'),
43
+ );
44
+ const terminalRoot = join(dirname(terminalEntry), '..');
45
+
46
+ const sources = {
47
+ rolesDir: join(terminalRoot, 'src', 'roles'),
48
+ hooksDir: join(terminalRoot, 'dist', 'hooks'),
49
+ bundleDir: join(terminalRoot, 'dist', 'bundle'),
50
+ agentsDir: join(terminalRoot, 'src', 'agents'),
51
+ };
52
+
53
+ // ---------------------------------------------------------------------------
54
+ // Environment
55
+ // ---------------------------------------------------------------------------
56
+
57
+ const homeDir = process.env.HOME ?? process.env.USERPROFILE;
58
+
59
+ if (!homeDir) {
60
+ console.error(red(' Error: Could not determine home directory.'));
61
+ process.exit(1);
62
+ }
63
+
64
+ const cwd = process.cwd();
65
+
66
+ // ---------------------------------------------------------------------------
67
+ // CLI flag parsing
68
+ // ---------------------------------------------------------------------------
69
+
70
+ const flag = parseInstallFlag(process.argv.slice(2));
71
+ const nonInteractive = flag !== null;
72
+
73
+ // ---------------------------------------------------------------------------
74
+ // Prompts
75
+ // ---------------------------------------------------------------------------
76
+
77
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
78
+ const prompts = createPrompts(rl);
79
+
80
+ // ---------------------------------------------------------------------------
81
+ // Main
82
+ // ---------------------------------------------------------------------------
83
+
84
+ /**
85
+ * Prompt the user for global/local install mode when no CLI flag is provided.
86
+ *
87
+ * @param {ReturnType<typeof createPrompts>} p
88
+ * @returns {Promise<'global' | 'local'>}
89
+ */
90
+ async function chooseMode(p) {
91
+ const choice = await p.choose('Where would you like to install?', [
92
+ `Global ${dim('(~/.claude)')} — available in all projects`,
93
+ `Local ${dim('(./.claude)')} — this project only`,
94
+ ]);
95
+
96
+ if (choice === '1' || choice.toLowerCase() === 'global') return 'global';
97
+ if (choice === '2' || choice.toLowerCase() === 'local') return 'local';
98
+
99
+ console.log(
100
+ red('\n Invalid choice. Run npx chief-clancy again and enter 1 or 2.'),
101
+ );
102
+ p.close();
103
+ process.exit(1);
104
+ }
105
+
106
+ async function main() {
107
+ printBanner(pkg.version);
108
+
109
+ const mode = flag ?? (await chooseMode(prompts));
110
+
111
+ if (flag) {
112
+ console.log(dim(` Mode: ${flag} (--${flag} flag)`));
113
+ }
114
+
115
+ const paths = resolveInstallPaths(mode, homeDir, cwd);
116
+
117
+ await runInstall({
118
+ mode,
119
+ paths,
120
+ sources,
121
+ version: pkg.version,
122
+ nonInteractive,
123
+ prompts,
124
+ cwd,
125
+ fs: {
126
+ exists: existsSync,
127
+ readFile: (p) => readFileSync(p, 'utf8'),
128
+ writeFile: (p, c) => writeFileSync(p, c, 'utf8'),
129
+ mkdir: (p) => mkdirSync(p, { recursive: true }),
130
+ copyFile: (s, d) => copyFileSync(s, d),
131
+ // TOCTOU: narrow window between lstat and the caller's write. Acceptable
132
+ // for a local CLI installer — no privileged paths are involved.
133
+ rejectSymlink: (p) => {
134
+ try {
135
+ if (lstatSync(p).isSymbolicLink()) {
136
+ throw new Error(`Symlink rejected: ${p}`);
137
+ }
138
+ } catch (err) {
139
+ if (/** @type {NodeJS.ErrnoException} */ (err).code !== 'ENOENT')
140
+ throw err;
141
+ }
142
+ },
143
+ },
144
+ });
145
+
146
+ prompts.close();
147
+ }
148
+
149
+ main().catch((err) => {
150
+ console.error(red(`\n Error: ${err.message}`));
151
+ prompts.close();
152
+ process.exit(1);
153
+ });
package/package.json CHANGED
@@ -1,97 +1,17 @@
1
1
  {
2
2
  "name": "chief-clancy",
3
- "version": "0.8.22",
4
- "description": "Autonomous, board-driven development for Claude Code — scaffolds docs, integrates Kanban boards, runs tickets in a loop.",
5
- "keywords": [
6
- "claude",
7
- "claude-code",
8
- "ai",
9
- "kanban",
10
- "jira",
11
- "github-issues",
12
- "linear",
13
- "autonomous",
14
- "slash-commands"
15
- ],
16
- "homepage": "https://github.com/Pushedskydiver/clancy#readme",
17
- "bugs": {
18
- "url": "https://github.com/Pushedskydiver/clancy/issues"
19
- },
20
- "repository": {
21
- "type": "git",
22
- "url": "git+https://github.com/Pushedskydiver/clancy.git"
23
- },
24
- "license": "MIT",
3
+ "version": "0.9.0",
4
+ "description": "Autonomous, board-driven development for Claude Code",
25
5
  "author": "Alex Clapperton",
6
+ "license": "MIT",
26
7
  "type": "module",
27
8
  "bin": {
28
- "clancy": "dist/installer/install.js"
9
+ "chief-clancy": "./bin/clancy.js"
29
10
  },
30
- "main": "dist/installer/install.js",
31
11
  "files": [
32
- "dist/bundle/",
33
- "dist/installer/",
34
- "dist/scripts/shared/env-parser/",
35
- "dist/utils/ansi/",
36
- "hooks/*.js",
37
- "hooks/package.json",
38
- "src/roles/",
39
- "src/agents/",
40
- "src/templates/",
41
- "registry/"
12
+ "bin"
42
13
  ],
43
- "scripts": {
44
- "clean": "rm -rf dist",
45
- "build": "npm run clean && tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && npm run bundle",
46
- "bundle": "node esbuild.config.js",
47
- "dev": "vitest",
48
- "test": "vitest run",
49
- "test:integration": "vitest run --config test/integration/vitest.config.integration.ts",
50
- "test:all": "npm test && npm run test:integration",
51
- "test:e2e": "vitest run --config test/e2e/vitest.config.e2e.ts",
52
- "test:e2e:gc": "npx tsx test/e2e/helpers/gc.ts",
53
- "test:fixtures:validate": "npx tsx test/integration/validate-fixtures.ts",
54
- "test:fixtures:live": "npx tsx test/e2e/validate-live-schemas.ts",
55
- "test:coverage": "vitest run --coverage",
56
- "lint": "eslint .",
57
- "lint:fix": "eslint . --fix",
58
- "format": "prettier --write .",
59
- "format:check": "prettier --check .",
60
- "typecheck": "tsc --noEmit",
61
- "prepublishOnly": "npm run build",
62
- "prepare": "husky"
63
- },
64
- "lint-staged": {
65
- "*.ts": [
66
- "eslint --fix",
67
- "prettier --write"
68
- ]
69
- },
70
- "engines": {
71
- "node": ">=22.0.0"
72
- },
73
- "overrides": {
74
- "tinyexec": "1.0.2"
75
- },
76
- "devDependencies": {
77
- "@eslint/js": "^10.0.1",
78
- "@trivago/prettier-plugin-sort-imports": "^6.0.2",
79
- "@types/node": "^25.4.0",
80
- "@vitest/coverage-v8": "^4.0.18",
81
- "esbuild": "^0.27.3",
82
- "eslint": "^10.0.3",
83
- "eslint-config-prettier": "^10.1.8",
84
- "eslint-plugin-prettier": "^5.5.5",
85
- "husky": "^9.1.7",
86
- "jiti": "^2.6.1",
87
- "lint-staged": "^16.3.3",
88
- "msw": "^2.12.14",
89
- "prettier": "^3.8.1",
90
- "tsc-alias": "^1.8.16",
91
- "tsx": "^4.21.0",
92
- "typescript": "^5.9.3",
93
- "typescript-eslint": "^8.57.0",
94
- "vitest": "^4.0.18",
95
- "zod": "^4.3.6"
14
+ "dependencies": {
15
+ "@chief-clancy/terminal": "0.1.0"
96
16
  }
97
- }
17
+ }
package/README.md DELETED
@@ -1,292 +0,0 @@
1
- # Clancy
2
-
3
- **Autonomous, board-driven development for Claude Code.**
4
-
5
- [![npm](https://img.shields.io/npm/v/chief-clancy?style=for-the-badge&color=cb3837)](https://www.npmjs.com/package/chief-clancy) [![License: MIT](https://img.shields.io/badge/License-MIT-blue?style=for-the-badge)](./LICENSE) [![Tests](https://img.shields.io/badge/tests-1479%20passing-brightgreen?style=for-the-badge)](docs/TESTING.md) [![GitHub Stars](https://img.shields.io/github/stars/Pushedskydiver/clancy?style=for-the-badge)](https://github.com/Pushedskydiver/clancy/stargazers)
6
-
7
- > [!WARNING]
8
- > Clancy is in early development. Expect bugs, breaking changes, and rough edges. If you hit an issue, please [open a bug report](https://github.com/Pushedskydiver/clancy/issues/new).
9
-
10
- ```bash
11
- npx chief-clancy
12
- ```
13
-
14
- Works on Mac, Linux, and Windows.
15
-
16
- [What it does](#what-it-does) · [Install](#install) · [Commands](#commands) · [Supported boards](#supported-boards) · [Comparison](./docs/COMPARISON.md) · [Roadmap](./ROADMAP.md) · [Contributing](./CONTRIBUTING.md)
17
-
18
- ---
19
-
20
- ![Clancy terminal preview](assets/terminal-preview.svg)
21
-
22
- Named after Chief Clancy Wiggum (The Simpsons) — built on the [Ralph technique](https://ghuntley.com/ralph/) by Geoffrey Huntley. [See lineage →](#lineage)
23
-
24
- ---
25
-
26
- ## What it does
27
-
28
- Clancy does six things:
29
-
30
- 1. **Scaffolds itself** into a project — scripts, docs, CLAUDE.md, .clancy/.env
31
- 2. **Scans your codebase** with 5 parallel specialist agents and writes 10 structured docs that Claude reads before every run
32
- 3. **Writes strategy briefs** — interviews you (or runs a devil's advocate AI-grill in AFK mode), decomposes work into vertical slices with HITL/AFK classification, and creates board tickets with blocking dependencies
33
- 4. **Plans tickets** — fetches backlog tickets, explores the codebase, and generates structured implementation plans posted as comments for human review
34
- 5. **Runs autonomously** — picking one ticket per loop from your Kanban board (skipping blocked candidates), implementing it, committing, and creating a PR (targeting an epic branch for parented tickets, or the base branch for standalone tickets)
35
- 6. **Verifies before delivery** — runs lint, test, and typecheck via verification gates with self-healing retry, guards against force push and destructive resets, and recovers from crashes via lock file detection
36
-
37
- Brief → approve → plan → implement. Pipeline labels (`clancy:brief` → `clancy:plan` → `clancy:build`) move tickets between stages automatically. One ticket per run. Fresh context window every iteration. No context rot.
38
-
39
- ---
40
-
41
- ## Who this is for
42
-
43
- Clancy is for developers who:
44
-
45
- - Use a Kanban board (Jira, GitHub Issues, Linear, Shortcut, Notion, or Azure DevOps) and want Claude to work through their backlog unattended
46
- - Are comfortable with Claude Code and want to extend it for team workflows — not just solo hacking
47
- - Have a codebase with enough structure that an AI agent can make meaningful progress on a ticket without constant hand-holding
48
- - Want to go AFK and come back to committed, merged work
49
-
50
- **Clancy is not for you if:**
51
-
52
- - You want to supervise every change — use Claude Code directly instead
53
- - Your tickets are large, vague, or span multiple sessions — Clancy works best with small, well-scoped tickets
54
- - You don't use a Kanban board — you can still use `/clancy:map-codebase` for codebase scanning, but the run loop won't apply
55
-
56
- Evaluating other tools? See [COMPARISON.md](./docs/COMPARISON.md) for a side-by-side with GSD and PAUL.
57
-
58
- ---
59
-
60
- ## Expectations
61
-
62
- Clancy is powerful but not magic. Here's what to expect:
63
-
64
- **It will get things wrong sometimes.** Claude can misread a ticket, make the wrong architectural choice, or produce code that doesn't compile. This is normal. Use `/clancy:once` to watch the first few runs, then review the output before going fully AFK. Over time you'll learn which ticket types Clancy handles well on your codebase.
65
-
66
- **Ticket quality matters more than you think.** A vague ticket produces vague implementation. Clancy works best when tickets have a clear summary, a description that explains the _why_, and concrete acceptance criteria. Use `/clancy:review` to score a ticket before running — it'll tell you exactly what's missing.
67
-
68
- **You still own the code.** Clancy pushes a feature branch and creates a pull request for every ticket — targeting an epic branch for parented tickets, or the base branch for standalone ones. When all children of an epic are done, Clancy creates a final PR from the epic branch to your base branch. Either way, treat it like code from a junior developer who works very fast — it needs a sanity check, not a full rewrite.
69
-
70
- **Some tickets will need a retry.** If Claude gets stuck or produces something obviously wrong, delete the branch and run `/clancy:once` again. Fresh context, fresh attempt. If it fails twice on the same ticket, the ticket probably needs more detail.
71
-
72
- **Clancy is token-heavy.** Each ticket starts a fresh Claude session that reads your codebase docs, CLAUDE.md, and then implements the ticket — before writing a single line of code, it has already consumed significant context. Rough estimates per ticket:
73
-
74
- | Ticket complexity | Approximate total tokens | Approximate cost (Sonnet) |
75
- |---|---|---|
76
- | Simple (small change, clear scope) | 50,000–100,000 | $0.25–$0.75 |
77
- | Medium (feature, 5–15 files touched) | 100,000–250,000 | $0.75–$2.00 |
78
- | Complex (large feature, many files) | 250,000–500,000+ | $2.00–$5.00+ |
79
-
80
- These are rough estimates — actual usage depends on your codebase doc size, how many files Claude reads during implementation, and how much code it writes. **Check Claude Code's usage dashboard after your first `/clancy:once` run to see real numbers for your codebase.**
81
-
82
- A few ways to manage costs:
83
- - Use a lighter model — set `CLANCY_MODEL=claude-haiku-4-5` in `.clancy/.env` for simpler tickets (significantly cheaper, less capable)
84
- - Keep `.clancy/docs/` files concise — they're read in full on every ticket
85
- - Use small, well-scoped tickets — fewer files read, less output generated
86
-
87
- ---
88
-
89
- ## Supported boards
90
-
91
- - **Jira** — via REST API v3, JQL, ADF description parsing
92
- - **GitHub Issues** — via REST API with PR filtering
93
- - **Linear** — via GraphQL, `viewer.assignedIssues`, `state.type: unstarted`
94
- - **Shortcut** — via REST API v3, workflow state resolution, epic stories
95
- - **Notion** — via REST API, database rows as tickets, property name overrides
96
- - **Azure DevOps** — via REST API, work items, org + project scoping
97
-
98
- Community can add boards — see [CONTRIBUTING.md](./CONTRIBUTING.md).
99
-
100
- ---
101
-
102
- ## Install
103
-
104
- ```bash
105
- npx chief-clancy
106
- ```
107
-
108
- You'll be asked: global install (`~/.claude`) or local (`./.claude`). Either works. Global makes commands available in all projects.
109
-
110
- **Prerequisites:**
111
-
112
- - [Claude Code](https://claude.ai/code) CLI installed
113
- - Node.js 22+ (`node -v`)
114
- - `git` installed (comes with most development environments)
115
-
116
- ### Permissions
117
-
118
- Clancy is designed to run Claude with `--dangerously-skip-permissions`:
119
-
120
- ```bash
121
- claude --dangerously-skip-permissions
122
- ```
123
-
124
- > [!TIP]
125
- > This is how Clancy is intended to be used — stopping to approve `git commit` and `node` 50 times defeats the purpose. Only use it on codebases you own and trust.
126
-
127
- ---
128
-
129
- ## Getting started
130
-
131
- ```bash
132
- # 1. Install Clancy commands
133
- npx chief-clancy
134
-
135
- # 2. Open a project in Claude Code, then:
136
- /clancy:init
137
-
138
- # 3. Scan your codebase (or say yes during init)
139
- /clancy:map-codebase
140
-
141
- # 4. Preview the first ticket (no changes made)
142
- /clancy:dry-run
143
-
144
- # 5. Watch your first ticket
145
- /clancy:once
146
-
147
- # 6. Go AFK
148
- /clancy:run
149
-
150
- # Fully autonomous (no prompts at any step):
151
- # /clancy:brief --afk #42 → /clancy:approve-brief --afk → /clancy:run
152
- ```
153
-
154
- ---
155
-
156
- ## Commands
157
-
158
- | Command | Description |
159
- | ---------------------- | ------------------------------------------------------------------------ |
160
- | `/clancy:brief` ² | Grill phase → strategy brief → vertical slice decomposition → tickets |
161
- | `/clancy:approve-brief` ² | Review and approve a strategy brief, then create board tickets |
162
- | `/clancy:plan` ¹ | Refine backlog tickets into structured implementation plans |
163
- | `/clancy:plan 3` ¹ | Plan up to 3 tickets in batch mode |
164
- | `/clancy:approve-plan` ¹ | Promote an approved plan to the ticket description |
165
- | `/clancy:init` | Wizard — choose board, collect config, scaffold everything |
166
- | `/clancy:run` | Loop mode — processes tickets until queue is empty or MAX_ITERATIONS hit |
167
- | `/clancy:run 20` | Same, override MAX_ITERATIONS to 20 for this session |
168
- | `/clancy:once` | Pick up one ticket and stop |
169
- | `/clancy:dry-run` | Preview next ticket without making changes — no git ops, no Claude call |
170
- | `/clancy:status` | Show next tickets without running — read-only |
171
- | `/clancy:review` | Score next ticket (0–100%) with actionable recommendations |
172
- | `/clancy:logs` | Format and display `.clancy/progress.txt` |
173
- | `/clancy:map-codebase` | Full 5-agent parallel codebase scan, writes 10 docs |
174
- | `/clancy:update-docs` | Incremental refresh — re-runs agents for changed areas |
175
- | `/clancy:settings` | View and change configuration — model, iterations, board, and more |
176
- | `/clancy:doctor` | Diagnose your setup — test every integration, report what's broken |
177
- | `/clancy:update` | Update Clancy to latest version |
178
- | `/clancy:uninstall` | Remove Clancy commands — optionally remove `.clancy/` too |
179
- | `/clancy:help` | Command reference |
180
-
181
- ¹ Planner is an optional role — see [Roles](#roles) below.
182
- ² Strategist is an optional role — see [Roles](#roles) below.
183
-
184
- ---
185
-
186
- ## Roles
187
-
188
- Clancy organises its commands into **roles** — each role handles a different stage of the development workflow.
189
-
190
- | Role | Purpose | Commands |
191
- | --- | --- | --- |
192
- | **Implementer** | Plan → code. Picks up tickets, implements, commits, merges | `/clancy:once`, `/clancy:run`, `/clancy:dry-run` |
193
- | **Reviewer** | Score ticket readiness and track progress | `/clancy:review`, `/clancy:status`, `/clancy:logs` |
194
- | **Setup** | Configure and maintain Clancy | `/clancy:init`, `/clancy:settings`, `/clancy:doctor`, etc. |
195
- | **Planner** *(optional)* | Refine backlog tickets into structured implementation plans | `/clancy:plan`, `/clancy:approve-plan` |
196
- | **Strategist** *(optional)* | Grill → brief → vertical slices → board tickets | `/clancy:brief`, `/clancy:approve-brief` |
197
-
198
- **Core roles** (Implementer, Reviewer, Setup) are always installed. **Optional roles** (Planner, Strategist) are opt-in — add them to `CLANCY_ROLES` in `.clancy/.env` and re-run the installer:
199
-
200
- ```bash
201
- # Enable optional roles
202
- echo 'CLANCY_ROLES="planner,strategist"' >> .clancy/.env
203
- npx chief-clancy@latest --local # or --global
204
- ```
205
-
206
- You can also toggle roles from within Claude Code using `/clancy:settings`.
207
-
208
- Each role has detailed documentation:
209
-
210
- - [Implementer](docs/roles/IMPLEMENTER.md) — picks up tickets, implements, commits, merges (AFK loop support)
211
- - [Reviewer](docs/roles/REVIEWER.md) — scores ticket readiness, tracks progress
212
- - [Setup & Maintenance](docs/roles/SETUP.md) — init wizard, settings, diagnostics, codebase mapping
213
- - [Planner](docs/roles/PLANNER.md) — refines backlog tickets into structured implementation plans
214
- - [Strategist](docs/roles/STRATEGIST.md) — grill phase, strategy briefs, vertical slice decomposition, board ticket creation
215
-
216
- For a visual overview of how roles, commands, and flows connect, see the [Visual Architecture](docs/VISUAL-ARCHITECTURE.md) diagrams.
217
-
218
- ---
219
-
220
- ## What gets created
221
-
222
- ```
223
- .clancy/
224
- clancy-once.js — picks up one ticket, implements, commits, merges
225
- clancy-afk.js — loop runner (board-agnostic)
226
- docs/ — 10 structured docs read before every run
227
- STACK.md
228
- INTEGRATIONS.md
229
- ARCHITECTURE.md
230
- CONVENTIONS.md
231
- TESTING.md
232
- GIT.md
233
- DESIGN-SYSTEM.md
234
- ACCESSIBILITY.md
235
- DEFINITION-OF-DONE.md
236
- CONCERNS.md
237
- progress.txt — append-only log of completed tickets
238
- .env — your board credentials (gitignored)
239
- .env.example — credential template for your board
240
- ```
241
-
242
- Clancy also merges a section into your `CLAUDE.md` (or creates one) that tells Claude to read all these docs before every run.
243
-
244
- ---
245
-
246
- ## Configuration
247
-
248
- Clancy supports optional enhancements — Figma design specs, Playwright visual checks, status transitions, and Slack/Teams notifications. All are configured via `.clancy/.env` or `/clancy:settings`.
249
-
250
- See [Configuration guide](docs/guides/CONFIGURATION.md) for full details and all environment variables.
251
-
252
- ---
253
-
254
- ## Lineage
255
-
256
- ![Chief Clancy Wiggum inspiration. Generated by Google Gemini](assets/inspired-chief-clancy.webp)
257
-
258
- Clancy is built on the **Ralph technique** coined by **Geoffrey Huntley** ([ghuntley.com/ralph/](https://ghuntley.com/ralph/)).
259
-
260
- Ralph in its purest form:
261
-
262
- ```bash
263
- while :; do cat PROMPT.md | claude-code; done
264
- ```
265
-
266
- Clancy is what happens when you take that idea seriously for team development. See [CREDITS.md](./CREDITS.md) for the full story.
267
-
268
- ---
269
-
270
- ## Security
271
-
272
- Clancy runs Claude with `--dangerously-skip-permissions` for unattended operation. It includes a credential guard hook, recommended permission deny lists, and token scope guidance.
273
-
274
- See [Security guide](docs/guides/SECURITY.md) for full details — permissions model, credential protection, token scopes, and webhook security.
275
-
276
- ---
277
-
278
- ## Troubleshooting
279
-
280
- **Start here:** run `/clancy:doctor` — it tests every integration and tells you exactly what's broken and how to fix it.
281
-
282
- See [Troubleshooting guide](docs/guides/TROUBLESHOOTING.md) for common issues and solutions.
283
-
284
- ---
285
-
286
- ## Contributing
287
-
288
- See [CONTRIBUTING.md](./CONTRIBUTING.md). The most useful contribution is adding a new board — it's a TypeScript module + a JSON entry.
289
-
290
- ## License
291
-
292
- MIT — see [LICENSE](./LICENSE).
@@ -1,6 +0,0 @@
1
- import{spawnSync as nt}from"node:child_process";import{existsSync as st}from"node:fs";import{dirname as rt,join as it,resolve as ct}from"node:path";import{setTimeout as L}from"node:timers/promises";import{fileURLToPath as U}from"node:url";function k(t){let o=Math.floor(t/1e3);if(o<60)return`${o}s`;let n=Math.floor(o/60),i=o%60;if(n<60)return i>0?`${n}m ${i}s`:`${n}m`;let l=Math.floor(n/60),a=n%60;return a>0?`${l}h ${a}m`:`${l}h`}function H(t){return t.includes("hooks.slack.com")}function K(t){return JSON.stringify({text:t})}function j(t){return JSON.stringify({type:"message",attachments:[{contentType:"application/vnd.microsoft.card.adaptive",content:{$schema:"http://adaptivecards.io/schemas/adaptive-card.json",type:"AdaptiveCard",version:"1.4",body:[{type:"TextBlock",text:t,wrap:!0}]}}]})}async function P(t,o){let n=H(t)?K(o):j(o);try{let i=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:n});i.ok||console.warn(`\u26A0 Notification failed: HTTP ${i.status}`)}catch{console.warn("\u26A0 Notification failed: could not reach webhook")}}var m=t=>`\x1B[2m${t}\x1B[0m`,x=t=>`\x1B[1m${t}\x1B[0m`;var M=t=>`\x1B[32m${t}\x1B[0m`,w=t=>`\x1B[31m${t}\x1B[0m`,N=t=>`\x1B[33m${t}\x1B[0m`;import{mkdirSync as z,readFileSync as G,writeFileSync as tt}from"node:fs";import{join as C}from"node:path";import{mkdirSync as ht,readFileSync as B,renameSync as gt,writeFileSync as St}from"node:fs";import{join as Y}from"node:path";function q(t){return Y(t,".clancy","quality.json")}function W(t){try{let o=B(q(t),"utf8"),n=JSON.parse(o);if(n&&typeof n.tickets=="object"&&n.tickets!==null&&!Array.isArray(n.tickets))return J(n),n}catch{}return{tickets:{},summary:{totalTickets:0,avgReworkCycles:0,avgVerificationRetries:0,avgDuration:0}}}function J(t){let o=Object.values(t.tickets),n=o.length;if(n===0){t.summary={totalTickets:0,avgReworkCycles:0,avgVerificationRetries:0,avgDuration:0};return}let i=0,l=0,a=0,s=0;for(let c of o)i+=c.reworkCycles,l+=c.verificationRetries,c.duration!=null&&(a+=c.duration,s++);t.summary={totalTickets:n,avgReworkCycles:Math.round(i/n*100)/100,avgVerificationRetries:Math.round(l/n*100)/100,avgDuration:s>0?Math.round(a/s*100)/100:0}}function b(t){let o=W(t);if(Object.keys(o.tickets).length!==0)return o}import{appendFileSync as Et,mkdirSync as xt,readFileSync as X}from"node:fs";import{dirname as Dt,join as Z}from"node:path";function I(t){let o=Z(t,".clancy","progress.txt"),n;try{n=X(o,"utf8")}catch{return[]}let i=[];for(let l of n.split(`
2
- `)){let a=l.trim();if(!a)continue;let s=a.split(" | ");if(s.length<4)continue;let c=s[0];if(s[1]==="BRIEF"||s[1]==="APPROVE_BRIEF"){i.push({timestamp:c,key:s[2],summary:s.slice(3).join(" | "),status:s[1]});continue}let p=s[1],u,d,h,S=[];for(let y=2;y<s.length;y++){let f=s[y],T=f.match(/^pr:(\d+)$/),r=f.match(/^parent:(.+)$/);T?d=parseInt(T[1],10):r?h=r[1]:y>=3&&!u&&f===f.toUpperCase()&&f.length>1?u=f:S.push(f)}u&&(u==="APPROVE"&&(u="APPROVE_PLAN"),i.push({timestamp:c,key:p,summary:S.join(" | "),status:u,...d!=null&&{prNumber:d},...h!=null&&{parent:h}}))}return i}var $=new Set(["DONE","PR_CREATED","PUSHED","EPIC_PR_CREATED","RESUMED"]),D=new Set(["SKIPPED","PUSH_FAILED","TIME_LIMIT"]);function et(t,o){let n=C(t,".clancy","costs.log"),i;try{i=G(n,"utf8")}catch{return[]}let l=[];for(let a of i.split(`
3
- `)){let s=a.trim();if(!s)continue;let c=s.split(" | ");if(c.length<4)continue;let p=c[0],u=new Date(p).getTime();Number.isNaN(u)||u<o||l.push({timestamp:p,key:c[1],duration:c[2],tokens:c[3]})}return l}function ot(t){return/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/.test(t)?new Date(t.replace(" ","T")+":00Z").getTime():NaN}function _(t,o,n){let i=Math.floor(o/6e4)*6e4,a=I(t).filter(e=>{let g=ot(e.timestamp);return!Number.isNaN(g)&&g>=i}),s=et(t,o),c=new Map;for(let e of s)c.set(e.key,e);let p=new Map;for(let e of a)p.set(e.key,e);let u=[];for(let e of p.values()){let g=c.get(e.key);u.push({key:e.key,summary:e.summary,status:e.status,...e.prNumber!=null&&{prNumber:e.prNumber},...g&&{duration:g.duration,tokens:g.tokens}})}let d=u.filter(e=>$.has(e.status)),h=u.filter(e=>D.has(e.status)),S=k(n-o),y=0;for(let e of s){let g=e.tokens.match(/~(\d[\d,]*)/);g&&(y+=parseInt(g[1].replace(/,/g,""),10))}let f=new Date(o),T=`${f.getUTCFullYear()}-${String(f.getUTCMonth()+1).padStart(2,"0")}-${String(f.getUTCDate()).padStart(2,"0")}`,r=[];r.push(`# AFK Session Report \u2014 ${T}`),r.push(""),r.push("## Summary"),r.push(`- Tickets completed: ${d.length}`),r.push(`- Tickets failed: ${h.length}`),r.push(`- Total duration: ${S}`),y>0&&r.push(`- Estimated token usage: ${y.toLocaleString("en-US")}`),r.push(""),r.push("## Tickets"),u.length===0&&(r.push(""),r.push("No tickets were processed in this session."));for(let e of u){let V=$.has(e.status)?"\u2713":"\u2717";r.push(""),r.push(`### ${V} ${e.key} \u2014 ${e.summary}`),e.duration&&r.push(`- Duration: ${e.duration}`),e.tokens&&r.push(`- Tokens: ${e.tokens}`),e.prNumber!=null&&r.push(`- PR: #${e.prNumber}`),r.push(`- Status: ${e.status}`)}let E=u.filter(e=>e.prNumber!=null).map(e=>`#${e.prNumber}`),R=h.map(e=>e.key);if(E.length>0||R.length>0){r.push(""),r.push("## Next Steps"),E.length>0&&r.push(`- Review PRs ${E.join(", ")}`);for(let e of R)r.push(`- ${e} needs manual intervention`)}try{let e=b(t);e&&(r.push(""),r.push("## Quality Metrics"),r.push(`- Avg rework cycles: ${e.summary.avgReworkCycles}`),r.push(`- Avg verification retries: ${e.summary.avgVerificationRetries}`),e.summary.avgDuration>0&&r.push(`- Avg delivery time: ${k(e.summary.avgDuration)}`))}catch{}r.push("");let A=r.join(`
4
- `);try{let e=C(t,".clancy","session-report.md");z(C(t,".clancy"),{recursive:!0}),tt(e,A,"utf8")}catch{}return A}function F(t){let o=t.trim().match(/^(\d{1,2}):(\d{2})$/);if(!o)return null;let n=parseInt(o[1],10),i=parseInt(o[2],10);return n<0||n>23||i<0||i>59?null:{hours:n,minutes:i}}var O=!1;function at(t,o,n=new Date){let i=F(t),l=F(o);if(!i||!l)return O||(console.warn("\u26A0 Invalid quiet hours format. Expected HH:MM (24h). Quiet hours disabled."),O=!0),0;let a=n.getHours()*60+n.getMinutes(),s=i.hours*60+i.minutes,c=l.hours*60+l.minutes,p=!1;if(s<c?p=a>=s&&a<c:s>c&&(p=a>=s||a<c),!p)return 0;let u=c-a;u<=0&&(u+=1440);let d=n.getSeconds()*1e3+n.getMilliseconds();return Math.max(0,u*6e4-d)}var v={noTickets:/No tickets found|No issues found|All done/,skipped:/Ticket skipped/,preflightFail:/^✗ /m};function ut(t){return v.noTickets.test(t)?{stop:!0,reason:"No more tickets \u2014 all done"}:v.skipped.test(t)?{stop:!0,reason:"Ticket was skipped \u2014 update the ticket and re-run"}:v.preflightFail.test(t)?{stop:!0,reason:"Preflight check failed"}:{stop:!1}}function lt(t){return nt("node",[t],{encoding:"utf8",stdio:["inherit","pipe","inherit"],cwd:process.cwd(),env:{...process.env,CLANCY_AFK_MODE:"1"}})}async function pt(t,o=5,n=lt){let i=it(t,"clancy-once.js");if(!st(i)){console.error(w("\u2717 clancy-once.js not found in"),t);return}console.log(m("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510")),console.log(m("\u2502")+x(" \u{1F916} Clancy \u2014 AFK mode ")+m("\u2502")),console.log(m("\u2502")+m(` "I'm on it. Proceed to the abandoned warehouse." `)+m("\u2502")),console.log(m("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));let l=Date.now();for(let s=1;s<=o;s++){let c=process.env.CLANCY_QUIET_START,p=process.env.CLANCY_QUIET_END;if(c&&p){let f=at(c,p);if(f>0){let T=Math.ceil(f/6e4);console.log(""),console.log(N(`\u23F8 Quiet hours active (${c}\u2013${p}). Sleeping ${T} minutes until ${p}.`)),await L(f),console.log(m(" Quiet hours ended. Resuming."))}}else(c&&!p||!c&&p)&&console.log(m(" \u26A0 Only one of CLANCY_QUIET_START / CLANCY_QUIET_END is set \u2014 skipping quiet hours check."));let u=Date.now();console.log(""),console.log(x(`\u{1F501} Iteration ${s}/${o}`));let d=await n(i),h=d.stdout??"";h&&process.stdout.write(h);let S=k(Date.now()-u);if(d.error){console.error(w(`\u2717 Failed to run clancy-once: ${d.error.message}`));return}let y=ut(h);if(y.stop){let f=k(Date.now()-l);console.log(""),console.log(m(` Iteration ${s} took ${S}`)),console.log(`
5
- ${y.reason}`),console.log(m(` Total: ${s} iteration${s>1?"s":""} in ${f}`)),Q(l);return}console.log(m(` Iteration ${s} took ${S}`)),s<o&&await L(2e3)}let a=k(Date.now()-l);console.log(""),console.log(M(`\u{1F3C1} Completed ${o} iterations`)+m(` (${a})`)),console.log(m(` "That's some good police work."`)),console.log(m(" Run clancy-afk again to continue.")),Q(l)}function Q(t){try{let o=_(process.cwd(),t,Date.now());console.log(""),console.log(m("\u2500\u2500\u2500 Session Report \u2500\u2500\u2500")),console.log(o);let n=process.env.CLANCY_NOTIFY_WEBHOOK;if(n){let a=`Clancy AFK: ${o.split(`
6
- `).filter(s=>s.startsWith("- Tickets")||s.startsWith("- Total")).join(". ")}. Report: .clancy/session-report.md`;P(n,a).catch(()=>{})}}catch{}}if(process.argv[1]&&U(import.meta.url)===ct(process.argv[1])){let t=rt(U(import.meta.url)),o=parseInt(process.env.MAX_ITERATIONS??"5",10)||5;pt(t,o)}export{ut as checkStopCondition,at as getQuietSleepMs,F as parseTime,pt as runAfkLoop};