@aragon-agent/cli 0.6.17 → 0.6.19
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 +121 -51
- package/README.md +48 -2
- package/dist/agent/controller.js +13 -0
- package/dist/agent/headless.js +7 -0
- package/dist/commands/builtins.js +18 -3
- package/dist/commands/index-build.d.ts +39 -0
- package/dist/commands/index-build.js +61 -0
- package/dist/config/cli-commands.d.ts +1 -1
- package/dist/config/cli-commands.js +3 -0
- package/dist/config/schema.d.ts +24 -4
- package/dist/config/schema.js +4 -0
- package/dist/exec/events.d.ts +4 -1
- package/dist/exec/runner.js +13 -0
- package/dist/input/csiu-keys.js +13 -2
- package/dist/input/limits.d.ts +15 -0
- package/dist/input/limits.js +15 -0
- package/dist/input/win32-input-mode.js +19 -1
- package/dist/logging/install.d.ts +9 -0
- package/dist/logging/install.js +11 -0
- package/dist/team/human-queue.d.ts +11 -0
- package/dist/team/human-queue.js +18 -1
- package/dist/team/limits.d.ts +40 -0
- package/dist/team/limits.js +41 -0
- package/dist/team/overseer.d.ts +157 -0
- package/dist/team/overseer.js +348 -0
- package/dist/team/report.js +26 -1
- package/dist/team/runtime.d.ts +71 -6
- package/dist/team/runtime.js +380 -23
- package/dist/team/subagent.d.ts +30 -1
- package/dist/team/subagent.js +8 -1
- package/dist/team/task-tool.js +4 -1
- package/dist/team/types.d.ts +63 -0
- package/dist/ui/App.d.ts +1 -1
- package/dist/ui/App.js +61 -0
- package/dist/ui/Header.js +13 -2
- package/dist/ui/PromptInput.js +11 -1
- package/dist/ui/overlays/HelpOverlay.js +1 -0
- package/package.json +2 -2
- package/skills/project-indexer/SKILL.md +163 -0
- package/skills/project-indexer/templates/cleancode-overrides.md +46 -0
- package/skills/project-indexer/templates/cleancode-template.md +94 -0
- package/skills/project-indexer/templates/config-template.md +69 -0
- package/skills/project-indexer/templates/index-template.md +129 -0
package/dist/team/types.d.ts
CHANGED
|
@@ -148,6 +148,57 @@ export interface SubagentRun {
|
|
|
148
148
|
attempt: number;
|
|
149
149
|
maxRetries: number;
|
|
150
150
|
};
|
|
151
|
+
/**
|
|
152
|
+
* Supervisor inspections this child received (team-overseer). Optional so
|
|
153
|
+
* every existing fixture compiles; absent and `0` read the same.
|
|
154
|
+
*/
|
|
155
|
+
interventions?: number;
|
|
156
|
+
/**
|
|
157
|
+
* Supervisor-driven rebuilds this child went through (team-overseer). The
|
|
158
|
+
* run object survives a rebuild with the same label, so this is where the
|
|
159
|
+
* report reads "this row is the Nth body of the same brief".
|
|
160
|
+
*/
|
|
161
|
+
replacements?: number;
|
|
162
|
+
}
|
|
163
|
+
/** What the supervisor may do with one inspected child. */
|
|
164
|
+
export type OverseerAction = 'wait' | 'nudge' | 'replace' | 'abandon';
|
|
165
|
+
/**
|
|
166
|
+
* One supervisor decision, already normalized and clamped.
|
|
167
|
+
*
|
|
168
|
+
* Produced ONLY by `normalizeOverseerDecision` (repair, never reject): an
|
|
169
|
+
* unparseable or out-of-budget model answer becomes `wait` with the default
|
|
170
|
+
* interval, because supervision must not kill a healthy child.
|
|
171
|
+
*/
|
|
172
|
+
export interface OverseerDecision {
|
|
173
|
+
action: OverseerAction;
|
|
174
|
+
/** <= TEAM_LIMITS.overseerReasonChars. Why, for the report and the log. */
|
|
175
|
+
reason: string;
|
|
176
|
+
/** `nudge` only: <= TEAM_LIMITS.overseerGuidanceChars. Steered into the child. */
|
|
177
|
+
guidance?: string;
|
|
178
|
+
/** Model-chosen next check, clamped to the structural range. */
|
|
179
|
+
nextCheckMs?: number;
|
|
180
|
+
}
|
|
181
|
+
/** One supervisor intervention, as recorded on the outcome and rendered. */
|
|
182
|
+
export interface OverseerIntervention {
|
|
183
|
+
label: string;
|
|
184
|
+
at: number;
|
|
185
|
+
action: OverseerAction;
|
|
186
|
+
reason: string;
|
|
187
|
+
/** What the trigger was: event stall, wall-clock check, or post-mortem. */
|
|
188
|
+
trigger: 'silence' | 'clock' | 'postmortem';
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* A terminal supervisor decision handed to the WORKER LOOP, which is the only
|
|
192
|
+
* place a child's `prompt()` is awaited and therefore the only place a
|
|
193
|
+
* lifecycle change can be applied without orphaning the replacement (I-OV1).
|
|
194
|
+
*
|
|
195
|
+
* Set by `TeamRuntime` on the handle, then `handle.abort()`; consumed and
|
|
196
|
+
* left in place (never cleared) by `runOne` after the await resolves.
|
|
197
|
+
*/
|
|
198
|
+
export interface OverseerVerdict {
|
|
199
|
+
action: 'replace' | 'abandon';
|
|
200
|
+
reason: string;
|
|
201
|
+
guidance?: string;
|
|
151
202
|
}
|
|
152
203
|
/** One message on the team bus. */
|
|
153
204
|
export interface TeamMessage {
|
|
@@ -168,6 +219,12 @@ export interface DispatchOutcome {
|
|
|
168
219
|
aborted: boolean;
|
|
169
220
|
/** Messages addressed to `lead`; surfaced in their own report section. */
|
|
170
221
|
leadMail: TeamMessage[];
|
|
222
|
+
/**
|
|
223
|
+
* Every supervisor decision that was applied (team-overseer), in order.
|
|
224
|
+
* Bounded by the per-child inspection budget; rendered as the report's
|
|
225
|
+
* Overseer section so the lead can see WHO was nudged or replaced and why.
|
|
226
|
+
*/
|
|
227
|
+
interventions?: OverseerIntervention[];
|
|
171
228
|
/**
|
|
172
229
|
* Summed across all children. Real spend; it must reach the status bar
|
|
173
230
|
* (§3.9). Every child runs the LEAD's model (main-agent parity), so one
|
|
@@ -212,6 +269,12 @@ export type TeamEvent = {
|
|
|
212
269
|
type: 'message';
|
|
213
270
|
dispatchId: string;
|
|
214
271
|
message: TeamMessage;
|
|
272
|
+
} | {
|
|
273
|
+
type: 'overseer';
|
|
274
|
+
dispatchId: string;
|
|
275
|
+
label: string;
|
|
276
|
+
decision: OverseerDecision;
|
|
277
|
+
trigger: OverseerIntervention['trigger'];
|
|
215
278
|
} | {
|
|
216
279
|
type: 'dispatch_end';
|
|
217
280
|
dispatchId: string;
|
package/dist/ui/App.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ import type { ConfirmRequest } from '../tools/index.js';
|
|
|
11
11
|
import { type Overlay, type ToastLevel } from '../agent/reducer.js';
|
|
12
12
|
import type { UpdateBridge } from '../update/types.js';
|
|
13
13
|
import type { MouseSource } from '../input/stdin-filter.js';
|
|
14
|
-
import type
|
|
14
|
+
import { type PasteBridge } from '../input/limits.js';
|
|
15
15
|
import type { SelectionBridge } from './selection/selection-controller.js';
|
|
16
16
|
import type { HumanInputBridge } from '../tools/human-input.js';
|
|
17
17
|
export interface ConfirmBridge {
|
package/dist/ui/App.js
CHANGED
|
@@ -48,6 +48,8 @@ import { useRenderGovernor } from './use-render-governor.js';
|
|
|
48
48
|
import { readScrollbarPerf, setPerfResetHook, setPerfSnapshotProvider, } from '../commands/perf.js';
|
|
49
49
|
import { buildFrameBudget } from './layout/budget.js';
|
|
50
50
|
import { OVERLAY_PAGE, useWheelRouting } from './use-wheel-routing.js';
|
|
51
|
+
import { INDEX_KEY_FRAME } from '../input/limits.js';
|
|
52
|
+
import { INDEX_SKILL_NAME, buildIndexConfirmSummary, hasProjectIndex, indexBuildInvocationArgs, } from '../commands/index-build.js';
|
|
51
53
|
import { installConsoleBridge } from './console-bridge.js';
|
|
52
54
|
import { publishExitSnapshot } from './exit-snapshot.js';
|
|
53
55
|
import { OverlayFrame } from './layout/OverlayFrame.js';
|
|
@@ -648,6 +650,15 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
648
650
|
dispatch({ type: 'teamUpdate', snapshot });
|
|
649
651
|
break;
|
|
650
652
|
}
|
|
653
|
+
case 'overseer': {
|
|
654
|
+
// A supervisor decision was applied (team-overseer). The roster
|
|
655
|
+
// already refreshed through the agent_update the runtime publishes
|
|
656
|
+
// with it; re-read the snapshot so the panel cannot miss the row.
|
|
657
|
+
const snap = controller.getTeamSnapshot();
|
|
658
|
+
if (snap)
|
|
659
|
+
dispatch({ type: 'teamUpdate', snapshot: snap });
|
|
660
|
+
break;
|
|
661
|
+
}
|
|
651
662
|
case 'usage': {
|
|
652
663
|
// Child spend is REAL SPEND and must reach the status bar, or the
|
|
653
664
|
// session cost readout under-reports by however much the team consumed
|
|
@@ -1553,6 +1564,46 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1553
1564
|
setConfirmState(null);
|
|
1554
1565
|
dispatch({ type: 'setOverlay', overlay: null });
|
|
1555
1566
|
};
|
|
1567
|
+
// --- Ctrl+I: run the bundled project-indexer skill. -------------------
|
|
1568
|
+
//
|
|
1569
|
+
// SAME PATH AS A TYPED COMMAND, ON PURPOSE. After the Yes/No gate (the
|
|
1570
|
+
// second confirmation the user asked for) this dispatches
|
|
1571
|
+
// `/skill:project-indexer <args>` through `executeSlashInput`, so the
|
|
1572
|
+
// load/activate/queue-frame/submit lifecycle and the streaming tool cards
|
|
1573
|
+
// in the transcript are the ones every skill command already has. The
|
|
1574
|
+
// busy guard refuses rather than queueing: a queued skill body would
|
|
1575
|
+
// arrive as steering mid-run, which is not what "build the index" means.
|
|
1576
|
+
const requestIndexBuild = () => {
|
|
1577
|
+
if (controller.isRunning() || interactionPhase.current !== 'idle') {
|
|
1578
|
+
// A notice entry, not a toast: the running status row's feedback slot is
|
|
1579
|
+
// owned by the activity phrase, and a busy warning nobody can read is
|
|
1580
|
+
// the same as no warning.
|
|
1581
|
+
notify('warn', 'The agent is busy. Let it finish before building the index (Ctrl+I).');
|
|
1582
|
+
return;
|
|
1583
|
+
}
|
|
1584
|
+
if (cfg.skills.enabled) {
|
|
1585
|
+
const record = controller.getSkillService().get(INDEX_SKILL_NAME);
|
|
1586
|
+
if (!record || record.disabled || record.invalid) {
|
|
1587
|
+
notify('warn', `Skill "${INDEX_SKILL_NAME}" is not available. Check /skills list.`);
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
else {
|
|
1592
|
+
notify('warn', 'Skills are disabled; the project index needs the bundled skill.');
|
|
1593
|
+
return;
|
|
1594
|
+
}
|
|
1595
|
+
const cwd = controller.getCwd();
|
|
1596
|
+
const existing = hasProjectIndex(cwd);
|
|
1597
|
+
void (async () => {
|
|
1598
|
+
const approved = await new Promise((resolve) => {
|
|
1599
|
+
setConfirmState({ summary: buildIndexConfirmSummary({ cwd, hasExistingIndex: existing }), resolve });
|
|
1600
|
+
dispatch({ type: 'setOverlay', overlay: 'confirm' });
|
|
1601
|
+
});
|
|
1602
|
+
if (approved) {
|
|
1603
|
+
await executeSlashInput(`/skill:${INDEX_SKILL_NAME} ${indexBuildInvocationArgs(existing)}`);
|
|
1604
|
+
}
|
|
1605
|
+
})();
|
|
1606
|
+
};
|
|
1556
1607
|
/** Answer every outstanding request with the same response and close up. */
|
|
1557
1608
|
const resolveHuman = (response) => {
|
|
1558
1609
|
for (const entry of [...pendingHuman.current])
|
|
@@ -1714,6 +1765,16 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1714
1765
|
setStatusExpanded(value => !value);
|
|
1715
1766
|
return;
|
|
1716
1767
|
}
|
|
1768
|
+
// Ctrl+I arrives as INDEX_KEY_FRAME, never as key.ctrl + 'i': legacy
|
|
1769
|
+
// terminals encode it as the Tab byte and the stdin filter's translators
|
|
1770
|
+
// are the only layer that can tell them apart (see input/limits.ts).
|
|
1771
|
+
// Guarded exactly like Ctrl+G above it.
|
|
1772
|
+
if (input.includes(INDEX_KEY_FRAME)) {
|
|
1773
|
+
if (tooSmall || stateRef.current.overlay)
|
|
1774
|
+
return;
|
|
1775
|
+
requestIndexBuild();
|
|
1776
|
+
return;
|
|
1777
|
+
}
|
|
1717
1778
|
if (tooSmall)
|
|
1718
1779
|
return;
|
|
1719
1780
|
// R-P1-7: the overlay branch MUST come before the transcript scroll branch
|
package/dist/ui/Header.js
CHANGED
|
@@ -5,7 +5,18 @@ import { Logo } from './Logo.js';
|
|
|
5
5
|
export function Header({ cwd, provider, model, hasKey, variant, theme, caps, columns = 80, statusExpanded = false, scrolledLines = 0, overlayOpen = false }) {
|
|
6
6
|
const scroll = Number.isFinite(scrolledLines) && scrolledLines > 0
|
|
7
7
|
? (scrolledLines > 9999 ? '^9999+' : '^' + Math.floor(scrolledLines)) + (overlayOpen ? ' ' : ' ') : '';
|
|
8
|
+
// The Ctrl+I project-index affordance sits LEFT of the ^G detail toggle:
|
|
9
|
+
// collapsed it names the key and the noun ('^I Index'); expanded - the
|
|
10
|
+
// same Ctrl+G state that adds the status detail row - it spells the action
|
|
11
|
+
// ('^I build index'). Hidden while an overlay owns the header and below the
|
|
12
|
+
// 72-column cutoff the composer hints also use: on a narrow terminal the
|
|
13
|
+
// scroll chip and the ^G toggle are the two affordances that must survive.
|
|
14
|
+
const indexHint = overlayOpen || columns < 72
|
|
15
|
+
? ''
|
|
16
|
+
: (statusExpanded ? '^I build index ' : '^I Index ');
|
|
8
17
|
const detail = overlayOpen ? (statusExpanded ? 'Details on' : 'Details off')
|
|
9
|
-
: '^G ' + (statusExpanded ? 'less' : 'more');
|
|
10
|
-
|
|
18
|
+
: indexHint + '^G ' + (statusExpanded ? 'less' : 'more');
|
|
19
|
+
// Widest content: '^9999+ ' (8) + '^I build index ^G less' (23) = 31.
|
|
20
|
+
const detailCells = indexHint ? 32 : 18;
|
|
21
|
+
return _jsxs(Box, { flexDirection: "row", height: 1, width: columns, flexShrink: 0, overflow: "hidden", children: [_jsx(Box, { width: Math.max(0, columns - detailCells), flexShrink: 0, overflow: "hidden", children: _jsxs(Text, { wrap: "truncate", children: [_jsx(Logo, { variant: variant === 'mini' ? 'mini' : 'bar', theme: theme, caps: caps }), variant !== 'mini' && _jsxs(_Fragment, { children: [_jsxs(Text, { color: theme.accent, children: [' ', provider, ":", model] }), _jsxs(Text, { color: theme.muted, children: [' ', basename(cwd) || cwd] }), _jsxs(Text, { color: hasKey ? theme.toolDone : theme.toolRunning, children: [' ', hasKey ? theme.symbols.keyOn : theme.symbols.keyOff] })] })] }) }), _jsx(Box, { width: detailCells, flexShrink: 0, justifyContent: "flex-end", children: _jsx(Text, { color: theme.muted, wrap: "truncate", children: scroll + detail }) })] });
|
|
11
22
|
}
|
package/dist/ui/PromptInput.js
CHANGED
|
@@ -37,7 +37,7 @@ import { draftMaxRows } from './composer-limits.js';
|
|
|
37
37
|
import { layoutComposer, splitRowAtColumn } from './composer-rows.js';
|
|
38
38
|
import { moveVisualCursor, snapGrapheme, stepGrapheme } from './editor-navigation.js';
|
|
39
39
|
import { interactionCopy } from './interaction-copy.js';
|
|
40
|
-
import { PASTE_DRAFT_MAX_BYTES, PASTE_MAX_BLOCKS, formatPasteSize, } from '../input/limits.js';
|
|
40
|
+
import { INDEX_KEY_FRAME, PASTE_DRAFT_MAX_BYTES, PASTE_MAX_BLOCKS, formatPasteSize, } from '../input/limits.js';
|
|
41
41
|
// ---------------------------------------------------------------------------
|
|
42
42
|
// The scroll chip (§4.2)
|
|
43
43
|
// ---------------------------------------------------------------------------
|
|
@@ -488,6 +488,16 @@ export function PromptInput({ cols: colsProp, terminalRows: terminalRowsProp, st
|
|
|
488
488
|
insert('\n');
|
|
489
489
|
return;
|
|
490
490
|
}
|
|
491
|
+
// App owns the Ctrl+I index frame (see input/limits.ts). Standalone it
|
|
492
|
+
// never reaches here - isControlSeq drops it at the printable-insert
|
|
493
|
+
// branch below - but a frame glued to typed text in one chunk must
|
|
494
|
+
// insert ONLY the text, so strip it before any other interpretation.
|
|
495
|
+
if (input.includes(INDEX_KEY_FRAME)) {
|
|
496
|
+
const rest = input.split(INDEX_KEY_FRAME).join('');
|
|
497
|
+
if (rest.length > 0)
|
|
498
|
+
insert(rest);
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
491
501
|
if (hasPasteFrame(input) || hasEnterFrame(input) || /[\r\n]/.test(input)) {
|
|
492
502
|
const intents = splitEnterFrames(input).map((frame) => frame.kind === 'paste' ? { ...frame, id: allocatePasteId() } : frame);
|
|
493
503
|
if (intents.length > 0)
|
|
@@ -20,6 +20,7 @@ function keyRows(times) {
|
|
|
20
20
|
[`Ctrl+C ${times}2`, 'Exit'],
|
|
21
21
|
['Ctrl+L', 'Redraw the frame'],
|
|
22
22
|
['Ctrl+T', 'Show/hide thinking (off by default)'],
|
|
23
|
+
['Ctrl+I', 'Build the project index (bundled skill; asks first; needs keyboard enhancement)'],
|
|
23
24
|
['Ctrl+G', 'Expand/collapse status details; disabled while an overlay is open'],
|
|
24
25
|
['PgUp / PgDn', 'Scroll a page (transcript, or this overlay)'],
|
|
25
26
|
['Shift+Up / Shift+Down', 'Scroll the transcript a line (full-screen mode)'],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aragon-agent/cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.19",
|
|
4
4
|
"description": "AragonAgent CLI — a Claude-Code / Codex-style interactive terminal UI (TUI) on top of the @aragon-agent/core engine. Installable and runnable via npm / npx.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "AragonAgent contributors",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"prepublishOnly": "npm run build"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@aragon-agent/core": "^0.2.
|
|
59
|
+
"@aragon-agent/core": "^0.2.31",
|
|
60
60
|
"chalk": "^5.6.2",
|
|
61
61
|
"cli-highlight": "^2.0.0",
|
|
62
62
|
"commander": "^12.1.0",
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: project-indexer
|
|
3
|
+
description: Generate and use a project index for quick codebase understanding in new sessions. Scans the workspace, extracts exported symbols, writes .claude-index/index.md plus config, and injects a Google-style Clean Code Guidelines section into CLAUDE.md. Use when the user says "explore the project", "generate index", "regenerate index", "更新索引", "生成索引", or presses Ctrl+I.
|
|
4
|
+
version: 1.1.0
|
|
5
|
+
license: MIT
|
|
6
|
+
keywords: [productivity, codebase, indexing, navigation, cleancode]
|
|
7
|
+
# The read-only tools (read_file, list_dir, glob, grep, ask_user, todo_write)
|
|
8
|
+
# are always available inside a skill frame via the tool floor; this ceiling
|
|
9
|
+
# adds exactly the two writers the generated files need.
|
|
10
|
+
allowed-tools:
|
|
11
|
+
- write_file
|
|
12
|
+
- edit_file
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Project Indexer
|
|
16
|
+
|
|
17
|
+
Generate a structured index of any codebase so a new session understands the
|
|
18
|
+
project with minimal context usage. The index is a **navigation map**, not a
|
|
19
|
+
code copy: enough to see the project globally (type, stack, architecture),
|
|
20
|
+
navigate on demand (feature map, file index), and know which symbols exist
|
|
21
|
+
without reading every file.
|
|
22
|
+
|
|
23
|
+
## Current request
|
|
24
|
+
|
|
25
|
+
$ARGUMENTS
|
|
26
|
+
|
|
27
|
+
## Step 1: Check for an existing index
|
|
28
|
+
|
|
29
|
+
- If `.claude-index/index.md` exists and has the key sections (Project
|
|
30
|
+
Overview, Feature Map, File Index), go to "Using an existing index".
|
|
31
|
+
- If it exists but is empty or malformed, tell the user and regenerate
|
|
32
|
+
(first-time flow), preserving `config.md` if intact.
|
|
33
|
+
- Otherwise run the first-time flow.
|
|
34
|
+
|
|
35
|
+
## Using an existing index
|
|
36
|
+
|
|
37
|
+
1. Read `.claude-index/index.md` and `CLAUDE.md` (if present).
|
|
38
|
+
2. Summarize in 3-5 bullets: structure, stack, feature areas, key entry
|
|
39
|
+
points. Offer to help.
|
|
40
|
+
3. For later questions, navigate via the Feature Map / File Index and read
|
|
41
|
+
real source only when detail is needed.
|
|
42
|
+
|
|
43
|
+
## First-time flow
|
|
44
|
+
|
|
45
|
+
### Step 2: Gather configuration (ask_user)
|
|
46
|
+
|
|
47
|
+
Ask with `ask_user`, defaults offered in every question:
|
|
48
|
+
|
|
49
|
+
1. **Exclusions** - A) defaults (node_modules, .git, dist, build,
|
|
50
|
+
__pycache__, .venv, vendor, coverage, .next, .nuxt, .cache, target, bin,
|
|
51
|
+
obj, .idea, .vscode) B) add more C) custom list.
|
|
52
|
+
2. **Priority directories** - A) auto-detect (recommended) B) specify C) all
|
|
53
|
+
equally.
|
|
54
|
+
3. **Tech stack** - auto-detect unless the user names one.
|
|
55
|
+
4. **Clean Code Guidelines** (injected into CLAUDE.md) - A) Google strict
|
|
56
|
+
(method 60, file 1000 - recommended) B) Google relaxed (method 600)
|
|
57
|
+
C) custom D) skip.
|
|
58
|
+
|
|
59
|
+
If C: ask **one `ask_user` call per limit** showing default and range -
|
|
60
|
+
MAX_FILE_LINES 1000 (200-5000), MAX_METHOD_LINES 60 (20-600),
|
|
61
|
+
MAX_FUNCTION_PARAMS 5 (2-10), MAX_CYCLOMATIC 10 (5-25); fall back to the
|
|
62
|
+
default on an out-of-range answer and say so in the summary.
|
|
63
|
+
MAX_LINE_LENGTH (default 100, Python 80) and MAX_NESTING_DEPTH (4) are never
|
|
64
|
+
asked. If the user skips every question, use all defaults.
|
|
65
|
+
|
|
66
|
+
Treat the answer as `CleanCodeConfig.preset`: strict | relaxed | custom |
|
|
67
|
+
disabled. D (`disabled`) skips the Clean Code composition below, never adds
|
|
68
|
+
markers, and persists `Enabled: No`.
|
|
69
|
+
|
|
70
|
+
### Step 3: Scan and analyze
|
|
71
|
+
|
|
72
|
+
1. Traverse the tree with the exclusion rules (`glob`, never one giant read).
|
|
73
|
+
2. Detect project type (frontend/backend/fullstack/library/CLI/monorepo),
|
|
74
|
+
languages with percentages, frameworks, entry points.
|
|
75
|
+
3. Group files by feature/domain and extract exported symbols - full support
|
|
76
|
+
for JS/TS (.js/.jsx/.ts/.tsx/.mjs) and Python; best-effort for Java,
|
|
77
|
+
Kotlin, Go, Rust, C/C++, C#, Ruby, PHP; unknown types are listed without
|
|
78
|
+
symbols.
|
|
79
|
+
4. Analyze module-level dependencies (import graph).
|
|
80
|
+
|
|
81
|
+
Adaptive sizing: <50 files -> full detail; 50-200 -> full coverage with
|
|
82
|
+
concise descriptions; 200+ -> detail the priority directories, summarize the
|
|
83
|
+
rest, group similar utilities. Over 1000 files after exclusions: warn and
|
|
84
|
+
suggest more exclusions before continuing; sample representative files with
|
|
85
|
+
glob/grep rather than reading everything. Keep language/framework findings in
|
|
86
|
+
working memory - do NOT write an intermediate JSON file.
|
|
87
|
+
|
|
88
|
+
### Step 4: Write `.claude-index/`
|
|
89
|
+
|
|
90
|
+
1. `config.md` - exclusions, priorities, tech stack, plus an Index Settings
|
|
91
|
+
block (Generated date, Project Root, Index Version) and a Clean Code
|
|
92
|
+
Settings block (Enabled / Preset / the six limits; when the user picked
|
|
93
|
+
skip, write `Enabled: No`, `Preset: disabled` and the strict numbers so a
|
|
94
|
+
later "enable cleancode" can flip it back).
|
|
95
|
+
2. `index.md` - follow `templates/index-template.md` in this skill's
|
|
96
|
+
directory: Project Overview, Feature Map (per area: entry file, one-line
|
|
97
|
+
description, key exports with signatures), Module Dependencies, File Index
|
|
98
|
+
(per-directory tables, one line per file).
|
|
99
|
+
|
|
100
|
+
### Step 5: Compose the Clean Code Guidelines (skip when disabled)
|
|
101
|
+
|
|
102
|
+
Read `templates/cleancode-template.md` and `templates/cleancode-overrides.md`
|
|
103
|
+
from this skill's directory, then:
|
|
104
|
+
|
|
105
|
+
1. Primary languages = every language with >= 10% of post-exclusion source
|
|
106
|
+
files; if none clears 10%, use the `Other` row.
|
|
107
|
+
2. Take the **minimum** across all matching rows for each numeric column.
|
|
108
|
+
3. Apply the user preset: strict/relaxed values intersect with the language
|
|
109
|
+
minimum (never looser than the baseline); custom numbers are honoured
|
|
110
|
+
verbatim with divergence noted.
|
|
111
|
+
4. Substitute every placeholder: `{{MAX_FILE_LINES}}`, `{{MAX_METHOD_LINES}}`,
|
|
112
|
+
`{{MAX_FUNCTION_PARAMS}}`, `{{MAX_CYCLOMATIC}}`, `{{MAX_LINE_LENGTH}}`,
|
|
113
|
+
`{{MAX_NESTING_DEPTH}}`, `{{PRIMARY_LANGUAGES}}` (comma-joined languages)
|
|
114
|
+
and `{{LANGUAGE_HINTS}}` (one `- Language: note` bullet per matching row,
|
|
115
|
+
deduped by exact string).
|
|
116
|
+
5. Verify no `{{...}}` placeholder remains. Do not count characters at
|
|
117
|
+
runtime.
|
|
118
|
+
|
|
119
|
+
### Step 6: Update CLAUDE.md
|
|
120
|
+
|
|
121
|
+
- `## Project Index` section: upsert (replace when present, else append at
|
|
122
|
+
end; create the file when missing). Contents: Location
|
|
123
|
+
`.claude-index/index.md`, Last Updated (YYYY-MM-DD), Contents line, a
|
|
124
|
+
Usage line, and "Regenerate: say 'regenerate index' / '更新索引'".
|
|
125
|
+
- `## Clean Code Guidelines` section (unless disabled): wrap the rendered
|
|
126
|
+
body in `<!-- project-indexer:cleancode:begin v1.1.0 -->` and
|
|
127
|
+
`<!-- project-indexer:cleancode:end -->`. Locate an existing block with
|
|
128
|
+
the version-agnostic regex `<!-- project-indexer:cleancode:begin
|
|
129
|
+
v\d+\.\d+\.\d+ -->` (NEVER a literal-version match) plus the literal end
|
|
130
|
+
marker; fallback: the region from a `## Clean Code Guidelines` heading to
|
|
131
|
+
the next `## ` heading or EOF. Replace the found block; if none, insert
|
|
132
|
+
after the Project Index section (first-insert only; when both sections
|
|
133
|
+
already exist preserve the user's ordering and everything between them).
|
|
134
|
+
Write UTF-8; add a trailing newline when missing.
|
|
135
|
+
|
|
136
|
+
### Step 7: Report
|
|
137
|
+
|
|
138
|
+
List what was created/updated, the feature-area count, files indexed and
|
|
139
|
+
symbols extracted. Offer to start work.
|
|
140
|
+
|
|
141
|
+
## Regeneration ("regenerate index" / "更新索引")
|
|
142
|
+
|
|
143
|
+
1. Read `config.md` and preserve exclusions, priorities, stack.
|
|
144
|
+
2. Clean Code handling: if CLAUDE.md has markers or the heading, ask
|
|
145
|
+
A) refresh B) keep C) remove; honour `Enabled: No` from config.md by
|
|
146
|
+
skipping silently with one reminder line. When no block exists and
|
|
147
|
+
Enabled is Yes/absent, compose and inject (announce before writing).
|
|
148
|
+
3. Confirm the plan (exclusions, priorities, cleancode decision) with the
|
|
149
|
+
user, then re-run Steps 3-7.
|
|
150
|
+
|
|
151
|
+
Special triggers: "disable cleancode" removes the block and sets
|
|
152
|
+
`Enabled: No`; "enable cleancode" re-composes it and sets `Enabled: Yes` -
|
|
153
|
+
both without a full rescan.
|
|
154
|
+
|
|
155
|
+
## Edge cases
|
|
156
|
+
|
|
157
|
+
- Inaccessible directories: list them under a "Skipped Directories" note and
|
|
158
|
+
continue; never fail the whole run for one unreadable path.
|
|
159
|
+
- No recognizable source files: generate a minimal index (overview + file
|
|
160
|
+
listing) and say so.
|
|
161
|
+
- The user pressed Ctrl+I: they already confirmed once - do not re-ask the
|
|
162
|
+
global "proceed?" question, but still ask the configuration questions
|
|
163
|
+
above (or reuse `.claude-index/config.md` when it exists).
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Clean Code — Language Override Table
|
|
2
|
+
|
|
3
|
+
> Read by `project-indexer` at Step 5. The skill intersects the rows below
|
|
4
|
+
> with the **primary languages** detected during the scan (any language whose
|
|
5
|
+
> source files account for >= 10% of the post-exclusion total).
|
|
6
|
+
>
|
|
7
|
+
> When two or more rows apply, the skill **takes the minimum** for every
|
|
8
|
+
> numeric column. Notes from each matching row are concatenated and rendered
|
|
9
|
+
> as bullets in the `{{LANGUAGE_HINTS}}` placeholder of
|
|
10
|
+
> `cleancode-template.md`.
|
|
11
|
+
>
|
|
12
|
+
> Defaults (used when no row matches, i.e. the `Other` row) live in the last
|
|
13
|
+
> row. Numeric units: `MAX_FILE_LINES` and `MAX_METHOD_LINES` are physical
|
|
14
|
+
> lines of source. `MAX_LINE_LENGTH` is columns. `MAX_FUNCTION_PARAMS` is the
|
|
15
|
+
> count of positional + keyword parameters in a single signature.
|
|
16
|
+
|
|
17
|
+
| Language | MAX_FILE_LINES | MAX_METHOD_LINES | MAX_LINE_LENGTH | MAX_FUNCTION_PARAMS | Notes |
|
|
18
|
+
|-------------|----------------|------------------|-----------------|---------------------|-------|
|
|
19
|
+
| TypeScript | 1000 | 60 | 100 | 5 | Prefer `interface` over `type` for public APIs. |
|
|
20
|
+
| JavaScript | 1000 | 60 | 100 | 5 | Avoid `any`; use JSDoc when no TS. |
|
|
21
|
+
| Python | 800 | 50 | 80 | 5 | PEP 8 + Google docstrings. |
|
|
22
|
+
| Go | 800 | 60 | 120 | 4 | Errors as values; no panic in library code. |
|
|
23
|
+
| Java | 1500 | 40 | 100 | 5 | Google Java Style. |
|
|
24
|
+
| Kotlin | 1500 | 40 | 100 | 5 | KtLint defaults. |
|
|
25
|
+
| Rust | 1000 | 60 | 100 | 5 | `clippy::pedantic` recommended. |
|
|
26
|
+
| C / C++ | 1000 | 50 | 100 | 5 | Google C++ Style. |
|
|
27
|
+
| Other | 1000 | 60 | 100 | 5 | Generic fallback. |
|
|
28
|
+
|
|
29
|
+
## Merge Rules
|
|
30
|
+
|
|
31
|
+
1. **Detection threshold** — only languages with >= 10% of source files (after
|
|
32
|
+
exclusions) contribute. If no language clears 10%, use the `Other` row.
|
|
33
|
+
2. **Numeric merge** — `min()` across every matching row, per column.
|
|
34
|
+
3. **Note merge** — collect each matching row's `Notes` cell, dedupe by exact
|
|
35
|
+
string match, render as a bullet list. Example for TypeScript + Python:
|
|
36
|
+
- `- TypeScript: Prefer interface over type for public APIs.`
|
|
37
|
+
- `- Python: PEP 8 + Google docstrings.`
|
|
38
|
+
4. **Fixed defaults (not in the table, applied unconditionally)** —
|
|
39
|
+
`MAX_CYCLOMATIC = 10`, `MAX_NESTING_DEPTH = 4`. These can still be
|
|
40
|
+
overridden by the user via the Custom preset.
|
|
41
|
+
|
|
42
|
+
## Adding a Language
|
|
43
|
+
|
|
44
|
+
When extending this table, keep the column order identical and use the
|
|
45
|
+
strictest reasonable value for each cell so the `min()` merge stays
|
|
46
|
+
meaningful for polyglot repos.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
cleancode-template.md — rendered into CLAUDE.md by project-indexer (Step 5).
|
|
3
|
+
Authoring rules:
|
|
4
|
+
- Chinese character body must measure 1,400–1,700 characters
|
|
5
|
+
(matched by regex `[一-鿿]`).
|
|
6
|
+
- Verify after editing by counting matches of `[一-鿿]` in this file.
|
|
7
|
+
- Placeholders: {{MAX_FILE_LINES}} {{MAX_METHOD_LINES}}
|
|
8
|
+
{{MAX_FUNCTION_PARAMS}} {{MAX_CYCLOMATIC}} {{MAX_LINE_LENGTH}}
|
|
9
|
+
{{MAX_NESTING_DEPTH}} {{PRIMARY_LANGUAGES}} {{LANGUAGE_HINTS}}
|
|
10
|
+
- At runtime the skill must ensure no `{{...}}` placeholder leaks into
|
|
11
|
+
the rendered output. Numeric character counting at runtime is NOT
|
|
12
|
+
required.
|
|
13
|
+
-->
|
|
14
|
+
|
|
15
|
+
> 本节由 `project-indexer` 自动维护,灵感取自谷歌风格,并按本项目主要语言收敛阈值。请勿手工编辑 markers 之间的内容,需要刷新时请运行「regenerate index」。
|
|
16
|
+
|
|
17
|
+
### 一、概述
|
|
18
|
+
|
|
19
|
+
本节是本项目在使用 AI 辅助开发时必须遵守的代码规范。在写或改动任何代码之前,先把下列条目读完一遍,再动键盘。规则的目的不是束缚发挥,而是让人类与 agent 写出的代码在阈值上保持可比的下限,避免出现千行文件与几百行方法,避免命名混乱、错误吞咽、测试缺失这些会在后续维护中迅速发酵的问题。所有阈值都允许根据项目实际情况调整,但调整必须在 `.claude-index/config.md` 中显式记录,并在评审中说明理由,不要靠默认值偷偷放宽。
|
|
20
|
+
|
|
21
|
+
### 二、文件与函数尺寸
|
|
22
|
+
|
|
23
|
+
- 单文件不超过 **{{MAX_FILE_LINES}}** 行,超过即按职责拆分到新模块。
|
|
24
|
+
- 单方法不超过 **{{MAX_METHOD_LINES}}** 行,写到一半感觉膨胀就先抽函数。
|
|
25
|
+
- 函数形参不超过 **{{MAX_FUNCTION_PARAMS}}** 个,多了请改用配置对象、命名参数或聚合结构体,禁止靠位置堆参数。
|
|
26
|
+
- 圈复杂度不超过 **{{MAX_CYCLOMATIC}}**,分支累积过多必须拆函数或引入查找表。
|
|
27
|
+
- 单行长度不超过 **{{MAX_LINE_LENGTH}}** 列,超长就换行或抽变量名。判定标准很简单:阈值超了就是技术债,提交前自查并修正。
|
|
28
|
+
|
|
29
|
+
### 三、命名
|
|
30
|
+
|
|
31
|
+
- 函数以动词开头表达动作,例如 `loadConfig`、`parseToken`;查询类用名词或 `get` / `find`。
|
|
32
|
+
- 返回布尔值的函数与变量以 `is`、`has`、`should`、`can` 起头,便于阅读条件表达式。
|
|
33
|
+
- 常量使用大写蛇形,模块名短小且语义化,避免堆叠首字母缩写。
|
|
34
|
+
- 不缩写领域词,宁可名字长五个字母也要让读者一眼看懂含义。
|
|
35
|
+
- 类与接口使用大驼峰命名,方法与变量使用小驼峰或语言推荐的蛇形写法,保持整个仓库风格一致。
|
|
36
|
+
- 避免使用 `data`、`info`、`temp`、`obj` 这类无信息量的名字;给出具体的领域名词。
|
|
37
|
+
|
|
38
|
+
### 四、注释与文档
|
|
39
|
+
|
|
40
|
+
- 注释只解释「为什么」,不复述「做什么」,代码本身就是「做什么」的真相。
|
|
41
|
+
- 所有公开 API 必须写 docstring,标注参数、返回值与可能抛出的异常。
|
|
42
|
+
- 删除一切僵尸注释与被注释掉的旧代码;保留 `TODO` 必须带责任人与日期。
|
|
43
|
+
- 行级注释稀缺即珍贵,不要在每行上方堆一句无信息量的话。
|
|
44
|
+
- 复杂业务规则、踩坑记录、临时性 workaround 必须在注释中写清楚原因与可回退条件。
|
|
45
|
+
|
|
46
|
+
### 五、错误处理
|
|
47
|
+
|
|
48
|
+
- 错误显式传播,不要默默吞异常或返回 `null` 假装一切正常。
|
|
49
|
+
- 外部输入在边界处一次性校验,进入内部后信任内部数据,禁止层层重复校验。
|
|
50
|
+
- 不要为「理论上不会发生」的分支写防御性 `try/except`,那只是噪音。
|
|
51
|
+
- 错误信息必须包含定位线索:发生在哪、关键参数是什么、下游应如何处理。
|
|
52
|
+
- 在 IO、网络、子进程等不可控边界,使用带超时与重试的结构化封装,而非裸调用。
|
|
53
|
+
- 捕获异常时尽量缩小作用域,只处理你确实有办法应对的错误类型,其他原样向上抛出。
|
|
54
|
+
- 对外暴露的错误码或异常类必须稳定,更名等同于破坏性变更,需要在变更日志中显式标注。
|
|
55
|
+
|
|
56
|
+
### 六、复杂度与嵌套
|
|
57
|
+
|
|
58
|
+
- 嵌套深度不超过 **{{MAX_NESTING_DEPTH}}** 层,深嵌套是函数过长的前兆。
|
|
59
|
+
- 优先使用早返回(guard clause)取代 `else` 阶梯。
|
|
60
|
+
- 单个表达式里出现超过三个布尔条件,立刻抽成命名清晰的辅助函数。
|
|
61
|
+
- 循环里若出现复杂条件分支,改写为查找表或策略对象往往更易读。
|
|
62
|
+
- 一个函数只做一件事;当解释函数职责时需要用「并」「同时」「再」这类连词,就是应该拆分的信号。
|
|
63
|
+
|
|
64
|
+
### 七、测试
|
|
65
|
+
|
|
66
|
+
- 修 bug 必须先写一个能稳定复现该 bug 的测试用例,再动业务代码。
|
|
67
|
+
- 公开行为都要有单元测试覆盖正常路径与至少一条异常路径。
|
|
68
|
+
- 测试名描述被验证的行为而非实现细节,例如 `rejects_expired_token`。
|
|
69
|
+
- 不允许 mock 数据库迁移、鉴权签名等高风险路径,必须使用真实集成测试。
|
|
70
|
+
- 单测应快速且可重复运行;外部依赖通过桩或本地容器隔离,禁止依赖共享环境状态。
|
|
71
|
+
- 任何被发现的回归 bug 都要补上对应的测试,把过去的坑变成未来的护栏。
|
|
72
|
+
|
|
73
|
+
### 八、代码评审清单
|
|
74
|
+
|
|
75
|
+
提交前对照以下 8 条自查,任何一条不满足即视为未完成:
|
|
76
|
+
|
|
77
|
+
1. 命名是否自解释,是否避免无意义缩写。
|
|
78
|
+
2. 文件、方法、参数、嵌套、圈复杂度是否都在阈值内。
|
|
79
|
+
3. 错误是否显式传播,边界校验是否到位。
|
|
80
|
+
4. 注释是否只解释「为什么」,是否还有僵尸代码或孤儿 TODO。
|
|
81
|
+
5. 测试是否覆盖正常路径与异常路径,是否能稳定复现已修复的 bug。
|
|
82
|
+
6. 是否引入新依赖,依赖是否必要、是否有安全或许可证风险。
|
|
83
|
+
7. 是否泄露密钥、个人数据、内部链接等敏感信息。
|
|
84
|
+
8. 是否与现有架构与命名风格一致,是否制造新的孤岛。
|
|
85
|
+
|
|
86
|
+
### 九、语言特有提示
|
|
87
|
+
|
|
88
|
+
当前项目主要语言:**{{PRIMARY_LANGUAGES}}**。
|
|
89
|
+
|
|
90
|
+
{{LANGUAGE_HINTS}}
|
|
91
|
+
|
|
92
|
+
### 十、元信息
|
|
93
|
+
|
|
94
|
+
模板版本与上方 markers 中的版本一致。说「regenerate index」或「更新索引」可重新生成本节内容;说「disable cleancode」可移除本节。
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Index Configuration
|
|
2
|
+
|
|
3
|
+
> Configuration for project-indexer. Edit this file to customize index generation.
|
|
4
|
+
|
|
5
|
+
## Excluded Directories
|
|
6
|
+
|
|
7
|
+
Directories to skip during indexing:
|
|
8
|
+
|
|
9
|
+
- node_modules
|
|
10
|
+
- .git
|
|
11
|
+
- dist
|
|
12
|
+
- build
|
|
13
|
+
- __pycache__
|
|
14
|
+
- .venv
|
|
15
|
+
- venv
|
|
16
|
+
- vendor
|
|
17
|
+
- coverage
|
|
18
|
+
- .next
|
|
19
|
+
- .nuxt
|
|
20
|
+
- .cache
|
|
21
|
+
- target
|
|
22
|
+
- bin
|
|
23
|
+
- obj
|
|
24
|
+
- .idea
|
|
25
|
+
- .vscode
|
|
26
|
+
|
|
27
|
+
## Priority Directories
|
|
28
|
+
|
|
29
|
+
Directories to index in detail (leave empty for auto-detection):
|
|
30
|
+
|
|
31
|
+
- src/
|
|
32
|
+
- lib/
|
|
33
|
+
- app/
|
|
34
|
+
- core/
|
|
35
|
+
|
|
36
|
+
## Tech Stack
|
|
37
|
+
|
|
38
|
+
Detected or specified technology stack:
|
|
39
|
+
|
|
40
|
+
- **Frontend:** {React | Vue | Angular | Svelte | Next.js | Nuxt | etc.}
|
|
41
|
+
- **Backend:** {Express | FastAPI | Django | Rails | Spring | etc.}
|
|
42
|
+
- **Language:** {TypeScript | JavaScript | Python | Go | etc.}
|
|
43
|
+
|
|
44
|
+
## Index Settings
|
|
45
|
+
|
|
46
|
+
- **Generated:** {YYYY-MM-DD HH:MM:SS}
|
|
47
|
+
- **Project Root:** {/path/to/project}
|
|
48
|
+
- **Index Version:** 1.0
|
|
49
|
+
- **Total Files Indexed:** {N}
|
|
50
|
+
- **Total Symbols Extracted:** {N}
|
|
51
|
+
|
|
52
|
+
## Clean Code Settings
|
|
53
|
+
|
|
54
|
+
- Enabled: {Yes | No}
|
|
55
|
+
- Preset: {strict | relaxed | custom | disabled}
|
|
56
|
+
- MAX_FILE_LINES: 1000
|
|
57
|
+
- MAX_METHOD_LINES: 60
|
|
58
|
+
- MAX_FUNCTION_PARAMS: 5
|
|
59
|
+
- MAX_CYCLOMATIC: 10
|
|
60
|
+
- MAX_LINE_LENGTH: 100
|
|
61
|
+
- MAX_NESTING_DEPTH: 4
|
|
62
|
+
|
|
63
|
+
## Regeneration Notes
|
|
64
|
+
|
|
65
|
+
Notes for future regeneration:
|
|
66
|
+
|
|
67
|
+
- {Any special considerations}
|
|
68
|
+
- {Files that need manual review}
|
|
69
|
+
- {Known limitations}
|