@punks/cli 1.0.1 → 1.0.3
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/README.md +47 -4
- package/dist/data/AGENTS.md +0 -6
- package/dist/data/catalog/hooks.ts +26 -0
- package/dist/data/catalog/lint.ts +11 -26
- package/dist/data/catalog/packs.ts +5 -3
- package/dist/data/catalog/skills.ts +9 -1
- package/dist/data/catalog/tools.ts +13 -1
- package/dist/data/scripts/sync-subagents.mjs +163 -120
- package/dist/data/subagents/manifest.mjs +148 -0
- package/dist/index.js +2589 -1944
- package/dist/skills/agnostic/backend/logging-best-practices/SKILL.md +127 -0
- package/dist/skills/agnostic/backend/logging-best-practices/rules/context.md +157 -0
- package/dist/skills/agnostic/backend/logging-best-practices/rules/pitfalls.md +118 -0
- package/dist/skills/agnostic/backend/logging-best-practices/rules/structure.md +193 -0
- package/dist/skills/agnostic/backend/logging-best-practices/rules/wide-events.md +113 -0
- package/dist/skills/agnostic/cli/dp-cli/SKILL.md +84 -0
- package/dist/skills/agnostic/cli/dp-cli/references/commands.md +33 -0
- package/dist/skills/agnostic/cli/dp-cli/references/post-command-flow.md +47 -0
- package/dist/skills/agnostic/debug/debug-agent/SKILL.md +184 -0
- package/dist/skills/agnostic/requirements/write-backlog/REFERENCE.md +1 -1
- package/dist/skills/languages/python/async-python-patterns/SKILL.md +735 -0
- package/dist/skills/languages/python/python-code-style/SKILL.md +360 -0
- package/dist/skills/languages/python/python-design-patterns/SKILL.md +433 -0
- package/dist/skills/languages/python/python-project-structure/SKILL.md +252 -0
- package/dist/skills/languages/python/python-testing-patterns/SKILL.md +622 -0
- package/dist/skills/languages/python/python-testing-patterns/references/advanced-patterns.md +411 -0
- package/dist/skills/languages/typescript/quality-types/SKILL.md +93 -0
- package/docs/README.md +14 -4
- package/docs/reference/dp-requirements.md +16 -1
- package/docs/runbooks/dp-cli-scaffolding.md +82 -10
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Devpunks CLI
|
|
2
2
|
|
|
3
3
|
`punks` scaffolds the AI operating context for each project phase, from requirements and backlog prep to the repo-aware setup pass.
|
|
4
|
-
This repo
|
|
5
|
-
|
|
4
|
+
This repo carries the executable CLI plus the canonical bundled scaffold baseline under `src/data` and `skills/`.
|
|
5
|
+
At runtime, scaffold content resolves from the latest stable GitHub release baseline with bundled fallback; the public [wearedevpunks/skills](https://github.com/wearedevpunks/skills) repo remains only the Vercel skills CLI interface.
|
|
6
6
|
|
|
7
7
|
## Commands
|
|
8
8
|
|
|
@@ -27,6 +27,7 @@ punks scaffold setup
|
|
|
27
27
|
punks scaffold setup -i /path/to/repo
|
|
28
28
|
punks scaffold setup -i /path/to/repo -o /path/to/output
|
|
29
29
|
bun run sync:skills
|
|
30
|
+
bun run baseline:build
|
|
30
31
|
```
|
|
31
32
|
|
|
32
33
|
`bun run sync:skills` updates a local cache clone of `wearedevpunks/skills` and then replaces only `skills/`, overwriting any local changes there.
|
|
@@ -35,7 +36,21 @@ bun run sync:skills
|
|
|
35
36
|
|
|
36
37
|
`punks scaffold backlog` does not currently copy any stage-specific packs. It prints the fixed operator prompt for the backlog gate so the lifecycle stays explicit.
|
|
37
38
|
|
|
38
|
-
`punks scaffold setup` scans the target repo from `cwd` or `-i`, writes scaffold output into the scan root or `-o`, copies the selected
|
|
39
|
+
`punks scaffold setup` scans the target repo from `cwd` or `-i`, resolves the active scaffold baseline, writes scaffold output into the scan root or `-o`, copies the selected baseline skills, resolves pack-owned lint assets into `.devpunks/specs/lint/`, and ensures required global tool prerequisites for those skills.
|
|
40
|
+
|
|
41
|
+
Use `--baseline bundled` to force the npm-shipped baseline, or `--refresh-baseline` to refetch the stable remote baseline.
|
|
42
|
+
|
|
43
|
+
Language packs are detected separately from framework packs. TypeScript is selected when scanned manifests depend on `typescript` or when nested source files include `.ts` / `.tsx`; Python is selected from nested `.py` files. Root-level config files and vendor, virtualenv, generated, scaffold, docs, examples, `scripts`, `opensrc`, cache, and build output directories are ignored.
|
|
44
|
+
|
|
45
|
+
The default subagent manifest includes Python templates that combine the language skills into `python-app`, `python-async`, and `python-testing` specialists.
|
|
46
|
+
|
|
47
|
+
To install only the local `dp-cli` operator skill from the public skills repo checkout:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npx skills add /Users/stefan/Desktop/repos/wearedevpunks-skills --skill dp-cli --full-depth
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The skill source lives at `/Users/stefan/Desktop/repos/wearedevpunks-skills/skills/agnostic/cli/dp-cli`.
|
|
39
54
|
|
|
40
55
|
The lint scaffold is intentionally agent-facing rather than repo-mutating:
|
|
41
56
|
|
|
@@ -47,13 +62,41 @@ The lint scaffold is intentionally agent-facing rather than repo-mutating:
|
|
|
47
62
|
Current scaffold-managed global tools:
|
|
48
63
|
|
|
49
64
|
- `agent-browser`
|
|
65
|
+
- `debug-agent`
|
|
50
66
|
- `opensrc`
|
|
67
|
+
- `portless`
|
|
68
|
+
- `skills`
|
|
69
|
+
|
|
70
|
+
On startup, `punks` checks npm for a newer CLI version. When it can infer whether the installed binary came from Bun, pnpm, or npm, it updates itself with the same global package manager; otherwise it prints a manual update hint. If the `dp-cli` operator skill is installed in the current project or globally, startup also asks the `skills` CLI to update that skill. Set `DP_NO_UPDATE_CHECK=1` or `DP_NO_SKILL_UPDATE_CHECK=1` to skip those checks.
|
|
71
|
+
|
|
72
|
+
## Publishing
|
|
73
|
+
|
|
74
|
+
Use one command per release type:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
bun run baseline:publish
|
|
78
|
+
bun run release:publish
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`bun run baseline:publish` builds the current scaffold baseline, creates or updates the `baseline/stable/<date>-<sha>` GitHub release, and uploads the baseline manifest plus tarball. Use it when scaffold data changes but the npm executable does not need a new version.
|
|
82
|
+
|
|
83
|
+
`bun run release:publish` publishes the current `package.json` version to npm, updates the `latest` and `next` dist-tags, and pushes the matching `v<version>` git tag. Commit and push the version bump before running it; the script refuses dirty worktrees and existing tags that point at another commit.
|
|
84
|
+
|
|
85
|
+
Before npm releases, authenticate against the public npm registry:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npm login --registry=https://registry.npmjs.org/
|
|
89
|
+
npm whoami
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The logged-in account must have publish access to `@punks/cli`.
|
|
51
93
|
|
|
52
94
|
## Development
|
|
53
95
|
|
|
54
96
|
```bash
|
|
55
97
|
bun install
|
|
56
98
|
bun run build
|
|
99
|
+
bun run baseline:build
|
|
57
100
|
bun run test
|
|
58
101
|
bun run check-types
|
|
59
102
|
```
|
package/dist/data/AGENTS.md
CHANGED
|
@@ -98,12 +98,6 @@ Strong success criteria let you loop independently. Weak criteria ("make it work
|
|
|
98
98
|
|
|
99
99
|
In readonly scenarios ONLY spawn subagents aggressively for parallelization opportunities outside code writing. Examples: research, auditing, docs updating, investigating multiple hypotheses, exploring unrelated areas. Fan out whenever possible, synthesize findings before acting.
|
|
100
100
|
|
|
101
|
-
## Harness Skill Layout
|
|
102
|
-
|
|
103
|
-
- Keep `.agents/skills/` as the main project-local skill directory.
|
|
104
|
-
- Keep `.claude/skills` as a symlink to `.agents/skills` for Claude compatibility.
|
|
105
|
-
- Do not create `.codex/skills`, `.cursor/skills`, or `.opencode/skills`; Codex, Cursor, and OpenCode should read from `.agents/skills`.
|
|
106
|
-
|
|
107
101
|
## Notes — Cross-session Memory
|
|
108
102
|
|
|
109
103
|
Notebook at `.agents/notes/`. Shared memory across sessions. Never ask permission.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface HookDefinition {
|
|
2
|
+
readonly id: string;
|
|
3
|
+
readonly harness: "shared";
|
|
4
|
+
readonly outputPath: string;
|
|
5
|
+
readonly description: string;
|
|
6
|
+
readonly sourcePath: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const hookCatalog = [
|
|
10
|
+
{
|
|
11
|
+
id: "format-edited-file",
|
|
12
|
+
harness: "shared",
|
|
13
|
+
outputPath: ".agents/hooks/format-edited-file.mjs",
|
|
14
|
+
description:
|
|
15
|
+
"Shared hook that auto-formats edited JS/TS/JSON files and lint-checks product files after tool use.",
|
|
16
|
+
sourcePath: "hooks/format-edited-file.mjs",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: "require-tests-for-pr",
|
|
20
|
+
harness: "shared",
|
|
21
|
+
outputPath: ".agents/hooks/require-tests-for-pr.mjs",
|
|
22
|
+
description:
|
|
23
|
+
"Shared hook that blocks PR creation while tests are failing.",
|
|
24
|
+
sourcePath: "hooks/require-tests-for-pr.mjs",
|
|
25
|
+
},
|
|
26
|
+
] as const satisfies ReadonlyArray<HookDefinition>;
|
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
export type LintAssetId =
|
|
2
|
-
| "effect-no-barrel-imports"
|
|
3
|
-
| "next-core"
|
|
4
|
-
| "react-core"
|
|
5
|
-
| "react-hooks"
|
|
6
|
-
| "react-you-might-not-need-an-effect"
|
|
7
|
-
| "tanstack-query-core"
|
|
8
|
-
| "vitest-core";
|
|
1
|
+
export type LintAssetId = string;
|
|
9
2
|
|
|
10
3
|
export interface LintJsPluginDefinition {
|
|
11
4
|
readonly name: string;
|
|
@@ -32,31 +25,24 @@ export interface LintAssetDefinition {
|
|
|
32
25
|
|
|
33
26
|
const sharedReactPlacement = {
|
|
34
27
|
recommendedConfigFile: ".oxlintrc.json",
|
|
35
|
-
recommendedFiles: [
|
|
36
|
-
|
|
37
|
-
"packages/ui/**/*.{js,jsx,ts,tsx}",
|
|
38
|
-
],
|
|
39
|
-
recommendedWorkspaces: ["apps/web", "packages/ui"],
|
|
28
|
+
recommendedFiles: ["<detected-react-workspace>/**/*.{js,jsx,ts,tsx}"],
|
|
29
|
+
recommendedWorkspaces: ["detected React workspaces"],
|
|
40
30
|
placementNotes:
|
|
41
|
-
"Add as an override for React-rendered code. Keep
|
|
31
|
+
"Add as an override for detected React-rendered code. Keep backend packages outside this override unless they render React.",
|
|
42
32
|
} as const satisfies LintPlacementDefinition;
|
|
43
33
|
|
|
44
34
|
const webOnlyPlacement = {
|
|
45
35
|
recommendedConfigFile: ".oxlintrc.json",
|
|
46
|
-
recommendedFiles: ["
|
|
47
|
-
recommendedWorkspaces: ["
|
|
36
|
+
recommendedFiles: ["<detected-nextjs-workspace>/**/*.{js,jsx,ts,tsx}"],
|
|
37
|
+
recommendedWorkspaces: ["detected Next.js workspaces"],
|
|
48
38
|
placementNotes:
|
|
49
39
|
"Place this in the Next.js app override. If the repo uses a different frontend app path, retarget the file globs to that workspace before composing the final Oxlint config.",
|
|
50
40
|
} as const satisfies LintPlacementDefinition;
|
|
51
41
|
|
|
52
42
|
const effectPlacement = {
|
|
53
43
|
recommendedConfigFile: ".oxlintrc.json",
|
|
54
|
-
recommendedFiles: [
|
|
55
|
-
|
|
56
|
-
"packages/api/**/*.{js,jsx,ts,tsx}",
|
|
57
|
-
"packages/db/**/*.{js,jsx,ts,tsx}",
|
|
58
|
-
],
|
|
59
|
-
recommendedWorkspaces: ["apps/server", "packages/api", "packages/db"],
|
|
44
|
+
recommendedFiles: ["<detected-effect-workspace>/**/*.{js,jsx,ts,tsx}"],
|
|
45
|
+
recommendedWorkspaces: ["detected Effect workspaces"],
|
|
60
46
|
placementNotes:
|
|
61
47
|
"Apply this only where Effect code lives. If the target repo renames or splits backend workspaces, move the override with those code paths instead of broadening it repo-wide.",
|
|
62
48
|
} as const satisfies LintPlacementDefinition;
|
|
@@ -64,11 +50,10 @@ const effectPlacement = {
|
|
|
64
50
|
const vitestPlacement = {
|
|
65
51
|
recommendedConfigFile: ".oxlintrc.json",
|
|
66
52
|
recommendedFiles: [
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"packages/api/**/*.{test,spec}.{js,jsx,ts,tsx}",
|
|
53
|
+
"<detected-test-workspace>/**/*.{test,spec}.{js,jsx,ts,tsx}",
|
|
54
|
+
"<detected-test-workspace>/src/test/**/*.{js,jsx,ts,tsx}",
|
|
70
55
|
],
|
|
71
|
-
recommendedWorkspaces: ["
|
|
56
|
+
recommendedWorkspaces: ["detected JavaScript/TypeScript test workspaces"],
|
|
72
57
|
placementNotes:
|
|
73
58
|
"Attach this to test-only globs. Expand or narrow the file patterns to match the repo's actual Vitest test layout instead of treating it as a global default.",
|
|
74
59
|
} as const satisfies LintPlacementDefinition;
|
|
@@ -23,10 +23,11 @@ export interface PackCatalogEntry {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export const packCatalog = [
|
|
26
|
-
{ id: "backend", category: "surface", triggerPackages: [], description: "Backend pack: domain structure
|
|
26
|
+
{ id: "backend", category: "surface", triggerPackages: [], description: "Backend pack: domain structure, recoverable actions, and logging.", skills: ["backend-domain-structure", "backend-recoverable-actions", "logging-best-practices"], lintAssets: [], hooks: [], promptSurfaces: ["shared-agents", "root-prompt-spec", "workspace-prompt-spec"], promptDetails: ["shared backend domain baseline", "workspace backend action/recovery guidance", "workspace logging/observability guidance"] },
|
|
27
27
|
{ id: "better-auth", category: "detected", triggerPackages: ["better-auth"], description: "Better Auth guidance pack.", skills: ["better-auth-best-practices"], lintAssets: [], hooks: [], promptSurfaces: ["shared-agents", "root-prompt-spec", "workspace-prompt-spec"], promptDetails: ["shared auth guardrails", "workspace auth boundaries"] },
|
|
28
|
+
{ id: "debug", category: "default", triggerPackages: [], description: "Default debug pack: debug-agent runtime-evidence workflow.", skills: ["debug-agent"], lintAssets: [], hooks: [], promptSurfaces: ["shared-agents", "root-prompt-spec", "workspace-prompt-spec"], promptDetails: ["shared runtime debugging baseline", "workspace evidence-first debugging workflow"] },
|
|
28
29
|
{ id: "docs", category: "default", triggerPackages: [], description: "Default docs pack: docs-maintenance.", skills: ["docs-maintenance"], lintAssets: [], hooks: [], promptSurfaces: ["shared-agents", "root-prompt-spec", "docs-prompt-spec"], promptDetails: ["shared docs baseline", "docs scope spec"] },
|
|
29
|
-
{ id: "drizzle", category: "detected", triggerPackages: ["drizzle-kit", "drizzle-orm"], description: "Drizzle guidance pack.", skills: [
|
|
30
|
+
{ id: "drizzle", category: "detected", triggerPackages: ["drizzle-kit", "drizzle-orm"], description: "Drizzle guidance pack.", skills: [], lintAssets: [], hooks: [], promptSurfaces: ["shared-agents", "root-prompt-spec", "workspace-prompt-spec"], promptDetails: ["shared data-layer baseline", "workspace db flow rules"] },
|
|
30
31
|
{ id: "effect", category: "detected", triggerPackages: ["effect", "@effect/"], description: "Effect guidance pack family.", skills: ["effect-authoring", "effect-best-practices", "effect-backend-structure", "effect-recoverable-actions"], lintAssets: ["effect-no-barrel-imports"], hooks: [], promptSurfaces: ["shared-agents", "root-prompt-spec", "workspace-prompt-spec"], promptDetails: ["shared effect authoring baseline", "workspace service/layer rules"] },
|
|
31
32
|
{ id: "elysia", category: "detected", triggerPackages: ["elysia"], description: "Elysia guidance pack.", skills: ["elysiajs"], lintAssets: [], hooks: [], promptSurfaces: ["shared-agents", "root-prompt-spec", "workspace-prompt-spec"], promptDetails: ["shared api-server baseline", "workspace route/plugin rules"] },
|
|
32
33
|
{ id: "frontend", category: "surface", triggerPackages: [], description: "Frontend pack: agent-browser, design taste, and frontend domain structure.", skills: ["agent-browser", "design-taste-frontend", "frontend-domain-structure"], lintAssets: [], hooks: [], promptSurfaces: ["shared-agents", "root-prompt-spec", "workspace-prompt-spec"], promptDetails: ["shared product-ui baseline", "workspace UX/browser guidance", "workspace frontend domain boundaries"] },
|
|
@@ -40,5 +41,6 @@ export const packCatalog = [
|
|
|
40
41
|
{ id: "tanstack-query", category: "detected", triggerPackages: ["@tanstack/react-query"], description: "TanStack Query guidance pack.", skills: ["tanstack-query"], lintAssets: ["tanstack-query-core"], hooks: [], promptSurfaces: ["shared-agents", "root-prompt-spec", "workspace-prompt-spec"], promptDetails: ["shared tanstack query baseline", "workspace server-state rules"] },
|
|
41
42
|
{ id: "trpc", category: "detected", triggerPackages: ["@trpc/"], description: "tRPC guidance pack.", skills: [], lintAssets: [], hooks: [], promptSurfaces: ["shared-agents", "root-prompt-spec", "workspace-prompt-spec"], promptDetails: ["shared contract-first baseline", "workspace client/server boundaries"] },
|
|
42
43
|
{ id: "turborepo", category: "detected", triggerPackages: ["turbo"], description: "Turborepo guidance pack.", skills: ["turborepo"], lintAssets: [], hooks: [], promptSurfaces: ["shared-agents", "root-prompt-spec", "workspace-prompt-spec"], promptDetails: ["shared monorepo baseline", "workspace task boundary rules"] },
|
|
43
|
-
{ id: "
|
|
44
|
+
{ id: "python", category: "language", triggerPackages: [], description: "Shared Python language pack for async, style, structure, testing, and design guidance.", skills: ["async-python-patterns", "python-code-style", "python-design-patterns", "python-project-structure", "python-testing-patterns"], lintAssets: [], hooks: [], promptSurfaces: [], promptDetails: [] },
|
|
45
|
+
{ id: "typescript", category: "language", triggerPackages: ["typescript"], description: "Shared JavaScript / TypeScript language pack for hook and language-level scaffold assets.", skills: ["quality-types"], lintAssets: [], hooks: ["format-edited-file", "require-tests-for-pr"], promptSurfaces: [], promptDetails: [] },
|
|
44
46
|
] as const satisfies ReadonlyArray<PackCatalogEntry>;
|
|
@@ -15,9 +15,11 @@ export const skillCatalog = [
|
|
|
15
15
|
{ id: "agent-browser", sourceDirectory: "skills/agnostic/frontend/agent-browser", tier: "framework", language: null, framework: "browser-automation", requiresTools: ["agent-browser"] },
|
|
16
16
|
{ id: "backend-domain-structure", sourceDirectory: "skills/agnostic/backend/backend-domain-structure", tier: "agnostic", language: null, framework: null, requiresTools: [] },
|
|
17
17
|
{ id: "backend-recoverable-actions", sourceDirectory: "skills/agnostic/backend/backend-recoverable-actions", tier: "agnostic", language: null, framework: null, requiresTools: [] },
|
|
18
|
+
{ id: "logging-best-practices", sourceDirectory: "skills/agnostic/backend/logging-best-practices", tier: "agnostic", language: null, framework: null, requiresTools: [] },
|
|
18
19
|
{ id: "better-auth-best-practices", sourceDirectory: "skills/frameworks/better-auth/better-auth-best-practices", tier: "framework", language: "typescript", framework: "better-auth", requiresTools: [] },
|
|
19
20
|
{ id: "create-plan", sourceDirectory: "skills/agnostic/planning/create-plan", tier: "agnostic", language: null, framework: null, requiresTools: [] },
|
|
20
21
|
{ id: "create-spec", sourceDirectory: "skills/agnostic/planning/create-spec", tier: "agnostic", language: null, framework: null, requiresTools: [] },
|
|
22
|
+
{ id: "debug-agent", sourceDirectory: "skills/agnostic/debug/debug-agent", tier: "agnostic", language: null, framework: null, requiresTools: ["debug-agent"] },
|
|
21
23
|
{ id: "docs-maintenance", sourceDirectory: "skills/agnostic/docs/docs-maintenance", tier: "agnostic", language: null, framework: null, requiresTools: [] },
|
|
22
24
|
{ id: "effect-authoring", sourceDirectory: "skills/frameworks/effect/effect-authoring", tier: "framework", language: "typescript", framework: "effect", requiresTools: ["opensrc"] },
|
|
23
25
|
{ id: "effect-backend-structure", sourceDirectory: "skills/frameworks/effect/effect-backend-structure", tier: "framework", language: "typescript", framework: "effect", requiresTools: ["opensrc"] },
|
|
@@ -32,6 +34,12 @@ export const skillCatalog = [
|
|
|
32
34
|
{ id: "next-best-practices", sourceDirectory: "skills/frameworks/nextjs/next-best-practices", tier: "framework", language: "typescript", framework: "nextjs", requiresTools: [] },
|
|
33
35
|
{ id: "next-cache-components", sourceDirectory: "skills/frameworks/nextjs/next-cache-components", tier: "framework", language: "typescript", framework: "nextjs", requiresTools: [] },
|
|
34
36
|
{ id: "parallel-research", sourceDirectory: "skills/agnostic/research/parallel-research", tier: "agnostic", language: null, framework: null, requiresTools: [] },
|
|
37
|
+
{ id: "async-python-patterns", sourceDirectory: "skills/languages/python/async-python-patterns", tier: "language", language: "python", framework: null, requiresTools: [] },
|
|
38
|
+
{ id: "python-code-style", sourceDirectory: "skills/languages/python/python-code-style", tier: "language", language: "python", framework: null, requiresTools: [] },
|
|
39
|
+
{ id: "python-design-patterns", sourceDirectory: "skills/languages/python/python-design-patterns", tier: "language", language: "python", framework: null, requiresTools: [] },
|
|
40
|
+
{ id: "python-project-structure", sourceDirectory: "skills/languages/python/python-project-structure", tier: "language", language: "python", framework: null, requiresTools: [] },
|
|
41
|
+
{ id: "python-testing-patterns", sourceDirectory: "skills/languages/python/python-testing-patterns", tier: "language", language: "python", framework: null, requiresTools: [] },
|
|
42
|
+
{ id: "quality-types", sourceDirectory: "skills/languages/typescript/quality-types", tier: "language", language: "typescript", framework: null, requiresTools: [] },
|
|
35
43
|
{ id: "async-react-patterns", sourceDirectory: "skills/frameworks/react/async-react-patterns", tier: "framework", language: "typescript", framework: "react", requiresTools: [] },
|
|
36
44
|
{ id: "requirements-grill", sourceDirectory: "skills/agnostic/requirements/requirements-grill", tier: "agnostic", language: null, framework: null, requiresTools: [] },
|
|
37
45
|
{ id: "simplify", sourceDirectory: "skills/agnostic/quality/simplify", tier: "agnostic", language: null, framework: null, requiresTools: [] },
|
|
@@ -44,4 +52,4 @@ export const skillCatalog = [
|
|
|
44
52
|
{ id: "write-backlog", sourceDirectory: "skills/agnostic/requirements/write-backlog", tier: "agnostic", language: null, framework: null, requiresTools: [] },
|
|
45
53
|
] as const satisfies ReadonlyArray<SkillCatalogEntry>;
|
|
46
54
|
|
|
47
|
-
export type SkillId =
|
|
55
|
+
export type SkillId = string;
|
|
@@ -5,6 +5,12 @@ export const toolCatalog = [
|
|
|
5
5
|
requiredBins: ["node"],
|
|
6
6
|
ensureCommands: [["agent-browser", "install"]],
|
|
7
7
|
},
|
|
8
|
+
{
|
|
9
|
+
id: "debug-agent",
|
|
10
|
+
packageName: "debug-agent",
|
|
11
|
+
requiredBins: ["node"],
|
|
12
|
+
ensureCommands: [],
|
|
13
|
+
},
|
|
8
14
|
{
|
|
9
15
|
id: "opensrc",
|
|
10
16
|
packageName: "opensrc",
|
|
@@ -17,6 +23,12 @@ export const toolCatalog = [
|
|
|
17
23
|
requiredBins: ["node"],
|
|
18
24
|
ensureCommands: [],
|
|
19
25
|
},
|
|
26
|
+
{
|
|
27
|
+
id: "skills",
|
|
28
|
+
packageName: "skills",
|
|
29
|
+
requiredBins: ["node"],
|
|
30
|
+
ensureCommands: [],
|
|
31
|
+
},
|
|
20
32
|
] as const;
|
|
21
33
|
|
|
22
|
-
export type ToolId =
|
|
34
|
+
export type ToolId = string;
|
|
@@ -52,92 +52,131 @@ const cursorManagedComment =
|
|
|
52
52
|
const opencodeManagedComment =
|
|
53
53
|
"<!-- Generated by scripts/sync-subagents.mjs. Edit .agents/subagents/manifest.mjs instead. -->";
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
]
|
|
55
|
+
function buildCodexAgentConfig({ hasFormatHook, hasTestHook }) {
|
|
56
|
+
const lines = [
|
|
57
|
+
"[agents]",
|
|
58
|
+
"max_threads = 6",
|
|
59
|
+
"max_depth = 1",
|
|
60
|
+
"",
|
|
61
|
+
"[features]",
|
|
62
|
+
"codex_hooks = true",
|
|
63
|
+
"",
|
|
64
|
+
"[hooks]",
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
if (hasFormatHook) {
|
|
68
|
+
lines.push(
|
|
69
|
+
"SessionStart = [",
|
|
70
|
+
' { matcher = "startup|resume", hooks = [',
|
|
71
|
+
' { type = "command", command = "node \\"$(git rev-parse --show-toplevel)/.codex/hooks/format-edited-file.mjs\\" session-start", statusMessage = "Capturing edited-file baseline", timeout = 30 },',
|
|
72
|
+
" ] },",
|
|
73
|
+
"]",
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (hasTestHook) {
|
|
78
|
+
lines.push(
|
|
79
|
+
"PreToolUse = [",
|
|
80
|
+
' { matcher = "Bash|.*[Pp]ull.?[Rr]equest.*|.*[Pp][Rr].?[Cc]reate.*|.*create[_-]?pull[_-]?request.*", hooks = [',
|
|
81
|
+
' { type = "command", command = "node \\"$(git rev-parse --show-toplevel)/.codex/hooks/require-tests-for-pr.mjs\\" codex", statusMessage = "Checking tests before PR creation", timeout = 600 },',
|
|
82
|
+
" ] },",
|
|
83
|
+
"]",
|
|
84
|
+
);
|
|
85
|
+
}
|
|
81
86
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
{
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
87
|
+
if (hasFormatHook) {
|
|
88
|
+
lines.push(
|
|
89
|
+
"PostToolUse = [",
|
|
90
|
+
' { matcher = "Bash|apply_patch|.*apply.?patch.*|edit|write|multiedit", hooks = [',
|
|
91
|
+
' { type = "command", command = "node \\"$(git rev-parse --show-toplevel)/.codex/hooks/format-edited-file.mjs\\" post", statusMessage = "Auto-formatting edited files", timeout = 30 },',
|
|
92
|
+
" ] },",
|
|
93
|
+
"]",
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
lines.push("");
|
|
98
|
+
return lines.join("\n");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function buildClaudeSettingsConfig({ hasFormatHook, hasTestHook }) {
|
|
102
|
+
return {
|
|
103
|
+
hooks: {
|
|
104
|
+
...(hasTestHook
|
|
105
|
+
? {
|
|
106
|
+
PreToolUse: [
|
|
107
|
+
{
|
|
108
|
+
matcher: "mcp__github__create_pull_request",
|
|
109
|
+
hooks: [
|
|
110
|
+
{
|
|
111
|
+
type: "command",
|
|
112
|
+
command: 'node "$CLAUDE_PROJECT_DIR"/.claude/hooks/require-tests-for-pr.mjs claude',
|
|
113
|
+
timeout: 600,
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
matcher: "Bash",
|
|
119
|
+
hooks: [
|
|
120
|
+
{
|
|
121
|
+
type: "command",
|
|
122
|
+
command: 'node "$CLAUDE_PROJECT_DIR"/.claude/hooks/require-tests-for-pr.mjs claude',
|
|
123
|
+
timeout: 600,
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
}
|
|
129
|
+
: {}),
|
|
130
|
+
...(hasFormatHook
|
|
131
|
+
? {
|
|
132
|
+
PostToolUse: [
|
|
133
|
+
{
|
|
134
|
+
matcher: "Edit|Write|MultiEdit",
|
|
135
|
+
hooks: [
|
|
136
|
+
{
|
|
137
|
+
type: "command",
|
|
138
|
+
command: 'node "$CLAUDE_PROJECT_DIR"/.claude/hooks/format-edited-file.mjs claude',
|
|
139
|
+
timeout: 30,
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
}
|
|
145
|
+
: {}),
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function buildCursorHooksConfig({ hasFormatHook, hasTestHook }) {
|
|
151
|
+
return {
|
|
152
|
+
version: 1,
|
|
153
|
+
hooks: {
|
|
154
|
+
...(hasTestHook
|
|
155
|
+
? {
|
|
156
|
+
beforeMCPExecution: [
|
|
157
|
+
{
|
|
158
|
+
command: "node .cursor/hooks/require-tests-for-pr.mjs cursor",
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
beforeShellExecution: [
|
|
162
|
+
{
|
|
163
|
+
command: "node .cursor/hooks/require-tests-for-pr.mjs cursor",
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
}
|
|
167
|
+
: {}),
|
|
168
|
+
...(hasFormatHook
|
|
169
|
+
? {
|
|
170
|
+
afterFileEdit: [
|
|
171
|
+
{
|
|
172
|
+
command: "node .cursor/hooks/format-edited-file.mjs cursor",
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
}
|
|
176
|
+
: {}),
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
}
|
|
141
180
|
|
|
142
181
|
function getSkillNames(agent) {
|
|
143
182
|
if (Array.isArray(agent.skills)) {
|
|
@@ -306,6 +345,18 @@ async function ensureSymlink(linkPath, targetPath) {
|
|
|
306
345
|
await symlink(relativeTarget, linkPath);
|
|
307
346
|
}
|
|
308
347
|
|
|
348
|
+
async function pathExists(filePath) {
|
|
349
|
+
try {
|
|
350
|
+
await lstat(filePath);
|
|
351
|
+
return true;
|
|
352
|
+
} catch (error) {
|
|
353
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
throw error;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
309
360
|
async function writeManagedFiles(directory, extension, renderedByName) {
|
|
310
361
|
await mkdir(directory, { recursive: true });
|
|
311
362
|
|
|
@@ -340,38 +391,26 @@ async function syncHookSurface() {
|
|
|
340
391
|
await mkdir(cursorHooksDir, { recursive: true });
|
|
341
392
|
await mkdir(opencodePluginsDir, { recursive: true });
|
|
342
393
|
|
|
343
|
-
|
|
344
|
-
path.join(
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
await ensureSymlink(
|
|
364
|
-
path.join(cursorHooksDir, "require-tests-for-pr.mjs"),
|
|
365
|
-
path.join(sharedHooksPath, "require-tests-for-pr.mjs"),
|
|
366
|
-
);
|
|
367
|
-
await ensureSymlink(
|
|
368
|
-
path.join(opencodePluginsDir, "format-edited-file.mjs"),
|
|
369
|
-
path.join(sharedHooksPath, "format-edited-file.mjs"),
|
|
370
|
-
);
|
|
371
|
-
await ensureSymlink(
|
|
372
|
-
path.join(opencodePluginsDir, "require-tests-for-pr.mjs"),
|
|
373
|
-
path.join(sharedHooksPath, "require-tests-for-pr.mjs"),
|
|
374
|
-
);
|
|
394
|
+
for (const hookName of ["format-edited-file.mjs", "require-tests-for-pr.mjs"]) {
|
|
395
|
+
const sourcePath = path.join(sharedHooksPath, hookName);
|
|
396
|
+
const targetPaths = [
|
|
397
|
+
path.join(codexHooksDir, hookName),
|
|
398
|
+
path.join(claudeHooksDir, hookName),
|
|
399
|
+
path.join(cursorHooksDir, hookName),
|
|
400
|
+
path.join(opencodePluginsDir, hookName),
|
|
401
|
+
];
|
|
402
|
+
|
|
403
|
+
if (await pathExists(sourcePath)) {
|
|
404
|
+
for (const targetPath of targetPaths) {
|
|
405
|
+
await ensureSymlink(targetPath, sourcePath);
|
|
406
|
+
}
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
for (const targetPath of targetPaths) {
|
|
411
|
+
await rm(targetPath, { force: true, recursive: true });
|
|
412
|
+
}
|
|
413
|
+
}
|
|
375
414
|
}
|
|
376
415
|
|
|
377
416
|
async function syncSkillMirrors() {
|
|
@@ -404,10 +443,14 @@ async function main() {
|
|
|
404
443
|
await mkdir(path.dirname(codexConfigPath), { recursive: true });
|
|
405
444
|
await mkdir(path.dirname(claudeSettingsPath), { recursive: true });
|
|
406
445
|
await mkdir(path.dirname(cursorHooksJsonPath), { recursive: true });
|
|
407
|
-
|
|
446
|
+
const availableHooks = {
|
|
447
|
+
hasFormatHook: await pathExists(path.join(sharedHooksPath, "format-edited-file.mjs")),
|
|
448
|
+
hasTestHook: await pathExists(path.join(sharedHooksPath, "require-tests-for-pr.mjs")),
|
|
449
|
+
};
|
|
450
|
+
await writeFile(codexConfigPath, buildCodexAgentConfig(availableHooks), "utf8");
|
|
408
451
|
await rm(path.join(codexDir, "hooks.json"), { force: true });
|
|
409
|
-
await writeFile(claudeSettingsPath, encodeJson(
|
|
410
|
-
await writeFile(cursorHooksJsonPath, encodeJson(
|
|
452
|
+
await writeFile(claudeSettingsPath, encodeJson(buildClaudeSettingsConfig(availableHooks)), "utf8");
|
|
453
|
+
await writeFile(cursorHooksJsonPath, encodeJson(buildCursorHooksConfig(availableHooks)), "utf8");
|
|
411
454
|
await writeManagedFiles(claudeAgentsDir, "md", claudeFiles);
|
|
412
455
|
await writeManagedFiles(codexAgentsDir, "toml", codexFiles);
|
|
413
456
|
await writeManagedFiles(cursorAgentsDir, "md", cursorFiles);
|