@c0x12c/ai-toolkit 1.15.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/.claude-plugin/marketplace.json +16 -0
- package/.claude-plugin/plugin.json +12 -0
- package/README.md +439 -0
- package/VERSION +1 -0
- package/agents/design-critic.md +127 -0
- package/agents/idea-killer.md +72 -0
- package/agents/infrastructure-expert.md +49 -0
- package/agents/micronaut-backend-expert.md +45 -0
- package/agents/phase-reviewer.md +150 -0
- package/agents/research-planner.md +70 -0
- package/agents/solution-architect-cto.md +49 -0
- package/agents/sre-architect.md +49 -0
- package/agents/team-coordinator.md +111 -0
- package/bin/cli.js +780 -0
- package/claude-md/00-header.md +39 -0
- package/claude-md/01-core.md +105 -0
- package/claude-md/05-database.md +20 -0
- package/claude-md/11-backend-micronaut.md +19 -0
- package/claude-md/20-frontend-react.md +44 -0
- package/claude-md/25-ux-design.md +56 -0
- package/claude-md/30-infrastructure.md +24 -0
- package/claude-md/30-project-mgmt.md +119 -0
- package/claude-md/40-product.md +39 -0
- package/claude-md/50-ops.md +34 -0
- package/claude-md/60-research.md +27 -0
- package/claude-md/90-footer.md +21 -0
- package/commands/spartan/brainstorm.md +134 -0
- package/commands/spartan/brownfield.md +157 -0
- package/commands/spartan/build.md +435 -0
- package/commands/spartan/careful.md +94 -0
- package/commands/spartan/commit-message.md +112 -0
- package/commands/spartan/content.md +17 -0
- package/commands/spartan/context-save.md +161 -0
- package/commands/spartan/contribute.md +140 -0
- package/commands/spartan/daily.md +42 -0
- package/commands/spartan/debug.md +308 -0
- package/commands/spartan/deep-dive.md +55 -0
- package/commands/spartan/deploy.md +207 -0
- package/commands/spartan/e2e.md +264 -0
- package/commands/spartan/env-setup.md +166 -0
- package/commands/spartan/epic.md +199 -0
- package/commands/spartan/fe-review.md +181 -0
- package/commands/spartan/figma-to-code.md +260 -0
- package/commands/spartan/forensics.md +46 -0
- package/commands/spartan/freeze.md +84 -0
- package/commands/spartan/fundraise.md +53 -0
- package/commands/spartan/gate-review.md +229 -0
- package/commands/spartan/gsd-upgrade.md +376 -0
- package/commands/spartan/guard.md +42 -0
- package/commands/spartan/init-project.md +178 -0
- package/commands/spartan/init-rules.md +298 -0
- package/commands/spartan/interview.md +154 -0
- package/commands/spartan/kickoff.md +73 -0
- package/commands/spartan/kotlin-service.md +109 -0
- package/commands/spartan/lean-canvas.md +222 -0
- package/commands/spartan/lint-rules.md +122 -0
- package/commands/spartan/map-codebase.md +124 -0
- package/commands/spartan/migration.md +82 -0
- package/commands/spartan/next-app.md +317 -0
- package/commands/spartan/next-feature.md +212 -0
- package/commands/spartan/onboard.md +326 -0
- package/commands/spartan/outreach.md +16 -0
- package/commands/spartan/phase.md +142 -0
- package/commands/spartan/pitch.md +18 -0
- package/commands/spartan/plan.md +210 -0
- package/commands/spartan/pr-ready.md +202 -0
- package/commands/spartan/project.md +106 -0
- package/commands/spartan/qa.md +222 -0
- package/commands/spartan/research.md +254 -0
- package/commands/spartan/review.md +132 -0
- package/commands/spartan/scan-rules.md +173 -0
- package/commands/spartan/sessions.md +143 -0
- package/commands/spartan/spec.md +131 -0
- package/commands/spartan/startup.md +257 -0
- package/commands/spartan/team.md +570 -0
- package/commands/spartan/teardown.md +161 -0
- package/commands/spartan/testcontainer.md +97 -0
- package/commands/spartan/tf-cost.md +123 -0
- package/commands/spartan/tf-deploy.md +116 -0
- package/commands/spartan/tf-drift.md +100 -0
- package/commands/spartan/tf-import.md +107 -0
- package/commands/spartan/tf-module.md +121 -0
- package/commands/spartan/tf-plan.md +100 -0
- package/commands/spartan/tf-review.md +106 -0
- package/commands/spartan/tf-scaffold.md +109 -0
- package/commands/spartan/tf-security.md +147 -0
- package/commands/spartan/think.md +221 -0
- package/commands/spartan/unfreeze.md +13 -0
- package/commands/spartan/update.md +134 -0
- package/commands/spartan/ux.md +1233 -0
- package/commands/spartan/validate.md +193 -0
- package/commands/spartan/web-to-prd.md +706 -0
- package/commands/spartan/workstreams.md +109 -0
- package/commands/spartan/write.md +16 -0
- package/commands/spartan.md +386 -0
- package/frameworks/00-framework-comparison-guide.md +317 -0
- package/frameworks/01-lean-canvas.md +196 -0
- package/frameworks/02-design-sprint.md +304 -0
- package/frameworks/03-foundation-sprint.md +337 -0
- package/frameworks/04-business-model-canvas.md +391 -0
- package/frameworks/05-customer-development.md +426 -0
- package/frameworks/06-jobs-to-be-done.md +358 -0
- package/frameworks/07-mom-test.md +392 -0
- package/frameworks/08-value-proposition-canvas.md +488 -0
- package/frameworks/09-javelin-board.md +428 -0
- package/frameworks/10-build-measure-learn.md +467 -0
- package/frameworks/11-mvp-approaches.md +533 -0
- package/frameworks/think-before-build.md +593 -0
- package/lib/assembler.js +197 -0
- package/lib/assembler.test.js +159 -0
- package/lib/detector.js +166 -0
- package/lib/detector.test.js +221 -0
- package/lib/packs.js +16 -0
- package/lib/resolver.js +272 -0
- package/lib/resolver.test.js +298 -0
- package/lib/worktree.sh +104 -0
- package/package.json +50 -0
- package/packs/backend-micronaut.yaml +35 -0
- package/packs/backend-nodejs.yaml +15 -0
- package/packs/backend-python.yaml +15 -0
- package/packs/core.yaml +37 -0
- package/packs/database.yaml +21 -0
- package/packs/frontend-react.yaml +24 -0
- package/packs/infrastructure.yaml +40 -0
- package/packs/ops.yaml +16 -0
- package/packs/packs.compiled.json +371 -0
- package/packs/product.yaml +22 -0
- package/packs/project-mgmt.yaml +24 -0
- package/packs/research.yaml +39 -0
- package/packs/shared-backend.yaml +14 -0
- package/packs/ux-design.yaml +21 -0
- package/rules/backend-micronaut/API_DESIGN.md +313 -0
- package/rules/backend-micronaut/BATCH_PROCESSING.md +92 -0
- package/rules/backend-micronaut/CONTROLLERS.md +388 -0
- package/rules/backend-micronaut/KOTLIN.md +414 -0
- package/rules/backend-micronaut/RETROFIT_PLACEMENT.md +290 -0
- package/rules/backend-micronaut/SERVICES_AND_BEANS.md +325 -0
- package/rules/core/NAMING_CONVENTIONS.md +208 -0
- package/rules/core/SKILL_AUTHORING.md +174 -0
- package/rules/core/TIMEZONE.md +316 -0
- package/rules/database/ORM_AND_REPO.md +289 -0
- package/rules/database/SCHEMA.md +146 -0
- package/rules/database/TRANSACTIONS.md +311 -0
- package/rules/frontend-react/FRONTEND.md +344 -0
- package/rules/infrastructure/MODULES.md +260 -0
- package/rules/infrastructure/NAMING.md +196 -0
- package/rules/infrastructure/PROVIDERS.md +309 -0
- package/rules/infrastructure/SECURITY.md +310 -0
- package/rules/infrastructure/STATE_AND_BACKEND.md +237 -0
- package/rules/infrastructure/STRUCTURE.md +234 -0
- package/rules/infrastructure/VARIABLES.md +285 -0
- package/rules/shared-backend/ARCHITECTURE.md +46 -0
- package/rules/ux-design/DESIGN_PROCESS.md +176 -0
- package/skills/api-endpoint-creator/SKILL.md +455 -0
- package/skills/api-endpoint-creator/error-handling-guide.md +244 -0
- package/skills/api-endpoint-creator/examples.md +522 -0
- package/skills/api-endpoint-creator/testing-patterns.md +302 -0
- package/skills/article-writing/SKILL.md +109 -0
- package/skills/article-writing/examples.md +59 -0
- package/skills/backend-api-design/SKILL.md +84 -0
- package/skills/backend-api-design/code-patterns.md +138 -0
- package/skills/brainstorm/SKILL.md +95 -0
- package/skills/browser-qa/SKILL.md +87 -0
- package/skills/browser-qa/playwright-snippets.md +110 -0
- package/skills/ci-cd-patterns/SKILL.md +108 -0
- package/skills/ci-cd-patterns/workflows.md +149 -0
- package/skills/competitive-teardown/SKILL.md +93 -0
- package/skills/competitive-teardown/example-analysis.md +50 -0
- package/skills/content-engine/SKILL.md +131 -0
- package/skills/content-engine/examples.md +72 -0
- package/skills/database-patterns/SKILL.md +72 -0
- package/skills/database-patterns/code-templates.md +114 -0
- package/skills/database-table-creator/SKILL.md +141 -0
- package/skills/database-table-creator/examples.md +552 -0
- package/skills/database-table-creator/kotlin-templates.md +400 -0
- package/skills/database-table-creator/migration-template.sql +68 -0
- package/skills/database-table-creator/validation-checklist.md +337 -0
- package/skills/deep-research/SKILL.md +80 -0
- package/skills/design-intelligence/SKILL.md +268 -0
- package/skills/design-workflow/SKILL.md +127 -0
- package/skills/design-workflow/checklists.md +45 -0
- package/skills/idea-validation/SKILL.md +129 -0
- package/skills/idea-validation/example-report.md +50 -0
- package/skills/investor-materials/SKILL.md +122 -0
- package/skills/investor-materials/example-outline.md +70 -0
- package/skills/investor-outreach/SKILL.md +112 -0
- package/skills/investor-outreach/examples.md +76 -0
- package/skills/kotlin-best-practices/SKILL.md +58 -0
- package/skills/kotlin-best-practices/code-patterns.md +132 -0
- package/skills/market-research/SKILL.md +99 -0
- package/skills/security-checklist/SKILL.md +65 -0
- package/skills/security-checklist/audit-reference.md +95 -0
- package/skills/service-debugging/SKILL.md +116 -0
- package/skills/service-debugging/common-issues.md +65 -0
- package/skills/startup-pipeline/SKILL.md +152 -0
- package/skills/terraform-best-practices/SKILL.md +244 -0
- package/skills/terraform-module-creator/SKILL.md +284 -0
- package/skills/terraform-review/SKILL.md +222 -0
- package/skills/terraform-security-audit/SKILL.md +280 -0
- package/skills/terraform-service-scaffold/SKILL.md +574 -0
- package/skills/testing-strategies/SKILL.md +116 -0
- package/skills/testing-strategies/examples.md +103 -0
- package/skills/testing-strategies/integration-test-setup.md +71 -0
- package/skills/ui-ux-pro-max/SKILL.md +238 -0
- package/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/skills/ui-ux-pro-max/data/styles.csv +68 -0
- package/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/skills/ui-ux-pro-max/python-setup.md +146 -0
- package/skills/ui-ux-pro-max/scripts/core.py +253 -0
- package/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/skills/ui-ux-pro-max/scripts/search.py +114 -0
- package/skills/web-to-prd/SKILL.md +478 -0
- package/templates/build-config.yaml +44 -0
- package/templates/commands-config.yaml +55 -0
- package/templates/competitor-analysis.md +60 -0
- package/templates/content/AGENT_TEMPLATE.md +47 -0
- package/templates/content/COMMAND_TEMPLATE.md +27 -0
- package/templates/content/RULE_TEMPLATE.md +40 -0
- package/templates/content/SKILL_TEMPLATE.md +41 -0
- package/templates/design-config.md +105 -0
- package/templates/design-doc.md +207 -0
- package/templates/epic.md +100 -0
- package/templates/feature-spec.md +181 -0
- package/templates/idea-canvas.md +47 -0
- package/templates/implementation-plan.md +159 -0
- package/templates/prd-template.md +86 -0
- package/templates/preamble.md +89 -0
- package/templates/project-readme.md +35 -0
- package/templates/quality-gates.md +230 -0
- package/templates/spartan-config.yaml +164 -0
- package/templates/user-interview.md +69 -0
- package/templates/validation-checklist.md +108 -0
- package/templates/workflow-backend-micronaut.md +409 -0
- package/templates/workflow-frontend-react.md +233 -0
package/lib/assembler.js
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// Spartan AI Toolkit — Assembler
|
|
2
|
+
// Builds CLAUDE.md and AGENTS.md from section files based on selected packs.
|
|
3
|
+
|
|
4
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
const ALWAYS_INCLUDE_TOP = ['00-header.md', '01-core.md'];
|
|
8
|
+
const ALWAYS_INCLUDE_BOTTOM = ['90-footer.md'];
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Assemble CLAUDE.md from section files.
|
|
12
|
+
* @param {string} claudeMdDir - Path to the claude-md/ directory
|
|
13
|
+
* @param {string[]} selectedPacks - Pack names the user picked
|
|
14
|
+
* @param {object} packDefs - The PACKS object from packs.js
|
|
15
|
+
* @returns {string} The full CLAUDE.md content
|
|
16
|
+
*/
|
|
17
|
+
export function assembleCLAUDEmd(claudeMdDir, selectedPacks, packDefs) {
|
|
18
|
+
let parts = [];
|
|
19
|
+
|
|
20
|
+
// Always include header + core
|
|
21
|
+
for (const file of ALWAYS_INCLUDE_TOP) {
|
|
22
|
+
const fp = join(claudeMdDir, file);
|
|
23
|
+
if (existsSync(fp)) {
|
|
24
|
+
parts.push(readFileSync(fp, 'utf-8'));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Pack-specific sections (in pack order, not random)
|
|
29
|
+
const seen = new Set();
|
|
30
|
+
for (const pack of selectedPacks) {
|
|
31
|
+
const def = packDefs[pack];
|
|
32
|
+
if (!def) continue;
|
|
33
|
+
for (const section of def.claudeSections) {
|
|
34
|
+
if (seen.has(section)) continue;
|
|
35
|
+
seen.add(section);
|
|
36
|
+
const fp = join(claudeMdDir, section);
|
|
37
|
+
if (existsSync(fp)) {
|
|
38
|
+
parts.push(readFileSync(fp, 'utf-8'));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Always include footer
|
|
44
|
+
for (const file of ALWAYS_INCLUDE_BOTTOM) {
|
|
45
|
+
const fp = join(claudeMdDir, file);
|
|
46
|
+
if (existsSync(fp)) {
|
|
47
|
+
parts.push(readFileSync(fp, 'utf-8'));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return parts.join('\n');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Assemble AGENTS.md from section files + agent definitions.
|
|
56
|
+
* Follows the AGENTS.md spec: Project Context, pack content, Agents, Boundaries.
|
|
57
|
+
* @param {string} claudeMdDir - Path to the claude-md/ directory
|
|
58
|
+
* @param {string} agentsDir - Path to the agents/ directory
|
|
59
|
+
* @param {string[]} selectedPacks - Pack names the user picked
|
|
60
|
+
* @param {object} packDefs - The PACKS object from packs.js
|
|
61
|
+
* @returns {string} The full AGENTS.md content
|
|
62
|
+
*/
|
|
63
|
+
export function assembleAGENTSmd(claudeMdDir, agentsDir, selectedPacks, packDefs) {
|
|
64
|
+
const parts = [];
|
|
65
|
+
|
|
66
|
+
// Header
|
|
67
|
+
parts.push('# AGENTS.md');
|
|
68
|
+
parts.push('');
|
|
69
|
+
parts.push('> Generated by [Spartan AI Toolkit](https://github.com/c0x12c/ai-toolkit)');
|
|
70
|
+
parts.push('');
|
|
71
|
+
|
|
72
|
+
// Project Context — pull from header if it exists
|
|
73
|
+
parts.push('## Project Context');
|
|
74
|
+
parts.push('');
|
|
75
|
+
const headerFile = join(claudeMdDir, '00-header.md');
|
|
76
|
+
if (existsSync(headerFile)) {
|
|
77
|
+
const header = readFileSync(headerFile, 'utf-8');
|
|
78
|
+
// Strip the top-level heading, keep the useful content
|
|
79
|
+
const lines = header.split('\n');
|
|
80
|
+
const contentLines = lines.filter(l => !l.startsWith('# '));
|
|
81
|
+
const trimmed = contentLines.join('\n').trim();
|
|
82
|
+
if (trimmed) {
|
|
83
|
+
parts.push(trimmed);
|
|
84
|
+
parts.push('');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Core principles — compact version
|
|
89
|
+
const coreFile = join(claudeMdDir, '01-core.md');
|
|
90
|
+
if (existsSync(coreFile)) {
|
|
91
|
+
const core = readFileSync(coreFile, 'utf-8');
|
|
92
|
+
parts.push(extractSection(core));
|
|
93
|
+
parts.push('');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Pack-specific sections
|
|
97
|
+
const seen = new Set();
|
|
98
|
+
for (const pack of selectedPacks) {
|
|
99
|
+
const def = packDefs[pack];
|
|
100
|
+
if (!def) continue;
|
|
101
|
+
for (const section of def.claudeSections) {
|
|
102
|
+
if (seen.has(section)) continue;
|
|
103
|
+
seen.add(section);
|
|
104
|
+
const fp = join(claudeMdDir, section);
|
|
105
|
+
if (existsSync(fp)) {
|
|
106
|
+
parts.push(extractSection(readFileSync(fp, 'utf-8')));
|
|
107
|
+
parts.push('');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Agents — list available agents from selected packs
|
|
113
|
+
const agentFiles = gatherAgents(selectedPacks, packDefs);
|
|
114
|
+
if (agentFiles.length > 0 && agentsDir) {
|
|
115
|
+
parts.push('## Available Agents');
|
|
116
|
+
parts.push('');
|
|
117
|
+
for (const agentFile of agentFiles) {
|
|
118
|
+
const fp = join(agentsDir, agentFile);
|
|
119
|
+
if (!existsSync(fp)) continue;
|
|
120
|
+
const content = readFileSync(fp, 'utf-8');
|
|
121
|
+
const name = extractFrontmatterField(content, 'name') || agentFile.replace('.md', '');
|
|
122
|
+
const desc = extractFrontmatterField(content, 'description') || '';
|
|
123
|
+
// Handle multiline descriptions — get first meaningful sentence
|
|
124
|
+
const cleanDesc = desc.replace(/\\n/g, ' ').split('.')[0].trim();
|
|
125
|
+
parts.push(`- **${name}**: ${cleanDesc}`);
|
|
126
|
+
}
|
|
127
|
+
parts.push('');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Boundaries — from footer "What NOT to Do" section
|
|
131
|
+
const footerFile = join(claudeMdDir, '90-footer.md');
|
|
132
|
+
if (existsSync(footerFile)) {
|
|
133
|
+
const footer = readFileSync(footerFile, 'utf-8');
|
|
134
|
+
const neverItems = extractNotToDoItems(footer);
|
|
135
|
+
if (neverItems.length > 0) {
|
|
136
|
+
parts.push('## Boundaries');
|
|
137
|
+
parts.push('');
|
|
138
|
+
parts.push('**NEVER:**');
|
|
139
|
+
for (const item of neverItems) {
|
|
140
|
+
parts.push(`- ${item}`);
|
|
141
|
+
}
|
|
142
|
+
parts.push('');
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return parts.join('\n').trimEnd() + '\n';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ── Helpers ──────────────────────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
/** Strip leading separator (---) and return the content. */
|
|
152
|
+
function extractSection(content) {
|
|
153
|
+
return content.replace(/^\s*---\s*\n?/, '').trim();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Get deduplicated agent files from selected packs. */
|
|
157
|
+
function gatherAgents(selectedPacks, packDefs) {
|
|
158
|
+
const seen = new Set();
|
|
159
|
+
const result = [];
|
|
160
|
+
for (const pack of selectedPacks) {
|
|
161
|
+
const def = packDefs[pack];
|
|
162
|
+
if (!def) continue;
|
|
163
|
+
for (const agent of (def.agents || [])) {
|
|
164
|
+
if (!seen.has(agent)) {
|
|
165
|
+
seen.add(agent);
|
|
166
|
+
result.push(agent);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Extract a YAML frontmatter field value (simple single-line). */
|
|
174
|
+
function extractFrontmatterField(content, field) {
|
|
175
|
+
const match = content.match(new RegExp(`^${field}:\\s*(.+)$`, 'm'));
|
|
176
|
+
return match ? match[1].trim().replace(/^["']|["']$/g, '') : null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Extract items from the "What NOT to Do" section only. */
|
|
180
|
+
function extractNotToDoItems(content) {
|
|
181
|
+
const lines = content.split('\n');
|
|
182
|
+
let inSection = false;
|
|
183
|
+
const items = [];
|
|
184
|
+
for (const line of lines) {
|
|
185
|
+
if (line.match(/what not to do/i)) {
|
|
186
|
+
inSection = true;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (inSection && line.match(/^#{1,3}\s/)) {
|
|
190
|
+
break; // hit next section
|
|
191
|
+
}
|
|
192
|
+
if (inSection && line.match(/^-\s+/)) {
|
|
193
|
+
items.push(line.replace(/^-\s+/, '').trim());
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return items;
|
|
197
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// Spartan AI Toolkit — Assembler Tests
|
|
2
|
+
// Run: node --test toolkit/lib/assembler.test.js
|
|
3
|
+
|
|
4
|
+
import { describe, it } from 'node:test';
|
|
5
|
+
import assert from 'node:assert/strict';
|
|
6
|
+
import { mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { assembleCLAUDEmd, assembleAGENTSmd } from './assembler.js';
|
|
9
|
+
|
|
10
|
+
// ── Test fixtures ────────────────────────────────────────────────
|
|
11
|
+
const TMP = join(import.meta.dirname, '..', '.test-tmp-assembler');
|
|
12
|
+
|
|
13
|
+
function setupFixtures() {
|
|
14
|
+
rmSync(TMP, { recursive: true, force: true });
|
|
15
|
+
|
|
16
|
+
// claude-md sections
|
|
17
|
+
const claudeMd = join(TMP, 'claude-md');
|
|
18
|
+
mkdirSync(claudeMd, { recursive: true });
|
|
19
|
+
writeFileSync(join(claudeMd, '00-header.md'), '# Spartan AI Toolkit\n\nIntro text here.');
|
|
20
|
+
writeFileSync(join(claudeMd, '01-core.md'), '\n---\n\n## Core Principles\n\n- TDD always\n- Spec before code');
|
|
21
|
+
writeFileSync(join(claudeMd, '11-backend.md'), '\n---\n\n## Backend\n\nKotlin + Micronaut stack.\n\n| Command | Purpose |\n|---|---|\n| `/spartan:build` | Build feature |');
|
|
22
|
+
writeFileSync(join(claudeMd, '20-frontend.md'), '\n---\n\n## Frontend\n\nReact + Next.js stack.');
|
|
23
|
+
writeFileSync(join(claudeMd, '90-footer.md'), '\n---\n\n## What NOT to Do\n\n- Don\'t skip tests');
|
|
24
|
+
|
|
25
|
+
// agents
|
|
26
|
+
const agents = join(TMP, 'agents');
|
|
27
|
+
mkdirSync(agents, { recursive: true });
|
|
28
|
+
writeFileSync(join(agents, 'backend-expert.md'), '---\nname: backend-expert\ndescription: Backend expert\nmodel: sonnet\n---\n\nYou are a backend expert.');
|
|
29
|
+
writeFileSync(join(agents, 'cto.md'), '---\nname: cto\ndescription: CTO advisor\nmodel: sonnet\n---\n\nYou are a CTO.');
|
|
30
|
+
|
|
31
|
+
return { claudeMd, agents };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function cleanup() {
|
|
35
|
+
rmSync(TMP, { recursive: true, force: true });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Mock pack definitions
|
|
39
|
+
const packDefs = {
|
|
40
|
+
core: { claudeSections: [], rules: [], skills: [], agents: [], commands: [] },
|
|
41
|
+
backend: {
|
|
42
|
+
claudeSections: ['11-backend.md'],
|
|
43
|
+
rules: ['backend/KOTLIN.md'],
|
|
44
|
+
skills: ['api-endpoint-creator'],
|
|
45
|
+
agents: ['backend-expert.md'],
|
|
46
|
+
commands: ['build'],
|
|
47
|
+
},
|
|
48
|
+
frontend: {
|
|
49
|
+
claudeSections: ['20-frontend.md'],
|
|
50
|
+
rules: ['frontend/FRONTEND.md'],
|
|
51
|
+
skills: ['ui-ux-pro-max'],
|
|
52
|
+
agents: [],
|
|
53
|
+
commands: ['next-app'],
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// ── Tests ────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
describe('assembleCLAUDEmd', () => {
|
|
60
|
+
it('includes header, core, pack sections, and footer', () => {
|
|
61
|
+
const { claudeMd } = setupFixtures();
|
|
62
|
+
try {
|
|
63
|
+
const result = assembleCLAUDEmd(claudeMd, ['backend'], packDefs);
|
|
64
|
+
assert.ok(result.includes('# Spartan AI Toolkit'), 'should have header');
|
|
65
|
+
assert.ok(result.includes('Core Principles'), 'should have core');
|
|
66
|
+
assert.ok(result.includes('Backend'), 'should have backend section');
|
|
67
|
+
assert.ok(result.includes('What NOT to Do'), 'should have footer');
|
|
68
|
+
assert.ok(!result.includes('Frontend'), 'should NOT have frontend');
|
|
69
|
+
} finally {
|
|
70
|
+
cleanup();
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('assembleAGENTSmd', () => {
|
|
76
|
+
it('returns a string with AGENTS.md header', () => {
|
|
77
|
+
const { claudeMd, agents } = setupFixtures();
|
|
78
|
+
try {
|
|
79
|
+
const result = assembleAGENTSmd(claudeMd, agents, ['backend'], packDefs);
|
|
80
|
+
assert.ok(typeof result === 'string');
|
|
81
|
+
assert.ok(result.includes('# AGENTS.md'), 'should have AGENTS.md header');
|
|
82
|
+
} finally {
|
|
83
|
+
cleanup();
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('includes project context section', () => {
|
|
88
|
+
const { claudeMd, agents } = setupFixtures();
|
|
89
|
+
try {
|
|
90
|
+
const result = assembleAGENTSmd(claudeMd, agents, ['backend'], packDefs);
|
|
91
|
+
assert.ok(result.includes('## Project Context'), 'should have project context');
|
|
92
|
+
assert.ok(result.includes('Spartan AI Toolkit'), 'should mention toolkit name');
|
|
93
|
+
} finally {
|
|
94
|
+
cleanup();
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('includes pack-specific content', () => {
|
|
99
|
+
const { claudeMd, agents } = setupFixtures();
|
|
100
|
+
try {
|
|
101
|
+
const result = assembleAGENTSmd(claudeMd, agents, ['backend'], packDefs);
|
|
102
|
+
assert.ok(result.includes('Backend') || result.includes('backend'), 'should have backend content');
|
|
103
|
+
} finally {
|
|
104
|
+
cleanup();
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('does NOT include packs the user did not pick', () => {
|
|
109
|
+
const { claudeMd, agents } = setupFixtures();
|
|
110
|
+
try {
|
|
111
|
+
const result = assembleAGENTSmd(claudeMd, agents, ['backend'], packDefs);
|
|
112
|
+
assert.ok(!result.includes('## Frontend'), 'should NOT have frontend section');
|
|
113
|
+
} finally {
|
|
114
|
+
cleanup();
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('includes agents section when agents exist', () => {
|
|
119
|
+
const { claudeMd, agents } = setupFixtures();
|
|
120
|
+
try {
|
|
121
|
+
const result = assembleAGENTSmd(claudeMd, agents, ['backend'], packDefs);
|
|
122
|
+
assert.ok(result.includes('backend-expert') || result.includes('Agents'), 'should have agents');
|
|
123
|
+
} finally {
|
|
124
|
+
cleanup();
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('includes boundaries section', () => {
|
|
129
|
+
const { claudeMd, agents } = setupFixtures();
|
|
130
|
+
try {
|
|
131
|
+
const result = assembleAGENTSmd(claudeMd, agents, ['backend'], packDefs);
|
|
132
|
+
assert.ok(result.includes('## Boundaries') || result.includes('NEVER'), 'should have boundaries');
|
|
133
|
+
} finally {
|
|
134
|
+
cleanup();
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('handles multiple packs', () => {
|
|
139
|
+
const { claudeMd, agents } = setupFixtures();
|
|
140
|
+
try {
|
|
141
|
+
const result = assembleAGENTSmd(claudeMd, agents, ['backend', 'frontend'], packDefs);
|
|
142
|
+
assert.ok(result.includes('Backend') || result.includes('backend'), 'should have backend');
|
|
143
|
+
assert.ok(result.includes('Frontend') || result.includes('frontend'), 'should have frontend');
|
|
144
|
+
} finally {
|
|
145
|
+
cleanup();
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('handles empty packs gracefully', () => {
|
|
150
|
+
const { claudeMd, agents } = setupFixtures();
|
|
151
|
+
try {
|
|
152
|
+
const result = assembleAGENTSmd(claudeMd, agents, ['core'], packDefs);
|
|
153
|
+
assert.ok(typeof result === 'string');
|
|
154
|
+
assert.ok(result.includes('# AGENTS.md'));
|
|
155
|
+
} finally {
|
|
156
|
+
cleanup();
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
});
|
package/lib/detector.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// Spartan AI Toolkit — Stack Detector
|
|
2
|
+
// Scans a project directory to detect tech stacks and map them to packs.
|
|
3
|
+
|
|
4
|
+
import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
// Packs that are "coming soon" — detect but don't auto-select
|
|
8
|
+
const COMING_SOON = new Set(['backend-nodejs', 'backend-python']);
|
|
9
|
+
|
|
10
|
+
// Skip these directories when scanning 1 level deep
|
|
11
|
+
const SKIP_DIRS = new Set([
|
|
12
|
+
'node_modules', '.git', '.gradle', 'build', 'dist', 'target',
|
|
13
|
+
'.next', '.nuxt', '__pycache__', '.venv', 'venv', '.idea',
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Detect tech stacks in a project directory.
|
|
18
|
+
* Scans root + 1 level deep for stack markers.
|
|
19
|
+
* @param {string} cwd - Directory to scan
|
|
20
|
+
* @returns {{ detected: Array<{pack: string, reason: string}>, comingSoon: Array<{pack: string, reason: string}> }}
|
|
21
|
+
*/
|
|
22
|
+
export function detectStacks(cwd) {
|
|
23
|
+
const detected = [];
|
|
24
|
+
const comingSoon = [];
|
|
25
|
+
const seen = new Set();
|
|
26
|
+
|
|
27
|
+
// Scan root
|
|
28
|
+
scanDir(cwd, '', detected, comingSoon, seen);
|
|
29
|
+
|
|
30
|
+
// Scan 1 level deep
|
|
31
|
+
try {
|
|
32
|
+
const entries = readdirSync(cwd);
|
|
33
|
+
for (const entry of entries) {
|
|
34
|
+
if (SKIP_DIRS.has(entry) || entry.startsWith('.')) continue;
|
|
35
|
+
const full = join(cwd, entry);
|
|
36
|
+
try {
|
|
37
|
+
if (statSync(full).isDirectory()) {
|
|
38
|
+
scanDir(full, entry, detected, comingSoon, seen);
|
|
39
|
+
}
|
|
40
|
+
} catch { /* ignore permission errors */ }
|
|
41
|
+
}
|
|
42
|
+
} catch { /* ignore if cwd can't be read */ }
|
|
43
|
+
|
|
44
|
+
return { detected, comingSoon };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function scanDir(dir, label, detected, comingSoon, seen) {
|
|
48
|
+
// ── Kotlin + Micronaut ───────────────────────────────────────
|
|
49
|
+
if (!seen.has('backend-micronaut')) {
|
|
50
|
+
const gradleFile = join(dir, 'build.gradle.kts');
|
|
51
|
+
const pomFile = join(dir, 'pom.xml');
|
|
52
|
+
const mnCliFile = join(dir, 'micronaut-cli.yml');
|
|
53
|
+
|
|
54
|
+
if (existsSync(gradleFile)) {
|
|
55
|
+
const content = readSafe(gradleFile);
|
|
56
|
+
if (content.includes('io.micronaut')) {
|
|
57
|
+
addResult('backend-micronaut', `build.gradle.kts${label ? ` in ${label}/` : ''} → Micronaut`, detected, comingSoon, seen);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (!seen.has('backend-micronaut') && existsSync(pomFile)) {
|
|
61
|
+
const content = readSafe(pomFile);
|
|
62
|
+
if (content.includes('io.micronaut') || content.includes('micronaut-')) {
|
|
63
|
+
addResult('backend-micronaut', `pom.xml${label ? ` in ${label}/` : ''} → Micronaut`, detected, comingSoon, seen);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (!seen.has('backend-micronaut') && existsSync(mnCliFile)) {
|
|
67
|
+
addResult('backend-micronaut', `micronaut-cli.yml${label ? ` in ${label}/` : ''}`, detected, comingSoon, seen);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── React / Next.js ──────────────────────────────────────────
|
|
72
|
+
if (!seen.has('frontend-react')) {
|
|
73
|
+
// Check next.config.* files first (high confidence)
|
|
74
|
+
for (const cfg of ['next.config.js', 'next.config.ts', 'next.config.mjs']) {
|
|
75
|
+
if (existsSync(join(dir, cfg))) {
|
|
76
|
+
addResult('frontend-react', `${cfg}${label ? ` in ${label}/` : ''} → Next.js`, detected, comingSoon, seen);
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Check package.json for next or react
|
|
82
|
+
if (!seen.has('frontend-react')) {
|
|
83
|
+
const pkg = readPackageJson(dir);
|
|
84
|
+
if (pkg) {
|
|
85
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
86
|
+
if (allDeps.next) {
|
|
87
|
+
addResult('frontend-react', `package.json${label ? ` in ${label}/` : ''} → Next.js`, detected, comingSoon, seen);
|
|
88
|
+
} else if (allDeps.react) {
|
|
89
|
+
addResult('frontend-react', `package.json${label ? ` in ${label}/` : ''} → React`, detected, comingSoon, seen);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ── Node.js Backend ──────────────────────────────────────────
|
|
96
|
+
// Only check if frontend-react was NOT found in this same package.json
|
|
97
|
+
if (!seen.has('backend-nodejs') && !seen.has('frontend-react-from-pkg-' + dir)) {
|
|
98
|
+
const pkg = readPackageJson(dir);
|
|
99
|
+
if (pkg) {
|
|
100
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
101
|
+
// Skip if this package has next or react (it's frontend, not backend)
|
|
102
|
+
if (!allDeps.next && !allDeps.react) {
|
|
103
|
+
const nodeBackendFrameworks = ['express', 'fastify', '@nestjs/core', 'koa', '@hapi/hapi', 'hapi'];
|
|
104
|
+
const found = nodeBackendFrameworks.find(f => allDeps[f]);
|
|
105
|
+
if (found) {
|
|
106
|
+
addResult('backend-nodejs', `package.json${label ? ` in ${label}/` : ''} → ${found}`, detected, comingSoon, seen);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ── Python Backend ───────────────────────────────────────────
|
|
113
|
+
if (!seen.has('backend-python')) {
|
|
114
|
+
const pythonFrameworks = ['fastapi', 'django', 'flask', 'starlette'];
|
|
115
|
+
|
|
116
|
+
// manage.py → Django
|
|
117
|
+
if (existsSync(join(dir, 'manage.py'))) {
|
|
118
|
+
addResult('backend-python', `manage.py${label ? ` in ${label}/` : ''} → Django`, detected, comingSoon, seen);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// pyproject.toml
|
|
122
|
+
if (!seen.has('backend-python') && existsSync(join(dir, 'pyproject.toml'))) {
|
|
123
|
+
const content = readSafe(join(dir, 'pyproject.toml')).toLowerCase();
|
|
124
|
+
const found = pythonFrameworks.find(f => content.includes(f));
|
|
125
|
+
if (found) {
|
|
126
|
+
addResult('backend-python', `pyproject.toml${label ? ` in ${label}/` : ''} → ${found}`, detected, comingSoon, seen);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// requirements.txt
|
|
131
|
+
if (!seen.has('backend-python') && existsSync(join(dir, 'requirements.txt'))) {
|
|
132
|
+
const content = readSafe(join(dir, 'requirements.txt')).toLowerCase();
|
|
133
|
+
const found = pythonFrameworks.find(f => content.includes(f));
|
|
134
|
+
if (found) {
|
|
135
|
+
addResult('backend-python', `requirements.txt${label ? ` in ${label}/` : ''} → ${found}`, detected, comingSoon, seen);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function addResult(pack, reason, detected, comingSoon, seen) {
|
|
142
|
+
seen.add(pack);
|
|
143
|
+
if (COMING_SOON.has(pack)) {
|
|
144
|
+
comingSoon.push({ pack, reason });
|
|
145
|
+
} else {
|
|
146
|
+
detected.push({ pack, reason });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function readSafe(filePath) {
|
|
151
|
+
try {
|
|
152
|
+
return readFileSync(filePath, 'utf-8');
|
|
153
|
+
} catch {
|
|
154
|
+
return '';
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function readPackageJson(dir) {
|
|
159
|
+
const pkgPath = join(dir, 'package.json');
|
|
160
|
+
if (!existsSync(pkgPath)) return null;
|
|
161
|
+
try {
|
|
162
|
+
return JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
163
|
+
} catch {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
}
|