ai-engineering-kit 0.2.0 → 0.4.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/dist/cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { basename as
|
|
5
|
-
import { intro, outro, confirm, select, isCancel as isCancel2, cancel as cancel2,
|
|
4
|
+
import { basename as basename3 } from "path";
|
|
5
|
+
import { intro, outro, confirm, select as select2, isCancel as isCancel2, cancel as cancel2, note } from "@clack/prompts";
|
|
6
6
|
|
|
7
7
|
// src/catalog/load.ts
|
|
8
8
|
import { readdirSync, readFileSync } from "fs";
|
|
@@ -22,29 +22,77 @@ function parseManifest(json) {
|
|
|
22
22
|
|
|
23
23
|
// src/catalog/definition.ts
|
|
24
24
|
var CATEGORIES = [
|
|
25
|
-
{
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
{
|
|
26
|
+
id: "core-workflow",
|
|
27
|
+
label: "Core workflow",
|
|
28
|
+
hint: "End-to-end loop: kickoff \u2192 PRD \u2192 plan \u2192 implement \u2192 review \u2192 verify",
|
|
29
|
+
from: "skills/core-workflow",
|
|
30
|
+
to: "ai/skills"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: "engineering",
|
|
34
|
+
label: "Engineering",
|
|
35
|
+
hint: "Daily coding skills \u2014 TDD, debugging, triage, architecture (from mattpocock/skills)",
|
|
36
|
+
from: "skills/engineering",
|
|
37
|
+
to: "ai/skills"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: "productivity",
|
|
41
|
+
label: "Productivity",
|
|
42
|
+
hint: "Workflow helpers \u2014 grilling, handoff, teaching, concise mode (from mattpocock/skills)",
|
|
43
|
+
from: "skills/productivity",
|
|
44
|
+
to: "ai/skills"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: "misc",
|
|
48
|
+
label: "Misc",
|
|
49
|
+
hint: "Occasional utilities \u2014 git guardrails, pre-commit, scaffolding (from mattpocock/skills)",
|
|
50
|
+
from: "skills/misc",
|
|
51
|
+
to: "ai/skills"
|
|
52
|
+
}
|
|
29
53
|
];
|
|
30
54
|
var TREE_COMPONENTS = [
|
|
31
|
-
{
|
|
32
|
-
|
|
55
|
+
{
|
|
56
|
+
id: "docs-foundations",
|
|
57
|
+
label: "Foundation docs",
|
|
58
|
+
hint: "Starter docs for product vision, guidelines, and technical decisions (docs/foundations/)",
|
|
59
|
+
from: "docs/foundations",
|
|
60
|
+
to: "docs/foundations"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: "ai-workspace",
|
|
64
|
+
label: "Workspace folders",
|
|
65
|
+
hint: "Folders the skills write to: brainstorms, PRDs, plans, reviews (ai/)",
|
|
66
|
+
from: "ai-workspace",
|
|
67
|
+
to: "ai"
|
|
68
|
+
}
|
|
33
69
|
];
|
|
34
70
|
var COMPONENTS = [
|
|
35
|
-
{
|
|
36
|
-
|
|
37
|
-
|
|
71
|
+
{
|
|
72
|
+
id: "skills",
|
|
73
|
+
label: "Skills",
|
|
74
|
+
hint: "Reusable AI playbooks the agent runs on demand (plan, implement, review, debug\u2026)",
|
|
75
|
+
unlocks: "categories"
|
|
76
|
+
},
|
|
77
|
+
...TREE_COMPONENTS.map((t) => ({ id: t.id, label: t.label, hint: t.hint })),
|
|
78
|
+
{
|
|
79
|
+
id: "entry-files",
|
|
80
|
+
label: "Agent entry files",
|
|
81
|
+
hint: "Instruction files that wire your coding agent to the skills (CLAUDE.md / AGENTS.md)",
|
|
82
|
+
unlocks: "agents"
|
|
83
|
+
}
|
|
38
84
|
];
|
|
39
85
|
var AGENTS = [
|
|
40
86
|
{
|
|
41
87
|
id: "claude-code",
|
|
42
88
|
label: "Claude Code",
|
|
89
|
+
hint: "Adds CLAUDE.md + a .claude/skills/ symlink for native skill discovery",
|
|
43
90
|
files: [{ from: "agents/claude-code.CLAUDE.md", to: "CLAUDE.md", substitute: true }]
|
|
44
91
|
},
|
|
45
92
|
{
|
|
46
93
|
id: "codex",
|
|
47
94
|
label: "Codex",
|
|
95
|
+
hint: "Adds AGENTS.md referencing ai/skills/",
|
|
48
96
|
files: [{ from: "agents/codex.AGENTS.md", to: "AGENTS.md", substitute: true }]
|
|
49
97
|
}
|
|
50
98
|
];
|
|
@@ -114,6 +162,31 @@ function resolveSelection(catalog, selection) {
|
|
|
114
162
|
});
|
|
115
163
|
}
|
|
116
164
|
|
|
165
|
+
// src/menu/summary.ts
|
|
166
|
+
import { basename } from "path";
|
|
167
|
+
var labelOf = (id) => COMPONENTS.find((c) => c.id === id)?.label ?? id;
|
|
168
|
+
function summarize(files, plan) {
|
|
169
|
+
const byPath = new Map(files.map((f) => [f.targetPath, f]));
|
|
170
|
+
const groups = /* @__PURE__ */ new Map();
|
|
171
|
+
const conflicts = [];
|
|
172
|
+
let written = 0;
|
|
173
|
+
for (const action of plan.actions) {
|
|
174
|
+
if (action.type === "conflict") {
|
|
175
|
+
conflicts.push(action.path);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (action.type !== "add" && action.type !== "refresh") continue;
|
|
179
|
+
const file = byPath.get(action.path);
|
|
180
|
+
if (!file) continue;
|
|
181
|
+
written++;
|
|
182
|
+
const group = groups.get(file.component) ?? groups.set(file.component, { component: file.component, label: labelOf(file.component), count: 0, names: [] }).get(file.component);
|
|
183
|
+
group.count++;
|
|
184
|
+
if (file.component === "entry-files") group.names.push(basename(file.targetPath));
|
|
185
|
+
}
|
|
186
|
+
const ordered = COMPONENTS.map((c) => groups.get(c.id)).filter((g) => g !== void 0);
|
|
187
|
+
return { groups: ordered, conflicts, written };
|
|
188
|
+
}
|
|
189
|
+
|
|
117
190
|
// src/planner/planner.ts
|
|
118
191
|
function planInstall(input) {
|
|
119
192
|
const files = resolveSelection(input.catalog, input.selection);
|
|
@@ -290,18 +363,25 @@ On Windows, enable Developer Mode (or run as admin) and re-run, or point Claude
|
|
|
290
363
|
}
|
|
291
364
|
|
|
292
365
|
// src/menu/prompt.ts
|
|
293
|
-
import { basename } from "path";
|
|
294
|
-
import { multiselect, text, isCancel, cancel } from "@clack/prompts";
|
|
366
|
+
import { basename as basename2 } from "path";
|
|
367
|
+
import { select, multiselect, text, isCancel, cancel } from "@clack/prompts";
|
|
295
368
|
|
|
296
369
|
// src/menu/menu.ts
|
|
297
370
|
function componentOptions() {
|
|
298
|
-
return COMPONENTS.map((c) => ({ value: c.id, label: c.label }));
|
|
371
|
+
return COMPONENTS.map((c) => ({ value: c.id, label: c.label, hint: c.hint }));
|
|
299
372
|
}
|
|
300
373
|
function categoryOptions() {
|
|
301
|
-
return CATEGORIES.map((c) => ({ value: c.id, label: c.label }));
|
|
374
|
+
return CATEGORIES.map((c) => ({ value: c.id, label: c.label, hint: c.hint }));
|
|
302
375
|
}
|
|
303
376
|
function agentOptions() {
|
|
304
|
-
return AGENTS.map((a) => ({ value: a.id, label: a.label }));
|
|
377
|
+
return AGENTS.map((a) => ({ value: a.id, label: a.label, hint: a.hint }));
|
|
378
|
+
}
|
|
379
|
+
function presetSelection(preset) {
|
|
380
|
+
return {
|
|
381
|
+
components: COMPONENTS.map((c) => c.id),
|
|
382
|
+
categories: preset === "everything" ? CATEGORIES.map((c) => c.id) : ["core-workflow"],
|
|
383
|
+
agents: AGENTS.map((a) => a.id)
|
|
384
|
+
};
|
|
305
385
|
}
|
|
306
386
|
function buildSelection(answers) {
|
|
307
387
|
return {
|
|
@@ -319,42 +399,31 @@ function required(value) {
|
|
|
319
399
|
}
|
|
320
400
|
return value;
|
|
321
401
|
}
|
|
402
|
+
async function pick(message, options) {
|
|
403
|
+
return required(await multiselect({ message, options, required: true }));
|
|
404
|
+
}
|
|
405
|
+
async function chooseSelection() {
|
|
406
|
+
const components = await pick("Select parts to install", componentOptions());
|
|
407
|
+
const categories = components.includes("skills") ? await pick("Select skill categories", categoryOptions()) : [];
|
|
408
|
+
const agents = components.includes("entry-files") ? await pick("Select coding agents", agentOptions()) : [];
|
|
409
|
+
return buildSelection({ components, categories, agents });
|
|
410
|
+
}
|
|
322
411
|
async function promptForSelection(projectDir) {
|
|
323
|
-
const
|
|
324
|
-
await
|
|
325
|
-
message:
|
|
326
|
-
options:
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
if (components.includes("skills")) {
|
|
332
|
-
categories = required(
|
|
333
|
-
await multiselect({
|
|
334
|
-
message: "Which skill categories?",
|
|
335
|
-
options: categoryOptions(),
|
|
336
|
-
required: true
|
|
337
|
-
})
|
|
338
|
-
);
|
|
339
|
-
}
|
|
340
|
-
let agents = [];
|
|
341
|
-
if (components.includes("entry-files")) {
|
|
342
|
-
agents = required(
|
|
343
|
-
await multiselect({
|
|
344
|
-
message: "Which coding agents?",
|
|
345
|
-
options: agentOptions(),
|
|
346
|
-
required: true
|
|
347
|
-
})
|
|
348
|
-
);
|
|
349
|
-
}
|
|
350
|
-
const projectName = required(
|
|
351
|
-
await text({
|
|
352
|
-
message: "Project name",
|
|
353
|
-
defaultValue: basename(projectDir),
|
|
354
|
-
placeholder: basename(projectDir)
|
|
412
|
+
const mode = required(
|
|
413
|
+
await select({
|
|
414
|
+
message: `How do you want to set up ${basename2(projectDir)}?`,
|
|
415
|
+
options: [
|
|
416
|
+
{ value: "everything", label: "Everything", hint: "all skills, docs, and workspace \u2014 Claude Code + Codex" },
|
|
417
|
+
{ value: "core", label: "Core skills only", hint: "core-workflow skills, docs, and workspace \u2014 both agents" },
|
|
418
|
+
{ value: "choose", label: "Choose what to install\u2026", hint: "pick components, skill categories, and agents" }
|
|
419
|
+
]
|
|
355
420
|
})
|
|
356
421
|
);
|
|
357
|
-
|
|
422
|
+
const selection = mode === "choose" ? await chooseSelection() : presetSelection(mode);
|
|
423
|
+
const projectName = selection.components.includes("entry-files") ? required(
|
|
424
|
+
await text({ message: "Project name", defaultValue: basename2(projectDir), placeholder: basename2(projectDir) })
|
|
425
|
+
) : basename2(projectDir);
|
|
426
|
+
return { selection, projectName };
|
|
358
427
|
}
|
|
359
428
|
|
|
360
429
|
// src/applier/update.ts
|
|
@@ -506,7 +575,29 @@ function planFor(projectDir, selection) {
|
|
|
506
575
|
onDisk: readOnDiskHashes(projectDir, files),
|
|
507
576
|
manifest: readManifest(projectDir)
|
|
508
577
|
});
|
|
509
|
-
return { catalog, plan };
|
|
578
|
+
return { catalog, files, plan };
|
|
579
|
+
}
|
|
580
|
+
function formatSummary(summary) {
|
|
581
|
+
if (summary.written === 0 && summary.conflicts.length === 0) return "Everything is already up to date.";
|
|
582
|
+
const width = Math.max(0, ...summary.groups.map((g) => g.label.length));
|
|
583
|
+
const lines = summary.groups.map((g) => {
|
|
584
|
+
const detail = g.names.length > 0 ? g.names.join(", ") : String(g.count);
|
|
585
|
+
return ` ${g.label.padEnd(width)} ${detail}`;
|
|
586
|
+
});
|
|
587
|
+
if (summary.conflicts.length > 0) {
|
|
588
|
+
lines.push("", `Conflicts \u2014 you edited these (you'll choose per file):`);
|
|
589
|
+
for (const path of summary.conflicts) lines.push(` ${path}`);
|
|
590
|
+
}
|
|
591
|
+
return lines.join("\n");
|
|
592
|
+
}
|
|
593
|
+
function successOutro(verb, written, selection) {
|
|
594
|
+
if (selection.components.includes("skills")) {
|
|
595
|
+
note(
|
|
596
|
+
"Open your coding agent in this project and run a skill:\n setup-foundations fill in docs/foundations\n kickoff brainstorm an MVP",
|
|
597
|
+
"Next steps"
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
outro(`${verb} ${written} file${written === 1 ? "" : "s"}.`);
|
|
510
601
|
}
|
|
511
602
|
function reportConflict(error) {
|
|
512
603
|
if (!(error instanceof ApplyConflictError)) return false;
|
|
@@ -526,7 +617,7 @@ function dryRun(args) {
|
|
|
526
617
|
function init(args) {
|
|
527
618
|
const projectDir = process.cwd();
|
|
528
619
|
const selection = selectionFromArgs(args);
|
|
529
|
-
const projectName = parseValue(args, "--name") ??
|
|
620
|
+
const projectName = parseValue(args, "--name") ?? basename3(projectDir);
|
|
530
621
|
try {
|
|
531
622
|
const result = runInit({ projectDir, selection, projectName });
|
|
532
623
|
console.log(`ai-engineering-kit ${kitVersion()} installed into ${projectDir}`);
|
|
@@ -546,7 +637,7 @@ async function runReRun(projectDir) {
|
|
|
546
637
|
const manifest = readManifest(projectDir);
|
|
547
638
|
intro(`ai-engineering-kit ${kitVersion()} \u2014 already installed`);
|
|
548
639
|
const mode = bail(
|
|
549
|
-
await
|
|
640
|
+
await select2({
|
|
550
641
|
message: "What would you like to do?",
|
|
551
642
|
options: [
|
|
552
643
|
{ value: "update", label: "Update installed parts to latest" },
|
|
@@ -556,7 +647,7 @@ async function runReRun(projectDir) {
|
|
|
556
647
|
})
|
|
557
648
|
);
|
|
558
649
|
let addParts = EMPTY_SELECTION;
|
|
559
|
-
let projectName =
|
|
650
|
+
let projectName = basename3(projectDir);
|
|
560
651
|
if (mode === "add" || mode === "both") {
|
|
561
652
|
const picked = await promptForSelection(projectDir);
|
|
562
653
|
addParts = picked.selection;
|
|
@@ -566,24 +657,27 @@ async function runReRun(projectDir) {
|
|
|
566
657
|
const applicable = {
|
|
567
658
|
actions: prepared.plan.actions.filter((a) => prepared.applyTypes.has(a.type))
|
|
568
659
|
};
|
|
569
|
-
|
|
660
|
+
const pending = applicable.actions.filter((a) => a.type !== "skip").length;
|
|
661
|
+
if (pending === 0) {
|
|
662
|
+
outro("Already up to date \u2014 nothing to do.");
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
note(formatSummary(summarize(prepared.files, applicable)), mode === "add" ? "This will add" : "This will update");
|
|
570
666
|
const resolutions = /* @__PURE__ */ new Map();
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
}
|
|
667
|
+
const conflicts = applicable.actions.filter((a) => a.type === "conflict");
|
|
668
|
+
for (const [index, action] of conflicts.entries()) {
|
|
669
|
+
const choice = bail(
|
|
670
|
+
await select2({
|
|
671
|
+
message: `Conflict ${index + 1}/${conflicts.length}: you edited ${action.path}, and a newer version exists.`,
|
|
672
|
+
options: [
|
|
673
|
+
{ value: "keep-mine", label: "Keep mine", hint: "discard the update for this file" },
|
|
674
|
+
{ value: "overwrite", label: "Use the new version", hint: "replace your file" },
|
|
675
|
+
{ value: "keep-theirs", label: "Keep mine + save theirs", hint: `writes ${basename3(action.path)}.new` }
|
|
676
|
+
]
|
|
677
|
+
})
|
|
678
|
+
);
|
|
679
|
+
resolutions.set(action.path, choice);
|
|
585
680
|
}
|
|
586
|
-
const pending = applicable.actions.filter((a) => a.type !== "skip").length;
|
|
587
681
|
const proceed = await confirm({ message: `Apply ${pending} changes in ${projectDir}?` });
|
|
588
682
|
if (isCancel2(proceed) || !proceed) {
|
|
589
683
|
outro("Nothing written.");
|
|
@@ -601,24 +695,24 @@ async function runReRun(projectDir) {
|
|
|
601
695
|
projectName,
|
|
602
696
|
applyTypes: prepared.applyTypes
|
|
603
697
|
});
|
|
604
|
-
|
|
698
|
+
successOutro(mode === "add" ? "Added" : "Updated", result.written.length, prepared.selection);
|
|
605
699
|
}
|
|
606
700
|
async function runInteractive() {
|
|
607
701
|
const projectDir = process.cwd();
|
|
608
702
|
if (readManifest(projectDir)) return runReRun(projectDir);
|
|
609
703
|
intro(`ai-engineering-kit ${kitVersion()}`);
|
|
610
704
|
const { selection, projectName } = await promptForSelection(projectDir);
|
|
611
|
-
const { plan } = planFor(projectDir, selection);
|
|
612
|
-
|
|
705
|
+
const { files, plan } = planFor(projectDir, selection);
|
|
706
|
+
note(formatSummary(summarize(files, plan)), "This will add");
|
|
613
707
|
const pending = plan.actions.filter((a) => a.type !== "skip").length;
|
|
614
|
-
const proceed = await confirm({ message: `
|
|
708
|
+
const proceed = await confirm({ message: `Install ${pending} files into ${projectDir}?` });
|
|
615
709
|
if (isCancel2(proceed) || !proceed) {
|
|
616
710
|
outro("Nothing written.");
|
|
617
711
|
return;
|
|
618
712
|
}
|
|
619
713
|
try {
|
|
620
714
|
const result = runInit({ projectDir, selection, projectName });
|
|
621
|
-
|
|
715
|
+
successOutro("Installed", result.written.length, selection);
|
|
622
716
|
} catch (error) {
|
|
623
717
|
if (!reportConflict(error)) throw error;
|
|
624
718
|
}
|
|
@@ -641,7 +735,7 @@ function performReRun(projectDir, mode, addParts) {
|
|
|
641
735
|
priorManifest: manifest,
|
|
642
736
|
selection: prepared.selection,
|
|
643
737
|
version: prepared.version,
|
|
644
|
-
projectName:
|
|
738
|
+
projectName: basename3(projectDir),
|
|
645
739
|
applyTypes: prepared.applyTypes
|
|
646
740
|
});
|
|
647
741
|
console.log(`${result.written.length} files written. Edited files were kept (use the interactive flow to resolve).`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-engineering-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "An opinionated, agent-agnostic AI-development kit — disciplined skills plus a structured workspace — installed and updated through one npx command.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: commit-organizer
|
|
3
|
+
description: Analyze all code changes across one or more repos and organize them into logical, separate conventional commits. Use when you have many unstaged/staged changes and want clean, atomic commits instead of one big commit.
|
|
4
|
+
argument-hint: "[repo-path]"
|
|
5
|
+
allowed-tools: Read, Bash(git *), Glob, Grep
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Commit Organizer
|
|
9
|
+
|
|
10
|
+
Analyze all code changes (staged and unstaged) in the current directory. If the directory contains multiple git repos (a workspace root), analyze each repo independently. Organize changes into logical, atomic conventional commits.
|
|
11
|
+
|
|
12
|
+
## How It Works
|
|
13
|
+
|
|
14
|
+
1. Detect whether the current directory is a git repo or a workspace containing multiple repos
|
|
15
|
+
2. For each repo, collect all modified, added, and untracked files
|
|
16
|
+
3. Read the diffs and new file contents to understand what each change does
|
|
17
|
+
4. Group related changes into logical commit units (e.g., a permission change + its test = one commit)
|
|
18
|
+
5. Order commits so dependencies come first
|
|
19
|
+
6. Present the commit plan to the user for approval
|
|
20
|
+
7. Execute the commits one by one
|
|
21
|
+
|
|
22
|
+
## Commit Message Rules
|
|
23
|
+
|
|
24
|
+
- Use conventional commits: `type(scope): description`
|
|
25
|
+
- Types: `feat`, `fix`, `refactor`, `style`, `docs`, `test`, `chore`
|
|
26
|
+
- One line only, max 72 characters
|
|
27
|
+
- Describe WHAT the change does from the user's perspective, not HOW
|
|
28
|
+
- Use imperative mood: "add", "fix", "ensure", "change", not "added", "fixes"
|
|
29
|
+
- Be specific — name the feature, the bug, the thing that changed
|
|
30
|
+
|
|
31
|
+
### Good Examples
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
feat: add per-card CSV export to Insights dashboard cards
|
|
35
|
+
feat: ensure bulk data export is only available to superAdmin users
|
|
36
|
+
fix: use YYYY/MM/DD date format in all CSV exports
|
|
37
|
+
refactor: extract shared RQS value mapping to exportUtils
|
|
38
|
+
feat: add export audit log with search and pagination
|
|
39
|
+
chore: add i18n keys for export and audit log UI
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Bad Examples
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
update code # too vague
|
|
46
|
+
feat: changes to export # what changes?
|
|
47
|
+
fix: fix bug # what bug?
|
|
48
|
+
feat: add new feature # what feature?
|
|
49
|
+
refactor: refactor stuff # what stuff?
|
|
50
|
+
feat: implement export refactoring # describes the task, not the change
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Grouping Strategy
|
|
54
|
+
|
|
55
|
+
Group changes into commits by **logical intent**, not by file type:
|
|
56
|
+
|
|
57
|
+
- A backend permission change + frontend visibility change for the same feature = ONE commit
|
|
58
|
+
- A new utility file + the component that uses it = ONE commit (if tightly coupled)
|
|
59
|
+
- i18n keys can be grouped with the feature that uses them, or in a separate commit if they span multiple features
|
|
60
|
+
- Unrelated changes to different features = SEPARATE commits
|
|
61
|
+
- A new GraphQL schema + resolver + frontend query/mutation for one feature = ONE commit
|
|
62
|
+
|
|
63
|
+
When in doubt, prefer fewer, more meaningful commits over many tiny ones.
|
|
64
|
+
|
|
65
|
+
## Execution
|
|
66
|
+
|
|
67
|
+
### Step 1: Detect Repos
|
|
68
|
+
|
|
69
|
+
If `$ARGUMENTS` is provided, use it as the path. Otherwise use the current working directory.
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Check if current dir is a git repo
|
|
73
|
+
git rev-parse --git-dir 2>/dev/null
|
|
74
|
+
|
|
75
|
+
# If not, find git repos one level deep (workspace mode)
|
|
76
|
+
for dir in */; do
|
|
77
|
+
if [ -d "$dir/.git" ]; then
|
|
78
|
+
echo "$dir"
|
|
79
|
+
fi
|
|
80
|
+
done
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Step 2: Collect Changes Per Repo
|
|
84
|
+
|
|
85
|
+
For each repo, run:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
git status -u # all changes including untracked (never use -uall)
|
|
89
|
+
git diff # unstaged changes
|
|
90
|
+
git diff --cached # staged changes
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
For untracked files, read their contents to understand what they add.
|
|
94
|
+
|
|
95
|
+
### Step 3: Analyze and Group
|
|
96
|
+
|
|
97
|
+
Read all diffs and new files. For each change, determine:
|
|
98
|
+
- What feature or concern does this belong to?
|
|
99
|
+
- Is it a feat, fix, refactor, style, docs, test, or chore?
|
|
100
|
+
- What other changes is it related to?
|
|
101
|
+
|
|
102
|
+
Group into commit units. Each unit has:
|
|
103
|
+
- A list of files to stage
|
|
104
|
+
- A commit message
|
|
105
|
+
- A brief rationale (for the user to review)
|
|
106
|
+
|
|
107
|
+
### Step 4: Present Plan
|
|
108
|
+
|
|
109
|
+
Show the user a numbered list of proposed commits in order:
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
Repository: cliezen-graph
|
|
113
|
+
|
|
114
|
+
1. feat: restrict bulk CSV export to superAdmin users
|
|
115
|
+
Files: routes/exportCsv.js
|
|
116
|
+
|
|
117
|
+
2. fix: use YYYY/MM/DD date format in bulk CSV exports
|
|
118
|
+
Files: routes/exports/csv.js
|
|
119
|
+
|
|
120
|
+
Repository: cliezen-dashboard
|
|
121
|
+
|
|
122
|
+
3. feat: add shared export utilities and CSV formatters
|
|
123
|
+
Files: src/utils/exportUtils.js, src/utils/cardExportFormatters.js
|
|
124
|
+
|
|
125
|
+
4. feat: add per-card CSV export to Insights dashboard cards
|
|
126
|
+
Files: src/composables/useCardExport.js, src/components/Insights/DashCard.vue, src/graphql/mutations.js
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Ask the user: "Does this commit plan look good? You can adjust, reorder, merge, or split any commits."
|
|
130
|
+
|
|
131
|
+
Wait for approval before proceeding. If the user says "go" or "yes" or "looks good", execute. If they request changes, adjust and re-present.
|
|
132
|
+
|
|
133
|
+
### Step 5: Execute Commits
|
|
134
|
+
|
|
135
|
+
For each commit unit, in order:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
cd <repo-path>
|
|
139
|
+
git add <file1> <file2> ... # stage only files for this commit
|
|
140
|
+
git commit -m "<message>" # commit with the planned message
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
After each commit, confirm success before moving to the next.
|
|
144
|
+
|
|
145
|
+
If a commit fails (e.g., pre-commit hook), stop and report the error. Do not skip or force.
|
|
146
|
+
|
|
147
|
+
### Step 6: Summary
|
|
148
|
+
|
|
149
|
+
After all commits are done, show a summary:
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
Done! Created N commits across M repos:
|
|
153
|
+
|
|
154
|
+
cliezen-graph (2 commits):
|
|
155
|
+
abc1234 feat: restrict bulk CSV export to superAdmin users
|
|
156
|
+
def5678 fix: use YYYY/MM/DD date format in bulk CSV exports
|
|
157
|
+
|
|
158
|
+
cliezen-dashboard (4 commits):
|
|
159
|
+
111aaaa feat: add shared export utilities and CSV formatters
|
|
160
|
+
222bbbb feat: add per-card CSV export to Insights dashboard cards
|
|
161
|
+
...
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Edge Cases
|
|
165
|
+
|
|
166
|
+
- If there are no changes in a repo, skip it silently
|
|
167
|
+
- If all changes belong to one logical unit, make one commit (don't force artificial splits)
|
|
168
|
+
- If a file has changes belonging to two different features, note this and ask the user how to handle it (commit the whole file with the more significant change, or suggest the user manually split it with `git add -p`)
|
|
169
|
+
- Never run `git add .` or `git add -A` — always stage specific files
|
|
170
|
+
- Never amend existing commits
|
|
171
|
+
- Never force push
|
|
172
|
+
- Never use `--no-verify`
|
|
173
|
+
- Never add Co-Authored-By, Signed-off-by, or any AI attribution trailers to commit messages
|
|
174
|
+
- Commit messages are the developer's own — no mention of Claude, AI, or any tool
|