agentcohort 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 +187 -0
- package/dist/args.d.ts +13 -0
- package/dist/args.js +92 -0
- package/dist/claudeMd.d.ts +33 -0
- package/dist/claudeMd.js +156 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +108 -0
- package/dist/fileOps.d.ts +15 -0
- package/dist/fileOps.js +50 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +30 -0
- package/dist/installer.d.ts +32 -0
- package/dist/installer.js +208 -0
- package/dist/logger.d.ts +43 -0
- package/dist/logger.js +63 -0
- package/dist/manifest.d.ts +18 -0
- package/dist/manifest.js +44 -0
- package/dist/paths.d.ts +15 -0
- package/dist/paths.js +52 -0
- package/dist/prompt.d.ts +32 -0
- package/dist/prompt.js +57 -0
- package/dist/templates/CLAUDE.section.md +62 -0
- package/dist/templates/agents/bug-fixer.md +67 -0
- package/dist/templates/agents/bug-hunter.md +67 -0
- package/dist/templates/agents/expert-council.md +83 -0
- package/dist/templates/agents/feature-implementer.md +69 -0
- package/dist/templates/agents/feature-planner.md +75 -0
- package/dist/templates/agents/final-reviewer.md +71 -0
- package/dist/templates/agents/perf-optimizer.md +63 -0
- package/dist/templates/agents/perf-reviewer.md +66 -0
- package/dist/templates/agents/performance-hunter.md +68 -0
- package/dist/templates/agents/regression-guard.md +61 -0
- package/dist/templates/agents/repo-scout.md +77 -0
- package/dist/templates/agents/reproduction-engineer.md +65 -0
- package/dist/templates/agents/root-cause-analyst.md +71 -0
- package/dist/templates/agents/solution-architect.md +71 -0
- package/dist/templates/agents/test-verifier.md +68 -0
- package/dist/templates/commands/auto-flow.md +41 -0
- package/dist/templates/commands/bug-audit.md +51 -0
- package/dist/templates/commands/bug-fix-approved.md +44 -0
- package/dist/templates/commands/dev-flow.md +41 -0
- package/dist/templates/commands/fix-blockers.md +36 -0
- package/dist/templates/commands/perf-hunt.md +40 -0
- package/dist/templates/commands/review-diff.md +39 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 agentcohort contributors
|
|
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,187 @@
|
|
|
1
|
+
# agentcohort
|
|
2
|
+
|
|
3
|
+
> Install a principal/staff-level **AI software-engineering organization** for
|
|
4
|
+
> [Claude Code](https://docs.claude.com/en/docs/claude-code) into any project
|
|
5
|
+
> with one command.
|
|
6
|
+
|
|
7
|
+
`agentcohort` is not just a template copier. It installs a coordinated set of
|
|
8
|
+
**15 subagents**, **7 workflow commands**, and **routing rules** that make
|
|
9
|
+
Claude Code work like a disciplined engineering org: explore before changing,
|
|
10
|
+
prove root cause before fixing, measure before optimizing, and review before
|
|
11
|
+
shipping.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm i agentcohort
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Or run it without installing:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx agentcohort init
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
> The npm package is `agentcohort`; the CLI command it installs is
|
|
28
|
+
> just `agentcohort`.
|
|
29
|
+
|
|
30
|
+
## Quick start
|
|
31
|
+
|
|
32
|
+
From the root of the project you want to equip:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
agentcohort init
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Then open Claude Code in that project and run:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
/auto-flow <describe your task, bug, or paste a diff>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`/auto-flow` classifies the work and routes it to the right pipeline.
|
|
45
|
+
|
|
46
|
+
### Commands
|
|
47
|
+
|
|
48
|
+
| Command | What it does |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `agentcohort init` | Install agents, commands and routing rules into the current project. |
|
|
51
|
+
| `agentcohort init --yes` | Non-interactive. Safe defaults (see below). |
|
|
52
|
+
| `agentcohort init --dry-run` | Print exactly what *would* change. Writes nothing. |
|
|
53
|
+
| `agentcohort init --force` | Overwrite conflicts / replace the routing section without prompting. |
|
|
54
|
+
| `agentcohort init --backup` | Always back up a file before overwriting it. |
|
|
55
|
+
| `agentcohort --version` | Print the version. |
|
|
56
|
+
| `agentcohort --help` | Show help. |
|
|
57
|
+
|
|
58
|
+
Flags compose: `agentcohort init --yes --backup`, `--force --backup`, etc.
|
|
59
|
+
|
|
60
|
+
## What files are created
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
.claude/
|
|
64
|
+
agents/
|
|
65
|
+
repo-scout.md solution-architect.md feature-planner.md
|
|
66
|
+
feature-implementer.md test-verifier.md final-reviewer.md
|
|
67
|
+
bug-hunter.md root-cause-analyst.md reproduction-engineer.md
|
|
68
|
+
regression-guard.md bug-fixer.md
|
|
69
|
+
performance-hunter.md perf-optimizer.md perf-reviewer.md
|
|
70
|
+
expert-council.md
|
|
71
|
+
commands/
|
|
72
|
+
auto-flow.md dev-flow.md bug-audit.md bug-fix-approved.md
|
|
73
|
+
perf-hunt.md review-diff.md fix-blockers.md
|
|
74
|
+
CLAUDE.md # a "# Agentcohort Routing Rules" section
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
A full example tree is in [`examples/generated-claude/`](./examples/generated-claude).
|
|
78
|
+
|
|
79
|
+
## The philosophy
|
|
80
|
+
|
|
81
|
+
**Core:** Explore → Architect → Plan → Implement → Test → Review
|
|
82
|
+
|
|
83
|
+
**Bugs:** Hunt → Evidence → Root Cause → Expert Council → **Human Approval** →
|
|
84
|
+
Fix → Regression Test → Verify → Review
|
|
85
|
+
|
|
86
|
+
**Performance:** Measure/Evidence → Bottleneck → Safe Optimization → Verify →
|
|
87
|
+
Performance Review
|
|
88
|
+
|
|
89
|
+
Every agent operates at a top-1% principal/staff standard: root-cause first,
|
|
90
|
+
production-grade correctness, no shallow fixes, no fixing without evidence, and
|
|
91
|
+
**a bug audit never fixes** — it produces a recommendation and stops at a human
|
|
92
|
+
approval gate.
|
|
93
|
+
|
|
94
|
+
## Using the workflow commands (inside Claude Code)
|
|
95
|
+
|
|
96
|
+
| Command | Pipeline | Use it for |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| `/auto-flow` | classify → route | When unsure — it picks the flow. |
|
|
99
|
+
| `/dev-flow` | scout → architect\* → planner → implementer → test-verifier → final-reviewer | Features & refactors. |
|
|
100
|
+
| `/bug-audit` | bug-hunter → root-cause-analyst → reproduction-engineer → expert-council | Bugs / regressions / bad data / stability. **No fixing.** |
|
|
101
|
+
| `/bug-fix-approved` | bug-fixer → regression-guard → test-verifier → final-reviewer | Implement a fix you already approved. |
|
|
102
|
+
| `/perf-hunt` | performance-hunter → architect\* → perf-optimizer → test-verifier → perf-reviewer | Slowness / bottlenecks. |
|
|
103
|
+
| `/review-diff` | final-reviewer | Review the current diff/PR. |
|
|
104
|
+
| `/fix-blockers` | feature-implementer → test-verifier | Fix only the blockers a review listed. |
|
|
105
|
+
|
|
106
|
+
\* the architect stage runs only when the change is architecture-sensitive.
|
|
107
|
+
|
|
108
|
+
### Model strategy
|
|
109
|
+
|
|
110
|
+
- **Haiku** — cheap exploration / scouting.
|
|
111
|
+
- **Sonnet** — implementation, testing, bug & performance hunting.
|
|
112
|
+
- **Opus** — architecture, root-cause analysis, expert council, final review.
|
|
113
|
+
|
|
114
|
+
## Customizing agents
|
|
115
|
+
|
|
116
|
+
The installed files are plain Markdown and **yours to edit**:
|
|
117
|
+
|
|
118
|
+
- Tune any agent in `.claude/agents/*.md` (role, rules, output format, the
|
|
119
|
+
`model:`/`tools:` frontmatter).
|
|
120
|
+
- Adjust a pipeline in `.claude/commands/*.md`.
|
|
121
|
+
- Put **your own** project notes in `CLAUDE.md` *outside* the
|
|
122
|
+
`# Agentcohort Routing Rules` section — that section is owned by the tool and
|
|
123
|
+
may be updated by a future `init`; everything else is never touched.
|
|
124
|
+
|
|
125
|
+
Re-running `agentcohort init` later will detect your edits as conflicts and ask
|
|
126
|
+
before changing them (or back them up with `--backup`).
|
|
127
|
+
|
|
128
|
+
## Safety notes
|
|
129
|
+
|
|
130
|
+
`agentcohort` is conservative by design:
|
|
131
|
+
|
|
132
|
+
- **Never deletes** your files.
|
|
133
|
+
- **Never silently overwrites.** Existing, differing files trigger a prompt
|
|
134
|
+
(skip / overwrite / backup + overwrite), or an explicit flag.
|
|
135
|
+
- **Idempotent.** Re-running on identical content reports *unchanged* and does
|
|
136
|
+
nothing.
|
|
137
|
+
- **CLAUDE.md is surgical.** A missing file is created; a file without our
|
|
138
|
+
section gets the section *appended* (your content preserved); an existing,
|
|
139
|
+
differing section is **left alone** in non-interactive mode (use `--force`
|
|
140
|
+
to update it). Only the delimited section is ever touched.
|
|
141
|
+
- **`--yes` safe defaults:** new files created; conflicting files
|
|
142
|
+
**backed up then updated**; an existing CLAUDE.md routing section **left
|
|
143
|
+
untouched**.
|
|
144
|
+
- **`--dry-run`** performs zero writes and zero backups.
|
|
145
|
+
- Backups are written next to the original as
|
|
146
|
+
`<file>.backup-YYYYMMDD-HHMMSS` and never overwrite an existing backup.
|
|
147
|
+
- Cross-platform (Windows/macOS/Linux), zero runtime dependencies, no
|
|
148
|
+
shell-specific behavior.
|
|
149
|
+
|
|
150
|
+
## Development
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
npm install
|
|
154
|
+
npm run build # tsc -> dist/, then copies templates
|
|
155
|
+
npm test # vitest
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Releases
|
|
159
|
+
|
|
160
|
+
Publishing is automated. **The version in `package.json` is the version that
|
|
161
|
+
gets published.** Every push to `main` runs the
|
|
162
|
+
[`Release`](.github/workflows/release.yml) workflow, which:
|
|
163
|
+
|
|
164
|
+
1. installs, builds and runs the full test suite;
|
|
165
|
+
2. publishes the **current** `package.json` version to npm —
|
|
166
|
+
https://www.npmjs.com/package/agentcohort (so the very first
|
|
167
|
+
release is exactly `0.1.0`, nothing skipped);
|
|
168
|
+
3. creates the annotated git tag `vX.Y.Z` on the published commit;
|
|
169
|
+
4. bumps to the next dev version (`patch` by default) and pushes a
|
|
170
|
+
`chore(release): published vX.Y.Z, open vX.Y.(Z+1) [skip ci]` commit back
|
|
171
|
+
to `main`.
|
|
172
|
+
|
|
173
|
+
So: to cut a normal release, just push to `main`. To release a `minor`/`major`
|
|
174
|
+
instead, bump `package.json` yourself in a regular commit before pushing (or
|
|
175
|
+
use the *Run workflow* button to control how the **next** pending version is
|
|
176
|
+
opened). If the pending version is already on npm, publish is skipped and the
|
|
177
|
+
job still succeeds (safe re-runs). The `[skip ci]` marker stops the release
|
|
178
|
+
commit from re-triggering the workflow (no publish loop).
|
|
179
|
+
|
|
180
|
+
**One-time setup:** add an npm **Automation** access token as the repository
|
|
181
|
+
secret `NPM_TOKEN` (GitHub → Settings → Secrets and variables → Actions →
|
|
182
|
+
*New repository secret*). Until that secret exists, the workflow's *Publish*
|
|
183
|
+
step will fail while build/test still pass.
|
|
184
|
+
|
|
185
|
+
## License
|
|
186
|
+
|
|
187
|
+
MIT
|
package/dist/args.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface ParsedArgs {
|
|
2
|
+
command: string | null;
|
|
3
|
+
yes: boolean;
|
|
4
|
+
dryRun: boolean;
|
|
5
|
+
force: boolean;
|
|
6
|
+
backup: boolean;
|
|
7
|
+
help: boolean;
|
|
8
|
+
version: boolean;
|
|
9
|
+
unknown: string[];
|
|
10
|
+
}
|
|
11
|
+
/** Pure, deterministic argument parser. Unknown tokens are collected, not thrown. */
|
|
12
|
+
export declare function parseArgs(argv: string[]): ParsedArgs;
|
|
13
|
+
export declare function helpText(): string;
|
package/dist/args.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseArgs = parseArgs;
|
|
4
|
+
exports.helpText = helpText;
|
|
5
|
+
const logger_1 = require("./logger");
|
|
6
|
+
const FLAGS = {
|
|
7
|
+
'--yes': 'yes',
|
|
8
|
+
'-y': 'yes',
|
|
9
|
+
'--dry-run': 'dryRun',
|
|
10
|
+
'--force': 'force',
|
|
11
|
+
'--backup': 'backup',
|
|
12
|
+
'--help': 'help',
|
|
13
|
+
'-h': 'help',
|
|
14
|
+
'--version': 'version',
|
|
15
|
+
'-v': 'version',
|
|
16
|
+
};
|
|
17
|
+
/** Pure, deterministic argument parser. Unknown tokens are collected, not thrown. */
|
|
18
|
+
function parseArgs(argv) {
|
|
19
|
+
const parsed = {
|
|
20
|
+
command: null,
|
|
21
|
+
yes: false,
|
|
22
|
+
dryRun: false,
|
|
23
|
+
force: false,
|
|
24
|
+
backup: false,
|
|
25
|
+
help: false,
|
|
26
|
+
version: false,
|
|
27
|
+
unknown: [],
|
|
28
|
+
};
|
|
29
|
+
for (const arg of argv) {
|
|
30
|
+
if (arg.startsWith('-')) {
|
|
31
|
+
const key = FLAGS[arg];
|
|
32
|
+
if (key) {
|
|
33
|
+
parsed[key] = true;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
parsed.unknown.push(arg);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else if (parsed.command === null) {
|
|
40
|
+
parsed.command = arg;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
parsed.unknown.push(arg);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return parsed;
|
|
47
|
+
}
|
|
48
|
+
function helpText() {
|
|
49
|
+
const b = (s) => (0, logger_1.paint)(s, 'bold');
|
|
50
|
+
return `
|
|
51
|
+
${b('agentcohort')} — install a principal/staff-level Claude Code AI engineering org.
|
|
52
|
+
|
|
53
|
+
${b('USAGE')}
|
|
54
|
+
agentcohort <command> [options]
|
|
55
|
+
|
|
56
|
+
${b('COMMANDS')}
|
|
57
|
+
init Install agents, workflow commands and routing rules
|
|
58
|
+
into ./.claude and ./CLAUDE.md of the current project.
|
|
59
|
+
|
|
60
|
+
${b('OPTIONS')}
|
|
61
|
+
--yes, -y Non-interactive. Safe defaults: new files created;
|
|
62
|
+
existing conflicting files backed up then updated;
|
|
63
|
+
an existing CLAUDE.md routing section is left untouched.
|
|
64
|
+
--dry-run Print exactly what would be created/updated. Writes
|
|
65
|
+
nothing. Implies non-interactive (safe defaults).
|
|
66
|
+
--force Overwrite conflicting files / replace the routing
|
|
67
|
+
section without prompting (no backup unless --backup).
|
|
68
|
+
--backup Always back up a file before overwriting it.
|
|
69
|
+
Backup name: <file>.backup-YYYYMMDD-HHMMSS
|
|
70
|
+
--help, -h Show this help.
|
|
71
|
+
--version, -v Print the version.
|
|
72
|
+
|
|
73
|
+
${b('WHAT GETS INSTALLED')}
|
|
74
|
+
.claude/agents/ 15 subagents (scout, architect, planner, implementer,
|
|
75
|
+
reviewer, bug-hunter, root-cause-analyst, ...).
|
|
76
|
+
.claude/commands/ 7 workflow commands.
|
|
77
|
+
CLAUDE.md Appends a "# Agentcohort Routing Rules" section.
|
|
78
|
+
|
|
79
|
+
${b('WORKFLOW COMMANDS (run inside Claude Code)')}
|
|
80
|
+
/auto-flow Classify the task and pick the right workflow.
|
|
81
|
+
/dev-flow Feature/refactor: scout -> architect -> plan ->
|
|
82
|
+
implement -> test -> review.
|
|
83
|
+
/bug-audit Investigate ONLY (no fixes): hunt -> evidence ->
|
|
84
|
+
root cause -> expert council -> recommendation.
|
|
85
|
+
/bug-fix-approved Fix an approved bug: fix -> regression -> test -> review.
|
|
86
|
+
/perf-hunt Measure -> bottleneck -> safe optimize -> verify -> review.
|
|
87
|
+
/review-diff Final reviewer on the current diff.
|
|
88
|
+
/fix-blockers Fix only listed blockers, then verify.
|
|
89
|
+
|
|
90
|
+
Existing files are NEVER deleted and NEVER silently overwritten.
|
|
91
|
+
`;
|
|
92
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers for managing the Agentcohort section inside a project CLAUDE.md.
|
|
3
|
+
*
|
|
4
|
+
* Design goals:
|
|
5
|
+
* - Never destroy the user's existing CLAUDE.md content.
|
|
6
|
+
* - Only ever touch our own clearly-delimited section.
|
|
7
|
+
* - Preserve the rest of the file byte-for-byte (no global EOL rewrites):
|
|
8
|
+
* we splice by string index instead of split/join.
|
|
9
|
+
* - Be fenced-code-block aware so a `# ...` line inside ``` ``` is not
|
|
10
|
+
* mistaken for a real heading boundary.
|
|
11
|
+
*/
|
|
12
|
+
export declare const SECTION_TITLE = "# Agentcohort Routing Rules";
|
|
13
|
+
/** Index where our section heading line starts, fenced-code-aware. -1 if absent. */
|
|
14
|
+
export declare function findSectionStart(content: string): number;
|
|
15
|
+
export declare function hasSection(content: string): boolean;
|
|
16
|
+
/** The exact current section block (heading through just before next heading/EOF). */
|
|
17
|
+
export declare function extractSection(content: string): string | null;
|
|
18
|
+
/** True when the existing section already equals the template (ignoring edge whitespace). */
|
|
19
|
+
export declare function sectionMatches(content: string, sectionMarkdown: string): boolean;
|
|
20
|
+
export type UpsertMode = 'append' | 'replace';
|
|
21
|
+
export interface UpsertResult {
|
|
22
|
+
result: string;
|
|
23
|
+
mode: UpsertMode;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Insert or replace the Agentcohort section.
|
|
27
|
+
* - Absent -> appended at the end, separated by a blank line.
|
|
28
|
+
* - Present -> the existing section block is replaced in place; everything
|
|
29
|
+
* before and after is preserved verbatim.
|
|
30
|
+
*/
|
|
31
|
+
export declare function upsertSection(content: string, sectionMarkdown: string): UpsertResult;
|
|
32
|
+
/** Minimal CLAUDE.md created when the project has none. */
|
|
33
|
+
export declare function buildInitialClaudeMd(sectionMarkdown: string): string;
|
package/dist/claudeMd.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Pure helpers for managing the Agentcohort section inside a project CLAUDE.md.
|
|
4
|
+
*
|
|
5
|
+
* Design goals:
|
|
6
|
+
* - Never destroy the user's existing CLAUDE.md content.
|
|
7
|
+
* - Only ever touch our own clearly-delimited section.
|
|
8
|
+
* - Preserve the rest of the file byte-for-byte (no global EOL rewrites):
|
|
9
|
+
* we splice by string index instead of split/join.
|
|
10
|
+
* - Be fenced-code-block aware so a `# ...` line inside ``` ``` is not
|
|
11
|
+
* mistaken for a real heading boundary.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.SECTION_TITLE = void 0;
|
|
15
|
+
exports.findSectionStart = findSectionStart;
|
|
16
|
+
exports.hasSection = hasSection;
|
|
17
|
+
exports.extractSection = extractSection;
|
|
18
|
+
exports.sectionMatches = sectionMatches;
|
|
19
|
+
exports.upsertSection = upsertSection;
|
|
20
|
+
exports.buildInitialClaudeMd = buildInitialClaudeMd;
|
|
21
|
+
exports.SECTION_TITLE = '# Agentcohort Routing Rules';
|
|
22
|
+
function* iterateLines(content) {
|
|
23
|
+
let i = 0;
|
|
24
|
+
const n = content.length;
|
|
25
|
+
while (i <= n) {
|
|
26
|
+
let nl = content.indexOf('\n', i);
|
|
27
|
+
if (nl === -1)
|
|
28
|
+
nl = n;
|
|
29
|
+
let contentEnd = nl;
|
|
30
|
+
if (contentEnd > i && content[contentEnd - 1] === '\r')
|
|
31
|
+
contentEnd -= 1;
|
|
32
|
+
yield {
|
|
33
|
+
start: i,
|
|
34
|
+
contentEnd,
|
|
35
|
+
next: nl === n ? n : nl + 1,
|
|
36
|
+
text: content.slice(i, contentEnd),
|
|
37
|
+
};
|
|
38
|
+
if (nl === n)
|
|
39
|
+
break;
|
|
40
|
+
i = nl + 1;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const FENCE_RE = /^\s{0,3}(```+|~~~+)/;
|
|
44
|
+
const TOP_HEADING_RE = /^# (?!#)/; // exactly one '#', then space, not '##'
|
|
45
|
+
/** Index where our section heading line starts, fenced-code-aware. -1 if absent. */
|
|
46
|
+
function findSectionStart(content) {
|
|
47
|
+
let inFence = false;
|
|
48
|
+
let fenceToken = '';
|
|
49
|
+
for (const line of iterateLines(content)) {
|
|
50
|
+
const fence = FENCE_RE.exec(line.text);
|
|
51
|
+
if (fence) {
|
|
52
|
+
const token = fence[1][0]; // '`' or '~'
|
|
53
|
+
if (!inFence) {
|
|
54
|
+
inFence = true;
|
|
55
|
+
fenceToken = token;
|
|
56
|
+
}
|
|
57
|
+
else if (token === fenceToken) {
|
|
58
|
+
inFence = false;
|
|
59
|
+
fenceToken = '';
|
|
60
|
+
}
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (inFence)
|
|
64
|
+
continue;
|
|
65
|
+
if (line.text.trimEnd() === exports.SECTION_TITLE)
|
|
66
|
+
return line.start;
|
|
67
|
+
}
|
|
68
|
+
return -1;
|
|
69
|
+
}
|
|
70
|
+
/** Index where the section ends (start of the next top-level heading) or EOF. */
|
|
71
|
+
function findSectionEnd(content, sectionStart) {
|
|
72
|
+
let inFence = false;
|
|
73
|
+
let fenceToken = '';
|
|
74
|
+
let passedHeading = false;
|
|
75
|
+
for (const line of iterateLines(content)) {
|
|
76
|
+
if (line.start < sectionStart)
|
|
77
|
+
continue;
|
|
78
|
+
if (!passedHeading) {
|
|
79
|
+
// This is our own heading line; skip it then start scanning.
|
|
80
|
+
passedHeading = true;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const fence = FENCE_RE.exec(line.text);
|
|
84
|
+
if (fence) {
|
|
85
|
+
const token = fence[1][0];
|
|
86
|
+
if (!inFence) {
|
|
87
|
+
inFence = true;
|
|
88
|
+
fenceToken = token;
|
|
89
|
+
}
|
|
90
|
+
else if (token === fenceToken) {
|
|
91
|
+
inFence = false;
|
|
92
|
+
fenceToken = '';
|
|
93
|
+
}
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (inFence)
|
|
97
|
+
continue;
|
|
98
|
+
if (TOP_HEADING_RE.test(line.text))
|
|
99
|
+
return line.start;
|
|
100
|
+
}
|
|
101
|
+
return content.length;
|
|
102
|
+
}
|
|
103
|
+
function hasSection(content) {
|
|
104
|
+
return findSectionStart(content) !== -1;
|
|
105
|
+
}
|
|
106
|
+
/** The exact current section block (heading through just before next heading/EOF). */
|
|
107
|
+
function extractSection(content) {
|
|
108
|
+
const start = findSectionStart(content);
|
|
109
|
+
if (start === -1)
|
|
110
|
+
return null;
|
|
111
|
+
const end = findSectionEnd(content, start);
|
|
112
|
+
return content.slice(start, end);
|
|
113
|
+
}
|
|
114
|
+
/** True when the existing section already equals the template (ignoring edge whitespace). */
|
|
115
|
+
function sectionMatches(content, sectionMarkdown) {
|
|
116
|
+
const current = extractSection(content);
|
|
117
|
+
if (current === null)
|
|
118
|
+
return false;
|
|
119
|
+
return current.trim() === sectionMarkdown.trim();
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Insert or replace the Agentcohort section.
|
|
123
|
+
* - Absent -> appended at the end, separated by a blank line.
|
|
124
|
+
* - Present -> the existing section block is replaced in place; everything
|
|
125
|
+
* before and after is preserved verbatim.
|
|
126
|
+
*/
|
|
127
|
+
function upsertSection(content, sectionMarkdown) {
|
|
128
|
+
const body = sectionMarkdown.replace(/\s+$/, '') + '\n';
|
|
129
|
+
const start = findSectionStart(content);
|
|
130
|
+
if (start === -1) {
|
|
131
|
+
const base = content.length === 0 ? '' : content.replace(/\s+$/, '') + '\n';
|
|
132
|
+
const separator = base === '' ? '' : '\n';
|
|
133
|
+
return { result: base + separator + body, mode: 'append' };
|
|
134
|
+
}
|
|
135
|
+
const end = findSectionEnd(content, start);
|
|
136
|
+
const prefix = content.slice(0, start);
|
|
137
|
+
const suffix = content.slice(end);
|
|
138
|
+
let result = prefix + body;
|
|
139
|
+
if (suffix.length > 0) {
|
|
140
|
+
// Ensure exactly one blank line between our section and the next heading.
|
|
141
|
+
result += '\n' + suffix.replace(/^\n+/, '');
|
|
142
|
+
}
|
|
143
|
+
return { result, mode: 'replace' };
|
|
144
|
+
}
|
|
145
|
+
/** Minimal CLAUDE.md created when the project has none. */
|
|
146
|
+
function buildInitialClaudeMd(sectionMarkdown) {
|
|
147
|
+
const header = [
|
|
148
|
+
'# Project Guidance for Claude Code',
|
|
149
|
+
'',
|
|
150
|
+
'This file is read by Claude Code at the start of every session.',
|
|
151
|
+
'The section below was installed by `agentcohort` and wires up the',
|
|
152
|
+
'AI software-engineering organization (agents + workflows).',
|
|
153
|
+
'',
|
|
154
|
+
].join('\n');
|
|
155
|
+
return upsertSection(header, sectionMarkdown).result;
|
|
156
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.main = main;
|
|
5
|
+
const installer_1 = require("./installer");
|
|
6
|
+
const prompt_1 = require("./prompt");
|
|
7
|
+
const logger_1 = require("./logger");
|
|
8
|
+
const paths_1 = require("./paths");
|
|
9
|
+
const args_1 = require("./args");
|
|
10
|
+
function printSummary(result) {
|
|
11
|
+
const counts = new Map();
|
|
12
|
+
let backups = 0;
|
|
13
|
+
for (const a of result.actions) {
|
|
14
|
+
counts.set(a.disposition, (counts.get(a.disposition) ?? 0) + 1);
|
|
15
|
+
if (a.backupPath)
|
|
16
|
+
backups += 1;
|
|
17
|
+
}
|
|
18
|
+
const part = (label, key) => {
|
|
19
|
+
const n = counts.get(key) ?? 0;
|
|
20
|
+
return n > 0 ? `${n} ${label}` : null;
|
|
21
|
+
};
|
|
22
|
+
const segments = [
|
|
23
|
+
part('created', 'created'),
|
|
24
|
+
part('updated', 'overwritten'),
|
|
25
|
+
part('section appended', 'appended-section'),
|
|
26
|
+
part('section updated', 'replaced-section'),
|
|
27
|
+
part('unchanged', 'unchanged'),
|
|
28
|
+
part('skipped', 'skipped'),
|
|
29
|
+
].filter((x) => x !== null);
|
|
30
|
+
process.stdout.write('\n');
|
|
31
|
+
const head = result.dryRun ? 'Dry run complete' : 'Agentcohort installed';
|
|
32
|
+
process.stdout.write(`${(0, logger_1.paint)(head, 'bold', 'green')} ${segments.join(' · ')}\n`);
|
|
33
|
+
if (backups > 0) {
|
|
34
|
+
process.stdout.write(`${(0, logger_1.paint)('•', 'cyan')} ${backups} backup(s) written alongside the originals.\n`);
|
|
35
|
+
}
|
|
36
|
+
if (!result.dryRun && ((counts.get('created') ?? 0) || (counts.get('overwritten') ?? 0))) {
|
|
37
|
+
process.stdout.write(`${(0, logger_1.paint)('•', 'cyan')} Next: open Claude Code in this project and run ${(0, logger_1.paint)('/auto-flow', 'bold')}.\n`);
|
|
38
|
+
}
|
|
39
|
+
if (result.dryRun) {
|
|
40
|
+
process.stdout.write(`${(0, logger_1.paint)('•', 'cyan')} Re-run without ${(0, logger_1.paint)('--dry-run', 'bold')} to apply.\n`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async function main(argv = process.argv.slice(2)) {
|
|
44
|
+
const args = (0, args_1.parseArgs)(argv);
|
|
45
|
+
if (args.unknown.length > 0) {
|
|
46
|
+
process.stderr.write((0, logger_1.paint)(`✗ Unknown argument(s): ${args.unknown.join(', ')}\n`, 'red'));
|
|
47
|
+
process.stdout.write((0, args_1.helpText)() + '\n');
|
|
48
|
+
return 1;
|
|
49
|
+
}
|
|
50
|
+
if (args.version) {
|
|
51
|
+
process.stdout.write((0, paths_1.getVersion)() + '\n');
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
if (args.help || args.command === null) {
|
|
55
|
+
process.stdout.write((0, args_1.helpText)() + '\n');
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
58
|
+
if (args.command !== 'init') {
|
|
59
|
+
process.stderr.write((0, logger_1.paint)(`✗ Unknown command: ${args.command}\n`, 'red'));
|
|
60
|
+
process.stdout.write((0, args_1.helpText)() + '\n');
|
|
61
|
+
return 1;
|
|
62
|
+
}
|
|
63
|
+
const stdinTTY = Boolean(process.stdin.isTTY);
|
|
64
|
+
const stdoutTTY = Boolean(process.stdout.isTTY);
|
|
65
|
+
const interactive = !args.yes && !args.force && !args.dryRun && stdinTTY && stdoutTTY;
|
|
66
|
+
const logger = (0, logger_1.createLogger)();
|
|
67
|
+
let resolverHandle = null;
|
|
68
|
+
process.stdout.write((0, logger_1.paint)('\nagentcohort', 'bold', 'cyan') + (0, logger_1.paint)(` v${(0, paths_1.getVersion)()}\n`, 'gray'));
|
|
69
|
+
if (args.dryRun) {
|
|
70
|
+
logger.info('Dry run — no files will be written.');
|
|
71
|
+
}
|
|
72
|
+
else if (!interactive && !args.yes && !args.force) {
|
|
73
|
+
logger.info('Non-interactive environment detected — using safe defaults (like --yes).');
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
if (interactive)
|
|
77
|
+
resolverHandle = (0, prompt_1.createInteractiveResolver)();
|
|
78
|
+
const result = await (0, installer_1.runInit)({
|
|
79
|
+
cwd: process.cwd(),
|
|
80
|
+
yes: args.yes,
|
|
81
|
+
dryRun: args.dryRun,
|
|
82
|
+
force: args.force,
|
|
83
|
+
backup: args.backup,
|
|
84
|
+
interactive,
|
|
85
|
+
resolver: resolverHandle?.resolve,
|
|
86
|
+
logger,
|
|
87
|
+
});
|
|
88
|
+
printSummary(result);
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
93
|
+
process.stderr.write((0, logger_1.paint)(`\n✗ ${message}\n`, 'red'));
|
|
94
|
+
return 1;
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
resolverHandle?.close();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Auto-run only when executed as the bin (not when imported by tests/tooling).
|
|
101
|
+
if (require.main === module) {
|
|
102
|
+
main()
|
|
103
|
+
.then((code) => process.exit(code))
|
|
104
|
+
.catch((err) => {
|
|
105
|
+
process.stderr.write((0, logger_1.paint)(`\n✗ Fatal: ${String(err)}\n`, 'red'));
|
|
106
|
+
process.exit(1);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Read a file's UTF-8 content, or null if it does not exist. */
|
|
2
|
+
export declare function readIfExists(path: string): string | null;
|
|
3
|
+
/** Exact byte-for-byte comparison (drives idempotency: identical => "unchanged"). */
|
|
4
|
+
export declare function contentEquals(a: string, b: string): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Backup filename suffix using LOCAL time: `backup-YYYYMMDD-HHMMSS`.
|
|
7
|
+
* Local time is intentional so the timestamp matches the user's wall clock.
|
|
8
|
+
*/
|
|
9
|
+
export declare function formatBackupSuffix(date: Date): string;
|
|
10
|
+
/** Absolute path of the backup file for `target` at `date`. */
|
|
11
|
+
export declare function backupPathFor(target: string, date: Date): string;
|
|
12
|
+
/** Copy an existing file to its backup path. Never deletes the original. */
|
|
13
|
+
export declare function backupFile(target: string, backupPath: string): void;
|
|
14
|
+
/** Create parent directories (recursive) then write the file as UTF-8. */
|
|
15
|
+
export declare function writeFileEnsuringDir(path: string, content: string): void;
|
package/dist/fileOps.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readIfExists = readIfExists;
|
|
4
|
+
exports.contentEquals = contentEquals;
|
|
5
|
+
exports.formatBackupSuffix = formatBackupSuffix;
|
|
6
|
+
exports.backupPathFor = backupPathFor;
|
|
7
|
+
exports.backupFile = backupFile;
|
|
8
|
+
exports.writeFileEnsuringDir = writeFileEnsuringDir;
|
|
9
|
+
const node_fs_1 = require("node:fs");
|
|
10
|
+
const node_path_1 = require("node:path");
|
|
11
|
+
/** Read a file's UTF-8 content, or null if it does not exist. */
|
|
12
|
+
function readIfExists(path) {
|
|
13
|
+
if (!(0, node_fs_1.existsSync)(path))
|
|
14
|
+
return null;
|
|
15
|
+
return (0, node_fs_1.readFileSync)(path, 'utf8');
|
|
16
|
+
}
|
|
17
|
+
/** Exact byte-for-byte comparison (drives idempotency: identical => "unchanged"). */
|
|
18
|
+
function contentEquals(a, b) {
|
|
19
|
+
return a === b;
|
|
20
|
+
}
|
|
21
|
+
/** Two-digit / four-digit zero padded. */
|
|
22
|
+
function pad(n, width = 2) {
|
|
23
|
+
return String(n).padStart(width, '0');
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Backup filename suffix using LOCAL time: `backup-YYYYMMDD-HHMMSS`.
|
|
27
|
+
* Local time is intentional so the timestamp matches the user's wall clock.
|
|
28
|
+
*/
|
|
29
|
+
function formatBackupSuffix(date) {
|
|
30
|
+
const y = pad(date.getFullYear(), 4);
|
|
31
|
+
const mo = pad(date.getMonth() + 1);
|
|
32
|
+
const d = pad(date.getDate());
|
|
33
|
+
const h = pad(date.getHours());
|
|
34
|
+
const mi = pad(date.getMinutes());
|
|
35
|
+
const s = pad(date.getSeconds());
|
|
36
|
+
return `backup-${y}${mo}${d}-${h}${mi}${s}`;
|
|
37
|
+
}
|
|
38
|
+
/** Absolute path of the backup file for `target` at `date`. */
|
|
39
|
+
function backupPathFor(target, date) {
|
|
40
|
+
return `${target}.${formatBackupSuffix(date)}`;
|
|
41
|
+
}
|
|
42
|
+
/** Copy an existing file to its backup path. Never deletes the original. */
|
|
43
|
+
function backupFile(target, backupPath) {
|
|
44
|
+
(0, node_fs_1.copyFileSync)(target, backupPath);
|
|
45
|
+
}
|
|
46
|
+
/** Create parent directories (recursive) then write the file as UTF-8. */
|
|
47
|
+
function writeFileEnsuringDir(path, content) {
|
|
48
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(path), { recursive: true });
|
|
49
|
+
(0, node_fs_1.writeFileSync)(path, content, 'utf8');
|
|
50
|
+
}
|