contextdevkit 1.8.0 → 1.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.
- package/CHANGELOG.md +12 -0
- package/install.mjs +14 -1
- package/package.json +3 -3
- package/templates/CLAUDE.md.tpl +2 -1
- package/templates/claude/commands/README.md +6 -2
- package/templates/claude/commands/audit/validate-doc.md +37 -0
- package/templates/claude/commands/bug-hunt.md +10 -2
- package/templates/claude/commands/forge/forge-new.md +6 -0
- package/templates/claude/commands/pipeline/dev-start.md +21 -6
- package/templates/claude/commands/pipeline/pipeline.md +5 -1
- package/templates/claude/commands/pipeline/ship.md +6 -1
- package/templates/claude/commands/roadmap.md +10 -1
- package/templates/claude/commands/vcs/changelog-social.md +30 -0
- package/templates/claude/commands/vcs/draft-changelog.md +28 -0
- package/templates/claude/commands/vcs/gh-triage.md +41 -0
- package/templates/contextkit/policy/complexity-rubric.json +52 -0
- package/templates/contextkit/runtime/config/paths.mjs +2 -0
- package/templates/contextkit/tools/scripts/complexity-rubric.mjs +164 -0
- package/templates/contextkit/tools/scripts/draft-changelog.mjs +101 -0
- package/templates/contextkit/tools/scripts/validate-doc.mjs +135 -0
- package/tools/install/cli.mjs +8 -1
- package/tools/install/migrate.mjs +162 -0
- package/tools/integration-test-migrate.mjs +151 -0
- package/tools/integration-test-tooling.mjs +25 -1
- package/tools/selfcheck-source.mjs +72 -0
- package/docs/CHANGELOG.md +0 -559
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,18 @@ this project follows [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
9
|
### Added
|
|
10
|
+
- **Legacy-install migration (rename follow-through).** `install.mjs` now carries
|
|
11
|
+
an old `vibekit/` install forward to `contextkit/` automatically on `npx
|
|
12
|
+
contextdevkit --update` (and via an explicit `node install.mjs --migrate
|
|
13
|
+
[--dry-run]`). New `tools/install/migrate.mjs`: atomically MOVES the folder
|
|
14
|
+
(preserving memory/ADRs, config + level, pipeline tasks, `.env`), rewrites the
|
|
15
|
+
rename tokens in `settings.json` (killing the duplicate-hook trap),
|
|
16
|
+
`.gitignore`, `.gitattributes`, git-hook wrappers, `contextkit/.env`, and
|
|
17
|
+
`CLAUDE.md` (the last two backed up to `*.bak`), and deletes the stale
|
|
18
|
+
`/vibe-*` + `setupvibedevkit` command files. Refuses (no-op + warning) when
|
|
19
|
+
BOTH folders exist; idempotent; never throws into the installer (rule 2). New
|
|
20
|
+
`tools/integration-test-migrate.mjs` (25 asserts) wired into `test` +
|
|
21
|
+
`prepublishOnly`.
|
|
10
22
|
- **agent-forge squad — Fase 6: declarative pipeline DSL + dry-run engine
|
|
11
23
|
(ADR-0015 Part A).** The forge's orchestration is now a diffable, simulate-
|
|
12
24
|
impact-mappable plan. New `templates/contextkit/squads/agent-forge/pipeline.yaml`
|
package/install.mjs
CHANGED
|
@@ -29,6 +29,7 @@ import { ensureDir, read, writeIfMissing, overwrite, copyTree, copyTreeIfMissing
|
|
|
29
29
|
import { detectStack, requireBasename, looksGreenfield } from './tools/install/project.mjs';
|
|
30
30
|
import { installGitHooks, patchGitignore, patchGitattributes } from './tools/install/git.mjs';
|
|
31
31
|
import { uninstall } from './tools/install/uninstall.mjs';
|
|
32
|
+
import { migrateLegacy } from './tools/install/migrate.mjs';
|
|
32
33
|
import { isValidLevel } from './templates/contextkit/runtime/config/levels.mjs';
|
|
33
34
|
import { parseArgs, HELP, prompt, LEVEL_LABELS } from './tools/install/cli.mjs';
|
|
34
35
|
|
|
@@ -63,6 +64,18 @@ async function main() {
|
|
|
63
64
|
return;
|
|
64
65
|
}
|
|
65
66
|
|
|
67
|
+
// Standalone migration: carry a legacy vibekit/ install forward, then stop.
|
|
68
|
+
if (args.migrate) {
|
|
69
|
+
const { report } = await migrateLegacy(target, { dryRun: args.dryRun });
|
|
70
|
+
console.log(report.length ? '\n' + report.join('\n') + '\n' : '\nℹ️ no legacy vibekit/ install found — nothing to migrate.\n');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Auto-migration: before ANYTHING reads contextkit/ (config, settings), carry a
|
|
75
|
+
// legacy vibekit/ install forward so `npx contextdevkit --update` just works.
|
|
76
|
+
const migration = await migrateLegacy(target, { dryRun: false });
|
|
77
|
+
if (migration.report.length) console.log('\n' + migration.report.join('\n') + '\n');
|
|
78
|
+
|
|
66
79
|
const interactive = !args.yes && process.stdout.isTTY;
|
|
67
80
|
let level = Number.isInteger(args.level) ? args.level : undefined;
|
|
68
81
|
let name = args.name;
|
|
@@ -146,7 +159,7 @@ async function main() {
|
|
|
146
159
|
|
|
147
160
|
// 5. Memory seeds: write only if missing. `.env.example` is seeded here so the
|
|
148
161
|
// user's edits survive re-install (ADR-0024 — media-gen credentials template).
|
|
149
|
-
for (const rel of ['memory/SESSIONS.md', 'memory/WORKSPACE.md', 'memory/GLOSSARY.md', 'memory/roadmap.md', 'memory/decisions/_TEMPLATE.md', 'memory/decisions/0000-record-architecture-decisions.md', 'memory/business-rules/_TEMPLATE.md', 'memory/predictions/.gitkeep', 'memory/sessions/.gitkeep', 'README.md', 'instrucoes.md', 'best-practices.md', 'behaviors.md', 'behaviors-examples.md', 'CLAUDE.child.md.tpl', 'squads/README.md', 'squads/_BRIEFING.md.tpl', '.env.example']) {
|
|
162
|
+
for (const rel of ['memory/SESSIONS.md', 'memory/WORKSPACE.md', 'memory/GLOSSARY.md', 'memory/roadmap.md', 'memory/decisions/_TEMPLATE.md', 'memory/decisions/0000-record-architecture-decisions.md', 'memory/business-rules/_TEMPLATE.md', 'memory/predictions/.gitkeep', 'memory/sessions/.gitkeep', 'README.md', 'instrucoes.md', 'best-practices.md', 'review-protocol.md', 'behaviors.md', 'behaviors-examples.md', 'CLAUDE.child.md.tpl', 'squads/README.md', 'squads/_BRIEFING.md.tpl', 'policy/complexity-rubric.json', '.env.example']) {
|
|
150
163
|
const src = join(TPL, 'contextkit', rel);
|
|
151
164
|
if (!existsSync(src)) continue;
|
|
152
165
|
const wrote = await writeIfMissing(join(target, 'contextkit', rel), await read(src), args.force);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "contextdevkit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "Portable, level-based AI-assisted development platform for Claude Code — hooks, slash commands, sub-agents, and durable project memory for any stack.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"install:here": "node install.mjs --target .",
|
|
11
11
|
"check": "node tools/selfcheck.mjs",
|
|
12
|
-
"test": "node tools/selfcheck.mjs && node tools/integration-test.mjs && node tools/integration-test-tooling.mjs && node tools/integration-test-tooling-pipeline.mjs && node tools/integration-test-tooling-agent-forge.mjs && node tools/integration-test-guards.mjs && node tools/integration-test-compozy.mjs",
|
|
13
|
-
"prepublishOnly": "node tools/selfcheck.mjs && node tools/integration-test.mjs && node tools/integration-test-tooling.mjs && node tools/integration-test-tooling-pipeline.mjs && node tools/integration-test-tooling-agent-forge.mjs && node tools/integration-test-guards.mjs && node tools/integration-test-compozy.mjs"
|
|
12
|
+
"test": "node tools/selfcheck.mjs && node tools/integration-test.mjs && node tools/integration-test-tooling.mjs && node tools/integration-test-tooling-pipeline.mjs && node tools/integration-test-tooling-agent-forge.mjs && node tools/integration-test-guards.mjs && node tools/integration-test-compozy.mjs && node tools/integration-test-migrate.mjs",
|
|
13
|
+
"prepublishOnly": "node tools/selfcheck.mjs && node tools/integration-test.mjs && node tools/integration-test-tooling.mjs && node tools/integration-test-tooling-pipeline.mjs && node tools/integration-test-tooling-agent-forge.mjs && node tools/integration-test-guards.mjs && node tools/integration-test-compozy.mjs && node tools/integration-test-migrate.mjs"
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
16
|
"install.mjs",
|
package/templates/CLAUDE.md.tpl
CHANGED
|
@@ -121,10 +121,11 @@ Setup: `/aidevtool-from0` (empty) · `/setupcontextdevkit` (existing). Daily: `/
|
|
|
121
121
|
· `/log-session` · `/new-adr` · `/close-version` · `/context-refresh` · `/dev-start`
|
|
122
122
|
· `/bug-hunt` · `/audit`. Multi-session: `/claim` · `/release` · `/worktree-new`.
|
|
123
123
|
Quality: `/simulate-impact` · `/tech-debt-sweep` · `/analyze-code-ia-practices`
|
|
124
|
-
· `/contract-check` · `/deps-audit` · `/deep-analysis` · `/test-plan` · `/scaffold-tests` · `/qa-signoff`. Product &
|
|
124
|
+
· `/contract-check` · `/deps-audit` · `/deep-analysis` · `/validate-doc` · `/test-plan` · `/scaffold-tests` · `/qa-signoff`. Product &
|
|
125
125
|
execution: `/roadmap` · `/pipeline` · `/ship` · `/retro` · `/context-stats`
|
|
126
126
|
· `/distill-sessions` · `/distill-apply`.
|
|
127
127
|
Structure & platform: `/squad` (squads) · `/git` (version control + remote)
|
|
128
|
+
· `/draft-changelog` · `/gh-triage` · `/changelog-social` (OSS repo-ops)
|
|
128
129
|
· `/claude-md` (scoped CLAUDE.md per module) · `/context-level` · `/context-config`
|
|
129
130
|
· `/context-doctor`.
|
|
130
131
|
|
|
@@ -32,7 +32,10 @@ templates/claude/commands/
|
|
|
32
32
|
│ ├── git.md
|
|
33
33
|
│ ├── claim.md
|
|
34
34
|
│ ├── release.md
|
|
35
|
-
│
|
|
35
|
+
│ ├── worktree-new.md
|
|
36
|
+
│ ├── draft-changelog.md ← commits → Keep-a-Changelog skeleton (ADR-0030)
|
|
37
|
+
│ ├── gh-triage.md ← GitHub issues → backlog, classified (ADR-0030)
|
|
38
|
+
│ └── changelog-social.md ← release → announcement copy, drafts only (ADR-0030)
|
|
36
39
|
│
|
|
37
40
|
├── forge/ ← agent-forge squad lifecycle
|
|
38
41
|
│ ├── forge-new.md
|
|
@@ -55,7 +58,8 @@ templates/claude/commands/
|
|
|
55
58
|
│ ├── tech-debt-sweep.md
|
|
56
59
|
│ ├── analyze-code-ia-practices.md
|
|
57
60
|
│ ├── contract-check.md
|
|
58
|
-
│
|
|
61
|
+
│ ├── seo-audit.md ← SEO + AISO static analysers (ADR-0025)
|
|
62
|
+
│ └── validate-doc.md ← ADR/roadmap quality rubric (ADR-0030)
|
|
59
63
|
│
|
|
60
64
|
└── setup/ ← installer + diagnostics
|
|
61
65
|
├── setupcontextdevkit.md
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Quality gate for our own planning artifacts (ADRs / roadmap) — measurability, trade-offs, no placeholders. Advisory, never blocks.
|
|
3
|
+
argument-hint: <path/to/doc.md> [--adr | --roadmap]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 📋 Validate a planning document
|
|
7
|
+
|
|
8
|
+
Run the document-quality rubric against **$ARGUMENTS** [ADR-0030]. This is the
|
|
9
|
+
prose sibling of `selfcheck`: where selfcheck validates *code wiring*, this
|
|
10
|
+
validates the *quality of the decision* an ADR or roadmap records — adapted from
|
|
11
|
+
EVO-METHOD/BMAD's `steps-v` validation chain (MIT).
|
|
12
|
+
|
|
13
|
+
1. Run the checker:
|
|
14
|
+
```
|
|
15
|
+
node contextkit/tools/scripts/validate-doc.mjs $ARGUMENTS
|
|
16
|
+
```
|
|
17
|
+
It auto-detects the rubric from the path (ADRs under `memory/decisions/`,
|
|
18
|
+
anything named `roadmap`), or force it with `--adr` / `--roadmap`.
|
|
19
|
+
|
|
20
|
+
2. **What the ADR rubric enforces:**
|
|
21
|
+
- Required sections present (**Context**, **Decision**, **Consequences**).
|
|
22
|
+
- A valid **Status** (Proposed / Accepted / Superseded).
|
|
23
|
+
- No leftover template placeholders (`NNNN`, `YYYY-MM-DD`, `<who>`, …).
|
|
24
|
+
- Context that states the *forces* without already giving the answer (depth).
|
|
25
|
+
- Consequences that own a **trade-off / risk** — a decision with only upsides
|
|
26
|
+
is under-examined.
|
|
27
|
+
- Follow-ups noted (what the decision obligates next).
|
|
28
|
+
|
|
29
|
+
3. **What the roadmap rubric enforces:** items read as **measurable** (a number,
|
|
30
|
+
date, or target), not aspirational.
|
|
31
|
+
|
|
32
|
+
4. **Act on the output.** Errors (❌) are real gaps — fix the artifact. Warnings
|
|
33
|
+
(⚠️) are smells — judge each. This command is **advisory**: it never blocks a
|
|
34
|
+
commit or push (constitution §8 — report honestly, don't gate silently).
|
|
35
|
+
|
|
36
|
+
> Tip: run it on a fresh ADR right after `/new-adr`, before you ask for the
|
|
37
|
+
> `Accepted` flip — it catches thin Context and missing trade-offs early.
|
|
@@ -28,8 +28,16 @@ Symptom under investigation:
|
|
|
28
28
|
6. **Only after root cause is confirmed**: propose the minimal fix, get approval, then implement.
|
|
29
29
|
Add a regression test if the stack supports it.
|
|
30
30
|
|
|
31
|
-
7. **Report + backlog.** Write a
|
|
32
|
-
|
|
31
|
+
7. **Report + backlog (RCA writeup).** Write a structured root-cause analysis —
|
|
32
|
+
not just a one-liner [ADR-0030]:
|
|
33
|
+
- **Symptom** — what was observed, with the exact error/repro.
|
|
34
|
+
- **Root cause** — the single underlying defect (the *why*, not the *where*).
|
|
35
|
+
- **Trigger** — what conditions surfaced it (why now / why here).
|
|
36
|
+
- **Fix** — the minimal change that removes the root cause.
|
|
37
|
+
- **Prevention** — the regression test + any guard (a selfcheck/lint rule) so
|
|
38
|
+
this class of bug can't silently return.
|
|
39
|
+
|
|
40
|
+
Record the bug — and any *related* issues you surfaced — in the
|
|
33
41
|
DevPipeline, point by point:
|
|
34
42
|
```
|
|
35
43
|
node contextkit/tools/scripts/pipeline.mjs add --type bug --priority <P0-P3> \
|
|
@@ -18,6 +18,12 @@ Package for **$ARGUMENTS** (or for the name the developer gives during the inter
|
|
|
18
18
|
- `packager` assembles + stamps provenance.
|
|
19
19
|
|
|
20
20
|
2. **Refuse to skip the interview.** Defaults are safe, not informed.
|
|
21
|
+
**Advanced elicitation** [ADR-0030]: don't just collect the answers to
|
|
22
|
+
`INTERVIEW_QUESTIONS` — probe the *unstated* edges that decide the package's
|
|
23
|
+
quality: the failure modes the agent must refuse, the cost ceiling, whether it
|
|
24
|
+
touches regulated data (run `complexity-rubric.mjs classify` on the role — an
|
|
25
|
+
LGPD/fintech domain pulls compliance requirements into the blueprint), and the
|
|
26
|
+
single hardest input it will face. Reflect assumptions back before the router runs.
|
|
21
27
|
|
|
22
28
|
3. **Verify before writing** — show the dev:
|
|
23
29
|
- The Agent Blueprint (YAML).
|
|
@@ -27,35 +27,50 @@ You just entered **dev-start** mode with the objective:
|
|
|
27
27
|
immutable rules + open backlog + recent ADRs) in a single call instead of
|
|
28
28
|
opening each file. Open a full source only if the pack flags something to inspect.
|
|
29
29
|
|
|
30
|
-
3. **
|
|
30
|
+
3. **Right-size the work** [ADR-0030]. Classify the objective before committing
|
|
31
|
+
to a process — don't over-engineer a typo or under-plan a migration:
|
|
32
|
+
```
|
|
33
|
+
node contextkit/tools/scripts/complexity-rubric.mjs classify "$ARGUMENTS"
|
|
34
|
+
```
|
|
35
|
+
It returns a **tier** (trivial → no ADR/no story · feature → story · architectural
|
|
36
|
+
→ `/new-adr` FIRST) and detects a **regulated domain**. If it flags a domain
|
|
37
|
+
(LGPD / fintech / healthcare …), **auto-route to the named agents** (e.g.
|
|
38
|
+
`@privacy-lgpd` + `@security`) and treat the work as architectural. The tier is
|
|
39
|
+
advisory, not a cage — state it and adjust with the user if it misreads.
|
|
40
|
+
|
|
41
|
+
4. **Define IN-SCOPE / OUT-OF-SCOPE explicitly** from the objective. Show the user:
|
|
31
42
|
```
|
|
32
43
|
✅ IN-SCOPE: <what we will touch>
|
|
33
44
|
❌ OUT-OF-SCOPE: <what we will NOT touch, even if tempting>
|
|
34
45
|
```
|
|
35
46
|
Ask for confirmation before proceeding if there is ambiguity.
|
|
36
47
|
|
|
37
|
-
|
|
48
|
+
5. **Scope lock during the session**:
|
|
38
49
|
- Do NOT suggest refactors in files outside IN-SCOPE.
|
|
39
50
|
- Do NOT "while we're here" rename/reorganize adjacent code.
|
|
40
51
|
- Do NOT add new dependencies without asking.
|
|
41
52
|
- If you spot a problem out of scope, **note it** and mention it at the END
|
|
42
53
|
("for next session: X, Y, Z") — do not act on it now.
|
|
54
|
+
- **Correct-course checkpoint** [ADR-0030]: if mid-session the work clearly
|
|
55
|
+
outgrows the agreed scope (a feature turns out to need a migration, a new
|
|
56
|
+
dependency, or an auth change), STOP and re-classify with the user before
|
|
57
|
+
continuing — don't silently let scope creep past the tier you agreed on.
|
|
43
58
|
|
|
44
|
-
|
|
59
|
+
6. **Break the objective into 3–7 concrete tasks** and track them with TodoWrite.
|
|
45
60
|
|
|
46
|
-
|
|
61
|
+
7. **Per-task scratch (optional)**: if you accumulate ephemeral notes while a
|
|
47
62
|
ticket is in `contextkit/pipeline/testing/`, drop them in a sibling file named
|
|
48
63
|
`NNN-*.scratch.md` next to the ticket. The pipeline's `.gitignore` excludes
|
|
49
64
|
`*.scratch.md` — scratches are local-only. At conclude time, summarise the
|
|
50
65
|
useful parts into the ticket body and let the scratch be discarded.
|
|
51
66
|
|
|
52
|
-
|
|
67
|
+
8. **Before opening a PR — re-check sync** [ADR-0026]. Run
|
|
53
68
|
`node contextkit/tools/scripts/sync-check.mjs prepr` (or just use `/git pr`, which
|
|
54
69
|
runs it): it re-confirms you are not behind `main` and that **no open PR
|
|
55
70
|
already exists for this branch** before you create one. Don't duplicate a PR;
|
|
56
71
|
push to update the existing one.
|
|
57
72
|
|
|
58
|
-
|
|
73
|
+
9. **At the end**: offer `/log-session` (or `/new-adr` if an architectural decision was made).
|
|
59
74
|
|
|
60
75
|
## Why this mode exists
|
|
61
76
|
|
|
@@ -23,7 +23,11 @@ Act as the **manager** of this board based on **$ARGUMENTS**:
|
|
|
23
23
|
--priority <P0-P3> --title "..." [--sla YYYY-MM-DD] [--roadmap P2.3]
|
|
24
24
|
```
|
|
25
25
|
Then open the new file in `contextkit/pipeline/backlog/` and fill the context +
|
|
26
|
-
acceptance criteria.
|
|
26
|
+
acceptance criteria. **Right-size first** [ADR-0030]:
|
|
27
|
+
`node contextkit/tools/scripts/complexity-rubric.mjs classify "<title>"` — an
|
|
28
|
+
architectural tier means the task should reference (or trigger) an ADR; a
|
|
29
|
+
regulated domain means tagging the owning agents (`@privacy-lgpd`/`@security`)
|
|
30
|
+
in the acceptance criteria.
|
|
27
31
|
- **move** — `node contextkit/tools/scripts/pipeline.mjs move <id> <backlog|testing|conclusion>`
|
|
28
32
|
as work progresses (testing when you start; conclusion when accepted). For a
|
|
29
33
|
concluded task, add a short outcome report to its file.
|
|
@@ -30,7 +30,12 @@ State which mode you're running at the start.
|
|
|
30
30
|
digest + immutable rules + recent ADRs in one call) and
|
|
31
31
|
`node contextkit/tools/scripts/adr-digest.mjs --search "<objective keywords>"` for the
|
|
32
32
|
ADRs relevant to the objective [ADR-0027] — open a full ADR only when needed.
|
|
33
|
-
|
|
33
|
+
Then **right-size the pipeline** [ADR-0030]:
|
|
34
|
+
`node contextkit/tools/scripts/complexity-rubric.mjs classify "$ARGUMENTS"`. A
|
|
35
|
+
**regulated domain** (LGPD / fintech / healthcare) makes the design + review
|
|
36
|
+
stages MANDATORY and pulls the named agents (e.g. `@privacy-lgpd`, `@security`)
|
|
37
|
+
into the squad; an **architectural** tier means the ADR in step 8 is required,
|
|
38
|
+
not optional. Restate the objective; define IN/OUT-OF-SCOPE (as `/dev-start`).
|
|
34
39
|
2. **Design** — delegate to `architect`: options, trade-offs, recommended path,
|
|
35
40
|
blast radius. If it crosses high-risk paths (L5), run `/simulate-impact` first.
|
|
36
41
|
◆ Checkpoint: confirm the design with the user.
|
|
@@ -25,8 +25,17 @@ Co-create it, don't dump a template. Ask, in a short adaptive round:
|
|
|
25
25
|
- the product vision + primary user journey (reuse `contextkit/memory/product.md` if present);
|
|
26
26
|
- the few **outcomes** that matter first, and roughly in what order;
|
|
27
27
|
- any hard deadlines/constraints.
|
|
28
|
+
|
|
29
|
+
**Advanced elicitation** [ADR-0030] — before drafting, surface what the user left
|
|
30
|
+
*unstated*: the assumed target user, the success metric, the riskiest assumption,
|
|
31
|
+
and what is explicitly OUT of scope for v1. Reflect these back as "here's what I'm
|
|
32
|
+
assuming — correct me" rather than guessing silently. A wrong assumption caught
|
|
33
|
+
here is worth ten reworked milestones.
|
|
34
|
+
|
|
28
35
|
Then draft phases `P1, P2, …` with items `P1.1, P1.2, …` — each a user-facing
|
|
29
|
-
capability + a one-line acceptance note
|
|
36
|
+
capability + a one-line **measurable** acceptance note (a number/date/target, not
|
|
37
|
+
an aspiration — it's what `/validate-doc --roadmap` checks). Show it, refine with
|
|
38
|
+
the user, save.
|
|
30
39
|
|
|
31
40
|
## from-existing — existing project
|
|
32
41
|
1. If `roadmap.mjs find` listed a roadmap/PRD/spec file → read it and **import /
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Turn a finished CHANGELOG release into announcement copy (release notes + short social posts). Drafts only — never posts.
|
|
3
|
+
argument-hint: [version | --unreleased]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 📣 Changelog → announcement copy
|
|
7
|
+
|
|
8
|
+
Turn a shipped release into human announcement copy [ADR-0030, OSS repo-ops].
|
|
9
|
+
This **drafts text only** — it never posts anywhere. Publishing is always a
|
|
10
|
+
deliberate human action (sending content to an external service is irreversible).
|
|
11
|
+
|
|
12
|
+
1. **Read the source.** Pull the target section from `docs/CHANGELOG.md` — a cut
|
|
13
|
+
version (`[X.Y.Z]`) or `[Unreleased]` if `--unreleased`. If you need the raw
|
|
14
|
+
commit context, run `node contextkit/tools/scripts/draft-changelog.mjs`.
|
|
15
|
+
|
|
16
|
+
2. **Draft three artifacts**, in order of shrinking length:
|
|
17
|
+
- **Release notes** (GitHub Releases / blog): a one-paragraph "why this matters",
|
|
18
|
+
then the highlights grouped by theme, then breaking changes + migration, then
|
|
19
|
+
a thank-you. Lead with user value, not commit nouns.
|
|
20
|
+
- **Short post** (~280 chars, X/Mastodon/LinkedIn): the single biggest win +
|
|
21
|
+
the version + a link placeholder `<release-url>`. No hashtag spam.
|
|
22
|
+
- **One-liner** (changelog RSS / Discord): `vX.Y.Z — <the headline change>`.
|
|
23
|
+
|
|
24
|
+
3. **Tone rules.** Concrete over hype. Name the user benefit, not the internal
|
|
25
|
+
refactor. No invented metrics, no "revolutionary". If the release is mostly
|
|
26
|
+
chores, say "maintenance release" honestly rather than inflating it.
|
|
27
|
+
|
|
28
|
+
4. **Hand back the drafts** for the user to review and post. Offer to save them to
|
|
29
|
+
a scratch file, but **do not publish** — no `gh release create`, no API calls,
|
|
30
|
+
unless the user explicitly asks in a follow-up.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Draft a [Unreleased] CHANGELOG skeleton from Conventional Commits since the last tag. Drafts only — never writes the file.
|
|
3
|
+
argument-hint: [--since <tag>]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 📝 Draft the changelog
|
|
7
|
+
|
|
8
|
+
Build a `[Unreleased]` skeleton from the commits since the last release [ADR-0030].
|
|
9
|
+
|
|
10
|
+
1. Run the drafter:
|
|
11
|
+
```
|
|
12
|
+
node contextkit/tools/scripts/draft-changelog.mjs $ARGUMENTS
|
|
13
|
+
```
|
|
14
|
+
It reads `git log <lastTag>..HEAD`, parses Conventional Commit subjects, and
|
|
15
|
+
groups them into Keep-a-Changelog sections (Added / Changed / Fixed / Removed /
|
|
16
|
+
Security / Documentation / Chores). `--since <tag>` overrides the range.
|
|
17
|
+
|
|
18
|
+
2. **Review and humanise.** The output is a *skeleton*, not the final entry:
|
|
19
|
+
- Merge duplicate/noisy lines; drop pure-chore churn the reader won't care about.
|
|
20
|
+
- Promote any ⚠️ BREAKING change to the top and explain the migration.
|
|
21
|
+
- Reference the relevant ADR(s) where a line records a decision.
|
|
22
|
+
|
|
23
|
+
3. **Paste into `docs/CHANGELOG.md`** under `[Unreleased]` yourself. This command
|
|
24
|
+
**never writes the file** — drafting is automated, the editorial call is yours.
|
|
25
|
+
To cut the version when ready, use `/close-version`.
|
|
26
|
+
|
|
27
|
+
> Pairs with `/changelog-social` (turn the finished entry into announcement copy)
|
|
28
|
+
> and `/close-version` (stamp `[Unreleased]` → `[X.Y.Z]` and tag).
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Triage open GitHub issues into the DevPipeline backlog — classify, prioritize, dedupe. Read-from-GitHub, write-to-backlog.
|
|
3
|
+
argument-hint: [issue number | --all-open]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 🗂️ GitHub issue triage
|
|
7
|
+
|
|
8
|
+
Turn unstructured GitHub issues into actionable, prioritized backlog tasks
|
|
9
|
+
[ADR-0030, OSS repo-ops]. This reads from GitHub and writes to the local
|
|
10
|
+
DevPipeline — it does **not** close, label, or comment on issues without your OK.
|
|
11
|
+
|
|
12
|
+
1. **Fetch.** Requires `gh` (the existing review provider — [ADR-0021]). If it's
|
|
13
|
+
missing or unauthed, say so and stop (rule 8 — skip, never fake):
|
|
14
|
+
```
|
|
15
|
+
gh issue list --state open --json number,title,body,labels,createdAt,author
|
|
16
|
+
```
|
|
17
|
+
For a single issue: `gh issue view <number> --json number,title,body,labels`.
|
|
18
|
+
|
|
19
|
+
2. **Classify each issue** with the complexity rubric [ADR-0030]:
|
|
20
|
+
```
|
|
21
|
+
node contextkit/tools/scripts/complexity-rubric.mjs classify "<issue title + gist>"
|
|
22
|
+
```
|
|
23
|
+
- **Type** — bug / feature / chore / docs (from the body + labels).
|
|
24
|
+
- **Tier & domain** — architectural tier ⇒ flag that an ADR is needed; a
|
|
25
|
+
regulated domain (LGPD / fintech / …) ⇒ tag `@privacy-lgpd` / `@security`.
|
|
26
|
+
- **Priority** — P0 data-loss/security/broken build · P1 broken core path · P2
|
|
27
|
+
degraded · P3 cosmetic.
|
|
28
|
+
|
|
29
|
+
3. **Dedupe.** Before adding, scan the existing board
|
|
30
|
+
(`node contextkit/tools/scripts/pipeline.mjs sync` then read the backlog) so you
|
|
31
|
+
don't create a duplicate of a task already tracked. Note the link instead.
|
|
32
|
+
|
|
33
|
+
4. **Add to the backlog**, one per issue, cross-referencing the issue number:
|
|
34
|
+
```
|
|
35
|
+
node contextkit/tools/scripts/pipeline.mjs add --type <bug|feature|chore> \
|
|
36
|
+
--priority <P0-P3> --source "gh#<number>" --title "<concise title>"
|
|
37
|
+
```
|
|
38
|
+
Fill each new file's context + acceptance criteria from the issue body.
|
|
39
|
+
|
|
40
|
+
5. **Report.** Summarise: triaged N issues → M new tasks (K duplicates skipped),
|
|
41
|
+
the priority spread, and any issue that needs a human decision before triage.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"_note": "Per-task complexity rubric (ADR-0030, adapted from EVO-METHOD/BMAD domain-complexity + project-types CSVs, MIT). Deterministic lookup — NOT LLM judgment. `tiers` right-size the ceremony per task; `domains` auto-route regulated work to the owning agents and force the architectural tier. Editing a row is an ADR act (constitution §9) — it is reviewable policy, not data. Levels (L1-L7) and tiers are ORTHOGONAL: a level is the platform ceiling, a tier is the per-task ceremony.",
|
|
4
|
+
"tiers": {
|
|
5
|
+
"trivial": {
|
|
6
|
+
"signals": ["typo", "rename", "comment", "bump version", "bump dep", "lint", "format", "whitespace", "changelog entry", "fix link", "docstring"],
|
|
7
|
+
"ceremony": { "adr": false, "story": false, "review": "light" },
|
|
8
|
+
"summary": "One cohesive low-risk edit. No ADR, no story — just do it and log if it touched an important path."
|
|
9
|
+
},
|
|
10
|
+
"feature": {
|
|
11
|
+
"signals": ["add", "feature", "endpoint", "component", "screen", "page", "command", "field", "report", "export"],
|
|
12
|
+
"ceremony": { "adr": false, "story": true, "review": "standard" },
|
|
13
|
+
"summary": "A user-visible increment. Roadmap entry + story + standard review. No ADR unless a structural decision surfaces."
|
|
14
|
+
},
|
|
15
|
+
"architectural": {
|
|
16
|
+
"signals": ["migrate", "migration", "new dependency", "add dependency", "auth", "authentication", "authorization", "schema", "breaking", "rewrite", "refactor architecture", "replace", "protocol", "data model", "multi-tenant", "encryption", "rename public", "deprecate"],
|
|
17
|
+
"ceremony": { "adr": true, "story": true, "review": "deep" },
|
|
18
|
+
"summary": "A decision with blast radius. /new-adr FIRST, then a story, then deep review. /simulate-impact if it crosses a high-risk path."
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"defaultTier": "feature",
|
|
22
|
+
"domains": {
|
|
23
|
+
"lgpd": {
|
|
24
|
+
"signals": ["cpf", "cnpj", "rg", "dados pessoais", "personal data", "dado pessoal", "lgpd", "consent", "consentimento", "titular", "data subject", "pii", "anonymiz", "anonimiz", "encarregado", "dpo"],
|
|
25
|
+
"complexity": "high",
|
|
26
|
+
"requiredAgents": ["privacy-lgpd", "security"],
|
|
27
|
+
"requiredSections": ["data-inventory", "legal-basis", "retention", "data-subject-rights"],
|
|
28
|
+
"note": "Brazilian personal-data handling (Lei 13.709/2018). High-risk by default."
|
|
29
|
+
},
|
|
30
|
+
"fintech": {
|
|
31
|
+
"signals": ["payment", "pagamento", "banking", "trading", "investment", "crypto", "wallet", "transaction", "kyc", "aml", "pci", "pix", "boleto", "charge", "subscription billing"],
|
|
32
|
+
"complexity": "high",
|
|
33
|
+
"requiredAgents": ["security", "privacy-lgpd"],
|
|
34
|
+
"requiredSections": ["compliance-matrix", "security-architecture", "audit-trail", "fraud-prevention"],
|
|
35
|
+
"note": "Money movement / financial data. Regional compliance + audit trail are load-bearing."
|
|
36
|
+
},
|
|
37
|
+
"healthcare": {
|
|
38
|
+
"signals": ["medical", "clinical", "patient", "paciente", "diagnostic", "diagnostico", "hipaa", "prontuario", "health record", "treatment", "therapy"],
|
|
39
|
+
"complexity": "high",
|
|
40
|
+
"requiredAgents": ["privacy-lgpd", "security"],
|
|
41
|
+
"requiredSections": ["clinical-requirements", "data-privacy", "safety-measures", "regulatory-pathway"],
|
|
42
|
+
"note": "Health data is sensitive personal data — privacy + safety are non-negotiable."
|
|
43
|
+
},
|
|
44
|
+
"general": {
|
|
45
|
+
"signals": [],
|
|
46
|
+
"complexity": "low",
|
|
47
|
+
"requiredAgents": [],
|
|
48
|
+
"requiredSections": [],
|
|
49
|
+
"note": "No regulated domain detected. Standard practices apply."
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -88,5 +88,7 @@ export function pathsFor(root = process.cwd()) {
|
|
|
88
88
|
roadmap: at(`${MEMORY_DIR}/roadmap.md`),
|
|
89
89
|
contractBaseline: at(`${MEMORY_DIR}/contract-baseline.json`),
|
|
90
90
|
bestPractices: at(`${PLATFORM_DIR}/best-practices.md`),
|
|
91
|
+
policy: at(`${PLATFORM_DIR}/policy`),
|
|
92
|
+
complexityRubric: at(`${PLATFORM_DIR}/policy/complexity-rubric.json`),
|
|
91
93
|
};
|
|
92
94
|
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `/complexity` — per-task complexity classifier (ADR-0030).
|
|
4
|
+
*
|
|
5
|
+
* Deterministic lookup over `contextkit/policy/complexity-rubric.json`: classifies
|
|
6
|
+
* a task description into a CEREMONY TIER (trivial / feature / architectural) and
|
|
7
|
+
* detects a REGULATED DOMAIN (lgpd / fintech / healthcare / …) that auto-routes to
|
|
8
|
+
* the owning agents and forces the architectural tier. This is NOT LLM judgment —
|
|
9
|
+
* it is a signal-table match, so `/dev-start`, `/pipeline` and `/ship` can right-size
|
|
10
|
+
* the process the same way every time.
|
|
11
|
+
*
|
|
12
|
+
* Zero runtime deps (rule 1): JSON.parse + string matching only. Never throws —
|
|
13
|
+
* a missing/malformed rubric falls back to the embedded DEFAULT_RUBRIC so the
|
|
14
|
+
* classifier still works in a project that seeded nothing (mirrors load.mjs).
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* node contextkit/tools/scripts/complexity-rubric.mjs classify "store user CPF + consent"
|
|
18
|
+
* node contextkit/tools/scripts/complexity-rubric.mjs classify "fix typo" --json
|
|
19
|
+
* node contextkit/tools/scripts/complexity-rubric.mjs show
|
|
20
|
+
*/
|
|
21
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
22
|
+
import { pathsFor } from '../../runtime/config/paths.mjs';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Embedded fallback — kept deliberately minimal (lgpd + general + the three
|
|
26
|
+
* tiers). The shipped seed `policy/complexity-rubric.json` is the real rubric;
|
|
27
|
+
* this only guarantees classification never crashes when the seed is absent.
|
|
28
|
+
*/
|
|
29
|
+
const DEFAULT_RUBRIC = Object.freeze({
|
|
30
|
+
version: 1,
|
|
31
|
+
tiers: {
|
|
32
|
+
trivial: { signals: ['typo', 'rename', 'comment', 'bump version', 'lint', 'format'], ceremony: { adr: false, story: false, review: 'light' } },
|
|
33
|
+
feature: { signals: ['add', 'feature', 'endpoint', 'component', 'screen', 'command'], ceremony: { adr: false, story: true, review: 'standard' } },
|
|
34
|
+
architectural: { signals: ['migrate', 'new dependency', 'auth', 'schema', 'breaking', 'rewrite', 'encryption'], ceremony: { adr: true, story: true, review: 'deep' } },
|
|
35
|
+
},
|
|
36
|
+
defaultTier: 'feature',
|
|
37
|
+
domains: {
|
|
38
|
+
lgpd: { signals: ['cpf', 'dados pessoais', 'personal data', 'lgpd', 'consent', 'pii'], complexity: 'high', requiredAgents: ['privacy-lgpd', 'security'], requiredSections: ['data-inventory', 'legal-basis', 'retention', 'data-subject-rights'] },
|
|
39
|
+
general: { signals: [], complexity: 'low', requiredAgents: [], requiredSections: [] },
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const COMPLEXITY_RANK = { low: 0, medium: 1, high: 2 };
|
|
44
|
+
|
|
45
|
+
/** Reads the rubric for `root`, falling back to DEFAULT_RUBRIC on any failure. Never throws. */
|
|
46
|
+
export function loadRubric(root = process.cwd()) {
|
|
47
|
+
const path = pathsFor(root).complexityRubric;
|
|
48
|
+
if (!existsSync(path)) return structuredClone(DEFAULT_RUBRIC);
|
|
49
|
+
try {
|
|
50
|
+
const parsed = JSON.parse(readFileSync(path, 'utf-8').replace(/^/, ''));
|
|
51
|
+
return parsed && parsed.tiers && parsed.domains ? parsed : structuredClone(DEFAULT_RUBRIC);
|
|
52
|
+
} catch {
|
|
53
|
+
return structuredClone(DEFAULT_RUBRIC);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** True when any signal occurs as a substring of the lowercased text. */
|
|
58
|
+
function hasAny(text, signals) {
|
|
59
|
+
return Array.isArray(signals) && signals.some((s) => s && text.includes(String(s).toLowerCase()));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Pure classifier. Tier precedence is architectural → feature → trivial → default
|
|
64
|
+
* (higher ceremony wins on overlap — constitution §8, refuse-low by default). A
|
|
65
|
+
* high-complexity domain match FORCES the architectural tier.
|
|
66
|
+
*
|
|
67
|
+
* @param {string} input task description
|
|
68
|
+
* @param {object} [rubric] loaded rubric (defaults to the embedded fallback)
|
|
69
|
+
* @returns {object} classification with tier, domain, requiredAgents, needsAdr…
|
|
70
|
+
*/
|
|
71
|
+
export function classify(input, rubric = DEFAULT_RUBRIC) {
|
|
72
|
+
const text = String(input || '').toLowerCase();
|
|
73
|
+
const tiers = rubric.tiers || {};
|
|
74
|
+
|
|
75
|
+
let tier = rubric.defaultTier || 'feature';
|
|
76
|
+
if (hasAny(text, tiers.architectural?.signals)) tier = 'architectural';
|
|
77
|
+
else if (hasAny(text, tiers.feature?.signals)) tier = 'feature';
|
|
78
|
+
else if (hasAny(text, tiers.trivial?.signals)) tier = 'trivial';
|
|
79
|
+
|
|
80
|
+
// Domain detection — collect every regulated domain whose signals appear.
|
|
81
|
+
const matched = [];
|
|
82
|
+
for (const [name, def] of Object.entries(rubric.domains || {})) {
|
|
83
|
+
if (name === 'general') continue;
|
|
84
|
+
const hits = (def.signals || []).filter((s) => text.includes(String(s).toLowerCase()));
|
|
85
|
+
if (hits.length) matched.push({ name, hits: hits.length, def });
|
|
86
|
+
}
|
|
87
|
+
matched.sort((a, b) => b.hits - a.hits);
|
|
88
|
+
|
|
89
|
+
const domain = matched[0]?.name || 'general';
|
|
90
|
+
const complexity = matched.reduce(
|
|
91
|
+
(acc, m) => (COMPLEXITY_RANK[m.def.complexity] > COMPLEXITY_RANK[acc] ? m.def.complexity : acc),
|
|
92
|
+
matched.length ? 'low' : (rubric.domains?.general?.complexity || 'low'),
|
|
93
|
+
);
|
|
94
|
+
const requiredAgents = [...new Set(matched.flatMap((m) => m.def.requiredAgents || []))];
|
|
95
|
+
const requiredSections = [...new Set(matched.flatMap((m) => m.def.requiredSections || []))];
|
|
96
|
+
|
|
97
|
+
// A regulated (high-complexity) domain forces the architectural tier.
|
|
98
|
+
const forcedByDomain = complexity === 'high' && tier !== 'architectural';
|
|
99
|
+
if (forcedByDomain) tier = 'architectural';
|
|
100
|
+
|
|
101
|
+
const ceremony = tiers[tier]?.ceremony || { adr: tier === 'architectural', story: tier !== 'trivial', review: 'standard' };
|
|
102
|
+
const needsAdr = tier === 'architectural' || ceremony.adr === true;
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
input: String(input || ''),
|
|
106
|
+
tier,
|
|
107
|
+
ceremony,
|
|
108
|
+
domain,
|
|
109
|
+
domains: matched.map((m) => m.name),
|
|
110
|
+
complexity,
|
|
111
|
+
requiredAgents,
|
|
112
|
+
requiredSections,
|
|
113
|
+
needsAdr,
|
|
114
|
+
forcedByDomain,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Human-readable report. */
|
|
119
|
+
function formatHuman(r) {
|
|
120
|
+
const lines = [];
|
|
121
|
+
lines.push(`🧭 Complexity classification`);
|
|
122
|
+
lines.push('─'.repeat(56));
|
|
123
|
+
lines.push(` Task : ${r.input || '(empty)'}`);
|
|
124
|
+
lines.push(` Tier : ${r.tier}${r.forcedByDomain ? ' (forced by regulated domain)' : ''}`);
|
|
125
|
+
lines.push(` Ceremony : ADR=${r.ceremony.adr ? 'yes' : 'no'} · story=${r.ceremony.story ? 'yes' : 'no'} · review=${r.ceremony.review}`);
|
|
126
|
+
lines.push(` Domain : ${r.domain}${r.domains.length > 1 ? ` (+ ${r.domains.slice(1).join(', ')})` : ''} · complexity=${r.complexity}`);
|
|
127
|
+
if (r.requiredAgents.length) lines.push(` Auto-route : ${r.requiredAgents.map((a) => `@${a}`).join(', ')}`);
|
|
128
|
+
if (r.requiredSections.length) lines.push(` Sections : ${r.requiredSections.join(', ')}`);
|
|
129
|
+
lines.push('');
|
|
130
|
+
if (r.needsAdr) lines.push(' ⚠️ Run /new-adr BEFORE implementing (architectural tier).');
|
|
131
|
+
if (r.requiredAgents.length) lines.push(` ⚠️ Regulated domain — bring in ${r.requiredAgents.map((a) => `@${a}`).join(' + ')} for this work.`);
|
|
132
|
+
if (!r.needsAdr && !r.requiredAgents.length) lines.push(' ✅ No special gate — proceed at the stated ceremony.');
|
|
133
|
+
return lines.join('\n');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function main() {
|
|
137
|
+
const argv = process.argv.slice(2);
|
|
138
|
+
const wantJson = argv.includes('--json');
|
|
139
|
+
const cmd = argv[0] === 'classify' || argv[0] === 'show' ? argv[0] : 'classify';
|
|
140
|
+
const rubric = loadRubric(process.cwd());
|
|
141
|
+
|
|
142
|
+
if (cmd === 'show') {
|
|
143
|
+
const summary = {
|
|
144
|
+
version: rubric.version,
|
|
145
|
+
tiers: Object.keys(rubric.tiers || {}),
|
|
146
|
+
domains: Object.keys(rubric.domains || {}),
|
|
147
|
+
};
|
|
148
|
+
console.log(wantJson ? JSON.stringify(summary, null, 2) : `Tiers: ${summary.tiers.join(', ')}\nDomains: ${summary.domains.join(', ')}`);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const text = argv.filter((a) => a !== 'classify' && a !== '--json').join(' ').trim();
|
|
153
|
+
if (!text) {
|
|
154
|
+
console.error('Usage: complexity-rubric.mjs classify "<task description>" [--json]');
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
const result = classify(text, rubric);
|
|
158
|
+
console.log(wantJson ? JSON.stringify(result, null, 2) : formatHuman(result));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Only run the CLI when invoked directly (not when imported by selfcheck/tests).
|
|
162
|
+
if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith('complexity-rubric.mjs')) {
|
|
163
|
+
main();
|
|
164
|
+
}
|