@jaimevalasek/aioson 1.6.0 → 1.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +74 -0
- package/README.md +729 -232
- package/docs/design-previews/pt.squarespace.com-homepage.html +889 -0
- package/docs/integrations/sdlc-genius-boundary.md +76 -0
- package/docs/integrations/sdlc-genius-eval-matrix.md +75 -0
- package/docs/integrations/sdlc-genius-install-checklist.md +93 -0
- package/docs/integrations/sdlc-genius-review-samples.md +86 -0
- package/docs/pt/README.md +3 -0
- package/docs/pt/agentes.md +1 -0
- package/docs/pt/comandos-cli.md +888 -2
- package/docs/pt/design-hybrid-forge.md +255 -6
- package/docs/pt/devlog-pipeline.md +270 -0
- package/docs/pt/fluxo-artefatos.md +178 -0
- package/docs/pt/hooks-session-guard.md +454 -0
- package/docs/pt/monitor-de-contexto.md +59 -5
- package/docs/pt/sdd-automation-scripts.md +557 -0
- package/docs/pt/site-forge.md +309 -0
- package/docs/pt/spec-learnings-pipeline.md +265 -0
- package/package.json +1 -1
- package/src/a2a/client.js +165 -0
- package/src/a2a/server.js +223 -0
- package/src/cli.js +235 -1
- package/src/commands/agent-audit.js +397 -0
- package/src/commands/agent-export-skill.js +229 -0
- package/src/commands/artifact-validate.js +189 -0
- package/src/commands/brief-gen.js +405 -0
- package/src/commands/brief-validate.js +65 -0
- package/src/commands/classify.js +256 -0
- package/src/commands/context-compact.js +49 -0
- package/src/commands/context-health.js +175 -0
- package/src/commands/context-monitor.js +71 -0
- package/src/commands/context-trim.js +177 -0
- package/src/commands/detect-test-runner.js +55 -0
- package/src/commands/devlog-export-brains.js +27 -0
- package/src/commands/devlog-process.js +292 -0
- package/src/commands/devlog-watch.js +131 -0
- package/src/commands/feature-close.js +165 -0
- package/src/commands/gate-check.js +228 -0
- package/src/commands/hooks-emit.js +253 -0
- package/src/commands/hooks-install.js +347 -0
- package/src/commands/learning-auto-promote.js +195 -0
- package/src/commands/learning-evolve.js +18 -9
- package/src/commands/learning-export.js +103 -0
- package/src/commands/learning-rollback.js +164 -0
- package/src/commands/live.js +25 -1
- package/src/commands/pattern-detect.js +33 -0
- package/src/commands/preflight-context.js +30 -0
- package/src/commands/preflight.js +208 -0
- package/src/commands/pulse-update.js +130 -0
- package/src/commands/runner-daemon.js +274 -0
- package/src/commands/runner-plan.js +70 -0
- package/src/commands/runner-queue-from-plan.js +166 -0
- package/src/commands/runner-queue.js +189 -0
- package/src/commands/runner-run.js +129 -0
- package/src/commands/runtime.js +47 -1
- package/src/commands/self-implement-loop.js +256 -0
- package/src/commands/session-guard.js +218 -0
- package/src/commands/sizing.js +165 -0
- package/src/commands/skill.js +65 -0
- package/src/commands/spec-checkpoint.js +177 -0
- package/src/commands/spec-status.js +79 -0
- package/src/commands/spec-sync.js +190 -0
- package/src/commands/spec-tasks.js +288 -0
- package/src/commands/squad-autorun.js +1220 -0
- package/src/commands/squad-bus.js +217 -0
- package/src/commands/squad-card.js +149 -0
- package/src/commands/squad-daemon.js +134 -0
- package/src/commands/squad-dependency-graph.js +164 -0
- package/src/commands/squad-review.js +106 -0
- package/src/commands/squad-scaffold.js +55 -0
- package/src/commands/squad-tool-register.js +157 -0
- package/src/commands/state-save.js +122 -0
- package/src/commands/update.js +2 -0
- package/src/commands/verify-gate.js +572 -0
- package/src/commands/workflow-execute.js +241 -0
- package/src/constants.js +22 -0
- package/src/install-profile.js +2 -2
- package/src/install-wizard.js +3 -2
- package/src/installer.js +6 -0
- package/src/lib/health-check.js +158 -0
- package/src/lib/hook-protocol.js +76 -0
- package/src/mcp/apps/squad-dashboard/app.js +163 -0
- package/src/mcp/apps/squad-dashboard/index.html +261 -0
- package/src/mcp/apps/squad-dashboard/mcp-manifest.json +23 -0
- package/src/mcp/resources/squad-state.js +130 -0
- package/src/preflight-engine.js +443 -0
- package/src/runner/cascade.js +97 -0
- package/src/runner/cli-launcher.js +109 -0
- package/src/runner/plan-importer.js +63 -0
- package/src/runner/queue-store.js +159 -0
- package/src/runtime-store.js +61 -3
- package/src/squad/agent-teams-adapter.js +264 -0
- package/src/squad/brief-validator.js +350 -0
- package/src/squad/bus-bridge.js +140 -0
- package/src/squad/context-compactor.js +265 -0
- package/src/squad/cross-ai-synthesizer.js +250 -0
- package/src/squad/hooks-generator.js +196 -0
- package/src/squad/inter-squad-events.js +175 -0
- package/src/squad/intra-bus.js +345 -0
- package/src/squad/learning-extractor.js +213 -0
- package/src/squad/pattern-detector.js +365 -0
- package/src/squad/preflight-context.js +296 -0
- package/src/squad/recovery-context.js +242 -71
- package/src/squad/reflection.js +365 -0
- package/src/squad/squad-scaffold.js +177 -0
- package/src/squad/state-manager.js +310 -0
- package/src/squad/task-decomposer.js +652 -0
- package/src/squad/verify-gate.js +303 -0
- package/src/updater.js +4 -5
- package/src/worker-runner.js +186 -1
- package/template/.aioson/agents/analyst.md +62 -1
- package/template/.aioson/agents/architect.md +61 -1
- package/template/.aioson/agents/copywriter.md +463 -0
- package/template/.aioson/agents/design-hybrid-forge.md +14 -0
- package/template/.aioson/agents/dev.md +271 -25
- package/template/.aioson/agents/deyvin.md +67 -8
- package/template/.aioson/agents/discovery-design-doc.md +44 -0
- package/template/.aioson/agents/genome.md +14 -0
- package/template/.aioson/agents/neo.md +83 -2
- package/template/.aioson/agents/orache.md +50 -4
- package/template/.aioson/agents/orchestrator.md +197 -1
- package/template/.aioson/agents/pm.md +35 -0
- package/template/.aioson/agents/product.md +50 -5
- package/template/.aioson/agents/profiler-enricher.md +14 -0
- package/template/.aioson/agents/profiler-forge.md +14 -0
- package/template/.aioson/agents/profiler-researcher.md +14 -0
- package/template/.aioson/agents/qa.md +273 -21
- package/template/.aioson/agents/setup.md +96 -10
- package/template/.aioson/agents/sheldon.md +131 -6
- package/template/.aioson/agents/site-forge.md +1753 -0
- package/template/.aioson/agents/squad.md +352 -0
- package/template/.aioson/agents/tester.md +53 -0
- package/template/.aioson/agents/ux-ui.md +203 -4
- package/template/.aioson/brains/README.md +128 -0
- package/template/.aioson/brains/_index.json +16 -0
- package/template/.aioson/brains/scripts/query.js +103 -0
- package/template/.aioson/brains/site-forge/visual-patterns.brain.json +205 -0
- package/template/.aioson/config.md +143 -13
- package/template/.aioson/constitution.md +33 -0
- package/template/.aioson/context/project-pulse.md +34 -0
- package/template/.aioson/docs/LAYERS.md +79 -0
- package/template/.aioson/docs/README.md +76 -0
- package/template/.aioson/docs/example-external-api-context.md +72 -0
- package/template/.aioson/genomes/copywriting.md +204 -0
- package/template/.aioson/locales/en/agents/architect.md +17 -0
- package/template/.aioson/locales/en/agents/dev.md +79 -13
- package/template/.aioson/locales/en/agents/orache.md +6 -0
- package/template/.aioson/locales/en/agents/orchestrator.md +24 -0
- package/template/.aioson/locales/en/agents/product.md +50 -0
- package/template/.aioson/locales/en/agents/sheldon.md +115 -0
- package/template/.aioson/locales/en/agents/squad.md +14 -0
- package/template/.aioson/locales/en/agents/tester.md +6 -0
- package/template/.aioson/locales/es/agents/analyst.md +2 -0
- package/template/.aioson/locales/es/agents/architect.md +19 -0
- package/template/.aioson/locales/es/agents/dev.md +64 -4
- package/template/.aioson/locales/es/agents/deyvin.md +2 -0
- package/template/.aioson/locales/es/agents/discovery-design-doc.md +2 -0
- package/template/.aioson/locales/es/agents/genome.md +2 -0
- package/template/.aioson/locales/es/agents/neo.md +2 -0
- package/template/.aioson/locales/es/agents/orache.md +2 -0
- package/template/.aioson/locales/es/agents/orchestrator.md +26 -0
- package/template/.aioson/locales/es/agents/pair.md +2 -0
- package/template/.aioson/locales/es/agents/pm.md +2 -0
- package/template/.aioson/locales/es/agents/product.md +52 -0
- package/template/.aioson/locales/es/agents/profiler-enricher.md +2 -0
- package/template/.aioson/locales/es/agents/profiler-forge.md +2 -0
- package/template/.aioson/locales/es/agents/profiler-researcher.md +2 -0
- package/template/.aioson/locales/es/agents/qa.md +2 -0
- package/template/.aioson/locales/es/agents/setup.md +2 -0
- package/template/.aioson/locales/es/agents/sheldon.md +117 -0
- package/template/.aioson/locales/es/agents/squad.md +16 -0
- package/template/.aioson/locales/es/agents/tester.md +9 -0
- package/template/.aioson/locales/es/agents/ux-ui.md +2 -0
- package/template/.aioson/locales/fr/agents/analyst.md +2 -0
- package/template/.aioson/locales/fr/agents/architect.md +19 -0
- package/template/.aioson/locales/fr/agents/dev.md +64 -4
- package/template/.aioson/locales/fr/agents/deyvin.md +2 -0
- package/template/.aioson/locales/fr/agents/discovery-design-doc.md +2 -0
- package/template/.aioson/locales/fr/agents/genome.md +2 -0
- package/template/.aioson/locales/fr/agents/neo.md +2 -0
- package/template/.aioson/locales/fr/agents/orache.md +2 -0
- package/template/.aioson/locales/fr/agents/orchestrator.md +26 -0
- package/template/.aioson/locales/fr/agents/pair.md +2 -0
- package/template/.aioson/locales/fr/agents/pm.md +2 -0
- package/template/.aioson/locales/fr/agents/product.md +52 -0
- package/template/.aioson/locales/fr/agents/profiler-enricher.md +2 -0
- package/template/.aioson/locales/fr/agents/profiler-forge.md +2 -0
- package/template/.aioson/locales/fr/agents/profiler-researcher.md +2 -0
- package/template/.aioson/locales/fr/agents/qa.md +2 -0
- package/template/.aioson/locales/fr/agents/setup.md +2 -0
- package/template/.aioson/locales/fr/agents/sheldon.md +117 -0
- package/template/.aioson/locales/fr/agents/squad.md +16 -0
- package/template/.aioson/locales/fr/agents/tester.md +9 -0
- package/template/.aioson/locales/fr/agents/ux-ui.md +2 -0
- package/template/.aioson/locales/pt-BR/agents/analyst.md +64 -3
- package/template/.aioson/locales/pt-BR/agents/architect.md +42 -0
- package/template/.aioson/locales/pt-BR/agents/dev.md +147 -14
- package/template/.aioson/locales/pt-BR/agents/deyvin.md +47 -0
- package/template/.aioson/locales/pt-BR/agents/neo.md +62 -1
- package/template/.aioson/locales/pt-BR/agents/orchestrator.md +158 -2
- package/template/.aioson/locales/pt-BR/agents/pm.md +95 -1
- package/template/.aioson/locales/pt-BR/agents/product.md +145 -18
- package/template/.aioson/locales/pt-BR/agents/qa.md +16 -0
- package/template/.aioson/locales/pt-BR/agents/setup.md +101 -18
- package/template/.aioson/locales/pt-BR/agents/sheldon.md +132 -1
- package/template/.aioson/locales/pt-BR/agents/squad.md +14 -0
- package/template/.aioson/locales/pt-BR/agents/tester.md +449 -0
- package/template/.aioson/rules/README.md +69 -0
- package/template/.aioson/rules/data-format-convention.md +136 -0
- package/template/.aioson/rules/example-monetary-values.md +30 -0
- package/template/.aioson/schemas/squad-manifest.schema.json +124 -3
- package/template/.aioson/skills/design/cognitive-core-ui/references/motion.md +2 -0
- package/template/.aioson/skills/design/pt.squarespace.com/.skill-meta.json +31 -0
- package/template/.aioson/skills/design/pt.squarespace.com/SKILL.md +66 -0
- package/template/.aioson/skills/design/pt.squarespace.com/references/components.md +368 -0
- package/template/.aioson/skills/design/pt.squarespace.com/references/design-tokens.md +150 -0
- package/template/.aioson/skills/design/pt.squarespace.com/references/motion.md +270 -0
- package/template/.aioson/skills/design/pt.squarespace.com/references/patterns.md +189 -0
- package/template/.aioson/skills/design/pt.squarespace.com/references/websites.md +165 -0
- package/template/.aioson/skills/marketing/references/anti-patterns.md +254 -0
- package/template/.aioson/skills/marketing/references/fascinations.md +192 -0
- package/template/.aioson/skills/marketing/references/five-acts.md +248 -0
- package/template/.aioson/skills/marketing/references/market-intelligence.md +198 -0
- package/template/.aioson/skills/marketing/references/offer-structure.md +203 -0
- package/template/.aioson/skills/marketing/references/one-belief.md +149 -0
- package/template/.aioson/skills/marketing/references/patterns.md +218 -0
- package/template/.aioson/skills/marketing/references/pms-research.md +193 -0
- package/template/.aioson/skills/marketing/vsl-craft.md +385 -0
- package/template/.aioson/skills/process/aioson-spec-driven/SKILL.md +1 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/analyst.md +30 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/architect.md +23 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/dev.md +47 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/deyvin.md +27 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/maintenance-and-state.md +35 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/product.md +25 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/qa.md +30 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/sheldon.md +25 -0
- package/template/.aioson/skills/process/design-hybrid-forge/SKILL.md +4 -1
- package/template/.aioson/skills/process/design-hybrid-forge/references/output-contract.md +15 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/pair-compatibility.md +32 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/quality-gates.md +20 -0
- package/template/.aioson/skills/process/simplify/SKILL.md +173 -0
- package/template/.aioson/skills/static/context-budget-guide.md +46 -0
- package/template/.aioson/skills/static/harness-sensors.md +74 -0
- package/template/.aioson/skills/static/landing-page-deploy.md +192 -0
- package/template/.aioson/skills/static/landing-page-forge.md +730 -0
- package/template/.aioson/skills/static/multi-agent-patterns.md +43 -0
- package/template/.aioson/skills/static/react-motion-patterns.md +22 -0
- package/template/.aioson/skills/static/static-html-patterns/checklists.md +43 -0
- package/template/.aioson/skills/static/static-html-patterns/css-tokens.md +609 -0
- package/template/.aioson/skills/static/static-html-patterns/motion.md +193 -0
- package/template/.aioson/skills/static/static-html-patterns/premium.md +711 -0
- package/template/.aioson/skills/static/static-html-patterns/structure.md +209 -0
- package/template/.aioson/skills/static/static-html-patterns/utilities.md +190 -0
- package/template/.aioson/skills/static/static-html-patterns.md +58 -1913
- package/template/.aioson/skills/static/threejs-patterns.md +929 -0
- package/template/.aioson/skills/static/ui-ux-modern.md +1 -0
- package/template/.aioson/skills/static/web-research-cache.md +112 -0
- package/template/.aioson/tasks/implementation-plan.md +21 -1
- package/template/.aioson/tasks/squad-create.md +22 -0
- package/template/.aioson/tasks/squad-design.md +30 -0
- package/template/.aioson/templates/squads/digital-marketing-agency/template.json +96 -0
- package/template/.claude/commands/aioson/agent/design-hybrid-forge.md +5 -0
- package/template/.claude/commands/aioson/agent/orache.md +5 -0
- package/template/.claude/commands/aioson/agent/sheldon.md +5 -0
- package/template/.claude/commands/aioson/agent/site-forge.md +5 -0
- package/template/AGENTS.md +55 -3
- package/template/CLAUDE.md +31 -0
- package/template/OPENCODE.md +4 -0
- package/template/researchs/.gitkeep +0 -0
- package/template/.aioson/skills/design-system/components/SKILL.md:Zone.Identifier +0 -0
- package/template/.aioson/skills/design-system/dashboards/SKILL.md:Zone.Identifier +0 -0
- package/template/.aioson/skills/design-system/foundations/SKILL.md:Zone.Identifier +0 -0
- package/template/.aioson/skills/design-system/motion/SKILL.md:Zone.Identifier +0 -0
- package/template/.aioson/skills/design-system/patterns/SKILL.md:Zone.Identifier +0 -0
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Squad executor reflection module
|
|
5
|
+
*
|
|
6
|
+
* Before marking a task DONE, an executor runs a self-critique pass
|
|
7
|
+
* against its output. If the output fails the quality checklist, the
|
|
8
|
+
* executor iterates (up to max_iterations) before escalating to the
|
|
9
|
+
* coordinator or marking as DONE_WITH_CONCERNS.
|
|
10
|
+
*
|
|
11
|
+
* Reflection is triggered automatically when:
|
|
12
|
+
* - A worker calls reflect() before returning its result
|
|
13
|
+
* - The task-decomposer runs a plan step with reflection enabled
|
|
14
|
+
* - The squad:autorun command is invoked with --reflect
|
|
15
|
+
*
|
|
16
|
+
* Checklist sources (in priority order):
|
|
17
|
+
* 1. squad.json → executors[slug].reflection.checklist
|
|
18
|
+
* 2. .aioson/squads/{slug}/quality.md (freeform checklist file)
|
|
19
|
+
* 3. Built-in generic quality criteria (fallback)
|
|
20
|
+
*
|
|
21
|
+
* Verdict:
|
|
22
|
+
* DONE — passed all checks, no issues
|
|
23
|
+
* DONE_WITH_CONCERNS — passed minimally but has minor issues (flagged)
|
|
24
|
+
* NEEDS_ITERATION — failed critical checks, should retry
|
|
25
|
+
* ESCALATE — exhausted iterations, coordinator must decide
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const fs = require('node:fs/promises');
|
|
29
|
+
const path = require('node:path');
|
|
30
|
+
const { checkMustHaves } = require('./verify-gate');
|
|
31
|
+
|
|
32
|
+
// ─── Built-in quality criteria (generic fallback) ────────────────────────────
|
|
33
|
+
|
|
34
|
+
const GENERIC_CHECKLIST = [
|
|
35
|
+
{ id: 'non_empty', label: 'Output is not empty', critical: true },
|
|
36
|
+
{ id: 'on_topic', label: 'Output addresses the task objective', critical: true },
|
|
37
|
+
{ id: 'no_truncation', label: 'Output is not abruptly cut off', critical: true },
|
|
38
|
+
{ id: 'no_filler', label: 'Output has no generic filler content', critical: false },
|
|
39
|
+
{ id: 'actionable', label: 'Output contains concrete information', critical: false }
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
// ─── Checklist loading ────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
async function loadSquadJson(projectDir, squadSlug) {
|
|
45
|
+
const p = path.join(projectDir, '.aioson', 'squads', squadSlug, 'squad.json');
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse(await fs.readFile(p, 'utf8'));
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function loadQualityFile(projectDir, squadSlug) {
|
|
54
|
+
const p = path.join(projectDir, '.aioson', 'squads', squadSlug, 'quality.md');
|
|
55
|
+
try {
|
|
56
|
+
const raw = await fs.readFile(p, 'utf8');
|
|
57
|
+
return parseQualityMarkdown(raw);
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function parseQualityMarkdown(content) {
|
|
64
|
+
const criteria = [];
|
|
65
|
+
const lines = content.split(/\r?\n/);
|
|
66
|
+
let id = 0;
|
|
67
|
+
|
|
68
|
+
for (const line of lines) {
|
|
69
|
+
// Support: "- [critical] Label" or "- Label" or "* Label"
|
|
70
|
+
const match = line.match(/^[-*]\s+(?:\[(\w+)\]\s+)?(.+)$/);
|
|
71
|
+
if (!match) continue;
|
|
72
|
+
|
|
73
|
+
const tag = (match[1] || '').toLowerCase();
|
|
74
|
+
const label = match[2].trim();
|
|
75
|
+
if (!label) continue;
|
|
76
|
+
|
|
77
|
+
criteria.push({
|
|
78
|
+
id: `custom_${++id}`,
|
|
79
|
+
label,
|
|
80
|
+
critical: tag === 'critical'
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return criteria.length > 0 ? criteria : null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function loadChecklist(projectDir, squadSlug, executorSlug) {
|
|
88
|
+
// 1. squad.json executor-specific checklist
|
|
89
|
+
const squadJson = await loadSquadJson(projectDir, squadSlug);
|
|
90
|
+
if (squadJson) {
|
|
91
|
+
const executorConfig = squadJson.executors && squadJson.executors[executorSlug];
|
|
92
|
+
const checklist = executorConfig && executorConfig.reflection && executorConfig.reflection.checklist;
|
|
93
|
+
if (Array.isArray(checklist) && checklist.length > 0) {
|
|
94
|
+
return checklist.map((item, i) => {
|
|
95
|
+
if (typeof item === 'string') {
|
|
96
|
+
return { id: `exec_${i}`, label: item, critical: false };
|
|
97
|
+
}
|
|
98
|
+
return { id: item.id || `exec_${i}`, label: item.label || item, critical: !!item.critical };
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 2. quality.md file
|
|
104
|
+
const fromFile = await loadQualityFile(projectDir, squadSlug);
|
|
105
|
+
if (fromFile) return fromFile;
|
|
106
|
+
|
|
107
|
+
// 3. Generic fallback
|
|
108
|
+
return GENERIC_CHECKLIST;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function loadMaxIterations(squadJson, executorSlug) {
|
|
112
|
+
const config = squadJson && squadJson.executors && squadJson.executors[executorSlug];
|
|
113
|
+
const val = config && config.reflection && config.reflection.max_iterations;
|
|
114
|
+
return Number.isFinite(val) && val > 0 ? Math.min(val, 5) : 2;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ─── Deterministic checks ────────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
const FILLER_PATTERNS = [
|
|
120
|
+
/\bi will\b/i,
|
|
121
|
+
/\bof course\b/i,
|
|
122
|
+
/\bcertainly\b/i,
|
|
123
|
+
/\bsure,?\s+here\b/i,
|
|
124
|
+
/\bas an ai\b/i,
|
|
125
|
+
/\bgreat question\b/i,
|
|
126
|
+
/\bhappy to help\b/i,
|
|
127
|
+
/\bI'?d be happy\b/i
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
function runBuiltinCheck(id, output) {
|
|
131
|
+
const text = String(output || '').trim();
|
|
132
|
+
switch (id) {
|
|
133
|
+
case 'non_empty':
|
|
134
|
+
return text.length > 0;
|
|
135
|
+
case 'on_topic':
|
|
136
|
+
// heuristic: output has at least 50 chars and is not just whitespace
|
|
137
|
+
return text.length >= 50;
|
|
138
|
+
case 'no_truncation':
|
|
139
|
+
// heuristic: doesn't end mid-sentence (no trailing comma or open paren)
|
|
140
|
+
return !/[,(\[{]$/.test(text.replace(/\s+$/, ''));
|
|
141
|
+
case 'no_filler':
|
|
142
|
+
return !FILLER_PATTERNS.some((re) => re.test(text));
|
|
143
|
+
case 'actionable':
|
|
144
|
+
// heuristic: contains at least one noun or verb indicator
|
|
145
|
+
return text.split(/\s+/).length >= 10;
|
|
146
|
+
default:
|
|
147
|
+
// Custom criteria without a built-in check — mark as needs-llm-review
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ─── Core reflection ─────────────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Run a reflection pass on an executor's output.
|
|
156
|
+
*
|
|
157
|
+
* @param {string} output — The text output to evaluate
|
|
158
|
+
* @param {object} context — { projectDir, squadSlug, executorSlug, taskTitle?, iteration?, task? }
|
|
159
|
+
* task: the full task object (optional) — enables must_haves verification
|
|
160
|
+
* @param {object} [options] — { checklist?, verbose? }
|
|
161
|
+
* @returns {Promise<ReflectionResult>}
|
|
162
|
+
*
|
|
163
|
+
* ReflectionResult:
|
|
164
|
+
* {
|
|
165
|
+
* verdict: 'DONE' | 'DONE_WITH_CONCERNS' | 'NEEDS_ITERATION' | 'ESCALATE',
|
|
166
|
+
* passed: boolean,
|
|
167
|
+
* score: number, // 0.0–1.0
|
|
168
|
+
* iteration: number, // current iteration number
|
|
169
|
+
* max_iterations: number,
|
|
170
|
+
* issues: string[], // failed criteria labels
|
|
171
|
+
* critical_failures: string[],
|
|
172
|
+
* needs_llm_review: string[], // criteria that couldn't be checked deterministically
|
|
173
|
+
* summary: string, // one-line human-readable result
|
|
174
|
+
* checklist: object[] // full results per criterion
|
|
175
|
+
* }
|
|
176
|
+
*/
|
|
177
|
+
async function reflect(output, context, options = {}) {
|
|
178
|
+
const { projectDir, squadSlug, executorSlug, taskTitle = 'task', iteration = 1, task } = context;
|
|
179
|
+
|
|
180
|
+
const squadJson = await loadSquadJson(projectDir, squadSlug);
|
|
181
|
+
const maxIterations = loadMaxIterations(squadJson, executorSlug);
|
|
182
|
+
|
|
183
|
+
const checklist = options.checklist
|
|
184
|
+
? options.checklist.map((item, i) => (
|
|
185
|
+
typeof item === 'string'
|
|
186
|
+
? { id: `opt_${i}`, label: item, critical: false }
|
|
187
|
+
: item
|
|
188
|
+
))
|
|
189
|
+
: await loadChecklist(projectDir, squadSlug, executorSlug);
|
|
190
|
+
|
|
191
|
+
const results = [];
|
|
192
|
+
const issues = [];
|
|
193
|
+
const criticalFailures = [];
|
|
194
|
+
const needsLlmReview = [];
|
|
195
|
+
|
|
196
|
+
for (const criterion of checklist) {
|
|
197
|
+
const checkResult = runBuiltinCheck(criterion.id, output);
|
|
198
|
+
|
|
199
|
+
if (checkResult === null) {
|
|
200
|
+
needsLlmReview.push(criterion.label);
|
|
201
|
+
results.push({ ...criterion, result: 'needs_review', passed: null });
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
results.push({ ...criterion, result: checkResult ? 'pass' : 'fail', passed: checkResult });
|
|
206
|
+
|
|
207
|
+
if (!checkResult) {
|
|
208
|
+
issues.push(criterion.label);
|
|
209
|
+
if (criterion.critical) criticalFailures.push(criterion.label);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ── must_haves verification (4-tier gate) ─────────────────────────────────
|
|
214
|
+
let mustHavesResult = null;
|
|
215
|
+
if (task && task.must_haves) {
|
|
216
|
+
mustHavesResult = await checkMustHaves(task.must_haves, output, projectDir).catch(() => null);
|
|
217
|
+
|
|
218
|
+
if (mustHavesResult) {
|
|
219
|
+
// Artifact failures are critical (file must exist and be substantive)
|
|
220
|
+
for (const failure of mustHavesResult.failures) {
|
|
221
|
+
criticalFailures.push(`[must_have] ${failure}`);
|
|
222
|
+
issues.push(`[must_have] ${failure}`);
|
|
223
|
+
}
|
|
224
|
+
// Warnings are non-critical (truths, key_links)
|
|
225
|
+
for (const warning of mustHavesResult.warnings) {
|
|
226
|
+
issues.push(`[must_have] ${warning}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const evaluated = results.filter((r) => r.passed !== null);
|
|
232
|
+
const passedCount = evaluated.filter((r) => r.passed).length;
|
|
233
|
+
const score = evaluated.length > 0 ? passedCount / evaluated.length : 1.0;
|
|
234
|
+
const passed = criticalFailures.length === 0;
|
|
235
|
+
|
|
236
|
+
let verdict;
|
|
237
|
+
if (criticalFailures.length > 0 && iteration < maxIterations) {
|
|
238
|
+
verdict = 'NEEDS_ITERATION';
|
|
239
|
+
} else if (criticalFailures.length > 0 && iteration >= maxIterations) {
|
|
240
|
+
verdict = 'ESCALATE';
|
|
241
|
+
} else if (issues.length > 0) {
|
|
242
|
+
verdict = 'DONE_WITH_CONCERNS';
|
|
243
|
+
} else {
|
|
244
|
+
verdict = 'DONE';
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const summary = buildSummary(verdict, score, issues, criticalFailures, taskTitle, iteration, maxIterations);
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
verdict,
|
|
251
|
+
passed,
|
|
252
|
+
score: Math.round(score * 100) / 100,
|
|
253
|
+
iteration,
|
|
254
|
+
max_iterations: maxIterations,
|
|
255
|
+
issues,
|
|
256
|
+
critical_failures: criticalFailures,
|
|
257
|
+
needs_llm_review: needsLlmReview,
|
|
258
|
+
must_haves_result: mustHavesResult,
|
|
259
|
+
summary,
|
|
260
|
+
checklist: results
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function buildSummary(verdict, score, issues, criticalFailures, taskTitle, iteration, maxIterations) {
|
|
265
|
+
const scoreStr = `${Math.round(score * 100)}%`;
|
|
266
|
+
switch (verdict) {
|
|
267
|
+
case 'DONE':
|
|
268
|
+
return `[DONE] "${taskTitle}" passed all checks (${scoreStr})`;
|
|
269
|
+
case 'DONE_WITH_CONCERNS':
|
|
270
|
+
return `[DONE_WITH_CONCERNS] "${taskTitle}" passed (${scoreStr}) with ${issues.length} minor issue(s): ${issues.slice(0, 2).join(', ')}`;
|
|
271
|
+
case 'NEEDS_ITERATION':
|
|
272
|
+
return `[NEEDS_ITERATION] "${taskTitle}" failed ${criticalFailures.length} critical check(s) — iteration ${iteration}/${maxIterations}: ${criticalFailures.slice(0, 2).join(', ')}`;
|
|
273
|
+
case 'ESCALATE':
|
|
274
|
+
return `[ESCALATE] "${taskTitle}" exhausted ${maxIterations} iterations — coordinator must decide. Failed: ${criticalFailures.join(', ')}`;
|
|
275
|
+
default:
|
|
276
|
+
return `[${verdict}] ${scoreStr}`;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Decide whether to iterate based on a reflection result.
|
|
282
|
+
* Returns true if another attempt is warranted.
|
|
283
|
+
*/
|
|
284
|
+
function shouldIterate(reflectionResult) {
|
|
285
|
+
return reflectionResult.verdict === 'NEEDS_ITERATION';
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Run a full reflection loop: reflect → iterate if needed → return final result.
|
|
290
|
+
*
|
|
291
|
+
* @param {function} executeFn — async function that produces output: async () => string
|
|
292
|
+
* @param {object} context — same as reflect()
|
|
293
|
+
* @param {object} [options] — { checklist?, onIteration?, verbose? }
|
|
294
|
+
* @returns {Promise<{ output: string, reflection: ReflectionResult, iterations: number }>}
|
|
295
|
+
*/
|
|
296
|
+
async function reflectLoop(executeFn, context, options = {}) {
|
|
297
|
+
const { projectDir, squadSlug, executorSlug } = context;
|
|
298
|
+
const squadJson = await loadSquadJson(projectDir, squadSlug);
|
|
299
|
+
const maxIterations = loadMaxIterations(squadJson, executorSlug);
|
|
300
|
+
|
|
301
|
+
let lastOutput = null;
|
|
302
|
+
let lastReflection = null;
|
|
303
|
+
let iteration = 1;
|
|
304
|
+
|
|
305
|
+
while (iteration <= maxIterations) {
|
|
306
|
+
lastOutput = await executeFn(iteration, lastReflection);
|
|
307
|
+
|
|
308
|
+
lastReflection = await reflect(lastOutput, { ...context, iteration }, options);
|
|
309
|
+
|
|
310
|
+
if (options.onIteration) {
|
|
311
|
+
try { await options.onIteration(iteration, lastOutput, lastReflection); } catch { /* ignore */ }
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (!shouldIterate(lastReflection)) break;
|
|
315
|
+
iteration++;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
output: lastOutput,
|
|
320
|
+
reflection: lastReflection,
|
|
321
|
+
iterations: iteration
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Format a reflection result as a markdown report.
|
|
327
|
+
* Useful for the bus (posting reflection results as feedback messages).
|
|
328
|
+
*/
|
|
329
|
+
function formatReport(result, executorSlug) {
|
|
330
|
+
const lines = [
|
|
331
|
+
`## Reflection: ${result.verdict}`,
|
|
332
|
+
`Executor: ${executorSlug || 'unknown'}`,
|
|
333
|
+
`Score: ${Math.round(result.score * 100)}% | Iteration: ${result.iteration}/${result.max_iterations}`,
|
|
334
|
+
'',
|
|
335
|
+
`**Summary:** ${result.summary}`
|
|
336
|
+
];
|
|
337
|
+
|
|
338
|
+
if (result.critical_failures.length > 0) {
|
|
339
|
+
lines.push('', '**Critical failures:**');
|
|
340
|
+
for (const f of result.critical_failures) lines.push(`- ❌ ${f}`);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (result.issues.length > result.critical_failures.length) {
|
|
344
|
+
lines.push('', '**Minor issues:**');
|
|
345
|
+
for (const issue of result.issues) {
|
|
346
|
+
if (!result.critical_failures.includes(issue)) lines.push(`- ⚠ ${issue}`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (result.needs_llm_review.length > 0) {
|
|
351
|
+
lines.push('', '**Needs LLM review (could not evaluate deterministically):**');
|
|
352
|
+
for (const c of result.needs_llm_review) lines.push(`- 🔍 ${c}`);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return lines.join('\n');
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
module.exports = {
|
|
359
|
+
reflect,
|
|
360
|
+
reflectLoop,
|
|
361
|
+
shouldIterate,
|
|
362
|
+
formatReport,
|
|
363
|
+
loadChecklist,
|
|
364
|
+
GENERIC_CHECKLIST
|
|
365
|
+
};
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Squad Scaffold — Plan 80, Script 3
|
|
5
|
+
*
|
|
6
|
+
* Deterministic generator of squad directory structure.
|
|
7
|
+
* Eliminates ~2,000 tokens of LLM usage by creating all 15+ files/directories
|
|
8
|
+
* that every squad needs via templates.
|
|
9
|
+
*
|
|
10
|
+
* Integrates with state-manager.js for STATE.md generation.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* node squad-scaffold.js --slug=<slug> --name="Name" --mode=content|code|hybrid
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('node:fs/promises');
|
|
17
|
+
const path = require('node:path');
|
|
18
|
+
const stateManager = require('./state-manager');
|
|
19
|
+
|
|
20
|
+
const SQUADS_DIR = path.join('.aioson', 'squads');
|
|
21
|
+
|
|
22
|
+
// ─── Templates ───────────���─────────────────────────��─────────────────────────
|
|
23
|
+
|
|
24
|
+
function agentsSkeleton(name, mode) {
|
|
25
|
+
const executorTypes = {
|
|
26
|
+
content: ['writer', 'editor', 'researcher'],
|
|
27
|
+
code: ['developer', 'reviewer', 'tester'],
|
|
28
|
+
hybrid: ['researcher', 'developer', 'writer']
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const types = executorTypes[mode] || executorTypes.hybrid;
|
|
32
|
+
const slots = types.map((t) => `### ${t}\n- **Role:** (define)\n- **Tools:** (define)\n- **Checklist:** (define)`);
|
|
33
|
+
|
|
34
|
+
return `# ${name} — Agent Roster\n\n${slots.join('\n\n')}\n`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function manifestSkeleton(slug, name, mode) {
|
|
38
|
+
return JSON.stringify({
|
|
39
|
+
slug,
|
|
40
|
+
name,
|
|
41
|
+
mode,
|
|
42
|
+
created_at: new Date().toISOString(),
|
|
43
|
+
budget: {
|
|
44
|
+
max_tokens_per_session: null,
|
|
45
|
+
max_tokens_per_task: null,
|
|
46
|
+
action_on_exceed: 'pause'
|
|
47
|
+
},
|
|
48
|
+
depends_on: [],
|
|
49
|
+
subscriptions: [],
|
|
50
|
+
hooks: {
|
|
51
|
+
pre_run: null,
|
|
52
|
+
post_run: null
|
|
53
|
+
},
|
|
54
|
+
anti_loop: {
|
|
55
|
+
threshold: 8,
|
|
56
|
+
action: 'feedback'
|
|
57
|
+
}
|
|
58
|
+
}, null, 2);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function squadMdSkeleton(name, mode) {
|
|
62
|
+
return `# ${name}\n\n## Overview\n(describe the squad's purpose)\n\n## Mode\n${mode}\n\n## Executors\nSee agents/agents.md\n\n## Workflows\n(define execution workflows)\n\n## Quality Gates\nSee checklists/quality.md\n`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function designDocSkeleton(name) {
|
|
66
|
+
return `# ${name} — Design Document\n\n## Problem Statement\n(what problem does this squad solve?)\n\n## Approach\n(how will it be solved?)\n\n## Executors & Responsibilities\n(who does what?)\n\n## Data Flow\n(how do executors communicate?)\n\n## Risks & Mitigations\n(what could go wrong?)\n`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function readinessSkeleton(name) {
|
|
70
|
+
return `# ${name} — Readiness Checklist\n\n- [ ] Squad manifest configured\n- [ ] All executors defined in agents.md\n- [ ] Quality checklist in place\n- [ ] At least one workflow defined\n- [ ] Design doc reviewed\n- [ ] Test run completed\n`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function qualitySkeleton(name) {
|
|
74
|
+
return `# ${name} — Quality Checklist\n\n## Per-Task Checks\n- [ ] Output matches acceptance criteria\n- [ ] No placeholder or TODO content\n- [ ] Files listed in brief are created/modified\n\n## Per-Session Checks\n- [ ] All tasks completed or properly escalated\n- [ ] Bus has no unresolved blocks\n- [ ] STATE.md updated with decisions\n\n## Per-Deliverable Checks\n- [ ] verify:gate passes on final output\n- [ ] No sensitive data in output files\n`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function learningsIndexSkeleton(name) {
|
|
78
|
+
return `# ${name} — Learnings Index\n\nsession_count: 0\n\n## Extracted Learnings\n*(populated automatically by learning-extractor after sessions)*\n\n## Manual Observations\n*(add observations here)*\n`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Scaffold a new squad directory structure.
|
|
85
|
+
*
|
|
86
|
+
* @param {string} projectDir — Project root
|
|
87
|
+
* @param {object} options — { slug, name, mode }
|
|
88
|
+
* @returns {Promise<object>} — { ok, slug, files, directories }
|
|
89
|
+
*/
|
|
90
|
+
async function scaffoldSquad(projectDir, options = {}) {
|
|
91
|
+
const { slug, name, mode = 'hybrid' } = options;
|
|
92
|
+
|
|
93
|
+
if (!slug) return { ok: false, error: 'slug is required' };
|
|
94
|
+
if (!name) return { ok: false, error: 'name is required' };
|
|
95
|
+
if (!['content', 'code', 'hybrid'].includes(mode)) {
|
|
96
|
+
return { ok: false, error: `Invalid mode "${mode}" — use content|code|hybrid` };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const squadDir = path.join(projectDir, SQUADS_DIR, slug);
|
|
100
|
+
|
|
101
|
+
// Check if slug already exists
|
|
102
|
+
try {
|
|
103
|
+
await fs.access(squadDir);
|
|
104
|
+
return { ok: false, error: `Squad "${slug}" already exists at ${squadDir}` };
|
|
105
|
+
} catch { /* expected — directory doesn't exist yet */ }
|
|
106
|
+
|
|
107
|
+
const createdFiles = [];
|
|
108
|
+
const createdDirs = [];
|
|
109
|
+
|
|
110
|
+
// Helper
|
|
111
|
+
async function writeFile(relPath, content) {
|
|
112
|
+
const fullPath = path.join(projectDir, relPath);
|
|
113
|
+
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
114
|
+
await fs.writeFile(fullPath, content, 'utf8');
|
|
115
|
+
createdFiles.push(relPath);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function ensureDir(relPath) {
|
|
119
|
+
const fullPath = path.join(projectDir, relPath);
|
|
120
|
+
await fs.mkdir(fullPath, { recursive: true });
|
|
121
|
+
createdDirs.push(relPath);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const base = path.join(SQUADS_DIR, slug);
|
|
125
|
+
|
|
126
|
+
// Squad directory files
|
|
127
|
+
await writeFile(path.join(base, 'agents', 'agents.md'), agentsSkeleton(name, mode));
|
|
128
|
+
await writeFile(path.join(base, 'squad.manifest.json'), manifestSkeleton(slug, name, mode));
|
|
129
|
+
await writeFile(path.join(base, 'squad.md'), squadMdSkeleton(name, mode));
|
|
130
|
+
await writeFile(path.join(base, 'docs', 'design-doc.md'), designDocSkeleton(name));
|
|
131
|
+
await writeFile(path.join(base, 'docs', 'readiness.md'), readinessSkeleton(name));
|
|
132
|
+
await writeFile(path.join(base, 'checklists', 'quality.md'), qualitySkeleton(name));
|
|
133
|
+
await writeFile(path.join(base, 'learnings', 'index.md'), learningsIndexSkeleton(name));
|
|
134
|
+
|
|
135
|
+
// Empty directories
|
|
136
|
+
await ensureDir(path.join(base, 'workflows'));
|
|
137
|
+
await ensureDir(path.join(base, 'scripts'));
|
|
138
|
+
await ensureDir(path.join(base, 'script-plans'));
|
|
139
|
+
await ensureDir(path.join(base, 'bus'));
|
|
140
|
+
|
|
141
|
+
// Output directories
|
|
142
|
+
await ensureDir(path.join('output', slug));
|
|
143
|
+
await ensureDir(path.join('aioson-logs', slug));
|
|
144
|
+
await ensureDir(path.join('media', slug));
|
|
145
|
+
|
|
146
|
+
// STATE.md via state-manager (already formatted)
|
|
147
|
+
await stateManager.writeState(projectDir, slug, stateManager.readState
|
|
148
|
+
? await stateManager.readState(projectDir, slug)
|
|
149
|
+
: {
|
|
150
|
+
meta: {
|
|
151
|
+
squad: slug,
|
|
152
|
+
current_session: '',
|
|
153
|
+
sessions_completed: 0,
|
|
154
|
+
tasks_completed_total: 0,
|
|
155
|
+
avg_tasks_per_session: 0,
|
|
156
|
+
last_activity: new Date().toISOString()
|
|
157
|
+
},
|
|
158
|
+
decisions: [],
|
|
159
|
+
blockers: [],
|
|
160
|
+
pending: [],
|
|
161
|
+
notes: [`Squad "${name}" scaffolded (mode: ${mode})`]
|
|
162
|
+
}
|
|
163
|
+
);
|
|
164
|
+
createdFiles.push(path.join(base, 'STATE.md'));
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
ok: true,
|
|
168
|
+
slug,
|
|
169
|
+
name,
|
|
170
|
+
mode,
|
|
171
|
+
files: createdFiles,
|
|
172
|
+
directories: createdDirs,
|
|
173
|
+
total: createdFiles.length + createdDirs.length
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
module.exports = { scaffoldSquad };
|