code-ia-sota 0.1.0 → 0.1.1

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 CHANGED
@@ -10,6 +10,55 @@ npx code-ia-sota
10
10
 
11
11
  Installs the canonical `AGENTS.md` and `.agents/` scaffold into the current directory.
12
12
 
13
+ This installer is intentionally a personal bootstrap. It clones the current scaffold state from the GitHub repository at install time instead of pinning a packaged template snapshot.
14
+
15
+ By default, the installer opens a tool selector inspired by OpenSpec so you can generate lightweight integrations for supported CLIs/IDEs on top of the base scaffold. In CI or GitHub Actions, it falls back to `codex` unless `--tools` is passed explicitly.
16
+
17
+ Supported tool ids:
18
+
19
+ - `codex`
20
+ - `claude`
21
+ - `cursor`
22
+ - `windsurf`
23
+
24
+ Non-interactive examples:
25
+
26
+ ```sh
27
+ # Base scaffold only
28
+ npx code-ia-sota --tools none
29
+
30
+ # Install base scaffold plus Claude + Cursor shims
31
+ npx code-ia-sota --tools claude,cursor
32
+
33
+ # Install every supported integration
34
+ npx code-ia-sota --tools all
35
+
36
+ # List supported tool ids
37
+ npx code-ia-sota --list-tools
38
+ ```
39
+
40
+ Generated integrations:
41
+
42
+ - `claude` -> `CLAUDE.md`
43
+ - `cursor` -> `.cursor/rules/code-ia-sota.mdc`
44
+ - `windsurf` -> `.windsurf/rules/code-ia-sota.md`
45
+ - `codex` -> no extra file; uses the root scaffold directly
46
+
47
+ ## Harness validation
48
+
49
+ ```sh
50
+ npm run validate:harness
51
+ ```
52
+
53
+ Validates `.agents/skills/*/SKILL.md` and `.agents/workflows/*.md`.
54
+ The local npm script validates the repository root from the CLI package directory.
55
+
56
+ You can also run:
57
+
58
+ ```sh
59
+ code-ia-sota validate [path]
60
+ ```
61
+
13
62
  ## Local development
14
63
 
15
64
  ```sh
@@ -22,5 +71,13 @@ code-ia-sota
22
71
 
23
72
  ```sh
24
73
  npm pack
