@tplog/hasapi 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +54 -0
- package/bin/hasapi.mjs +292 -0
- package/hasapi-skills/README.md +56 -0
- package/hasapi-skills/_shared/common.md +240 -0
- package/hasapi-skills/_shared/custom-risks-guide.md +48 -0
- package/hasapi-skills/_shared/decay-risks.md +294 -0
- package/hasapi-skills/_shared/remedy-guide.md +37 -0
- package/hasapi-skills/_shared/source-coverage.md +248 -0
- package/hasapi-skills/_shared/test-decay-risks.md +246 -0
- package/hasapi-skills/hasapi-audit/SKILL.md +42 -0
- package/hasapi-skills/hasapi-audit/architecture-guide.md +195 -0
- package/hasapi-skills/hasapi-audit/onboarding-guide.md +89 -0
- package/hasapi-skills/hasapi-debt/SKILL.md +35 -0
- package/hasapi-skills/hasapi-debt/debt-guide.md +125 -0
- package/hasapi-skills/hasapi-diagnosing-bugs/SKILL.md +134 -0
- package/hasapi-skills/hasapi-diagnosing-bugs/scripts/hitl-loop.template.sh +41 -0
- package/hasapi-skills/hasapi-grilling/SKILL.md +10 -0
- package/hasapi-skills/hasapi-handoff/SKILL.md +16 -0
- package/hasapi-skills/hasapi-health/SKILL.md +37 -0
- package/hasapi-skills/hasapi-health/health-guide.md +89 -0
- package/hasapi-skills/hasapi-implement/SKILL.md +15 -0
- package/hasapi-skills/hasapi-resolving-merge-conflicts/SKILL.md +14 -0
- package/hasapi-skills/hasapi-review/SKILL.md +37 -0
- package/hasapi-skills/hasapi-review/pr-review-guide.md +163 -0
- package/hasapi-skills/hasapi-setup/SKILL.md +121 -0
- package/hasapi-skills/hasapi-setup/domain.md +51 -0
- package/hasapi-skills/hasapi-setup/issue-tracker-github.md +22 -0
- package/hasapi-skills/hasapi-setup/issue-tracker-gitlab.md +23 -0
- package/hasapi-skills/hasapi-setup/issue-tracker-local.md +19 -0
- package/hasapi-skills/hasapi-setup/triage-labels.md +15 -0
- package/hasapi-skills/hasapi-sweep/SKILL.md +38 -0
- package/hasapi-skills/hasapi-sweep/sweep-guide.md +264 -0
- package/hasapi-skills/hasapi-tdd/SKILL.md +108 -0
- package/hasapi-skills/hasapi-tdd/mocking.md +59 -0
- package/hasapi-skills/hasapi-tdd/refactoring.md +10 -0
- package/hasapi-skills/hasapi-tdd/tests.md +61 -0
- package/hasapi-skills/hasapi-test/SKILL.md +36 -0
- package/hasapi-skills/hasapi-test/test-guide.md +147 -0
- package/hasapi-skills/hasapi-to-issues/SKILL.md +84 -0
- package/hasapi-skills/hasapi-to-prd/SKILL.md +75 -0
- package/package.json +39 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 hasapi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# hasapi
|
|
2
|
+
|
|
3
|
+
One command to set up the [Pi](https://github.com/earendil-works/pi-mono) coding agent the way I like it — a curated set of extensions plus the `hasapi-*` skills, installed globally.
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx @tplog/hasapi
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
It will:
|
|
12
|
+
|
|
13
|
+
1. Install `pi` for you if it isn't on PATH yet.
|
|
14
|
+
2. Install the curated pi extensions (`pi install <source>`).
|
|
15
|
+
3. Copy the bundled `hasapi-skills/` into `~/.pi/agent/skills/` and enable skill commands.
|
|
16
|
+
|
|
17
|
+
Install is **idempotent** — extensions already in your pi settings are skipped, and skills are re-copied so re-running updates them safely.
|
|
18
|
+
|
|
19
|
+
## Commands
|
|
20
|
+
|
|
21
|
+
| Command | What it does |
|
|
22
|
+
| --- | --- |
|
|
23
|
+
| `npx @tplog/hasapi` | Install everything (extensions + skills) |
|
|
24
|
+
| `npx @tplog/hasapi status` | Show which extensions/skills are installed |
|
|
25
|
+
| `npx @tplog/hasapi remove` | Remove the hasapi extensions and skills |
|
|
26
|
+
| `npx @tplog/hasapi doctor` | Check your environment for common problems |
|
|
27
|
+
|
|
28
|
+
## What gets installed
|
|
29
|
+
|
|
30
|
+
**Extensions** (`pi install`):
|
|
31
|
+
|
|
32
|
+
- `npm:pi-markdown-preview` — Markdown preview
|
|
33
|
+
- `npm:pi-notify` — desktop notifications
|
|
34
|
+
- `npm:@aliou/pi-processes` — background process management
|
|
35
|
+
- `npm:@plannotator/pi-extension` — planning and annotation workflow
|
|
36
|
+
- `npm:pi-mcp-adapter` — MCP server integration
|
|
37
|
+
- `npm:@narumitw/pi-goal` — goal tracking
|
|
38
|
+
- `npm:@juicesharp/rpiv-ask-user-question` — ask-user prompts
|
|
39
|
+
- `npm:context-mode` — context-mode tooling
|
|
40
|
+
- `npm:@narumitw/pi-btw` — side-chat popover
|
|
41
|
+
- `npm:pi-generative-ui` — generative UI
|
|
42
|
+
- `npm:glimpseui` — native UI widgets and dialogs
|
|
43
|
+
- `npm:pi-provider-kimi-code` — Kimi model provider
|
|
44
|
+
- `npm:pi-claude-bridge` — Claude Code provider bridge
|
|
45
|
+
- `npm:@narumitw/pi-chrome-devtools` — Chrome DevTools control
|
|
46
|
+
|
|
47
|
+
**Skills** — the `hasapi-skills/` bundle (diagnosis + implementation chain) copied to
|
|
48
|
+
`~/.pi/agent/skills/hasapi-skills/`. The folder moves as a unit so each skill's
|
|
49
|
+
`../_shared/*` references keep resolving.
|
|
50
|
+
|
|
51
|
+
## Editing the catalog
|
|
52
|
+
|
|
53
|
+
Extensions live in the `EXTENSIONS` array at the top of `bin/hasapi.mjs`. Add an entry
|
|
54
|
+
with an `id`, `source` (`npm:` or `git:`), and `description`.
|
package/bin/hasapi.mjs
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import {
|
|
4
|
+
existsSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
copyFileSync,
|
|
8
|
+
mkdirSync,
|
|
9
|
+
cpSync,
|
|
10
|
+
rmSync,
|
|
11
|
+
} from "node:fs";
|
|
12
|
+
import { homedir, platform } from "node:os";
|
|
13
|
+
import { dirname, join } from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
import { cwd } from "node:process";
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Catalog
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// pi extensions installed via `pi install <source>`. Curate to taste.
|
|
21
|
+
const EXTENSIONS = [
|
|
22
|
+
{ id: "markdown-preview", source: "npm:pi-markdown-preview", description: "Markdown preview" },
|
|
23
|
+
{ id: "notify", source: "npm:pi-notify", description: "Desktop notifications" },
|
|
24
|
+
{ id: "processes", source: "npm:@aliou/pi-processes", description: "Background process management" },
|
|
25
|
+
{ id: "plannotator", source: "npm:@plannotator/pi-extension", description: "Planning and annotation workflow" },
|
|
26
|
+
{ id: "mcp-adapter", source: "npm:pi-mcp-adapter", description: "MCP server integration" },
|
|
27
|
+
{ id: "goal", source: "npm:@narumitw/pi-goal", description: "Goal tracking" },
|
|
28
|
+
{ id: "ask-user-question", source: "npm:@juicesharp/rpiv-ask-user-question", description: "Ask-user prompts" },
|
|
29
|
+
{ id: "context-mode", source: "npm:context-mode", description: "Context-mode tooling" },
|
|
30
|
+
{ id: "btw", source: "npm:@narumitw/pi-btw", description: "Side-chat popover" },
|
|
31
|
+
{ id: "generative-ui", source: "npm:pi-generative-ui", description: "Generative UI" },
|
|
32
|
+
{ id: "glimpse", source: "npm:glimpseui", description: "Native UI widgets and dialogs" },
|
|
33
|
+
{ id: "provider-kimi-code", source: "npm:pi-provider-kimi-code", description: "Kimi model provider" },
|
|
34
|
+
{ id: "claude-bridge", source: "npm:pi-claude-bridge", description: "Claude Code provider bridge" },
|
|
35
|
+
{ id: "chrome-devtools", source: "npm:@narumitw/pi-chrome-devtools", description: "Chrome DevTools control" },
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
// Bundled skills dir, copied as a unit into ~/.pi/agent/skills/hasapi-skills.
|
|
39
|
+
// The whole folder moves together so each skill's ../_shared/* paths still resolve.
|
|
40
|
+
const SKILLS_BUNDLE_DIR = "hasapi-skills";
|
|
41
|
+
|
|
42
|
+
const PI_CORE_SPEC = "@earendil-works/pi-coding-agent";
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Small ANSI helpers (no deps)
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
48
|
+
const paint = (code) => (s) => (useColor ? `\x1b[${code}m${s}\x1b[0m` : String(s));
|
|
49
|
+
const bold = paint("1");
|
|
50
|
+
const dim = paint("2");
|
|
51
|
+
const red = paint("31");
|
|
52
|
+
const green = paint("32");
|
|
53
|
+
const yellow = paint("33");
|
|
54
|
+
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Paths
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
59
|
+
const bundledSkillsPath = () => join(HERE, "..", SKILLS_BUNDLE_DIR);
|
|
60
|
+
const settingsPath = () => join(homedir(), ".pi", "agent", "settings.json");
|
|
61
|
+
const skillsRoot = () => join(homedir(), ".pi", "agent", "skills");
|
|
62
|
+
const installedSkillsPath = () => join(skillsRoot(), SKILLS_BUNDLE_DIR);
|
|
63
|
+
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// spawn / settings
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
function spawnCommand(command, args = [], options = {}) {
|
|
68
|
+
// On Windows npm/pi resolve to .cmd shims that need a shell.
|
|
69
|
+
const shell = platform() === "win32";
|
|
70
|
+
return spawnSync(command, args, { shell, ...options });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function hasCmd(name) {
|
|
74
|
+
const probe = spawnCommand(platform() === "win32" ? "where" : "which", [name], { stdio: "ignore" });
|
|
75
|
+
return probe.status === 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function runPi(args) {
|
|
79
|
+
return spawnCommand("pi", args, { stdio: "inherit" }).status ?? 1;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function readSettings() {
|
|
83
|
+
const path = settingsPath();
|
|
84
|
+
if (!existsSync(path)) return { path, exists: false, parsed: {}, error: null };
|
|
85
|
+
try {
|
|
86
|
+
return { path, exists: true, parsed: JSON.parse(readFileSync(path, "utf8")), error: null };
|
|
87
|
+
} catch (err) {
|
|
88
|
+
return { path, exists: true, parsed: null, error: err instanceof Error ? err.message : String(err) };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function writeSettings(mutate) {
|
|
93
|
+
const current = readSettings();
|
|
94
|
+
if (current.error) return { ok: false, path: current.path, error: current.error };
|
|
95
|
+
const settings = current.parsed ?? {};
|
|
96
|
+
if (!mutate(settings)) return { ok: true, path: current.path, changed: false };
|
|
97
|
+
mkdirSync(dirname(current.path), { recursive: true });
|
|
98
|
+
if (current.exists) {
|
|
99
|
+
const stamp = new Date().toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
|
|
100
|
+
copyFileSync(current.path, `${current.path}.hasapi.${stamp}.bak`);
|
|
101
|
+
}
|
|
102
|
+
writeFileSync(current.path, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
103
|
+
return { ok: true, path: current.path, changed: true };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function installedSources() {
|
|
107
|
+
const { parsed } = readSettings();
|
|
108
|
+
const set = new Set();
|
|
109
|
+
for (const entry of parsed?.packages ?? []) {
|
|
110
|
+
if (typeof entry === "string") set.add(entry);
|
|
111
|
+
else if (entry && typeof entry.source === "string") set.add(entry.source);
|
|
112
|
+
}
|
|
113
|
+
return set;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
// install steps
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
function ensurePi() {
|
|
120
|
+
if (hasCmd("pi")) return true;
|
|
121
|
+
console.log(yellow("`pi` not found on PATH."));
|
|
122
|
+
console.log(`Installing Pi via \`npm install -g ${PI_CORE_SPEC}\``);
|
|
123
|
+
const code = spawnCommand("npm", ["install", "-g", PI_CORE_SPEC], { stdio: "inherit" }).status;
|
|
124
|
+
if (code !== 0) {
|
|
125
|
+
console.error(red(`Failed to install Pi. You may need sudo:\n sudo npm install -g ${PI_CORE_SPEC}`));
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
if (!hasCmd("pi")) {
|
|
129
|
+
console.error(red("Installed Pi, but `pi` is still not on PATH. Open a new shell and re-run."));
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function installExtensions() {
|
|
136
|
+
const installed = installedSources();
|
|
137
|
+
let failures = 0;
|
|
138
|
+
for (const ext of EXTENSIONS) {
|
|
139
|
+
if (installed.has(ext.source)) {
|
|
140
|
+
console.log(dim(` • ${ext.id} — already installed`));
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
console.log(`→ pi install ${ext.source}`);
|
|
144
|
+
if (runPi(["install", ext.source]) !== 0) {
|
|
145
|
+
console.error(red(` ✗ failed to install ${ext.id}`));
|
|
146
|
+
failures++;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return failures;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function installSkills() {
|
|
153
|
+
const src = bundledSkillsPath();
|
|
154
|
+
if (!existsSync(src)) {
|
|
155
|
+
console.error(red(`Bundled skills not found at ${src}`));
|
|
156
|
+
return 1;
|
|
157
|
+
}
|
|
158
|
+
const dest = installedSkillsPath();
|
|
159
|
+
mkdirSync(skillsRoot(), { recursive: true });
|
|
160
|
+
cpSync(src, dest, { recursive: true });
|
|
161
|
+
console.log(green(` ✓ skills → ${dest}`));
|
|
162
|
+
return 0;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function enableSkillCommands() {
|
|
166
|
+
const result = writeSettings((s) => {
|
|
167
|
+
if (s.enableSkillCommands === true) return false;
|
|
168
|
+
s.enableSkillCommands = true;
|
|
169
|
+
return true;
|
|
170
|
+
});
|
|
171
|
+
if (!result.ok) {
|
|
172
|
+
console.error(red(`Could not update settings (${result.error}). Set "enableSkillCommands": true manually.`));
|
|
173
|
+
return 1;
|
|
174
|
+
}
|
|
175
|
+
if (result.changed) console.log(green(' ✓ enableSkillCommands: true'));
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
// commands
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
function cmdInstall() {
|
|
183
|
+
console.log(bold("hasapi") + dim(" — installing onto pi\n"));
|
|
184
|
+
if (!ensurePi()) return 127;
|
|
185
|
+
|
|
186
|
+
console.log(bold("Extensions"));
|
|
187
|
+
const extFailures = installExtensions();
|
|
188
|
+
|
|
189
|
+
console.log(bold("\nSkills"));
|
|
190
|
+
const skillFailures = installSkills();
|
|
191
|
+
const settingsFailures = enableSkillCommands();
|
|
192
|
+
|
|
193
|
+
const failures = extFailures + skillFailures + settingsFailures;
|
|
194
|
+
console.log("");
|
|
195
|
+
if (failures > 0) {
|
|
196
|
+
console.error(red(`Done with ${failures} failure(s).`));
|
|
197
|
+
return 1;
|
|
198
|
+
}
|
|
199
|
+
console.log(green("Done. Run `pi` to start."));
|
|
200
|
+
return 0;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function cmdStatus() {
|
|
204
|
+
const installed = installedSources();
|
|
205
|
+
console.log(bold("Extensions"));
|
|
206
|
+
for (const ext of EXTENSIONS) {
|
|
207
|
+
const ok = installed.has(ext.source);
|
|
208
|
+
console.log(` ${ok ? green("✓") : yellow("•")} ${ext.id} ${dim(ext.source)}`);
|
|
209
|
+
}
|
|
210
|
+
console.log(bold("\nSkills"));
|
|
211
|
+
const skillsOk = existsSync(installedSkillsPath());
|
|
212
|
+
console.log(` ${skillsOk ? green("✓") : yellow("•")} ${SKILLS_BUNDLE_DIR} ${dim(installedSkillsPath())}`);
|
|
213
|
+
const { parsed } = readSettings();
|
|
214
|
+
const cmdsOn = parsed?.enableSkillCommands === true;
|
|
215
|
+
console.log(` ${cmdsOn ? green("✓") : yellow("•")} enableSkillCommands`);
|
|
216
|
+
return 0;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function cmdRemove() {
|
|
220
|
+
let failures = 0;
|
|
221
|
+
const installed = installedSources();
|
|
222
|
+
console.log(bold("Removing extensions"));
|
|
223
|
+
for (const ext of EXTENSIONS) {
|
|
224
|
+
if (!installed.has(ext.source)) {
|
|
225
|
+
console.log(dim(` • ${ext.id} — not installed`));
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
console.log(`→ pi remove ${ext.source}`);
|
|
229
|
+
if (runPi(["remove", ext.source]) !== 0) {
|
|
230
|
+
console.error(red(` ✗ failed to remove ${ext.id}`));
|
|
231
|
+
failures++;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
console.log(bold("\nRemoving skills"));
|
|
235
|
+
const dest = installedSkillsPath();
|
|
236
|
+
if (existsSync(dest)) {
|
|
237
|
+
rmSync(dest, { recursive: true, force: true });
|
|
238
|
+
console.log(green(` ✓ removed ${dest}`));
|
|
239
|
+
} else {
|
|
240
|
+
console.log(dim(` • skills not present`));
|
|
241
|
+
}
|
|
242
|
+
return failures > 0 ? 1 : 0;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function cmdDoctor() {
|
|
246
|
+
const checks = [];
|
|
247
|
+
const [major] = process.versions.node.split(".").map(Number);
|
|
248
|
+
checks.push([major >= 20, `Node ${process.versions.node} (need >=20)`]);
|
|
249
|
+
checks.push([hasCmd("pi"), "`pi` on PATH"]);
|
|
250
|
+
const s = readSettings();
|
|
251
|
+
checks.push([!s.error, s.error ? `settings.json invalid JSON (${s.error})` : `settings.json readable`]);
|
|
252
|
+
checks.push([existsSync(bundledSkillsPath()), `bundled skills present`]);
|
|
253
|
+
let ok = true;
|
|
254
|
+
for (const [pass, label] of checks) {
|
|
255
|
+
console.log(` ${pass ? green("✓") : red("✗")} ${label}`);
|
|
256
|
+
if (!pass) ok = false;
|
|
257
|
+
}
|
|
258
|
+
return ok ? 0 : 1;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function cmdHelp() {
|
|
262
|
+
console.log(`${bold("hasapi")} — install hasapi extensions + skills onto pi
|
|
263
|
+
|
|
264
|
+
Usage:
|
|
265
|
+
npx @tplog/hasapi Install everything (extensions + skills)
|
|
266
|
+
npx @tplog/hasapi status Show what's installed
|
|
267
|
+
npx @tplog/hasapi remove Remove hasapi extensions + skills
|
|
268
|
+
npx @tplog/hasapi doctor Check environment
|
|
269
|
+
npx @tplog/hasapi help Show this help`);
|
|
270
|
+
return 0;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ---------------------------------------------------------------------------
|
|
274
|
+
// entry
|
|
275
|
+
// ---------------------------------------------------------------------------
|
|
276
|
+
const command = process.argv[2] ?? "install";
|
|
277
|
+
const dispatch = {
|
|
278
|
+
install: cmdInstall,
|
|
279
|
+
status: cmdStatus,
|
|
280
|
+
remove: cmdRemove,
|
|
281
|
+
doctor: cmdDoctor,
|
|
282
|
+
help: cmdHelp,
|
|
283
|
+
"--help": cmdHelp,
|
|
284
|
+
"-h": cmdHelp,
|
|
285
|
+
};
|
|
286
|
+
const handler = dispatch[command];
|
|
287
|
+
if (!handler) {
|
|
288
|
+
console.error(red(`Unknown command: ${command}`));
|
|
289
|
+
cmdHelp();
|
|
290
|
+
process.exit(2);
|
|
291
|
+
}
|
|
292
|
+
process.exit(handler());
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# hasapi-skills(刃 · pi 定制包)
|
|
2
|
+
|
|
3
|
+
> HasaPi(刃)= 刀刃。开发应如刀:**需求抓准,精准实现。**
|
|
4
|
+
> 诊断层来自 brooks-lint(建立在 Frederick Brooks《人月神话》等经典之上,
|
|
5
|
+
> 文本中保留这些作者引用——那是刀刃赖以锋利的钢材),
|
|
6
|
+
> 实现层来自 mattpocock 的执行链。统一以 hasapi-* 命名。
|
|
7
|
+
|
|
8
|
+
## 安装
|
|
9
|
+
|
|
10
|
+
unzip hasapi-skills.zip -d ~/.pi/agent/skills/
|
|
11
|
+
|
|
12
|
+
得到 ~/.pi/agent/skills/hasapi-skills/...,pi 递归发现全部 15 个 skill。
|
|
13
|
+
确保 ~/.pi/agent/settings.json 内 "enableSkillCommands": true。
|
|
14
|
+
|
|
15
|
+
> 关键:_shared/ 必须与 hasapi-* 同级(本包已保证)。诊断类 skill 用
|
|
16
|
+
> ../_shared/common.md 等相对路径读共享文件,挪动布局会断。
|
|
17
|
+
|
|
18
|
+
## 15 个 skill(+ _shared 资源)
|
|
19
|
+
|
|
20
|
+
诊断/质检(6,多数自动触发)—— 钢之背,负责"看得准":
|
|
21
|
+
hasapi-health hasapi-audit hasapi-review hasapi-test hasapi-debt hasapi-sweep
|
|
22
|
+
实现链(7)—— 刃之锋,负责"切得正":
|
|
23
|
+
hasapi-grilling hasapi-to-prd hasapi-to-issues hasapi-implement
|
|
24
|
+
hasapi-tdd hasapi-handoff hasapi-setup
|
|
25
|
+
日常高频(2,自动触发):
|
|
26
|
+
hasapi-diagnosing-bugs(查具体 bug) hasapi-resolving-merge-conflicts(解合并冲突)
|
|
27
|
+
|
|
28
|
+
_shared/ 不是 skill(无 SKILL.md),是诊断层共享参考文件,勿删勿改名。
|
|
29
|
+
|
|
30
|
+
## 自动 vs 手动
|
|
31
|
+
|
|
32
|
+
- 自然说话即自动触发:hasapi-{health,audit,review,test,debt,sweep,grilling,tdd,
|
|
33
|
+
diagnosing-bugs,resolving-merge-conflicts}
|
|
34
|
+
- 必须手动 /skill:name:hasapi-{to-prd,to-issues,implement,handoff,setup}
|
|
35
|
+
(凡是"改外部世界 / 切阶段"的,扳机留给人)
|
|
36
|
+
|
|
37
|
+
## 推荐节奏(头尾诊断、中间实现)
|
|
38
|
+
|
|
39
|
+
1. /skill:hasapi-health 进场体检,拿 baseline
|
|
40
|
+
2. hasapi-grilling 逼清需求(刀要抓准的地方,人重仓)
|
|
41
|
+
3. /skill:hasapi-to-prd(大功能)→ /skill:hasapi-to-issues 落成 ready-for-agent
|
|
42
|
+
4. /skill:hasapi-implement + hasapi-tdd 精准实现(人可 AFK)
|
|
43
|
+
5. hasapi-review / hasapi-test 质检(人守闸门,逐条 accept/dismiss)
|
|
44
|
+
6. hasapi-debt 记债进 backlog(可喂回 to-issues)
|
|
45
|
+
7. /skill:hasapi-handoff <重点> 趁上下文干净,存档换会话
|
|
46
|
+
|
|
47
|
+
日常:bug 用 hasapi-diagnosing-bugs;合并卡住用 hasapi-resolving-merge-conflicts。
|
|
48
|
+
|
|
49
|
+
## 注意
|
|
50
|
+
|
|
51
|
+
- 每个项目首次使用前跑一次 /skill:hasapi-setup 配好 GitHub issue tracker,
|
|
52
|
+
否则 implement / to-issues 不知道去哪取活、往哪写。
|
|
53
|
+
- hasapi-sweep 会自动改代码:只在熟悉某库、想一键清理时手动调,陌生库别用。
|
|
54
|
+
- 故意未含 matt 的 review / architecture 类 skill——诊断走 hasapi-review,避免抢活。
|
|
55
|
+
- tdd 文本中保留了对 /codebase-design、/domain-modeling 的引用(不在本包内),
|
|
56
|
+
模型会自行适配;如需要可另行补装。
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# Brooks-Lint — Shared Framework
|
|
2
|
+
|
|
3
|
+
Code and test quality diagnosis using principles from twelve classic software engineering books.
|
|
4
|
+
Use `source-coverage.md` to keep those sources grounded in real evidence, exceptions, and tradeoffs.
|
|
5
|
+
|
|
6
|
+
## The Iron Law
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
NEVER suggest fixes before completing risk diagnosis.
|
|
10
|
+
EVERY finding must follow: Symptom → Source → Consequence → Remedy.
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Violating this law produces reviews that list rule violations without explaining why they
|
|
14
|
+
matter. A finding without a consequence and a remedy is not a finding — it is noise.
|
|
15
|
+
|
|
16
|
+
> **On-demand sections (skip unless the condition applies):**
|
|
17
|
+
> - "Remedy Mode" — only when user passes `--fix` or asks to fix findings
|
|
18
|
+
> - "Post-Report Triage" — only in interactive sessions after the report is output
|
|
19
|
+
> - "History Tracking" — only after the Health Score is computed
|
|
20
|
+
|
|
21
|
+
## Project Config
|
|
22
|
+
|
|
23
|
+
Before executing the review, attempt to read `.brooks-lint.yaml` from the project root.
|
|
24
|
+
If the file exists, parse and apply its settings before proceeding.
|
|
25
|
+
If the file does not exist, continue with defaults (all risks enabled, no ignores).
|
|
26
|
+
|
|
27
|
+
In a multi-mode session, re-read only if the user says the config has changed.
|
|
28
|
+
|
|
29
|
+
### Supported settings
|
|
30
|
+
|
|
31
|
+
**`disable`** — list of risk codes to skip entirely. Findings for disabled risks are
|
|
32
|
+
silently omitted from the report and do not affect the Health Score.
|
|
33
|
+
Valid codes: `R1` `R2` `R3` `R4` `R5` `R6` `T1` `T2` `T3` `T4` `T5` `T6`
|
|
34
|
+
|
|
35
|
+
**`severity`** — override the severity of a specific risk for this project.
|
|
36
|
+
Valid values: `critical` `warning` `suggestion`
|
|
37
|
+
Example: `R1: suggestion` means every R1 finding is downgraded to Suggestion regardless
|
|
38
|
+
of what the guide says.
|
|
39
|
+
|
|
40
|
+
**`ignore`** — list of glob patterns. Files matching any pattern are excluded from
|
|
41
|
+
analysis. Findings that arise solely from ignored files are omitted.
|
|
42
|
+
Common entries: `**/*.generated.*`, `**/vendor/**`, `**/migrations/**`
|
|
43
|
+
|
|
44
|
+
**`focus`** — non-empty list of risk codes to evaluate; all others are skipped.
|
|
45
|
+
Omit this key (or leave it empty) to evaluate all non-disabled risks.
|
|
46
|
+
Cannot be combined with a non-empty `disable` list.
|
|
47
|
+
|
|
48
|
+
**Minimal example:**
|
|
49
|
+
```yaml
|
|
50
|
+
version: 1
|
|
51
|
+
disable:
|
|
52
|
+
- T5
|
|
53
|
+
severity:
|
|
54
|
+
R1: suggestion
|
|
55
|
+
ignore:
|
|
56
|
+
- "**/*.generated.*"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
If `.brooks-lint.yaml` contains a `custom_risks` map, read `custom-risks-guide.md`
|
|
60
|
+
from the `_shared/` directory for loading and scanning instructions.
|
|
61
|
+
|
|
62
|
+
### Config Validation
|
|
63
|
+
|
|
64
|
+
Before applying, check for errors and mention each in the report:
|
|
65
|
+
- Invalid risk code (not R1–R6, T1–T6, or a defined `Cx` code): skip it, note `"Config warning: X is not a valid risk code"`
|
|
66
|
+
- Invalid severity value (not `critical`/`warning`/`suggestion`): skip it, note the error
|
|
67
|
+
- Both `disable` and `focus` are non-empty: treat as a config error, ignore both, note it
|
|
68
|
+
|
|
69
|
+
If the YAML fails to parse entirely, skip config loading and proceed with defaults.
|
|
70
|
+
|
|
71
|
+
### Config Reporting
|
|
72
|
+
|
|
73
|
+
If a config file was found and applied, add this line immediately after the **Scope** line
|
|
74
|
+
in the report:
|
|
75
|
+
`Config: .brooks-lint.yaml applied (N risks disabled, M paths ignored)`
|
|
76
|
+
|
|
77
|
+
Include N and M even if zero. Omit this line if no config file was found.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Auto Scope Detection
|
|
82
|
+
|
|
83
|
+
When no files or code are specified, detect scope automatically:
|
|
84
|
+
|
|
85
|
+
**PR Review:** `git diff --cached` → `git diff` → `git diff main...HEAD` → ask user.
|
|
86
|
+
|
|
87
|
+
**Architecture Audit / Tech Debt:** Entire project by default. `--since=<ref>`: run `git diff <ref>...HEAD --name-only`, analyze only modules containing changed files; note "Incremental audit — modules touched since <ref>".
|
|
88
|
+
|
|
89
|
+
**Test Quality:** All test files by default. If a diff exists, prioritize test files co-located with changed production files (`src/foo.ts` → `src/foo.test.ts`).
|
|
90
|
+
|
|
91
|
+
**Health Dashboard:** Entire project by default. If user provides a path, scope all dimension sub-scans to that path.
|
|
92
|
+
|
|
93
|
+
**Scope line:** Always state what was detected — e.g., `Scope: staged changes (3 files)` or `Scope: branch changes vs main (12 files)`.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## The Six Decay Risks
|
|
98
|
+
|
|
99
|
+
Navigation index only — canonical definitions (symptoms, severity guides, sources, "What Not
|
|
100
|
+
to Flag" guards) live in `decay-risks.md`. Do not duplicate or edit diagnostic questions here;
|
|
101
|
+
update `decay-risks.md` directly. Book-level coverage, exceptions, and tradeoffs are in
|
|
102
|
+
`source-coverage.md`.
|
|
103
|
+
|
|
104
|
+
| Risk | Diagnostic Question |
|
|
105
|
+
|------|---------------------|
|
|
106
|
+
| Cognitive Overload | How much mental effort to understand this? |
|
|
107
|
+
| Change Propagation | How many unrelated things break on one change? |
|
|
108
|
+
| Knowledge Duplication | Is the same decision expressed in multiple places? |
|
|
109
|
+
| Accidental Complexity | Is the code more complex than the problem? |
|
|
110
|
+
| Dependency Disorder | Do dependencies flow in a consistent direction? |
|
|
111
|
+
| Domain Model Distortion | Does the code faithfully represent the domain? |
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Report Template
|
|
116
|
+
|
|
117
|
+
**Language rule:** Output the report in the same language the user is using. Translate the
|
|
118
|
+
per-finding content and the one-sentence verdict to match the user's language. Keep the
|
|
119
|
+
following in English: Iron Law field labels (Symptom / Source / Consequence / Remedy),
|
|
120
|
+
book titles, principle and smell names (e.g. "Shotgun Surgery", "Divergent Change"),
|
|
121
|
+
and fixed structural headers from the template below (`Findings`, `Summary`,
|
|
122
|
+
`Module Dependency Graph`, `Critical`, `Warning`, `Suggestion`).
|
|
123
|
+
|
|
124
|
+
````
|
|
125
|
+
# Brooks-Lint Review
|
|
126
|
+
|
|
127
|
+
**Mode:** [PR Review / Architecture Audit / Tech Debt Assessment / Test Quality Review]
|
|
128
|
+
**Scope:** [file(s), directory, or description of what was reviewed]
|
|
129
|
+
**Health Score:** XX/100
|
|
130
|
+
|
|
131
|
+
[One sentence overall verdict]
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Module Dependency Graph
|
|
136
|
+
|
|
137
|
+
<!-- Mode 2 (Architecture Audit) ONLY — omit this section for other modes -->
|
|
138
|
+
<!-- classDef colors: see architecture-guide.md Step 1 Rule 6 -->
|
|
139
|
+
|
|
140
|
+
```mermaid
|
|
141
|
+
graph TD
|
|
142
|
+
...
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Findings
|
|
148
|
+
|
|
149
|
+
<!-- Sort all findings by severity: Critical first, then Warning, then Suggestion -->
|
|
150
|
+
<!-- If no findings in a severity tier, omit that tier's heading -->
|
|
151
|
+
|
|
152
|
+
### 🔴 Critical
|
|
153
|
+
|
|
154
|
+
**[Risk Name] — [Short descriptive title]**
|
|
155
|
+
Symptom: [exactly what was observed in the code]
|
|
156
|
+
Source: [Book title — Principle or Smell name]
|
|
157
|
+
Consequence: [what breaks or gets worse if this is not fixed]
|
|
158
|
+
Remedy: [concrete, specific action]
|
|
159
|
+
|
|
160
|
+
### 🟡 Warning
|
|
161
|
+
|
|
162
|
+
**[Risk Name] — [Short descriptive title]**
|
|
163
|
+
Symptom: ...
|
|
164
|
+
Source: ...
|
|
165
|
+
Consequence: ...
|
|
166
|
+
Remedy: ...
|
|
167
|
+
|
|
168
|
+
### 🟢 Suggestion
|
|
169
|
+
|
|
170
|
+
**[Risk Name] — [Short descriptive title]**
|
|
171
|
+
Symptom: ...
|
|
172
|
+
Source: ...
|
|
173
|
+
Consequence: ...
|
|
174
|
+
Remedy: ...
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Summary
|
|
179
|
+
|
|
180
|
+
[2–3 sentences: what is the most important action, and what is the overall trend]
|
|
181
|
+
````
|
|
182
|
+
|
|
183
|
+
## Remedy Mode
|
|
184
|
+
|
|
185
|
+
When the user passes `--fix` or asks to "fix the findings", read
|
|
186
|
+
`remedy-guide.md` from the `_shared/` directory before writing the report.
|
|
187
|
+
|
|
188
|
+
## Health Score Calculation
|
|
189
|
+
|
|
190
|
+
Base score: 100
|
|
191
|
+
Deductions:
|
|
192
|
+
- Each 🔴 Critical finding: −15
|
|
193
|
+
- Each 🟡 Warning finding: −5
|
|
194
|
+
- Each 🟢 Suggestion finding: −1
|
|
195
|
+
Floor: 0 (score cannot go below 0)
|
|
196
|
+
|
|
197
|
+
## History Tracking
|
|
198
|
+
|
|
199
|
+
After generating the Health Score, attempt to append a record to `.brooks-lint-history.json`
|
|
200
|
+
in the project root.
|
|
201
|
+
|
|
202
|
+
**Append logic:**
|
|
203
|
+
1. Read the file (or start with empty array if it doesn't exist)
|
|
204
|
+
2. Append: `{ date, mode, score, findings: { critical, warning, suggestion }, scope }`
|
|
205
|
+
3. Write the file back
|
|
206
|
+
|
|
207
|
+
**Trend display:** If the history file exists and contains at least one prior record for
|
|
208
|
+
the same mode, add a Trend line after the Health Score in the report:
|
|
209
|
+
|
|
210
|
+
**Trend:** 85 → 82 (−3) over last 3 runs
|
|
211
|
+
|
|
212
|
+
Show the most recent prior score and the delta. If delta is 0: "Stable at 82".
|
|
213
|
+
If this is the first run for this mode: "First run — no trend data".
|
|
214
|
+
|
|
215
|
+
## Post-Report Triage (Optional)
|
|
216
|
+
|
|
217
|
+
**Guard:** Interactive sessions only — skip in CI/headless mode.
|
|
218
|
+
|
|
219
|
+
After reporting Warning or Suggestion findings, offer:
|
|
220
|
+
> Would you like to triage these findings? (accept / dismiss / defer / skip)
|
|
221
|
+
|
|
222
|
+
For each finding one at a time (lowest severity first): show title, ask `[a]ccept / [d]ismiss / [f]defer / [s]kip`; wait for reply before moving to the next.
|
|
223
|
+
|
|
224
|
+
**Dismiss:** ask one-line reason → append to `.brooks-lint.yaml` under `suppress:` → downgraded to info in future runs.
|
|
225
|
+
|
|
226
|
+
**Defer:** same as dismiss, add `expires: YYYY-MM-DD` (default 90 days) → resurfaces at original severity after expiry.
|
|
227
|
+
|
|
228
|
+
**Suppress matching at scan time:** for each `suppress:` entry, match `risk` code and file `pattern` against findings.
|
|
229
|
+
- Both match → downgrade to info (not counted in Health Score, shown under collapsed "Suppressed" section).
|
|
230
|
+
- `expires` is past → ignore entry, finding resurfaces. Note in Summary: "N suppressed findings have expired and are now active again."
|
|
231
|
+
|
|
232
|
+
## Reference Files
|
|
233
|
+
|
|
234
|
+
Read on demand:
|
|
235
|
+
|
|
236
|
+
| File | When to Read |
|
|
237
|
+
|------|-------------|
|
|
238
|
+
| `source-coverage.md` | At the start of every review, before writing findings |
|
|
239
|
+
| `decay-risks.md` | Before any production-code review or architecture/debt assessment |
|
|
240
|
+
| `test-decay-risks.md` | Before any test review and before the PR Review "Quick Test Check" step |
|