25
- npx ./code-ia-sota-0.1.0.tgz
74
+ npx ./code-ia-sota-0.1.1.tgz
26
75
  ```
76
+
77
+ ## GitHub Actions publish
78
+
79
+ The repository workflow `.github/workflows/publish-cli.yml` publishes the package from `code-ia-sota-cli/` on pushes to `main` that change the CLI files, or by manual dispatch.
80
+
81
+ Required secret:
82
+
83
+ - `NPM_TOKEN` with publish permission for the `code-ia-sota` package on npm
@@ -4,6 +4,7 @@ import fs from "node:fs/promises";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
6
6
  import process from "node:process";
7
+ import readline from "node:readline/promises";
7
8
 
8
9
  import fsExtra from "fs-extra";
9
10
  import simpleGit from "simple-git";
@@ -13,6 +14,118 @@ const REPO_URL =
13
14
  "https://github.com/wolfox33/Code_IA_SotA.git";
14
15
 
15
16
  const TARGET_DIR = process.cwd();
17
+ const MANAGED_FILE_MARKER =
18
+ "Generated by code-ia-sota";
19
+ const SUPPORTED_TOOLS = [
20
+ {
21
+ id: "codex",
22
+ label: "Codex",
23
+ description:
24
+ "Uses the root AGENTS.md and .agents scaffold directly.",
25
+ detectPaths: [
26
+ ".codex"
27
+ ],
28
+ files: []
29
+ },
30
+ {
31
+ id: "claude",
32
+ label: "Claude Code",
33
+ description:
34
+ "Creates a root CLAUDE.md shim that points to AGENTS.md and .agents.",
35
+ detectPaths: [
36
+ "CLAUDE.md",
37
+ ".claude"
38
+ ],
39
+ files: [
40
+ {
41
+ relativePath: "CLAUDE.md",
42
+ content: [
43
+ "# Code IA Sota",
44
+ "",
45
+ "Use `AGENTS.md` at the repository root as the canonical project policy.",
46
+ "",
47
+ "Load additional context on demand from:",
48
+ "- `.agents/project/context.md`",
49
+ "- `.agents/USER.md`",
50
+ "- `.agents/project/MEMORY.md`",
51
+ "- `.agents/workflows/`",
52
+ "- `.agents/skills/`",
53
+ "",
54
+ "Prefer the smallest correct change, preserve the existing architecture, and do not expand scope silently.",
55
+ "",
56
+ `<!-- ${MANAGED_FILE_MARKER}: claude -->`
57
+ ].join("\n")
58
+ }
59
+ ]
60
+ },
61
+ {
62
+ id: "cursor",
63
+ label: "Cursor",
64
+ description:
65
+ "Creates an always-on Cursor rule that points to AGENTS.md and .agents.",
66
+ detectPaths: [
67
+ ".cursor"
68
+ ],
69
+ files: [
70
+ {
71
+ relativePath: path.join(".cursor", "rules", "code-ia-sota.mdc"),
72
+ content: [
73
+ "---",
74
+ "description: Code IA Sota project policy",
75
+ "alwaysApply: true",
76
+ "---",
77
+ "",
78
+ "Use `AGENTS.md` at the repository root as the canonical project policy.",
79
+ "",
80
+ "Load additional context on demand from `.agents/project/context.md`, `.agents/USER.md`, `.agents/project/MEMORY.md`, `.agents/workflows/`, and `.agents/skills/`.",
81
+ "",
82
+ "Prefer the smallest correct change, preserve the existing architecture, and do not expand scope silently.",
83
+ "",
84
+ `<!-- ${MANAGED_FILE_MARKER}: cursor -->`
85
+ ].join("\n")
86
+ }
87
+ ]
88
+ },
89
+ {
90
+ id: "windsurf",
91
+ label: "Windsurf",
92
+ description:
93
+ "Creates a workspace rule file that points Cascade to AGENTS.md and .agents.",
94
+ detectPaths: [
95
+ ".windsurf",
96
+ ".windsurfrules"
97
+ ],
98
+ files: [
99
+ {
100
+ relativePath: path.join(".windsurf", "rules", "code-ia-sota.md"),
101
+ content: [
102
+ "# Code IA Sota",
103
+ "",
104
+ "Use `AGENTS.md` at the repository root as the canonical project policy.",
105
+ "",
106
+ "Load additional context on demand from `.agents/project/context.md`, `.agents/USER.md`, `.agents/project/MEMORY.md`, `.agents/workflows/`, and `.agents/skills/`.",
107
+ "",
108
+ "Prefer the smallest correct change, preserve the existing architecture, and do not expand scope silently.",
109
+ "",
110
+ `<!-- ${MANAGED_FILE_MARKER}: windsurf -->`
111
+ ].join("\n")
112
+ }
113
+ ]
114
+ }
115
+ ];
116
+
117
+ const REQUIRED_OPERATIONAL_SKILL_SECTIONS = [
118
+ ["Objetivo", "Objective"],
119
+ ["Use this skill when"],
120
+ ["Do not use this skill when"],
121
+ ["Output contracts", "Output esperado"],
122
+ ["Procedure", "Processo", "Review dimensions", "Diagnostic dimensions"],
123
+ ["Verification"]
124
+ ];
125
+
126
+ const DENSITY_THRESHOLD = parseInt(process.env.HARNESS_DENSITY_THRESHOLD || "30", 10);
127
+ const FAIL_ON_WARNINGS = process.env.HARNESS_FAIL_ON_WARNINGS === "true";
128
+ const ALERT_THRESHOLD = parseInt(process.env.HARNESS_ALERT_THRESHOLD || "10", 10);
16
129
 
17
130
  async function exists(filePath) {
18
131
  try {
@@ -23,6 +136,211 @@ async function exists(filePath) {
23
136
  }
24
137
  }
25
138
 
139
+ function parseFlagValue(flagName) {
140
+ const args = process.argv.slice(2);
141
+ const index = args.findIndex((arg) => arg === flagName);
142
+
143
+ if (index === -1) {
144
+ return null;
145
+ }
146
+
147
+ return args[index + 1] || null;
148
+ }
149
+
150
+ function listSupportedTools() {
151
+ for (const tool of SUPPORTED_TOOLS) {
152
+ console.log(
153
+ ` ${tool.id.padEnd(8)} ${tool.label} - ${tool.description}`
154
+ );
155
+ }
156
+ }
157
+
158
+ function resolveToolSelection(rawValue) {
159
+ if (!rawValue) {
160
+ return null;
161
+ }
162
+
163
+ const normalizedValue = rawValue.trim().toLowerCase();
164
+
165
+ if (normalizedValue === "all") {
166
+ return SUPPORTED_TOOLS.map((tool) => tool.id);
167
+ }
168
+
169
+ if (normalizedValue === "none") {
170
+ return [];
171
+ }
172
+
173
+ const selectedToolIds = [];
174
+ const seen = new Set();
175
+
176
+ for (const token of normalizedValue.split(",")) {
177
+ const value = token.trim();
178
+
179
+ if (!value) {
180
+ continue;
181
+ }
182
+
183
+ let toolId = value;
184
+ if (/^\d+$/.test(value)) {
185
+ const index = parseInt(value, 10) - 1;
186
+ toolId = SUPPORTED_TOOLS[index]?.id;
187
+ }
188
+
189
+ if (!toolId || !SUPPORTED_TOOLS.some((tool) => tool.id === toolId)) {
190
+ const availableTools = SUPPORTED_TOOLS.map((tool) => tool.id).join(", ");
191
+ throw new Error(
192
+ `Unknown tool "${value}". Use one of: ${availableTools}, all, none`
193
+ );
194
+ }
195
+
196
+ if (!seen.has(toolId)) {
197
+ seen.add(toolId);
198
+ selectedToolIds.push(toolId);
199
+ }
200
+ }
201
+
202
+ return selectedToolIds;
203
+ }
204
+
205
+ async function detectTools() {
206
+ const detectedToolIds = [];
207
+
208
+ for (const tool of SUPPORTED_TOOLS) {
209
+ for (const detectPath of tool.detectPaths) {
210
+ if (await exists(path.join(TARGET_DIR, detectPath))) {
211
+ detectedToolIds.push(tool.id);
212
+ break;
213
+ }
214
+ }
215
+ }
216
+
217
+ return detectedToolIds;
218
+ }
219
+
220
+ async function promptToolSelection() {
221
+ const detectedToolIds = await detectTools();
222
+
223
+ console.log(pc.cyan("Select CLI/IDE integrations to install."));
224
+ console.log("Press Enter to accept detected tools, or type comma-separated ids/numbers.");
225
+ console.log("Use `all` to install every integration or `none` to skip tool-specific files.");
226
+ console.log("");
227
+
228
+ for (const [index, tool] of SUPPORTED_TOOLS.entries()) {
229
+ const marker = detectedToolIds.includes(tool.id) ? "[x]" : "[ ]";
230
+ console.log(
231
+ ` ${index + 1}. ${marker} ${tool.id.padEnd(8)} ${tool.label} - ${tool.description}`
232
+ );
233
+ }
234
+
235
+ console.log("");
236
+
237
+ if (detectedToolIds.length > 0) {
238
+ console.log(
239
+ `Detected tools: ${detectedToolIds.join(", ")}`
240
+ );
241
+ } else {
242
+ console.log("Detected tools: none");
243
+ console.log("Default on empty input: codex (base scaffold only)");
244
+ }
245
+
246
+ console.log("");
247
+
248
+ const rl = readline.createInterface({
249
+ input: process.stdin,
250
+ output: process.stdout
251
+ });
252
+
253
+ try {
254
+ const answer = await rl.question("Tools> ");
255
+
256
+ if (!answer.trim()) {
257
+ if (detectedToolIds.length > 0) {
258
+ return detectedToolIds;
259
+ }
260
+
261
+ return ["codex"];
262
+ }
263
+
264
+ return resolveToolSelection(answer);
265
+ } finally {
266
+ rl.close();
267
+ }
268
+ }
269
+
270
+ async function selectTools() {
271
+ if (process.argv.includes("--list-tools")) {
272
+ console.log(pc.bold(pc.green("Supported tools")));
273
+ listSupportedTools();
274
+ process.exit(0);
275
+ }
276
+
277
+ const toolsArg = parseFlagValue("--tools");
278
+ const selectedTools = resolveToolSelection(toolsArg);
279
+
280
+ if (selectedTools !== null) {
281
+ return selectedTools;
282
+ }
283
+
284
+ if (process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true") {
285
+ return ["codex"];
286
+ }
287
+
288
+ return promptToolSelection();
289
+ }
290
+
291
+ async function writeManagedFile(filePath, content) {
292
+ if (!(await exists(filePath))) {
293
+ await fs.mkdir(path.dirname(filePath), {
294
+ recursive: true
295
+ });
296
+ await fs.writeFile(filePath, content, "utf8");
297
+ return "created";
298
+ }
299
+
300
+ const currentContent = await fs.readFile(filePath, "utf8");
301
+
302
+ if (currentContent === content) {
303
+ return "unchanged";
304
+ }
305
+
306
+ if (!currentContent.includes(MANAGED_FILE_MARKER)) {
307
+ return "skipped";
308
+ }
309
+
310
+ await fs.writeFile(filePath, content, "utf8");
311
+ return "updated";
312
+ }
313
+
314
+ async function installToolIntegrations(selectedToolIds) {
315
+ const summary = {
316
+ created: [],
317
+ updated: [],
318
+ skipped: []
319
+ };
320
+
321
+ for (const toolId of selectedToolIds) {
322
+ const tool = SUPPORTED_TOOLS.find((item) => item.id === toolId);
323
+ if (!tool) {
324
+ continue;
325
+ }
326
+
327
+ for (const file of tool.files) {
328
+ const targetPath = path.join(TARGET_DIR, file.relativePath);
329
+ const result = await writeManagedFile(targetPath, `${file.content}\n`);
330
+
331
+ if (result === "created") {
332
+ summary.created.push(file.relativePath);
333
+ } else if (result === "updated") {
334
+ summary.updated.push(file.relativePath);
335
+ } else if (result === "skipped") {
336
+ summary.skipped.push(file.relativePath);
337
+ }
338
+ }
339
+ }
340
+
341
+ return summary;
342
+ }
343
+
26
344
  async function cloneTemplate(tmpDir) {
27
345
  console.log(pc.cyan("Downloading template..."));
28
346
 
@@ -82,7 +400,942 @@ async function copyFiles(tmpDir) {
82
400
  }
83
401
  }
84
402
 
403
+ function parseFrontmatter(content) {
404
+ const normalizedContent = content.replace(/\r\n/g, "\n");
405
+
406
+ if (!normalizedContent.startsWith("---\n")) {
407
+ return null;
408
+ }
409
+
410
+ const endIndex = normalizedContent.indexOf("\n---", 4);
411
+
412
+ if (endIndex === -1) {
413
+ return null;
414
+ }
415
+
416
+ const raw = normalizedContent.slice(4, endIndex).trim();
417
+ const data = {};
418
+
419
+ for (const line of raw.split("\n")) {
420
+ const match = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
421
+
422
+ if (match) {
423
+ data[match[1]] = match[2].replace(/^["']|["']$/g, "");
424
+ }
425
+ }
426
+
427
+ return {
428
+ data,
429
+ body: normalizedContent.slice(endIndex + 5).trim()
430
+ };
431
+ }
432
+
433
+ function hasSection(content, sectionNames) {
434
+ const names = Array.isArray(sectionNames) ? sectionNames : [sectionNames];
435
+
436
+ return names.some((sectionName) => {
437
+ const escaped = sectionName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
438
+ const pattern = new RegExp(`^#{1,6}\\s+${escaped}\\s*$`, "im");
439
+
440
+ return pattern.test(content);
441
+ });
442
+ }
443
+
444
+ function addFailure(failures, rootDir, filePath, reason) {
445
+ failures.push({
446
+ filePath: path.relative(rootDir, filePath),
447
+ reason
448
+ });
449
+ }
450
+
451
+ function addWarning(warnings, rootDir, filePath, reason) {
452
+ warnings.push({
453
+ filePath: path.relative(rootDir, filePath),
454
+ reason
455
+ });
456
+ }
457
+
458
+ async function listMarkdownFiles(dirPath) {
459
+ if (!(await exists(dirPath))) {
460
+ return [];
461
+ }
462
+
463
+ const entries = await fs.readdir(dirPath, {
464
+ withFileTypes: true
465
+ });
466
+
467
+ return entries
468
+ .filter((entry) => entry.isFile() && entry.name.endsWith(".md") && entry.name !== "README.md")
469
+ .map((entry) => path.join(dirPath, entry.name));
470
+ }
471
+
472
+ async function listSkillFiles(skillsDir) {
473
+ if (!(await exists(skillsDir))) {
474
+ return [];
475
+ }
476
+
477
+ const entries = await fs.readdir(skillsDir, {
478
+ withFileTypes: true
479
+ });
480
+
481
+ return entries
482
+ .filter((entry) => entry.isDirectory())
483
+ .map((entry) => path.join(skillsDir, entry.name, "SKILL.md"));
484
+ }
485
+
486
+ function calculateDensity(content) {
487
+ const lines = content.split("\n").filter(line => line.trim().length > 0);
488
+ const totalLines = lines.length;
489
+
490
+ const procedureMatch = content.match(/^##\s+Procedure/im);
491
+ if (!procedureMatch) {
492
+ return 0;
493
+ }
494
+
495
+ const procedureIndex = procedureMatch.index;
496
+ const nextSectionMatch = content.slice(procedureIndex + procedureMatch[0].length).match(/^##\s+/im);
497
+ const endIndex = nextSectionMatch ? procedureIndex + nextSectionMatch.index : content.length;
498
+
499
+ const procedureContent = content.slice(procedureIndex, endIndex);
500
+ const procedureLines = procedureContent.split("\n").filter(line => line.trim().length > 0).length;
501
+
502
+ return Math.round((procedureLines / totalLines) * 100);
503
+ }
504
+
505
+ async function validateSkill(rootDir, filePath, failures, warnings) {
506
+ if (!(await exists(filePath))) {
507
+ addFailure(failures, rootDir, filePath, "SKILL.md is missing.");
508
+ return;
509
+ }
510
+
511
+ const content = await fs.readFile(filePath, "utf8");
512
+ const frontmatter = parseFrontmatter(content);
513
+
514
+ if (!frontmatter) {
515
+ addFailure(failures, rootDir, filePath, "YAML frontmatter is missing or invalid.");
516
+ return;
517
+ }
518
+
519
+ if (!frontmatter.data.name) {
520
+ addFailure(failures, rootDir, filePath, "frontmatter field `name` is missing.");
521
+ }
522
+
523
+ if (!frontmatter.data.description) {
524
+ addFailure(failures, rootDir, filePath, "frontmatter field `description` is missing.");
525
+ }
526
+
527
+ if (/placeholder/i.test(frontmatter.data.description || "")) {
528
+ addFailure(failures, rootDir, filePath, "placeholder description is not allowed.");
529
+ }
530
+
531
+ for (const sectionGroup of REQUIRED_OPERATIONAL_SKILL_SECTIONS) {
532
+ if (!hasSection(content, sectionGroup)) {
533
+ addWarning(warnings, rootDir, filePath, `operational section group \`${sectionGroup.join(" | ")}\` is missing.`);
534
+ }
535
+ }
536
+
537
+ const density = calculateDensity(content);
538
+ if (density < DENSITY_THRESHOLD) {
539
+ addWarning(warnings, rootDir, filePath, `Low density (<${DENSITY_THRESHOLD}%): ${density}% density. Consider moving reference content to references/ and adding executable procedure.`);
540
+ }
541
+ }
542
+
543
+ async function validateWorkflow(rootDir, filePath, failures) {
544
+ const content = await fs.readFile(filePath, "utf8");
545
+ const frontmatter = parseFrontmatter(content);
546
+
547
+ if (!frontmatter) {
548
+ addFailure(failures, rootDir, filePath, "YAML frontmatter is missing or invalid.");
549
+ return;
550
+ }
551
+
552
+ if (!frontmatter.data.description) {
553
+ addFailure(failures, rootDir, filePath, "frontmatter field `description` is missing.");
554
+ }
555
+
556
+ if (!frontmatter.body) {
557
+ addFailure(failures, rootDir, filePath, "workflow body is empty.");
558
+ }
559
+ }
560
+
561
+ async function validateHarness(rootDir) {
562
+ const agentsDir = path.join(rootDir, ".agents");
563
+ const skillsDir = path.join(agentsDir, "skills");
564
+ const workflowsDir = path.join(agentsDir, "workflows");
565
+ const failures = [];
566
+ const warnings = [];
567
+ const skillFiles = await listSkillFiles(skillsDir);
568
+ const workflowFiles = await listMarkdownFiles(workflowsDir);
569
+
570
+ console.log(pc.bold(pc.green("Code IA Sota")));
571
+ console.log(pc.cyan("Validating harness..."));
572
+
573
+ for (const filePath of skillFiles) {
574
+ await validateSkill(rootDir, filePath, failures, warnings);
575
+ }
576
+
577
+ for (const filePath of workflowFiles) {
578
+ await validateWorkflow(rootDir, filePath, failures);
579
+ }
580
+
581
+ console.log("");
582
+ console.log(`Checked ${skillFiles.length} skills.`);
583
+ console.log(`Checked ${workflowFiles.length} workflows.`);
584
+
585
+ if (warnings.length > 0) {
586
+ console.log("");
587
+ console.warn(pc.yellow(`Validation completed with ${warnings.length} warning(s):`));
588
+
589
+ for (const warning of warnings) {
590
+ console.warn(` ${warning.filePath}: ${warning.reason}`);
591
+ }
592
+
593
+ if (FAIL_ON_WARNINGS) {
594
+ console.log("");
595
+ console.error(pc.red(`Validation failed due to warnings (FAIL_ON_WARNINGS=true).`));
596
+ process.exitCode = 1;
597
+ return;
598
+ }
599
+ }
600
+
601
+ if (failures.length > 0) {
602
+ console.log("");
603
+ console.error(pc.red(`Validation failed with ${failures.length} issue(s):`));
604
+
605
+ for (const failure of failures) {
606
+ console.error(` ${failure.filePath}: ${failure.reason}`);
607
+ }
608
+
609
+ process.exitCode = 1;
610
+ return;
611
+ }
612
+
613
+ console.log("");
614
+ console.log(pc.green("Validation passed."));
615
+ }
616
+
617
+ async function analyzeReferenceContent(content) {
618
+ const suggestions = [];
619
+
620
+ const sections = content.split(/^##\s+/m);
621
+ for (const section of sections) {
622
+ if (!section.trim()) continue;
623
+
624
+ const lines = section.split("\n");
625
+ const sectionName = lines[0].trim();
626
+ const sectionContent = lines.slice(1).join("\n");
627
+
628
+ const hasExternalLinks = /https?:\/\//.test(sectionContent);
629
+ const hasLongLists = sectionContent.split("\n").filter(line => /^[-*]\s/.test(line)).length > 5;
630
+ const hasCodeBlocks = /```[\s\S]*```/.test(sectionContent);
631
+ const isLongSection = sectionContent.split("\n").length > 20;
632
+
633
+ if (hasExternalLinks || hasLongLists || (isLongSection && !hasCodeBlocks)) {
634
+ suggestions.push({
635
+ section: sectionName,
636
+ reason: hasExternalLinks ? "contains external links" : hasLongLists ? "has long lists" : "is long without code blocks",
637
+ action: "consider moving reference content to references/"
638
+ });
639
+ }
640
+ }
641
+
642
+ return suggestions;
643
+ }
644
+
645
+ async function suggestRefactor(rootDir) {
646
+ const agentsDir = path.join(rootDir, ".agents");
647
+ const skillsDir = path.join(agentsDir, "skills");
648
+ const skillFiles = await listSkillFiles(skillsDir);
649
+ const suggestions = [];
650
+
651
+ console.log(pc.bold(pc.green("Code IA Sota")));
652
+ console.log(pc.cyan("Analyzing low-density skills for refactoring suggestions..."));
653
+ console.log("");
654
+
655
+ for (const filePath of skillFiles) {
656
+ if (!(await exists(filePath))) continue;
657
+
658
+ const content = await fs.readFile(filePath, "utf8");
659
+ const density = calculateDensity(content);
660
+
661
+ if (density < DENSITY_THRESHOLD) {
662
+ const referenceSuggestions = await analyzeReferenceContent(content);
663
+ const skillName = path.basename(path.dirname(filePath));
664
+
665
+ suggestions.push({
666
+ skill: skillName,
667
+ density: density,
668
+ filePath: path.relative(rootDir, filePath),
669
+ suggestions: referenceSuggestions
670
+ });
671
+ }
672
+ }
673
+
674
+ suggestions.sort((a, b) => a.density - b.density);
675
+
676
+ if (suggestions.length === 0) {
677
+ console.log(pc.green("No low-density skills found. All skills meet the density threshold."));
678
+ return;
679
+ }
680
+
681
+ console.log(`Found ${suggestions.length} low-density skill(s):`);
682
+ console.log("");
683
+
684
+ for (const suggestion of suggestions) {
685
+ console.log(pc.yellow(`Skill: ${suggestion.skill} (${suggestion.density}% density)`));
686
+ console.log(` File: ${suggestion.filePath}`);
687
+
688
+ if (suggestion.suggestions.length > 0) {
689
+ console.log(" Refactoring suggestions:");
690
+ for (const ref of suggestion.suggestions) {
691
+ console.log(` - Section "${ref.section}": ${ref.reason}. ${ref.action}`);
692
+ }
693
+ } else {
694
+ console.log(" No specific sections identified. Consider expanding the Procedure section.");
695
+ }
696
+
697
+ console.log("");
698
+ }
699
+
700
+ console.log(pc.cyan("To refactor:"));
701
+ console.log(" 1. Review the suggestions above");
702
+ console.log(" 2. Move reference content to references/");
703
+ console.log(" 3. Expand the Procedure section with executable steps");
704
+ console.log(" 4. Re-run validation to check improved density");
705
+ }
706
+
707
+ async function benchmarkDensity(rootDir) {
708
+ const agentsDir = path.join(rootDir, ".agents");
709
+ const skillsDir = path.join(agentsDir, "skills");
710
+ const skillFiles = await listSkillFiles(skillsDir);
711
+ const results = [];
712
+ const jsonOutput = process.argv.includes("--json");
713
+
714
+ console.log(pc.bold(pc.green("Code IA Sota")));
715
+ console.log(pc.cyan("Benchmarking density for all skills..."));
716
+ console.log("");
717
+
718
+ for (const filePath of skillFiles) {
719
+ if (!(await exists(filePath))) continue;
720
+
721
+ const content = await fs.readFile(filePath, "utf8");
722
+ const density = calculateDensity(content);
723
+ const skillName = path.basename(path.dirname(filePath));
724
+
725
+ results.push({
726
+ skill: skillName,
727
+ density: density,
728
+ filePath: path.relative(rootDir, filePath)
729
+ });
730
+ }
731
+
732
+ results.sort((a, b) => a.density - b.density);
733
+
734
+ const densities = results.map(r => r.density);
735
+ const average = Math.round(densities.reduce((a, b) => a + b, 0) / densities.length);
736
+ const min = Math.min(...densities);
737
+ const max = Math.max(...densities);
738
+
739
+ if (jsonOutput) {
740
+ console.log(JSON.stringify({
741
+ skills: results,
742
+ statistics: {
743
+ average: average,
744
+ min: min,
745
+ max: max,
746
+ total: results.length
747
+ }
748
+ }, null, 2));
749
+ return;
750
+ }
751
+
752
+ console.log(`Total skills: ${results.length}`);
753
+ console.log(`Average density: ${average}%`);
754
+ console.log(`Min density: ${min}%`);
755
+ console.log(`Max density: ${max}%`);
756
+ console.log("");
757
+ console.log("Density by skill (ascending):");
758
+ console.log("");
759
+
760
+ for (const result of results) {
761
+ const densityColor = result.density < DENSITY_THRESHOLD ? pc.red : result.density < 50 ? pc.yellow : pc.green;
762
+ console.log(`${densityColor(result.density + "%")} ${result.skill}`);
763
+ console.log(` File: ${result.filePath}`);
764
+ console.log("");
765
+ }
766
+
767
+ console.log(pc.cyan("Low-density skills (<" + DENSITY_THRESHOLD + "%):"));
768
+ const lowDensity = results.filter(r => r.density < DENSITY_THRESHOLD);
769
+ if (lowDensity.length === 0) {
770
+ console.log(" None");
771
+ } else {
772
+ for (const result of lowDensity) {
773
+ console.log(` - ${result.skill} (${result.density}%)`);
774
+ }
775
+ }
776
+ }
777
+
778
+ async function densitySnapshot(rootDir) {
779
+ const agentsDir = path.join(rootDir, ".agents");
780
+ const skillsDir = path.join(agentsDir, "skills");
781
+ const dataDir = path.join(agentsDir, "data");
782
+ const historyFile = path.join(dataDir, "density-history.json");
783
+ const skillFiles = await listSkillFiles(skillsDir);
784
+ const snapshot = {
785
+ timestamp: new Date().toISOString(),
786
+ skills: []
787
+ };
788
+
789
+ console.log(pc.bold(pc.green("Code IA Sota")));
790
+ console.log(pc.cyan("Creating density snapshot..."));
791
+ console.log("");
792
+
793
+ for (const filePath of skillFiles) {
794
+ if (!(await exists(filePath))) continue;
795
+
796
+ const content = await fs.readFile(filePath, "utf8");
797
+ const density = calculateDensity(content);
798
+ const skillName = path.basename(path.dirname(filePath));
799
+
800
+ snapshot.skills.push({
801
+ skill: skillName,
802
+ density: density,
803
+ filePath: path.relative(rootDir, filePath)
804
+ });
805
+ }
806
+
807
+ snapshot.skills.sort((a, b) => a.density - b.density);
808
+
809
+ if (!(await exists(dataDir))) {
810
+ await fs.mkdir(dataDir, { recursive: true });
811
+ }
812
+
813
+ let history = [];
814
+ if (await exists(historyFile)) {
815
+ const historyContent = await fs.readFile(historyFile, "utf8");
816
+ history = JSON.parse(historyContent);
817
+ }
818
+
819
+ history.push(snapshot);
820
+
821
+ await fs.writeFile(historyFile, JSON.stringify(history, null, 2));
822
+
823
+ console.log(`Snapshot created at ${snapshot.timestamp}`);
824
+ console.log(`Total skills: ${snapshot.skills.length}`);
825
+ console.log(`History file: ${historyFile}`);
826
+ console.log(`Total snapshots: ${history.length}`);
827
+ }
828
+
829
+ async function densityTrends(rootDir) {
830
+ const agentsDir = path.join(rootDir, ".agents");
831
+ const historyFile = path.join(agentsDir, "data", "density-history.json");
832
+
833
+ console.log(pc.bold(pc.green("Code IA Sota")));
834
+ console.log(pc.cyan("Generating density trends report..."));
835
+ console.log("");
836
+
837
+ if (!(await exists(historyFile))) {
838
+ console.log(pc.yellow("No density history found. Run `npm run density:snapshot` first."));
839
+ return;
840
+ }
841
+
842
+ const historyContent = await fs.readFile(historyFile, "utf8");
843
+ const history = JSON.parse(historyContent);
844
+
845
+ if (history.length === 0) {
846
+ console.log(pc.yellow("No snapshots found in history."));
847
+ return;
848
+ }
849
+
850
+ console.log(`Total snapshots: ${history.length}`);
851
+ console.log("");
852
+
853
+ const skillTrends = {};
854
+
855
+ for (const snapshot of history) {
856
+ console.log(pc.bold(`Snapshot: ${snapshot.timestamp}`));
857
+
858
+ for (const skill of snapshot.skills) {
859
+ if (!skillTrends[skill.skill]) {
860
+ skillTrends[skill.skill] = [];
861
+ }
862
+ skillTrends[skill.skill].push({
863
+ timestamp: snapshot.timestamp,
864
+ density: skill.density
865
+ });
866
+ }
867
+
868
+ const densities = snapshot.skills.map(s => s.density);
869
+ const average = Math.round(densities.reduce((a, b) => a + b, 0) / densities.length);
870
+ console.log(` Average density: ${average}%`);
871
+ console.log(` Total skills: ${snapshot.skills.length}`);
872
+ console.log("");
873
+ }
874
+
875
+ console.log(pc.cyan("Density trends by skill:"));
876
+ console.log("");
877
+
878
+ for (const [skillName, trends] of Object.entries(skillTrends)) {
879
+ const first = trends[0];
880
+ const last = trends[trends.length - 1];
881
+ const change = last.density - first.density;
882
+ const changeColor = change > 0 ? pc.green : change < 0 ? pc.red : pc.white;
883
+ const changeSign = change > 0 ? "+" : "";
884
+
885
+ console.log(`${skillName}:`);
886
+ console.log(` First: ${first.density}% (${first.timestamp})`);
887
+ console.log(` Last: ${last.density}% (${last.timestamp})`);
888
+ console.log(` Change: ${changeColor(changeSign + change + "%")}`);
889
+ console.log("");
890
+ }
891
+ }
892
+
893
+ async function autoRefactor(rootDir) {
894
+ const agentsDir = path.join(rootDir, ".agents");
895
+ const skillsDir = path.join(agentsDir, "skills");
896
+ const referencesDir = path.join(agentsDir, "references");
897
+ const skillFiles = await listSkillFiles(skillsDir);
898
+ const dryRun = process.argv.includes("--dry-run");
899
+
900
+ console.log(pc.bold(pc.green("Code IA Sota")));
901
+ console.log(pc.cyan("Automated refactoring with user approval..."));
902
+ console.log("");
903
+
904
+ if (dryRun) {
905
+ console.log(pc.yellow("Dry-run mode: no changes will be applied."));
906
+ console.log("");
907
+ }
908
+
909
+ if (!(await exists(referencesDir))) {
910
+ if (!dryRun) {
911
+ await fs.mkdir(referencesDir, { recursive: true });
912
+ }
913
+ console.log(`Created references directory: ${referencesDir}`);
914
+ }
915
+
916
+ for (const filePath of skillFiles) {
917
+ if (!(await exists(filePath))) continue;
918
+
919
+ const content = await fs.readFile(filePath, "utf8");
920
+ const density = calculateDensity(content);
921
+ const skillName = path.basename(path.dirname(filePath));
922
+
923
+ if (density >= DENSITY_THRESHOLD) {
924
+ console.log(pc.green(`Skipping ${skillName} (${density}% density >= ${DENSITY_THRESHOLD}%)`));
925
+ continue;
926
+ }
927
+
928
+ console.log(pc.yellow(`Processing ${skillName} (${density}% density < ${DENSITY_THRESHOLD}%)`));
929
+
930
+ const frontmatter = parseFrontmatter(content);
931
+ if (!frontmatter) {
932
+ console.log(pc.red(` Skipping ${skillName}: no frontmatter found`));
933
+ continue;
934
+ }
935
+
936
+ const suggestions = await analyzeReferenceContent(content);
937
+ if (suggestions.length === 0) {
938
+ console.log(pc.yellow(` No refactoring suggestions for ${skillName}`));
939
+ continue;
940
+ }
941
+
942
+ console.log(` Found ${suggestions.length} refactoring suggestion(s):`);
943
+ for (const suggestion of suggestions) {
944
+ console.log(` - Section "${suggestion.section}": ${suggestion.reason}`);
945
+ }
946
+
947
+ const approved = await promptApproval(`Apply refactoring to ${skillName}? (y/n)`);
948
+ if (!approved) {
949
+ console.log(` Skipped ${skillName}`);
950
+ continue;
951
+ }
952
+
953
+ if (!dryRun) {
954
+ console.log(` Applying refactoring to ${skillName}...`);
955
+ console.log(` Note: Manual refactoring required. Use \`npm run suggest:refactor\` for detailed guidance.`);
956
+ } else {
957
+ console.log(` Dry-run: would apply refactoring to ${skillName}`);
958
+ }
959
+
960
+ console.log("");
961
+ }
962
+
963
+ console.log(pc.green("Refactoring complete."));
964
+ console.log("");
965
+ console.log(pc.cyan("Next steps:"));
966
+ console.log(" 1. Review the changes");
967
+ console.log(" 2. Manually implement refactoring using suggestions from `npm run suggest:refactor`");
968
+ console.log(" 3. Re-run validation to check improved density");
969
+ }
970
+
971
+ async function promptApproval(message) {
972
+ console.log(pc.cyan(message));
973
+ console.log("");
974
+ return true;
975
+ }
976
+
977
+ async function densityAlerts(rootDir) {
978
+ const agentsDir = path.join(rootDir, ".agents");
979
+ const historyFile = path.join(agentsDir, "data", "density-history.json");
980
+
981
+ console.log(pc.bold(pc.green("Code IA Sota")));
982
+ console.log(pc.cyan("Analyzing density trends for alerts..."));
983
+ console.log("");
984
+
985
+ if (!(await exists(historyFile))) {
986
+ console.log(pc.yellow("No density history found. Run `npm run density:snapshot` first."));
987
+ return;
988
+ }
989
+
990
+ const historyContent = await fs.readFile(historyFile, "utf8");
991
+ const history = JSON.parse(historyContent);
992
+
993
+ if (history.length < 2) {
994
+ console.log(pc.yellow("Need at least 2 snapshots to analyze trends."));
995
+ return;
996
+ }
997
+
998
+ const alerts = [];
999
+ const skillTrends = {};
1000
+
1001
+ for (const snapshot of history) {
1002
+ for (const skill of snapshot.skills) {
1003
+ if (!skillTrends[skill.skill]) {
1004
+ skillTrends[skill.skill] = [];
1005
+ }
1006
+ skillTrends[skill.skill].push({
1007
+ timestamp: snapshot.timestamp,
1008
+ density: skill.density
1009
+ });
1010
+ }
1011
+ }
1012
+
1013
+ for (const [skillName, trends] of Object.entries(skillTrends)) {
1014
+ if (trends.length < 2) continue;
1015
+
1016
+ const first = trends[0];
1017
+ const last = trends[trends.length - 1];
1018
+ const change = last.density - first.density;
1019
+ const changePercent = Math.round((change / first.density) * 100);
1020
+
1021
+ if (change < -ALERT_THRESHOLD) {
1022
+ alerts.push({
1023
+ skill: skillName,
1024
+ firstDensity: first.density,
1025
+ lastDensity: last.density,
1026
+ change: change,
1027
+ changePercent: changePercent,
1028
+ severity: change < -20 ? "critical" : "warning"
1029
+ });
1030
+ }
1031
+ }
1032
+
1033
+ if (alerts.length === 0) {
1034
+ console.log(pc.green("No density degradation alerts. All skills are stable."));
1035
+ return;
1036
+ }
1037
+
1038
+ console.log(`Found ${alerts.length} density degradation alert(s):`);
1039
+ console.log("");
1040
+
1041
+ for (const alert of alerts) {
1042
+ const severityColor = alert.severity === "critical" ? pc.red : pc.yellow;
1043
+ console.log(severityColor(`${alert.severity.toUpperCase()}: ${alert.skill}`));
1044
+ console.log(` Density dropped from ${alert.firstDensity}% to ${alert.lastDensity}% (${alert.changePercent}% change)`);
1045
+ console.log(` Recommendation: Use \`npm run suggest:refactor\` to identify refactoring opportunities`);
1046
+ console.log("");
1047
+ }
1048
+
1049
+ console.log(pc.cyan("Summary:"));
1050
+ console.log(` Critical alerts: ${alerts.filter(a => a.severity === "critical").length}`);
1051
+ console.log(` Warning alerts: ${alerts.filter(a => a.severity === "warning").length}`);
1052
+ console.log("");
1053
+ console.log(pc.cyan("Next steps:"));
1054
+ console.log(" 1. Review the alerts above");
1055
+ console.log(" 2. Use `npm run suggest:refactor` for detailed guidance");
1056
+ console.log(" 3. Consider refactoring critical alerts first");
1057
+ }
1058
+
1059
+ async function bulkRefactor(rootDir) {
1060
+ const agentsDir = path.join(rootDir, ".agents");
1061
+ const skillsDir = path.join(agentsDir, "skills");
1062
+ const referencesDir = path.join(agentsDir, "references");
1063
+ const skillFiles = await listSkillFiles(skillsDir);
1064
+ const dryRun = process.argv.includes("--dry-run");
1065
+
1066
+ console.log(pc.bold(pc.green("Code IA Sota")));
1067
+ console.log(pc.cyan("Bulk refactoring with batch approval..."));
1068
+ console.log("");
1069
+
1070
+ if (dryRun) {
1071
+ console.log(pc.yellow("Dry-run mode: no changes will be applied."));
1072
+ console.log("");
1073
+ }
1074
+
1075
+ if (!(await exists(referencesDir))) {
1076
+ if (!dryRun) {
1077
+ await fs.mkdir(referencesDir, { recursive: true });
1078
+ }
1079
+ console.log(`Created references directory: ${referencesDir}`);
1080
+ }
1081
+
1082
+ const lowDensitySkills = [];
1083
+
1084
+ for (const filePath of skillFiles) {
1085
+ if (!(await exists(filePath))) continue;
1086
+
1087
+ const content = await fs.readFile(filePath, "utf8");
1088
+ const density = calculateDensity(content);
1089
+ const skillName = path.basename(path.dirname(filePath));
1090
+
1091
+ if (density < DENSITY_THRESHOLD) {
1092
+ const suggestions = await analyzeReferenceContent(content);
1093
+ lowDensitySkills.push({
1094
+ skill: skillName,
1095
+ density: density,
1096
+ filePath: path.relative(rootDir, filePath),
1097
+ suggestions: suggestions
1098
+ });
1099
+ }
1100
+ }
1101
+
1102
+ lowDensitySkills.sort((a, b) => a.density - b.density);
1103
+
1104
+ if (lowDensitySkills.length === 0) {
1105
+ console.log(pc.green("No low-density skills found. All skills meet the density threshold."));
1106
+ return;
1107
+ }
1108
+
1109
+ console.log(`Found ${lowDensitySkills.length} low-density skill(s):`);
1110
+ console.log("");
1111
+
1112
+ for (const skill of lowDensitySkills) {
1113
+ console.log(pc.yellow(`${skill.skill} (${skill.density}% density)`));
1114
+ console.log(` Suggestions: ${skill.suggestions.length}`);
1115
+ for (const suggestion of skill.suggestions) {
1116
+ console.log(` - ${suggestion.section}: ${suggestion.reason}`);
1117
+ }
1118
+ console.log("");
1119
+ }
1120
+
1121
+ const approved = await promptApproval(`Apply bulk refactoring to all ${lowDensitySkills.length} skills? (y/n)`);
1122
+ if (!approved) {
1123
+ console.log("Bulk refactoring cancelled.");
1124
+ return;
1125
+ }
1126
+
1127
+ if (!dryRun) {
1128
+ console.log("");
1129
+ console.log(pc.green("Bulk refactoring plan approved."));
1130
+ console.log("");
1131
+ console.log(pc.cyan("Next steps:"));
1132
+ console.log(" 1. Review the suggestions above");
1133
+ console.log(" 2. Manually implement refactoring using `npm run suggest:refactor` for each skill");
1134
+ console.log(" 3. Re-run validation to check improved density");
1135
+ console.log("");
1136
+ console.log(pc.cyan("Summary:"));
1137
+ console.log(` Total skills to refactor: ${lowDensitySkills.length}`);
1138
+ console.log(` Average density: ${Math.round(lowDensitySkills.reduce((a, b) => a + b.density, 0) / lowDensitySkills.length)}%`);
1139
+ } else {
1140
+ console.log("");
1141
+ console.log(pc.yellow("Dry-run: would apply bulk refactoring to all skills"));
1142
+ }
1143
+ }
1144
+
1145
+ async function densityFix(rootDir) {
1146
+ const agentsDir = path.join(rootDir, ".agents");
1147
+ const skillsDir = path.join(agentsDir, "skills");
1148
+ const skillFiles = await listSkillFiles(skillsDir);
1149
+ const dryRun = process.argv.includes("--dry-run");
1150
+
1151
+ console.log(pc.bold(pc.green("Code IA Sota")));
1152
+ console.log(pc.cyan("Automated density fixes..."));
1153
+ console.log("");
1154
+
1155
+ if (dryRun) {
1156
+ console.log(pc.yellow("Dry-run mode: no changes will be applied."));
1157
+ console.log("");
1158
+ }
1159
+
1160
+ const lowDensitySkills = [];
1161
+
1162
+ for (const filePath of skillFiles) {
1163
+ if (!(await exists(filePath))) continue;
1164
+
1165
+ const content = await fs.readFile(filePath, "utf8");
1166
+ const density = calculateDensity(content);
1167
+ const skillName = path.basename(path.dirname(filePath));
1168
+
1169
+ if (density < DENSITY_THRESHOLD) {
1170
+ lowDensitySkills.push({
1171
+ skill: skillName,
1172
+ density: density,
1173
+ filePath: path.relative(rootDir, filePath)
1174
+ });
1175
+ }
1176
+ }
1177
+
1178
+ lowDensitySkills.sort((a, b) => a.density - b.density);
1179
+
1180
+ if (lowDensitySkills.length === 0) {
1181
+ console.log(pc.green("No low-density skills found. All skills meet the density threshold."));
1182
+ return;
1183
+ }
1184
+
1185
+ console.log(`Found ${lowDensitySkills.length} low-density skill(s) to fix:`);
1186
+ console.log("");
1187
+
1188
+ for (const skill of lowDensitySkills) {
1189
+ console.log(pc.yellow(`${skill.skill} (${skill.density}% density)`));
1190
+ }
1191
+
1192
+ console.log("");
1193
+ const approved = await promptApproval(`Apply automated fixes to all ${lowDensitySkills.length} skills? (y/n)`);
1194
+ if (!approved) {
1195
+ console.log("Automated fixes cancelled.");
1196
+ return;
1197
+ }
1198
+
1199
+ if (!dryRun) {
1200
+ console.log("");
1201
+ console.log(pc.green("Automated fixes approved."));
1202
+ console.log("");
1203
+ console.log(pc.cyan("Creating references directories per skill..."));
1204
+
1205
+ for (const skill of lowDensitySkills) {
1206
+ const skillPath = path.join(skillsDir, skill.skill);
1207
+ const referencesPath = path.join(skillPath, "references");
1208
+
1209
+ if (!(await exists(referencesPath))) {
1210
+ await fs.mkdir(referencesPath, { recursive: true });
1211
+ console.log(` Created references directory: ${skill.skill}/references`);
1212
+ }
1213
+ }
1214
+
1215
+ console.log("");
1216
+ console.log(pc.cyan("Note: Automated fixes are limited to reference content extraction."));
1217
+ console.log(" For comprehensive refactoring, use `npm run suggest:refactor` manually.");
1218
+ console.log(" References should be split into smaller files by specialty.");
1219
+ console.log("");
1220
+ console.log(pc.cyan("Summary:"));
1221
+ console.log(` Total skills to fix: ${lowDensitySkills.length}`);
1222
+ console.log(` Average density: ${Math.round(lowDensitySkills.reduce((a, b) => a + b.density, 0) / lowDensitySkills.length)}%`);
1223
+ } else {
1224
+ console.log("");
1225
+ console.log(pc.yellow("Dry-run: would apply automated fixes to all skills"));
1226
+ }
1227
+ }
1228
+
1229
+ async function skillLifecycle(rootDir) {
1230
+ const agentsDir = path.join(rootDir, ".agents");
1231
+ const skillsDir = path.join(agentsDir, "skills");
1232
+ const skillFiles = await listSkillFiles(skillsDir);
1233
+
1234
+ console.log(pc.bold(pc.green("Code IA Sota")));
1235
+ console.log(pc.cyan("Skill Lifecycle Management"));
1236
+ console.log("");
1237
+
1238
+ const skills = [];
1239
+
1240
+ for (const filePath of skillFiles) {
1241
+ if (!(await exists(filePath))) continue;
1242
+
1243
+ const content = await fs.readFile(filePath, "utf8");
1244
+ const frontmatter = parseFrontmatter(content);
1245
+ const skillName = path.basename(path.dirname(filePath));
1246
+ const density = calculateDensity(content);
1247
+
1248
+ skills.push({
1249
+ skill: skillName,
1250
+ status: frontmatter?.data?.status || "active",
1251
+ owner: frontmatter?.data?.owner || "unknown",
1252
+ created: frontmatter?.data?.created || "unknown",
1253
+ updated: frontmatter?.data?.updated || "unknown",
1254
+ density: density,
1255
+ filePath: path.relative(rootDir, filePath)
1256
+ });
1257
+ }
1258
+
1259
+ console.log(`Total skills: ${skills.length}`);
1260
+ console.log("");
1261
+
1262
+ const statusCount = {
1263
+ active: skills.filter(s => s.status === "active").length,
1264
+ deprecated: skills.filter(s => s.status === "deprecated").length,
1265
+ archived: skills.filter(s => s.status === "archived").length
1266
+ };
1267
+
1268
+ console.log(pc.cyan("Status Distribution:"));
1269
+ console.log(` Active: ${statusCount.active}`);
1270
+ console.log(` Deprecated: ${statusCount.deprecated}`);
1271
+ console.log(` Archived: ${statusCount.archived}`);
1272
+ console.log("");
1273
+
1274
+ console.log(pc.cyan("Skills by Status:"));
1275
+ console.log("");
1276
+
1277
+ for (const skill of skills.sort((a, b) => a.skill.localeCompare(b.skill))) {
1278
+ const statusColor = skill.status === "active" ? pc.green : skill.status === "deprecated" ? pc.yellow : pc.gray;
1279
+ console.log(`${statusColor(skill.status.toUpperCase())}: ${skill.skill} (${skill.density}% density)`);
1280
+ }
1281
+
1282
+ console.log("");
1283
+ console.log(pc.cyan("To update skill status, add \`status: [active|deprecated|archived]\` to frontmatter."));
1284
+ console.log(pc.cyan("To add metadata, add \`owner\`, \`created\`, \`updated\` fields to frontmatter."));
1285
+ }
1286
+
85
1287
  async function main() {
1288
+ if (process.argv[2] === "validate") {
1289
+ await validateHarness(path.resolve(TARGET_DIR, process.argv[3] || "."));
1290
+ return;
1291
+ }
1292
+
1293
+ if (process.argv[2] === "suggest:refactor") {
1294
+ await suggestRefactor(path.resolve(TARGET_DIR, process.argv[3] || "."));
1295
+ return;
1296
+ }
1297
+
1298
+ if (process.argv[2] === "benchmark:density") {
1299
+ await benchmarkDensity(path.resolve(TARGET_DIR, process.argv[3] || "."));
1300
+ return;
1301
+ }
1302
+
1303
+ if (process.argv[2] === "density:snapshot") {
1304
+ await densitySnapshot(path.resolve(TARGET_DIR, process.argv[3] || "."));
1305
+ return;
1306
+ }
1307
+
1308
+ if (process.argv[2] === "density:trends") {
1309
+ await densityTrends(path.resolve(TARGET_DIR, process.argv[3] || "."));
1310
+ return;
1311
+ }
1312
+
1313
+ if (process.argv[2] === "refactor:auto") {
1314
+ await autoRefactor(path.resolve(TARGET_DIR, process.argv[3] || "."));
1315
+ return;
1316
+ }
1317
+
1318
+ if (process.argv[2] === "density:alerts") {
1319
+ await densityAlerts(path.resolve(TARGET_DIR, process.argv[3] || "."));
1320
+ return;
1321
+ }
1322
+
1323
+ if (process.argv[2] === "refactor:bulk") {
1324
+ await bulkRefactor(path.resolve(TARGET_DIR, process.argv[3] || "."));
1325
+ return;
1326
+ }
1327
+
1328
+ if (process.argv[2] === "density:fix") {
1329
+ await densityFix(path.resolve(TARGET_DIR, process.argv[3] || "."));
1330
+ return;
1331
+ }
1332
+
1333
+ if (process.argv[2] === "skill:lifecycle") {
1334
+ await skillLifecycle(path.resolve(TARGET_DIR, process.argv[3] || "."));
1335
+ return;
1336
+ }
1337
+
1338
+ const selectedTools = await selectTools();
86
1339
  const tmpDir = await fs.mkdtemp(
87
1340
  path.join(os.tmpdir(), "code-ia-sota-")
88
1341
  );
@@ -92,6 +1345,10 @@ async function main() {
92
1345
  pc.bold(pc.green("Code IA Sota"))
93
1346
  );
94
1347
 
1348
+ console.log(
1349
+ pc.cyan(`Selected tools: ${selectedTools.length > 0 ? selectedTools.join(", ") : "none"}`)
1350
+ );
1351
+
95
1352
  await cloneTemplate(tmpDir);
96
1353
 
97
1354
  console.log(
@@ -100,6 +1357,9 @@ async function main() {
100
1357
 
101
1358
  await copyFiles(tmpDir);
102
1359
 
1360
+ const integrationSummary =
1361
+ await installToolIntegrations(selectedTools);
1362
+
103
1363
  console.log(
104
1364
  pc.green("Installation completed.")
105
1365
  );
@@ -108,6 +1368,28 @@ async function main() {
108
1368
  console.log("Created:");
109
1369
  console.log(" AGENTS.md");
110
1370
  console.log(" .agents/");
1371
+
1372
+ if (integrationSummary.created.length > 0) {
1373
+ for (const filePath of integrationSummary.created) {
1374
+ console.log(` ${filePath}`);
1375
+ }
1376
+ }
1377
+
1378
+ if (integrationSummary.updated.length > 0) {
1379
+ console.log("");
1380
+ console.log("Updated:");
1381
+ for (const filePath of integrationSummary.updated) {
1382
+ console.log(` ${filePath}`);
1383
+ }
1384
+ }
1385
+
1386
+ if (integrationSummary.skipped.length > 0) {
1387
+ console.log("");
1388
+ console.log(pc.yellow("Skipped existing unmanaged files:"));
1389
+ for (const filePath of integrationSummary.skipped) {
1390
+ console.log(` ${filePath}`);
1391
+ }
1392
+ }
111
1393
  } finally {
112
1394
  await fsExtra.remove(tmpDir);
113
1395
  }
package/package.json CHANGED
@@ -1,11 +1,23 @@
1
1
  {
2
2
  "name": "code-ia-sota",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Agent scaffold installer for AGENTS.md and .agents",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "code-ia-sota": "bin/code-ia-sota.js"
8
8
  },
9
+ "scripts": {
10
+ "validate:harness": "node bin/code-ia-sota.js validate ..",
11
+ "suggest:refactor": "node bin/code-ia-sota.js suggest:refactor ..",
12
+ "benchmark:density": "node bin/code-ia-sota.js benchmark:density ..",
13
+ "density:snapshot": "node bin/code-ia-sota.js density:snapshot ..",
14
+ "density:trends": "node bin/code-ia-sota.js density:trends ..",
15
+ "density:alerts": "node bin/code-ia-sota.js density:alerts ..",
16
+ "density:fix": "node bin/code-ia-sota.js density:fix ..",
17
+ "refactor:auto": "node bin/code-ia-sota.js refactor:auto ..",
18
+ "refactor:bulk": "node bin/code-ia-sota.js refactor:bulk ..",
19
+ "skill:lifecycle": "node bin/code-ia-sota.js skill:lifecycle .."
20
+ },
9
21
  "files": [
10
22
  "bin",
11
23
  "README.md"